From baf668c48d4f9c22a217a25d979f56a397fcc585 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sun, 12 Jul 2026 02:58:56 +0800 Subject: [PATCH] fix(web): restore DropdownMenuItem onSelect bridge and add missing Internal ID locale key The design-system consolidation refactor dropped the onSelect-to-onClick bridge for Base UI menu items, silently breaking menu actions such as 'Delete All Disabled' on the channels page, and removed the 'Internal ID' translation key still used by the task details dialog. --- .../src/components/ui/dropdown-menu-events.ts | 25 +++++++++ .../src/components/ui/dropdown-menu.test.tsx | 51 +++++++++++++++++++ .../src/components/ui/dropdown-menu.tsx | 18 ++++++- web/default/src/i18n/locales/en.json | 1 + web/default/src/i18n/locales/fr.json | 1 + web/default/src/i18n/locales/ja.json | 1 + web/default/src/i18n/locales/ru.json | 1 + web/default/src/i18n/locales/vi.json | 1 + web/default/src/i18n/locales/zh-TW.json | 1 + web/default/src/i18n/locales/zh.json | 1 + 10 files changed, 100 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/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!": "内部服务器错误!",