diff --git a/.changeset/gold-otters-judge.md b/.changeset/gold-otters-judge.md new file mode 100644 index 000000000..86ed5f707 --- /dev/null +++ b/.changeset/gold-otters-judge.md @@ -0,0 +1,5 @@ +--- +'@radix-ui/react-dropdown-menu': minor +--- + +move focus to the last item when opening with ArrowUp diff --git a/packages/react/dropdown-menu/src/dropdown-menu.tsx b/packages/react/dropdown-menu/src/dropdown-menu.tsx index 94ea815f2..570a57b47 100644 --- a/packages/react/dropdown-menu/src/dropdown-menu.tsx +++ b/packages/react/dropdown-menu/src/dropdown-menu.tsx @@ -33,6 +33,8 @@ type DropdownMenuContextValue = { onOpenChange(open: boolean): void; onOpenToggle(): void; modal: boolean; + wasOpenedWithArrowUp: boolean; + setWasOpenedWithArrowUp(value: boolean): void; }; const [DropdownMenuProvider, useDropdownMenuContext] = @@ -65,6 +67,14 @@ const DropdownMenu: React.FC = (props: ScopedProps { + if (!open) { + setWasOpenedWithArrowUp(false); + } + }, [open]); return ( = (props: ScopedProps setOpen((prevOpen) => !prevOpen), [setOpen])} modal={modal} + wasOpenedWithArrowUp={wasOpenedWithArrowUp} + setWasOpenedWithArrowUp={setWasOpenedWithArrowUp} > {children} @@ -130,7 +142,11 @@ const DropdownMenuTrigger = React.forwardRef @@ -167,7 +183,7 @@ const CONTENT_NAME = 'DropdownMenuContent'; type DropdownMenuContentElement = React.ComponentRef; type MenuContentProps = React.ComponentPropsWithoutRef; -interface DropdownMenuContentProps extends Omit {} +interface DropdownMenuContentProps extends MenuContentProps {} const DropdownMenuContent = React.forwardRef( (props: ScopedProps, forwardedRef) => { @@ -183,6 +199,21 @@ const DropdownMenuContent = React.forwardRef { + // If opened with ArrowUp, focus last item instead of first + if (context.wasOpenedWithArrowUp) { + event.preventDefault(); + // Focus the last item + const target = event.target as HTMLElement; + const items = target.querySelectorAll('[role="menuitem"]:not([data-disabled])'); + const lastItem = items[items.length - 1] as HTMLElement; + if (lastItem) { + setTimeout(() => lastItem.focus(), 0); + } + } + // Call the original onEntryFocus if provided + contentProps.onEntryFocus?.(event); + }} onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => { if (!hasInteractedOutsideRef.current) context.triggerRef.current?.focus(); hasInteractedOutsideRef.current = false;