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 {
}