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.
This commit is contained in:
parent
7fe896d2f8
commit
2b89989f62
@ -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
|
||||
|
||||
25
web/default/src/components/ui/dropdown-menu-events.ts
vendored
Normal file
25
web/default/src/components/ui/dropdown-menu-events.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
import type * as React from 'react'
|
||||
|
||||
export type DropdownMenuItemSelectEvent = React.MouseEvent<HTMLElement> & {
|
||||
preventBaseUIHandler?: () => void
|
||||
}
|
||||
|
||||
export type DropdownMenuItemSelectHandler = (
|
||||
event: DropdownMenuItemSelectEvent
|
||||
) => void
|
||||
|
||||
export function handleDropdownMenuItemSelect(
|
||||
event: DropdownMenuItemSelectEvent,
|
||||
onClick?: React.MouseEventHandler<HTMLElement>,
|
||||
onSelect?: DropdownMenuItemSelectHandler
|
||||
) {
|
||||
onClick?.(event)
|
||||
|
||||
if (!event.defaultPrevented) {
|
||||
onSelect?.(event)
|
||||
}
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
event.preventBaseUIHandler?.()
|
||||
}
|
||||
}
|
||||
50
web/default/src/components/ui/dropdown-menu.test.tsx
vendored
Normal file
50
web/default/src/components/ui/dropdown-menu.test.tsx
vendored
Normal file
@ -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<typeof handleDropdownMenuItemSelect>[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)
|
||||
})
|
||||
})
|
||||
17
web/default/src/components/ui/dropdown-menu.tsx
vendored
17
web/default/src/components/ui/dropdown-menu.tsx
vendored
@ -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 <MenuPrimitive.Root data-slot='dropdown-menu' {...props} />
|
||||
@ -96,11 +100,21 @@ function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = 'default',
|
||||
onClick,
|
||||
onSelect,
|
||||
...props
|
||||
}: MenuPrimitive.Item.Props & {
|
||||
}: Omit<MenuPrimitive.Item.Props, 'onSelect'> & {
|
||||
inset?: boolean
|
||||
variant?: 'default' | 'destructive'
|
||||
onSelect?: DropdownMenuItemSelectHandler
|
||||
}) {
|
||||
const handleClick = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLElement>) => {
|
||||
handleDropdownMenuItemSelect(event, onClick, onSelect)
|
||||
},
|
||||
[onClick, onSelect]
|
||||
)
|
||||
|
||||
return (
|
||||
<MenuPrimitive.Item
|
||||
data-slot='dropdown-menu-item'
|
||||
@ -110,6 +124,7 @@ function DropdownMenuItem({
|
||||
"group/dropdown-menu-item focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:*:[svg]:text-destructive relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
onClick={onClick || onSelect ? handleClick : undefined}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user