Skip to content

Commit

Permalink
fix(spindle-ui): attach event handlers as needed
Browse files Browse the repository at this point in the history
esc handler should be attached only when the drop menu is opened.
  • Loading branch information
kc7891 committed Dec 13, 2023
1 parent 93b5c13 commit 22e48e9
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 17 deletions.
139 changes: 137 additions & 2 deletions packages/spindle-ui/src/DropdownMenu/DropdownMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { Button } from '../Button';
import { BLOCK_NAME, DropdownMenu } from './DropdownMenu';

const ANIMATION_DURATION = 300;
const useDropdownMenuOpen = () => {
const [open, setOpen] = useState(true);
const useDropdownMenuOpen = (initialState?: boolean) => {
const [open, setOpen] = useState(initialState ?? true);
const onClick = () => {
setOpen((prevOpen) => !prevOpen);
};
Expand Down Expand Up @@ -227,4 +227,139 @@ describe('<DropdownMenu />', () => {
screen.getByText(/triggerButton/).nextElementSibling,
).not.toBeInTheDocument();
});

test('The DropdownMenu should be closed when you trigger esc key.', async () => {
const triggerRef = createRef<HTMLButtonElement>();
const onMenuButtonClick = jest.fn();
const { result } = renderHook(() => useDropdownMenuOpen());
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });

expect(result.current.open).toBe(true);

const { rerender } = render(
<>
<DropdownMenu.Frame>
<Button onClick={result.current.onClick} ref={triggerRef}>
triggerButton
</Button>
<DropdownMenu.List
onClose={result.current.onClose}
open={result.current.open}
triggerRef={triggerRef}
>
<DropdownMenu.ListItem onClick={onMenuButtonClick}>
<DropdownMenu.Title>testTitle</DropdownMenu.Title>
</DropdownMenu.ListItem>
</DropdownMenu.List>
</DropdownMenu.Frame>
</>,
);

const menu = screen.getByText(/triggerButton/).nextElementSibling;
expect(menu).toHaveClass(`${BLOCK_NAME}-menu`);
expect(menu).toBeInTheDocument();

await user.keyboard('{esc}');

expect(menu).toHaveClass('is-fade-out');

act(() => {
jest.advanceTimersByTime(ANIMATION_DURATION);
if (menu) fireEvent.animationEnd(menu);
});

rerender(
<>
<DropdownMenu.Frame>
<Button onClick={result.current.onClick} ref={triggerRef}>
triggerButton
</Button>
<DropdownMenu.List
onClose={result.current.onClose}
open={result.current.open}
triggerRef={triggerRef}
>
<DropdownMenu.ListItem onClick={onMenuButtonClick}>
<DropdownMenu.Title>testTitle</DropdownMenu.Title>
</DropdownMenu.ListItem>
</DropdownMenu.List>
</DropdownMenu.Frame>
</>,
);

expect(result.current.open).toBe(false);
expect(
screen.getByText(/triggerButton/).nextElementSibling,
).not.toBeInTheDocument();
});

test('The active element after Esc keydown is trigger element when the Dropdown menu is opened.', async () => {
const triggerRef = createRef<HTMLButtonElement>();
const onMenuButtonClick = jest.fn();
const { result } = renderHook(() => useDropdownMenuOpen());
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });

expect(result.current.open).toBe(true);

render(
<>
<DropdownMenu.Frame>
<Button onClick={result.current.onClick} ref={triggerRef}>
triggerButton
</Button>
<DropdownMenu.List
onClose={result.current.onClose}
open={result.current.open}
triggerRef={triggerRef}
>
<DropdownMenu.ListItem onClick={onMenuButtonClick}>
<DropdownMenu.Title>testTitle</DropdownMenu.Title>
</DropdownMenu.ListItem>
</DropdownMenu.List>
</DropdownMenu.Frame>
</>,
);

// const menu = screen.getByText(/triggerButton/).nextElementSibling;
await user.keyboard('{esc}');
act(() => {
jest.advanceTimersByTime(ANIMATION_DURATION);
});
expect(document.activeElement).toBe(triggerRef.current);
});

test('The active element after Esc keydown is not trigger element when the Dropdown menu is closed.', async () => {
const triggerRef = createRef<HTMLButtonElement>();
const onMenuButtonClick = jest.fn();
const { result } = renderHook(() => useDropdownMenuOpen(false));
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });

expect(result.current.open).toBe(false);

render(
<>
<DropdownMenu.Frame>
<Button onClick={result.current.onClick} ref={triggerRef}>
triggerButton
</Button>
<DropdownMenu.List
onClose={result.current.onClose}
open={result.current.open}
triggerRef={triggerRef}
>
<DropdownMenu.ListItem onClick={onMenuButtonClick}>
<DropdownMenu.Title>testTitle</DropdownMenu.Title>
</DropdownMenu.ListItem>
</DropdownMenu.List>
</DropdownMenu.Frame>
</>,
);

// const menu = screen.getByText(/triggerButton/).nextElementSibling;
await user.keyboard('{esc}');
act(() => {
jest.advanceTimersByTime(ANIMATION_DURATION);
});
expect(document.activeElement).not.toBe(triggerRef.current);
});
});
31 changes: 16 additions & 15 deletions packages/spindle-ui/src/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface ListProps extends DefaultProps {

export const BLOCK_NAME = 'spui-DropdownMenu';
const FADE_IN_ANIMATION = 'spui-DropdownMenu-fade-in';
const CLOSE_KEY_LIST = ['Escape', 'Esc'];
const CLOSE_KEY_LIST = ['ESCAPE', 'ESC'];
const MENU_WIDTH = 256;

const Caption = ({ children }: DefaultProps) => {
Expand Down Expand Up @@ -86,7 +86,7 @@ const List = ({

const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (CLOSE_KEY_LIST.includes(e.key)) {
if (CLOSE_KEY_LIST.includes(e.key.toUpperCase())) {
onClickCloser();
}
},
Expand Down Expand Up @@ -123,27 +123,28 @@ const List = ({

useEffect(() => {
const menu = menuEl.current;
menu?.addEventListener('animationend', handleAnimationEnd, false);
if (open) {
menu?.addEventListener('animationend', handleAnimationEnd, false);
}

return () =>
menu?.removeEventListener('animationend', handleAnimationEnd, false);
}, [menuEl, handleAnimationEnd]);
}, [menuEl, handleAnimationEnd, open]);

useEffect(() => {
window.addEventListener('click', onClickBody, false);
if (open) {
window.addEventListener('click', onClickBody, false);
}

return () => {
window.removeEventListener('click', onClickBody, false);
};
}, [onClickBody]);
return () => window.removeEventListener('click', onClickBody, false);
}, [onClickBody, open]);

useEffect(() => {
window.addEventListener('keydown', handleKeyDown);

return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]);
if (open) {
window.addEventListener('keydown', handleKeyDown);
}
return () => window.removeEventListener('keydown', handleKeyDown);
}, [handleKeyDown, open]);

if (!open) {
return <></>;
Expand Down

0 comments on commit 22e48e9

Please sign in to comment.