Skip to content

Commit

Permalink
feat: handle focus within dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela committed Sep 5, 2024
1 parent 993358f commit b557e25
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/components/Dialog/DialogAnchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,18 @@ export function useDialogAnchor<T extends HTMLElement>({

type DialogAnchorProps = PropsWithChildren<Partial<DialogAnchorOptions>> & {
id: string;
focus?: boolean;
trapFocus?: boolean;
} & ComponentProps<'div'>;

export const DialogAnchor = ({
children,
className,
focus = true,
id,
placement = 'auto',
referenceElement = null,
trapFocus,
...restDivProps
}: DialogAnchorProps) => {
const open = useDialogIsOpen(id);
Expand All @@ -66,6 +70,43 @@ export const DialogAnchor = ({
referenceElement,
});

// handle focus and focus trap inside the dialog
useEffect(() => {
if (!popperElementRef.current || !focus || !open) return;
const container = popperElementRef.current;
container.focus();

if (!trapFocus) return;
const handleKeyDownWithTabRoundRobin = (event: KeyboardEvent) => {
if (event.key !== 'Tab') return;

const focusableElements = getFocusableElements(container);

Check warning on line 83 in src/components/Dialog/DialogAnchor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Dialog/DialogAnchor.tsx#L83

Added line #L83 was not covered by tests
if (focusableElements.length === 0) return;

const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;

Check warning on line 87 in src/components/Dialog/DialogAnchor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Dialog/DialogAnchor.tsx#L86-L87

Added lines #L86 - L87 were not covered by tests
if (firstElement === lastElement) {
event.preventDefault();
firstElement.focus();

Check warning on line 90 in src/components/Dialog/DialogAnchor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Dialog/DialogAnchor.tsx#L89-L90

Added lines #L89 - L90 were not covered by tests
}

// Trap focus within the group
if (event.shiftKey && document.activeElement === firstElement) {
// If Shift + Tab on the first element, move focus to the last element
event.preventDefault();
lastElement.focus();

Check warning on line 97 in src/components/Dialog/DialogAnchor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Dialog/DialogAnchor.tsx#L96-L97

Added lines #L96 - L97 were not covered by tests
} else if (!event.shiftKey && document.activeElement === lastElement) {
// If Tab on the last element, move focus to the first element
event.preventDefault();
firstElement.focus();

Check warning on line 101 in src/components/Dialog/DialogAnchor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Dialog/DialogAnchor.tsx#L100-L101

Added lines #L100 - L101 were not covered by tests
}
};

container.addEventListener('keydown', handleKeyDownWithTabRoundRobin);

return () => container.removeEventListener('keydown', handleKeyDownWithTabRoundRobin);
}, [focus, popperElementRef, open, trapFocus]);

return (
<DialogPortalEntry dialogId={id}>
<div
Expand All @@ -75,9 +116,16 @@ export const DialogAnchor = ({
data-testid='str-chat__dialog-contents'
ref={popperElementRef}
style={styles.popper}
tabIndex={0}
>
{children}
</div>
</DialogPortalEntry>
);
};

function getFocusableElements(container: HTMLElement) {
return container.querySelectorAll(

Check warning on line 128 in src/components/Dialog/DialogAnchor.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Dialog/DialogAnchor.tsx#L128

Added line #L128 was not covered by tests
'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])',
);
}
1 change: 1 addition & 0 deletions src/components/MessageActions/MessageActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export const MessageActions = <
id={dialogId}
placement={isMine ? 'top-end' : 'top-start'}
referenceElement={actionsBoxButtonRef.current}
trapFocus
>
<MessageActionsBox
getMessageActions={getMessageActions}
Expand Down

0 comments on commit b557e25

Please sign in to comment.