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 0000000000..0f49d3f81e --- /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 0000000000..892991b180 --- /dev/null +++ b/web/default/src/components/ui/dropdown-menu.test.tsx @@ -0,0 +1,51 @@ +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 e061bd26ab..714d98feac 100644 --- a/web/default/src/components/ui/dropdown-menu.tsx +++ b/web/default/src/components/ui/dropdown-menu.tsx @@ -23,6 +23,11 @@ import * as React from 'react' import { cn } from '@/lib/utils' +import { + handleDropdownMenuItemSelect, + type DropdownMenuItemSelectHandler, +} from './dropdown-menu-events' + function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { return } @@ -97,11 +102,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 ( ) diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json index eeeeb2f34d..684000459d 100644 --- a/web/default/src/i18n/locales/en.json +++ b/web/default/src/i18n/locales/en.json @@ -2322,6 +2322,7 @@ "Inter-group overrides": "Inter-group overrides", "Inter-group ratio overrides": "Inter-group ratio overrides", "Interface Language": "Interface Language", + "Internal ID": "Internal ID", "Internal Notes": "Internal Notes", "Internal notes (not shown to users)": "Internal notes (not shown to users)", "Internal Server Error!": "Internal Server Error!", diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json index 863a941607..41434c1b42 100644 --- a/web/default/src/i18n/locales/fr.json +++ b/web/default/src/i18n/locales/fr.json @@ -2322,6 +2322,7 @@ "Inter-group overrides": "Dérogations inter-groupes", "Inter-group ratio overrides": "Dérogations de ratio inter-groupes", "Interface Language": "Langue de l'interface", + "Internal ID": "ID interne", "Internal Notes": "Notes internes", "Internal notes (not shown to users)": "Notes internes (non visibles par les utilisateurs)", "Internal Server Error!": "Erreur interne du serveur !", diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json index c8e04c340e..dda29bd4e4 100644 --- a/web/default/src/i18n/locales/ja.json +++ b/web/default/src/i18n/locales/ja.json @@ -2322,6 +2322,7 @@ "Inter-group overrides": "グループ間上書き", "Inter-group ratio overrides": "グループ間比率上書き", "Interface Language": "インターフェース言語", + "Internal ID": "内部ID", "Internal Notes": "内部メモ", "Internal notes (not shown to users)": ":内部メモ(ユーザーには表示されません)", "Internal Server Error!": "内部サーバーエラー!", diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json index d4f201cea4..f695edc970 100644 --- a/web/default/src/i18n/locales/ru.json +++ b/web/default/src/i18n/locales/ru.json @@ -2322,6 +2322,7 @@ "Inter-group overrides": "Переопределения между группами", "Inter-group ratio overrides": "Переопределения соотношений между группами", "Interface Language": "Язык интерфейса", + "Internal ID": "Внутренний ID", "Internal Notes": "Внутренние заметки", "Internal notes (not shown to users)": "Внутренние заметки (не показываются пользователям)", "Internal Server Error!": "Внутренняя ошибка сервера!", diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json index 78d240e822..c8abead643 100644 --- a/web/default/src/i18n/locales/vi.json +++ b/web/default/src/i18n/locales/vi.json @@ -2322,6 +2322,7 @@ "Inter-group overrides": "Ghi đè liên nhóm", "Inter-group ratio overrides": "Tỷ lệ liên nhóm ghi đè", "Interface Language": "Ngôn ngữ giao diện", + "Internal ID": "ID nội bộ", "Internal Notes": "Ghi chú nội bộ", "Internal notes (not shown to users)": "Ghi chú nội bộ (không hiển thị cho người dùng)", "Internal Server Error!": "Lỗi máy chủ nội bộ!", diff --git a/web/default/src/i18n/locales/zh-TW.json b/web/default/src/i18n/locales/zh-TW.json index a288cfd71c..457be2fa93 100644 --- a/web/default/src/i18n/locales/zh-TW.json +++ b/web/default/src/i18n/locales/zh-TW.json @@ -2322,6 +2322,7 @@ "Inter-group overrides": "分組間覆蓋", "Inter-group ratio overrides": "分組間比例覆蓋", "Interface Language": "介面語言", + "Internal ID": "內部 ID", "Internal Notes": "內部備註", "Internal notes (not shown to users)": "內部備註(不顯示給用戶)", "Internal Server Error!": "內部伺服器錯誤!", diff --git a/web/default/src/i18n/locales/zh.json b/web/default/src/i18n/locales/zh.json index 25b612e72a..b4ffcafad8 100644 --- a/web/default/src/i18n/locales/zh.json +++ b/web/default/src/i18n/locales/zh.json @@ -2322,6 +2322,7 @@ "Inter-group overrides": "分组间覆盖", "Inter-group ratio overrides": "分组间比例覆盖", "Interface Language": "界面语言", + "Internal ID": "内部 ID", "Internal Notes": "内部备注", "Internal notes (not shown to users)": "内部备注(不显示给用户)", "Internal Server Error!": "内部服务器错误!",