Skip to content

Commit 02be492

Browse files
authored
Merge pull request #282 from jaksenko/patch-1
Expose moveFocus()
2 parents 2b4325e + 7391f69 commit 02be492

File tree

3 files changed

+26
-2
lines changed

3 files changed

+26
-2
lines changed

src/use-dropdown-menu.test.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface Props {
1111

1212
const TestComponent: React.FC<Props> = ({ options }) => {
1313
const [itemCount, setItemCount] = useState(4);
14-
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(itemCount, options);
14+
const { buttonProps, itemProps, isOpen, moveFocus, setIsOpen } = useDropdownMenu(itemCount, options);
1515

1616
const clickHandlers: (() => void)[] = [(): void => console.log('Item one clicked'), (): void => setIsOpen(false)];
1717

@@ -28,6 +28,7 @@ const TestComponent: React.FC<Props> = ({ options }) => {
2828
key={i}
2929
id={`menu-item-${i + 1}`}
3030
onClick={clickHandlers[i]}
31+
onMouseEnter={(): void => moveFocus(i)}
3132
href={i > 1 ? 'https://example.com' : undefined}
3233
>
3334
{i + 1} Item
@@ -540,3 +541,23 @@ it('Moves the focus to the menu item with a label that starts with the correspon
540541

541542
expect(screen.getByText('3 Item')).toHaveFocus();
542543
});
544+
545+
it('Moves the focus to the menu item currently hovered over by mouse', () => {
546+
render(<TestComponent />);
547+
548+
userEvent.tab();
549+
550+
userEvent.type(screen.getByText('Primary'), '{enter}', {
551+
skipClick: true,
552+
});
553+
554+
fireEvent(
555+
screen.getByText('3 Item'),
556+
new MouseEvent('mouseover', {
557+
bubbles: true,
558+
cancelable: true,
559+
})
560+
);
561+
562+
expect(screen.getByText('3 Item')).toHaveFocus();
563+
});

src/use-dropdown-menu.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface DropdownMenuResponse {
2525
}[];
2626
readonly isOpen: boolean;
2727
readonly setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
28+
readonly moveFocus: (itemIndex: number) => void;
2829
}
2930

3031
export default function useDropdownMenu(itemCount: number, options?: DropdownMenuOptions): DropdownMenuResponse {
@@ -233,5 +234,5 @@ export default function useDropdownMenu(itemCount: number, options?: DropdownMen
233234
}));
234235

235236
// Return a listener for the button, individual list items, and the state of the menu
236-
return { buttonProps, itemProps, isOpen, setIsOpen } as const;
237+
return { buttonProps, itemProps, isOpen, setIsOpen, moveFocus } as const;
237238
}

website/docs/design/return-object.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ This Hook returns an object of the following shape:
2626
];
2727
isOpen: boolean;
2828
setIsOpen: (newValue: boolean) => void;
29+
moveFocus: (itemIndex: number) => void;
2930
}
3031
```
3132

@@ -44,3 +45,4 @@ This Hook returns an object of the following shape:
4445
- **ref:** A React ref applied to each menu item, used to manage focus.
4546
- **isOpen:** A boolean value indicating if the menu is open or closed. The developer should use this value to make the menu visible or not.
4647
- **setIsOpen:** A function useful for allowing the developer to programmatically open/close the menu.
48+
- **moveFocus:** A function that changes internal pointer of currently focused element. This is useful when you want to allow seamless switching between keyboard and mouse use.

0 commit comments

Comments
 (0)