import type * as React from 'react'; import type { AutoFocusEvent } from '../utils/dismissal.js'; import type { MenuSubContentProps, MenuSubProps, MenuSubTriggerProps } from '../utils/menuSub.js'; import type { PopperContentProps } from '../utils/popper.js'; import type { PortalProps } from '../utils/portal.js'; import type { PolymorphicTag, PrimitivePropsWithRef } from '../utils/primitive.js'; import type { FocusOutsideEvent, InteractOutsideEvent } from '../utils/useDismiss.js'; /** One menu entry — a discriminated union whose plain action variant carries no `type` field — narrowed with `'type' in item`, recursing for unlimited submenu nesting. */ export type MenuItem = { /** Text rendered inside the item, and fallback typeahead. */ label: string; /** * Called when this item is activated, before the recipe's own `onSelect`. Both fire. * @default undefined — no-op if omitted. */ onSelect?: (() => void) | undefined; /** * Keyboard hint rendered as a `MenubarShortcut` at the item's trailing edge, `aria-hidden`. * @default undefined — no shortcut. */ shortcut?: string | undefined; /** * Marks item `aria-disabled` and blocks selection, staying in roving order and focusable for discoverability. * @default false */ disabled?: boolean | undefined; /** * Text typeahead matches this item by, reflected to `data-text-value`. * @default undefined — the item's rendered text is used. */ textValue?: string | undefined; } | { /** Renders a `MenubarSeparator`: a horizontal rule between item groups. */ type: 'separator'; } | { /** Renders a `MenubarCheckboxItem` (`role="menuitemcheckbox"`) with a `MenubarItemIndicator` shown while checked. */ type: 'checkbox'; /** Text rendered inside the checkbox item. */ label: string; /** * Checked state, externally controlled: reports state via `onCheckedChange`. * @default false */ checked?: boolean | undefined; /** * Fires with the new checked state when the item is selected. * @default undefined — no-op if omitted. */ onCheckedChange?: ((checked: boolean) => void) | undefined; /** * Keyboard hint rendered as a `MenubarShortcut` at the item's trailing edge. * @default undefined — no shortcut. */ shortcut?: string | undefined; /** * Marks item `aria-disabled` and blocks selection, staying focusable. * @default false */ disabled?: boolean | undefined; /** * Text typeahead matches this item by, reflected to `data-text-value`. * @default undefined — the item's rendered text is used. */ textValue?: string | undefined; } | { /** Renders a `MenubarRadioGroup` of mutually-exclusive options. */ type: 'radio-group'; /** * Selected radio option's value, externally controlled. * @default undefined — no option selected. */ value?: string | undefined; /** * Fires with the newly selected option's `value`. * @default undefined — no-op if omitted. */ onValueChange?: ((value: string) => void) | undefined; /** The radio options, one `MenubarRadioItem` each. */ items: { /** Text rendered inside the radio item. */ label: string; /** Compared against group `value` to decide checked state. */ value: string; /** * Marks option `aria-disabled` and blocks selection, staying focusable. * @default false */ disabled?: boolean | undefined; /** * Text typeahead matches this option by, reflected to `data-text-value`. * @default undefined — the option's rendered text is used. */ textValue?: string | undefined; }[]; } | { /** Renders a nested submenu trio: `MenubarSubTrigger` and `MenubarSubContent`. */ type: 'submenu'; /** Text rendered inside the submenu trigger. */ label: string; /** Submenu entries, recursing through the `MenuItem` union. */ items: MenuItem[]; /** * Marks submenu trigger `aria-disabled` and blocks opening, staying focusable. * @default false */ disabled?: boolean | undefined; /** * Text typeahead matches the trigger by, reflected to `data-text-value`. * @default undefined — the trigger's rendered text is used. */ textValue?: string | undefined; } | { /** Renders a `MenubarGroup` (`role="group"`) with optional `MenubarLabel` caption. */ type: 'group'; /** * Group heading rendered as `MenubarLabel`, the group's accessible name. * @default undefined — the group is unnamed. */ label?: string | undefined; /** Group entries, recursing through the `MenuItem` union. */ items: MenuItem[]; }; /** * One menu on the bar: a trigger label and its panel's entries. */ export interface MenubarMenu { /** Text rendered inside the menu's trigger. */ label: string; /** The menu panel's entries. */ items: MenuItem[]; /** * Identifier matched against the bar's open `menu` to open/report by name. * @default undefined — the menu's index, as a string. */ value?: string | undefined; /** * Marks trigger disabled and skips it in the bar's roving focus. * @default false */ disabled?: boolean | undefined; } /** * Which menu end focus lands on when opened: `'first'` (Enter/Space/ArrowDown) * or `'last'` (ArrowUp), following WAI-ARIA menubar semantics. */ export type MenubarOpenIntent = 'first' | 'last'; /** Axis the bar lays triggers on, reflected as `data-orientation`. */ export type MenubarOrientation = 'horizontal' | 'vertical'; /** @internal Shared state distributed from {@link Menubar} to its parts via context. */ export interface MenubarContextValue { menu: string; onMenuChange: (menu: string) => void; /** The bar's axis, read by Trigger to know which arrows open a menu and which rove along the bar. */ orientation: MenubarOrientation; /** Id of the trigger holding the menubar's single tab stop; every other trigger is `tabIndex` -1. */ currentTabStopId: string | null; setCurrentTabStopId: (id: string) => void; } /** Props for the {@link Menubar} recipe, whose standard attributes pass to the `
`; use `MenubarRoot` for manual composition. */ export interface MenubarProps extends Omit, 'onSelect' | 'children'> { /** * The bar's menus, one trigger and panel each. * @default undefined — an empty bar. */ menus?: MenubarMenu[] | undefined; /** * Fires with the activated entry (plain, checkbox, submenu item, or radio option) * after that entry's own `onSelect`/`onCheckedChange`/`onValueChange`. * @default undefined — no-op if omitted. */ onSelect?: ((item: MenuItem) => void) | undefined; /** * Renders an entry's content in place of its `label`; the item element's role, id, wiring * and disabled state stay the library's. Not called for separators or groups. * @default undefined — renders the entry's `label`. */ renderItem?: ((item: MenuItem) => React.ReactNode) | undefined; /** * Currently open menu, for controlled usage; pair with `onMenuChange`. * @default undefined — uncontrolled. */ menu?: string | undefined; /** * Initially open menu's value, for uncontrolled usage. * @default '' */ defaultMenu?: string | undefined; /** * Fires with the newly open menu's value, controlled or uncontrolled. * @default undefined — no-op if omitted. */ onMenuChange?: ((menu: string) => void) | undefined; /** * Layout axis for the bar, reflected as `aria-orientation`; sets which arrows rove * along the bar. An open menu is always vertical. * @default 'horizontal' */ orientation?: MenubarOrientation | undefined; /** * Whether roving focus across the bar wraps from the last trigger to the first. * @default true */ loop?: boolean | undefined; /** * Spacing rung the skin reads for the bar's triggers, mirrored to `data-density`. * @default undefined — the skin's own default spacing. */ density?: 'compact' | 'comfortable' | (string & {}) | undefined; /** * Size rung the skin reads for the bar, mirrored to `data-size` on the root and fed to derived SubContent panels. * @default undefined — the skin's own default size. */ size?: 'sm' | 'md' | 'lg' | (string & {}) | undefined; /** * DOM node the derived Portal renders the overlay into, so it inherits that subtree's theme and stacking context (e.g. a `[data-skdx-theme]` wrapper). * @default undefined — `document.body`. */ portalContainer?: Element | DocumentFragment | null | undefined; } /** @internal Per-menu state distributed from {@link MenubarMenu} to its descendants via context. */ export interface MenubarMenuContextValue { menuValue: string; triggerRef: React.RefObject; contentRef: React.RefObject; contentId: string; /** This menu's Trigger id, which Content points `aria-labelledby` at. */ triggerId: string; open: boolean; /** True once opened by explicit click vs. hover/focus. */ openedExplicitlyRef: React.RefObject; onOpen: () => void; onClose: () => void; /** Returns focus to this menu's Trigger, firing `close-auto-focus` from Escape, Tab, and item selection. */ focusTrigger: () => void; /** Content's `onCloseAutoFocus` handler, parked here so `focusTrigger` can reach it. */ closeAutoFocusRef: React.RefObject<((event: AutoFocusEvent) => void) | undefined>; /** Which menu end the last open request asked for: `'first'` or `'last'`, read once on open. */ openIntentRef: React.RefObject; /** * Bumped on every `onOpen()` call, including a re-request of an * already-open menu, so the auto-focus effect can key off it alongside `open`. */ openRequestId: number; } /** Props for a single {@link MenubarMenu}. */ export interface MenubarMenuProps { /** Unique identifier matched against the bar's open `menu`. */ value: string; /** * The menu's trigger and content, typically `MenubarTrigger` with `MenubarPortal`/`MenubarContent`. * @default undefined — renders nothing. */ children?: React.ReactNode | undefined; /** * Fires once the menu's open state change has finished on screen, after animation ends. * @default undefined — no-op if omitted. */ onOpenChangeComplete?: ((open: boolean) => void) | undefined; } /** Props for {@link MenubarTrigger}, the menubar item that opens its menu's content. */ export interface MenubarTriggerProps extends PrimitivePropsWithRef<'button'> { /** * Disables trigger and excludes it from roving-focus navigation. * @default false */ disabled?: boolean | undefined; } /** Props for {@link MenubarPortal}, which teleports menu content elsewhere in the DOM. */ export interface MenubarPortalProps extends PortalProps { /** * Keep portal mounted while closed, for animation libraries needing outgoing content during exit. * @default false */ forceMount?: boolean | undefined; } /** Props for {@link MenubarContent}, the popover panel of menu items. */ export interface MenubarContentProps extends PopperContentProps { /** * Keep panel mounted while closed, for animation libraries needing outgoing content during exit. * @default false */ forceMount?: boolean | undefined; /** * Whether arrow-key navigation wraps from last item to first. * @default true */ loop?: boolean | undefined; /** * Fires on Escape press while open; call `preventDefault()` to keep open. * @default undefined — no-op if omitted. */ onEscapeKeyDown?: ((event: KeyboardEvent) => void) | undefined; /** * Fires on pointerdown outside content; call `preventDefault()` to keep open. * @default undefined — no-op if omitted. */ onPointerDownOutside?: ((event: PointerEvent) => void) | undefined; /** * Fires when focus moves outside content and menubar; call `preventDefault()` to keep open. * Receives a cancelable stand-in for `focusin` (not itself cancelable) carrying the original. * @default undefined — no-op if omitted. */ onFocusOutside?: ((event: FocusOutsideEvent) => void) | undefined; /** * Fires for either outside interaction (pointerdown or focus) before specific handlers, * so one `preventDefault()` vetoes both paths. * @default undefined — no-op if omitted. */ onInteractOutside?: ((event: InteractOutsideEvent) => void) | undefined; /** * Fires just before focus returns to trigger on Escape, Tab, or item selection; * call `preventDefault()` to place focus yourself. * @default undefined — no-op if omitted. */ onCloseAutoFocus?: ((event: AutoFocusEvent) => void) | undefined; } /** Props for a single {@link MenubarItem} entry. */ export interface MenubarItemProps extends Omit, 'onSelect'> { /** * Tag name from the library's allowlist instead of the default `
`; use `asChild` for elements needing their own attributes. * @default 'div' */ as?: PolymorphicTag | undefined; /** * Whether selecting this item closes the menu; `onSelect`'s `preventDefault()` can veto independently. * @default undefined — selecting the item closes the menu. */ closeOnSelect?: boolean | undefined; /** * Marks item `aria-disabled` and blocks selection, staying in roving order and focusable for discoverability. * @default false */ disabled?: boolean | undefined; /** * Text typeahead matches this item by, reflected to `data-text-value`, important for icon-only or richly rendered items. * @default undefined — the item's rendered text is used. */ textValue?: string | undefined; /** * Called when the item is activated; call `preventDefault()` to keep menu open. * @default undefined — no-op if omitted, menu closes on select. */ onSelect?: ((event: Event) => void) | undefined; } /** Props for {@link MenubarCheckboxItem}, a toggleable menu item. */ export interface MenubarCheckboxItemProps extends MenubarItemProps { /** * Whether the item renders as checked. * @default false */ checked?: boolean | undefined; /** * Fires with the new checked state when the item is selected. * @default undefined — no-op if omitted. */ onCheckedChange?: ((checked: boolean) => void) | undefined; } /** @internal Shared state distributed from {@link MenubarRadioGroup} to its `MenubarRadioItem`s via context. */ export interface MenuRadioGroupContextValue { value?: string | undefined; onValueChange?: ((value: string) => void) | undefined; } /** Props for {@link MenubarRadioGroup}, wrapping mutually-exclusive radio items. */ export interface MenubarRadioGroupProps extends PrimitivePropsWithRef<'div'> { /** * Selected item's value; compared against each radio item's own `value`. * @default undefined — no item checked. */ value?: string | undefined; /** * Fires with the new value when a radio item in this group is selected. * @default undefined — no-op if omitted. */ onValueChange?: ((value: string) => void) | undefined; } /** Props for a single {@link MenubarRadioItem} within a `MenubarRadioGroup`. */ export interface MenubarRadioItemProps extends MenubarItemProps { /** Unique identifier matched against the group's checked `value`. */ value: string; } /** Props for {@link MenubarItemIndicator}, shown while checkbox or radio item is checked. */ export interface MenubarItemIndicatorProps extends PrimitivePropsWithRef<'span'> { /** * Keep indicator mounted while unchecked, for animation libraries needing outgoing content. * @default false */ forceMount?: boolean | undefined; } /** Props for {@link MenubarGroup}, a grouping wrapper for related `MenubarItem`s. */ export interface MenubarGroupProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link MenubarLabel}, a non-interactive heading within a menu. */ export interface MenubarLabelProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link MenubarSeparator}, a visual divider between menu items. */ export interface MenubarSeparatorProps extends PrimitivePropsWithRef<'div'> { } /** Props for {@link MenubarShortcut}, the keyboard hint at an item's trailing edge. */ export interface MenubarShortcutProps extends PrimitivePropsWithRef<'span'> { } /** Props for {@link MenubarSub}, the root of a nested submenu. */ export interface MenubarSubProps extends MenuSubProps { } /** Props for {@link MenubarSubTrigger}, the parent-menu item that opens a submenu and anchors it. */ export interface MenubarSubTriggerProps extends MenuSubTriggerProps { } /** Props for {@link MenubarSubContent}, the submenu's floating panel. */ export interface MenubarSubContentProps extends MenuSubContentProps { }