From a32ab27ce0ce573fd9ab5dcbe0c6269b32b44ebe Mon Sep 17 00:00:00 2001 From: yyh Date: Tue, 3 Mar 2026 14:45:02 +0800 Subject: [PATCH] refactor(dropdown-menu): improve primitive defaults and deduplicate account-dropdown - Add overflow handling (max-h-[var(--available-height)]) to Popup - Add disabled styles (cursor-not-allowed, opacity-50) to Item and SubTrigger - Change hover token to bg-state-base-hover for consistency - Build arrow icon into SubTrigger so callers don't repeat it - Style DropdownMenuGroupLabel with default typography - Extract shared MenuItemContent and ExternalLinkIndicator into menu-item-content.tsx - Remove duplicated className constants and component definitions across account-dropdown files - Remove !important overrides from callers now that primitive defaults are correct - Remove manual max-h-[70vh] from SubContent (handled by primitive) --- .../base/ui/dropdown-menu/index.tsx | 30 +++++++-- .../header/account-dropdown/compliance.tsx | 36 ++--------- .../header/account-dropdown/index.tsx | 43 +++---------- .../account-dropdown/menu-item-content.tsx | 31 ++++++++++ .../header/account-dropdown/support.tsx | 62 +++++-------------- 5 files changed, 84 insertions(+), 118 deletions(-) create mode 100644 web/app/components/header/account-dropdown/menu-item-content.tsx diff --git a/web/app/components/base/ui/dropdown-menu/index.tsx b/web/app/components/base/ui/dropdown-menu/index.tsx index 3eac03f7db..941106cdae 100644 --- a/web/app/components/base/ui/dropdown-menu/index.tsx +++ b/web/app/components/base/ui/dropdown-menu/index.tsx @@ -11,13 +11,27 @@ export const DropdownMenuPortal = Menu.Portal export const DropdownMenuTrigger = Menu.Trigger export const DropdownMenuSub = Menu.SubmenuRoot export const DropdownMenuGroup = Menu.Group -export const DropdownMenuGroupLabel = Menu.GroupLabel export const DropdownMenuRadioGroup = Menu.RadioGroup export const DropdownMenuRadioItem = Menu.RadioItem export const DropdownMenuRadioItemIndicator = Menu.RadioItemIndicator export const DropdownMenuCheckboxItem = Menu.CheckboxItem export const DropdownMenuCheckboxItemIndicator = Menu.CheckboxItemIndicator +export function DropdownMenuGroupLabel({ + className, + ...props +}: React.ComponentPropsWithoutRef) { + return ( + + ) +} + type DropdownMenuContentProps = { children: React.ReactNode placement?: Placement @@ -69,7 +83,7 @@ function renderDropdownMenuPopup({ > + > + {children} + + ) } @@ -172,7 +191,8 @@ export function DropdownMenuItem({ - -
{label}
- {trailing} - - ) -} - type ComplianceDocActionVisualProps = { isCurrentPlanCanDownload: boolean isPending: boolean @@ -176,7 +151,7 @@ function ComplianceDocRowItem({ return ( @@ -199,15 +174,14 @@ export default function Compliance() { return ( - - + } /> - -
{label}
- {trailing} - - ) -} type AccountMenuRouteItemProps = { href: string @@ -67,10 +44,10 @@ function AccountMenuRouteItem({ }: AccountMenuRouteItemProps) { return ( } > - + ) } @@ -90,10 +67,10 @@ function AccountMenuExternalItem({ }: AccountMenuExternalItemProps) { return ( } > - + ) } @@ -113,18 +90,14 @@ function AccountMenuActionItem({ }: AccountMenuActionItemProps) { return ( - + ) } -function ExternalLinkIndicator() { - return -} - type AccountMenuSectionProps = { children: ReactNode } @@ -257,7 +230,7 @@ export default function AppSelector() { )}
- } diff --git a/web/app/components/header/account-dropdown/menu-item-content.tsx b/web/app/components/header/account-dropdown/menu-item-content.tsx new file mode 100644 index 0000000000..47f0042047 --- /dev/null +++ b/web/app/components/header/account-dropdown/menu-item-content.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from 'react' +import { cn } from '@/utils/classnames' + +const menuLabelClassName = 'min-w-0 grow truncate px-1 text-text-secondary system-md-regular' +const menuLeadingIconClassName = 'size-4 shrink-0 text-text-tertiary' + +export const menuTrailingIconClassName = 'size-[14px] shrink-0 text-text-tertiary' + +type MenuItemContentProps = { + iconClassName: string + label: ReactNode + trailing?: ReactNode +} + +export function MenuItemContent({ + iconClassName, + label, + trailing, +}: MenuItemContentProps) { + return ( + <> + +
{label}
+ {trailing} + + ) +} + +export function ExternalLinkIndicator() { + return +} diff --git a/web/app/components/header/account-dropdown/support.tsx b/web/app/components/header/account-dropdown/support.tsx index fc3d3ab7f9..7ec2766977 100644 --- a/web/app/components/header/account-dropdown/support.tsx +++ b/web/app/components/header/account-dropdown/support.tsx @@ -1,4 +1,3 @@ -import type { ReactNode } from 'react' import { useTranslation } from 'react-i18next' import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu' import { toggleZendeskWindow } from '@/app/components/base/zendesk/utils' @@ -6,43 +5,13 @@ import { Plan } from '@/app/components/billing/type' import { ZENDESK_WIDGET_KEY } from '@/config' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' -import { cn } from '@/utils/classnames' import { mailToSupport } from '../utils/util' - -const submenuTriggerClassName = '!mx-0 !h-8 !rounded-lg !px-3 data-[highlighted]:!bg-state-base-hover' -const submenuItemClassName = '!mx-0 !h-8 !rounded-lg !px-3 data-[highlighted]:!bg-state-base-hover' -const menuLabelClassName = 'grow px-1 text-text-secondary system-md-regular' -const menuLeadingIconClassName = 'size-4 shrink-0 text-text-tertiary' -const menuTrailingIconClassName = 'size-[14px] shrink-0 text-text-tertiary' +import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content' type SupportProps = { closeAccountDropdown: () => void } -type SupportMenuItemContentProps = { - iconClassName: string - label: ReactNode - trailing?: ReactNode -} - -function SupportMenuItemContent({ - iconClassName, - label, - trailing, -}: SupportMenuItemContentProps) { - return ( - <> - -
{label}
- {trailing} - - ) -} - -function SupportExternalLinkIndicator() { - return -} - // Submenu-only: this component must be rendered within an existing DropdownMenu root. export default function Support({ closeAccountDropdown }: SupportProps) { const { t } = useTranslation() @@ -53,26 +22,25 @@ export default function Support({ closeAccountDropdown }: SupportProps) { return ( - - + } /> {hasDedicatedChannel && hasZendeskWidget && ( { toggleZendeskWindow(true) closeAccountDropdown() }} > - @@ -80,34 +48,34 @@ export default function Support({ closeAccountDropdown }: SupportProps) { )} {hasDedicatedChannel && !hasZendeskWidget && ( } > - } + trailing={} /> )} } > - } + trailing={} /> } > - } + trailing={} />