'use client' import React from 'react' import { cn } from '../../utils/cn' import { Chevron02DownIcon } from '../icons-v2-generated' import { Button, type ButtonProps } from './button' import { ActionsMenuDropdown, type ActionsMenuGroup, type ActionsMenuItem } from './actions-menu' export interface DropdownButtonProps { /** Button label rendered next to the chevron. */ label: React.ReactNode /** Optional leading icon rendered before the label. */ icon?: React.ReactNode /** * Items shown in the dropdown (single flat group). For grouped menus with * separators pass `groups` instead — exactly one of the two is required. */ items?: ActionsMenuItem[] /** Grouped items (with optional separators). Takes precedence over `items`. */ groups?: ActionsMenuGroup[] /** * Render the trigger as a standard `Button` of this variant (`outline`, * `accent`, …) instead of the default card-colored seam trigger. Use this * when the dropdown sits in a row of ordinary buttons and must match them. */ variant?: ButtonProps['variant'] /** Button size for the `variant` trigger. Ignored without `variant`. */ size?: ButtonProps['size'] /** Spinner state for the trigger (either look). */ loading?: boolean disabled?: boolean className?: string ariaLabel?: string align?: 'start' | 'center' | 'end' side?: 'top' | 'right' | 'bottom' | 'left' /** * Forwarded to the dropdown content. The standard Radix dropdown→dialog * recipe: pass `(e) => e.preventDefault()` when a menu item opens a modal, * otherwise closing the menu refocuses its trigger AFTER the dialog has * taken focus, yanking focus back OUT of the modal the action just opened. */ onCloseAutoFocus?: (event: Event) => void } /** * THE multi-action button — a labeled trigger with a chevron opening an * `ActionsMenu`. One component, so the trigger styling, chevron, and menu * chrome cannot drift between surfaces. * * Two trigger looks — BOTH render through the unified `Button`: * - default (no `variant`): the card-colored seam trigger — `Button * variant="outline"` with the chevron in its `splitIcon` slot, so the label * and chevron are separated by a divider, the whole surface is one click * target, and the chevron rotates while open. Used standalone or via * `PageActions` when a `PageActionButton` has `dropdownItems` set. * - `variant="outline"` (or any Button variant): a standard `Button` with the * chevron as its right icon, so the trigger matches sibling buttons in the * same row — including `loading` and `leftIcon` support. * * Item clicks close the menu (ActionsMenuDropdown's default; opt out per item * with `closeOnSelect: false`). */ export function DropdownButton({ label, icon, items, groups, variant, size = 'small-legacy', loading = false, disabled, className, ariaLabel, align = 'end', side = 'bottom', onCloseAutoFocus }: DropdownButtonProps) { const [open, setOpen] = React.useState(false) // If the trigger becomes disabled/loading WHILE the menu is open, close it — // and only ever block OPEN transitions below, so Escape/outside-click can // still dismiss an already-open menu. React.useEffect(() => { if (disabled || loading) setOpen(false) }, [disabled, loading]) const handleOpenChange = React.useCallback( (next: boolean) => { if (next && (disabled || loading)) return setOpen(next) }, [disabled, loading] ) const resolvedAriaLabel = ariaLabel || (typeof label === 'string' ? label : undefined) const trigger = variant ? ( ) : ( // The unified `Button`'s split layout IS the seam anatomy (label, full-height // divider, trailing glyph, one click target) — no bespoke ) return ( ) }