From 2b89989f6251f560759a80a216bd665e111b59a3 Mon Sep 17 00:00:00 2001 From: Li Duoyang Date: Tue, 12 May 2026 16:23:24 +0800 Subject: [PATCH] fix(default): support DropdownMenuItem onSelect (#4787) fix(ui): add onSelect compat wrapper for DropdownMenuItem Bridges Base UI DropdownMenu with Radix-style onSelect so existing consumers work without migration. --- .dockerignore | 4 ++ .../src/components/ui/dropdown-menu-events.ts | 25 ++++++++++ .../src/components/ui/dropdown-menu.test.tsx | 50 +++++++++++++++++++ .../src/components/ui/dropdown-menu.tsx | 17 ++++++- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 web/default/src/components/ui/dropdown-menu-events.ts create mode 100644 web/default/src/components/ui/dropdown-menu.test.tsx diff --git a/.dockerignore b/.dockerignore index 2cf7cad4..7a719362 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,8 @@ docs .eslintcache .gocache /web/node_modules +/web/default/node_modules +/web/default/dist +/web/classic/node_modules +/web/classic/dist !THIRD-PARTY-LICENSES.md diff --git a/web/default/src/components/ui/dropdown-menu-events.ts b/web/default/src/components/ui/dropdown-menu-events.ts new file mode 100644 index 00000000..0f49d3f8 --- /dev/null +++ b/web/default/src/components/ui/dropdown-menu-events.ts @@ -0,0 +1,25 @@ +import type * as React from 'react' + +export type DropdownMenuItemSelectEvent = React.MouseEvent & { + preventBaseUIHandler?: () => void +} + +export type DropdownMenuItemSelectHandler = ( + event: DropdownMenuItemSelectEvent +) => void + +export function handleDropdownMenuItemSelect( + event: DropdownMenuItemSelectEvent, + onClick?: React.MouseEventHandler, + onSelect?: DropdownMenuItemSelectHandler +) { + onClick?.(event) + + if (!event.defaultPrevented) { + onSelect?.(event) + } + + if (event.defaultPrevented) { + event.preventBaseUIHandler?.() + } +} diff --git a/web/default/src/components/ui/dropdown-menu.test.tsx b/web/default/src/components/ui/dropdown-menu.test.tsx new file mode 100644 index 00000000..33917663 --- /dev/null +++ b/web/default/src/components/ui/dropdown-menu.test.tsx @@ -0,0 +1,50 @@ +import assert from 'node:assert/strict' +import { describe, test } from 'node:test' +import { handleDropdownMenuItemSelect } from './dropdown-menu-events' + +function createMenuEvent() { + let defaultPrevented = false + let baseUIHandlerPrevented = false + + return { + get defaultPrevented() { + return defaultPrevented + }, + preventDefault() { + defaultPrevented = true + }, + preventBaseUIHandler() { + baseUIHandlerPrevented = true + }, + get baseUIHandlerPrevented() { + return baseUIHandlerPrevented + }, + } as unknown as Parameters[0] & { + baseUIHandlerPrevented: boolean + } +} + +describe('DropdownMenuItem onSelect compatibility', () => { + test('calls the Radix-style onSelect handler on item click', () => { + const event = createMenuEvent() + let selected = false + + handleDropdownMenuItemSelect(event, undefined, () => { + selected = true + }) + + assert.equal(selected, true) + assert.equal(event.baseUIHandlerPrevented, false) + }) + + test('keeps the Base UI menu open when onSelect prevents default', () => { + const event = createMenuEvent() + + handleDropdownMenuItemSelect(event, undefined, (selectEvent) => { + selectEvent.preventDefault() + }) + + assert.equal(event.defaultPrevented, true) + assert.equal(event.baseUIHandlerPrevented, true) + }) +}) diff --git a/web/default/src/components/ui/dropdown-menu.tsx b/web/default/src/components/ui/dropdown-menu.tsx index e0da050c..f45ffb83 100644 --- a/web/default/src/components/ui/dropdown-menu.tsx +++ b/web/default/src/components/ui/dropdown-menu.tsx @@ -21,6 +21,10 @@ import { Menu as MenuPrimitive } from '@base-ui/react/menu' import { ArrowRight01Icon, Tick02Icon } from '@hugeicons/core-free-icons' import { HugeiconsIcon } from '@hugeicons/react' import { cn } from '@/lib/utils' +import { + handleDropdownMenuItemSelect, + type DropdownMenuItemSelectHandler, +} from './dropdown-menu-events' function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { return @@ -96,11 +100,21 @@ function DropdownMenuItem({ className, inset, variant = 'default', + onClick, + onSelect, ...props -}: MenuPrimitive.Item.Props & { +}: Omit & { inset?: boolean variant?: 'default' | 'destructive' + onSelect?: DropdownMenuItemSelectHandler }) { + const handleClick = React.useCallback( + (event: React.MouseEvent) => { + handleDropdownMenuItemSelect(event, onClick, onSelect) + }, + [onClick, onSelect] + ) + return ( )