mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app): keep focus and its ring on a spoiler after it is revealed (#1961)
This commit is contained in:
1 parent
38e86acffe
commit
b51562d0e3
2 files changed
+35
-28
No files matched your search
@@ -6,6 +6,7 @@ import {msg} from '@lingui/core/macro';
|
|||||||
import {useLingui} from '@lingui/react/macro';
|
import {useLingui} from '@lingui/react/macro';
|
||||||
import {clsx} from 'clsx';
|
import {clsx} from 'clsx';
|
||||||
import type {FC, ReactNode} from 'react';
|
import type {FC, ReactNode} from 'react';
|
||||||
|
import {useCallback, useLayoutEffect, useRef} from 'react';
|
||||||
|
|
||||||
const REVEAL_SPOILER_DESCRIPTOR = msg({
|
const REVEAL_SPOILER_DESCRIPTOR = msg({
|
||||||
message: 'Reveal spoiler',
|
message: 'Reveal spoiler',
|
||||||
@@ -27,9 +28,26 @@ interface SpoilerOverlayProps {
|
|||||||
|
|
||||||
export const SpoilerOverlay: FC<SpoilerOverlayProps> = ({hidden, onReveal, children, label, inline, className}) => {
|
export const SpoilerOverlay: FC<SpoilerOverlayProps> = ({hidden, onReveal, children, label, inline, className}) => {
|
||||||
const {i18n} = useLingui();
|
const {i18n} = useLingui();
|
||||||
|
const wrapperElementRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const pendingFocusHandoffRef = useRef(false);
|
||||||
const ariaLabel = label ?? i18n._(REVEAL_SPOILER_DESCRIPTOR);
|
const ariaLabel = label ?? i18n._(REVEAL_SPOILER_DESCRIPTOR);
|
||||||
|
const handleRevealRequest = useCallback(() => {
|
||||||
|
pendingFocusHandoffRef.current = true;
|
||||||
|
onReveal();
|
||||||
|
}, [onReveal]);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (hidden) {
|
||||||
|
pendingFocusHandoffRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!pendingFocusHandoffRef.current) return;
|
||||||
|
pendingFocusHandoffRef.current = false;
|
||||||
|
wrapperElementRef.current?.focus({preventScroll: true});
|
||||||
|
}, [hidden]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={wrapperElementRef}
|
||||||
|
tabIndex={-1}
|
||||||
className={clsx(styles.container, inline && styles.inline, hidden && styles.hidden, className)}
|
className={clsx(styles.container, inline && styles.inline, hidden && styles.hidden, className)}
|
||||||
data-flx="app.spoiler-overlay.container"
|
data-flx="app.spoiler-overlay.container"
|
||||||
>
|
>
|
||||||
@@ -41,7 +59,7 @@ export const SpoilerOverlay: FC<SpoilerOverlayProps> = ({hidden, onReveal, child
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.overlayButton}
|
className={styles.overlayButton}
|
||||||
onClick={onReveal}
|
onClick={handleRevealRequest}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
data-flx="app.spoiler-overlay.overlay-button.reveal"
|
data-flx="app.spoiler-overlay.overlay-button.reveal"
|
||||||
>
|
>
|
||||||
|
|||||||
+16
-27
@@ -93,17 +93,30 @@ export const SpoilerRenderer = observer(function SpoilerRenderer({
|
|||||||
const wrapperClass = isBlock ? markupStyles.blockSpoilerWrapper : markupStyles.spoilerWrapper;
|
const wrapperClass = isBlock ? markupStyles.blockSpoilerWrapper : markupStyles.spoilerWrapper;
|
||||||
const spoilerClass = isBlock ? markupStyles.blockSpoiler : markupStyles.spoiler;
|
const spoilerClass = isBlock ? markupStyles.blockSpoiler : markupStyles.spoiler;
|
||||||
const shouldReveal = !hidden || autoRevealed;
|
const shouldReveal = !hidden || autoRevealed;
|
||||||
|
const revealTriggerProps = shouldReveal
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
onClick: handleClick,
|
||||||
|
onKeyDown: handleKeyDown,
|
||||||
|
role: 'button',
|
||||||
|
'aria-label': i18n._(CLICK_TO_REVEAL_SPOILER_DESCRIPTOR),
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={id}
|
key={id}
|
||||||
className={wrapperClass}
|
className={wrapperClass}
|
||||||
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.span"
|
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.span"
|
||||||
>
|
>
|
||||||
{shouldReveal ? (
|
<FocusRing
|
||||||
|
offset={-2}
|
||||||
|
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.focus-ring"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={spoilerClass}
|
className={spoilerClass}
|
||||||
data-revealed={shouldReveal}
|
data-revealed={shouldReveal}
|
||||||
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.span--2"
|
tabIndex={shouldReveal ? -1 : 0}
|
||||||
|
{...revealTriggerProps}
|
||||||
|
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.button.click"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={markupStyles.spoilerContent}
|
className={markupStyles.spoilerContent}
|
||||||
@@ -113,31 +126,7 @@ export const SpoilerRenderer = observer(function SpoilerRenderer({
|
|||||||
{renderChildren(node.children)}
|
{renderChildren(node.children)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
</FocusRing>
|
||||||
<FocusRing
|
|
||||||
offset={-2}
|
|
||||||
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.focus-ring"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={spoilerClass}
|
|
||||||
data-revealed={shouldReveal}
|
|
||||||
onClick={handleClick}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-label={i18n._(CLICK_TO_REVEAL_SPOILER_DESCRIPTOR)}
|
|
||||||
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.button.click"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={markupStyles.spoilerContent}
|
|
||||||
aria-hidden
|
|
||||||
data-flx="messaging.markdown.renderers.common.formatting-elements.spoiler-renderer.span--4"
|
|
||||||
>
|
|
||||||
{renderChildren(node.children)}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</FocusRing>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user