Skip to content

Commit 039e095

Browse files
committed
feat(Dropdown): default arrow key handling to focus items
1 parent 0fe487a commit 039e095

File tree

1 file changed

+29
-0
lines changed

1 file changed

+29
-0
lines changed

packages/react-core/src/components/Dropdown/Dropdown.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export interface DropdownProps extends MenuProps, OUIAProps {
5151
onOpenChange?: (isOpen: boolean) => void;
5252
/** Keys that trigger onOpenChange, defaults to tab and escape. It is highly recommended to include Escape in the array, while Tab may be omitted if the menu contains non-menu items that are focusable. */
5353
onOpenChangeKeys?: string[];
54+
/** Custom callback to override the default behaviour when pressing up/down arrows. Default is focusing the menu items (first item on arrow down, last item on arrow up). */
55+
onArrowUpDownKeyDown?: (event: KeyboardEvent) => void;
5456
/** Indicates if the menu should be without the outer box-shadow. */
5557
isPlain?: boolean;
5658
/** Indicates if the menu should be scrollable. */
@@ -85,6 +87,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
8587
toggle,
8688
shouldFocusToggleOnSelect = false,
8789
onOpenChange,
90+
onArrowUpDownKeyDown,
8891
isPlain,
8992
isScrollable,
9093
innerRef,
@@ -127,6 +130,23 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
127130
}, [isOpen]);
128131

129132
React.useEffect(() => {
133+
const onArrowUpDownKeyDownDefault = (event: KeyboardEvent) => {
134+
event.preventDefault();
135+
136+
let listItem: HTMLLIElement;
137+
if (event.key === 'ArrowDown') {
138+
listItem = menuRef.current?.querySelector('li');
139+
} else {
140+
const allItems = menuRef.current?.querySelectorAll('li');
141+
listItem = allItems ? allItems[allItems.length - 1] : null;
142+
}
143+
144+
const focusableElement = listItem?.querySelector(
145+
'button:not(:disabled),input:not(:disabled),a:not([aria-disabled="true"])'
146+
);
147+
focusableElement && (focusableElement as HTMLElement).focus();
148+
};
149+
130150
const handleMenuKeys = (event: KeyboardEvent) => {
131151
// Close the menu on tab or escape if onOpenChange is provided
132152
if (
@@ -139,6 +159,14 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
139159
toggleRef.current?.focus();
140160
}
141161
}
162+
163+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
164+
if (onArrowUpDownKeyDown) {
165+
onArrowUpDownKeyDown(event);
166+
} else {
167+
onArrowUpDownKeyDownDefault(event);
168+
}
169+
}
142170
};
143171

144172
const handleClick = (event: MouseEvent) => {
@@ -163,6 +191,7 @@ const DropdownBase: React.FunctionComponent<DropdownProps> = ({
163191
toggleRef,
164192
onOpenChange,
165193
onOpenChangeKeys,
194+
onArrowUpDownKeyDown,
166195
shouldPreventScrollOnItemFocus,
167196
shouldFocusFirstItemOnOpen,
168197
focusTimeoutDelay

0 commit comments

Comments
 (0)