{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"dropdown-menu","type":"registry:ui","title":"Dropdown Menu","description":"Menu button on the native Popover API with a highlight that glides between items, shortcuts, checkbox and radio items, submenus, typeahead and full keyboard support.","dependencies":["motion","lucide-react"],"registryDependencies":["utils"],"files":[{"path":"registry/ui/dropdown-menu.tsx","type":"registry:ui","content":"\"use client\";\n\nimport {\n  createContext,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type ButtonHTMLAttributes,\n  type Dispatch,\n  type HTMLAttributes,\n  type KeyboardEvent,\n  type MouseEvent,\n  type ReactNode,\n  type RefObject,\n  type SetStateAction,\n} from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { Check, ChevronRight } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n// Menus are native `popover=\"auto\"` elements: top layer, light dismiss, and nested\n// popovers (submenus live inside their parent in the DOM) stack correctly.\nconst css = `\n.ui-menu{position:fixed;inset:auto;margin:0;overflow:visible;opacity:0;scale:.95;translate:var(--from,0 0);transition:opacity .12s ease-in,scale .12s ease-in,translate .12s ease-in,display .12s allow-discrete,overlay .12s allow-discrete}\n.ui-menu:popover-open{opacity:1;scale:1;translate:0 0;transition-duration:.22s;transition-timing-function:cubic-bezier(.2,.9,.3,1.1)}\n@starting-style{.ui-menu:popover-open{opacity:0;scale:.95;translate:var(--from,0 0)}}\n.ui-menu[data-side=bottom]{--from:0 -6px;transform-origin:var(--origin) top}\n.ui-menu[data-side=top]{--from:0 6px;transform-origin:var(--origin) bottom}\n.ui-menu[data-side=right]{--from:-6px 0;transform-origin:left var(--origin)}\n.ui-menu[data-side=left]{--from:6px 0;transform-origin:right var(--origin)}\n@media (prefers-reduced-motion:reduce){.ui-menu{transition-duration:0s!important}}\n`;\n\ntype Side = \"top\" | \"right\" | \"bottom\" | \"left\";\ntype Align = \"start\" | \"center\" | \"end\";\ntype FocusIntent = \"first\" | \"last\" | \"content\" | null;\nconst OPPOSITE = { top: \"bottom\", bottom: \"top\", left: \"right\", right: \"left\" } as const;\n\n/** Position a fixed element beside an anchor: flip if the preferred side lacks room, then shift into the viewport. */\nfunction place(anchor: DOMRect, el: HTMLElement, preferred: Side, align: Align, offset: number, alignOffset: number, pad = 8) {\n  const w = el.offsetWidth;\n  const h = el.offsetHeight;\n  const vw = document.documentElement.clientWidth;\n  const vh = document.documentElement.clientHeight;\n  const room = { top: anchor.top, bottom: vh - anchor.bottom, left: anchor.left, right: vw - anchor.right };\n  let side: Side = preferred;\n  const v0 = side === \"top\" || side === \"bottom\";\n  if (room[side] < (v0 ? h : w) + offset + pad && room[OPPOSITE[side]] > room[side]) side = OPPOSITE[side];\n  const vertical = side === \"top\" || side === \"bottom\";\n  const along = (start: number, size: number, len: number) =>\n    align === \"start\" ? start + alignOffset : align === \"end\" ? start + size - len - alignOffset : start + size / 2 - len / 2;\n  let x = vertical ? along(anchor.left, anchor.width, w) : side === \"right\" ? anchor.right + offset : anchor.left - offset - w;\n  let y = vertical ? (side === \"bottom\" ? anchor.bottom + offset : anchor.top - offset - h) : along(anchor.top, anchor.height, h);\n  x = Math.min(Math.max(pad, x), vw - w - pad);\n  y = Math.min(Math.max(pad, y), vh - h - pad);\n  el.style.left = `${x}px`;\n  el.style.top = `${y}px`;\n  el.dataset.side = side;\n  el.style.setProperty(\"--origin\", `${vertical ? anchor.left + anchor.width / 2 - x : anchor.top + anchor.height / 2 - y}px`);\n}\n\ntype RootCtx = {\n  open: boolean;\n  setOpen: (open: boolean, focus?: FocusIntent) => void;\n  /** Close everything and put focus back on the trigger. */\n  closeAll: () => void;\n  /** Ids of open submenus, one per nesting level. */\n  openPath: string[];\n  setOpenPath: Dispatch<SetStateAction<string[]>>;\n  /** Where focus should land when the next menu opens (consumed once). */\n  setPendingFocus: (focus: FocusIntent) => void;\n  takePendingFocus: () => FocusIntent;\n  ids: { trigger: string; content: string };\n};\ntype MenuCtx = {\n  level: number;\n  highlighted: string | null;\n  setHighlighted: Dispatch<SetStateAction<string | null>>;\n  layoutId: string;\n  menuRef: RefObject<HTMLDivElement | null>;\n};\ntype SubCtx = { id: string; level: number; open: boolean; contentId: string };\ntype RadioCtx = { value?: string; onValueChange?: (value: string) => void };\n\nconst RootContext = createContext<RootCtx | null>(null);\nconst MenuContext = createContext<MenuCtx | null>(null);\nconst SubContext = createContext<SubCtx | null>(null);\nconst RadioContext = createContext<RadioCtx | null>(null);\n\nfunction useRoot() {\n  const ctx = useContext(RootContext);\n  if (!ctx) throw new Error(\"DropdownMenu parts must be rendered inside <DropdownMenu>.\");\n  return ctx;\n}\nfunction useMenu() {\n  const ctx = useContext(MenuContext);\n  if (!ctx) throw new Error(\"Menu items must be rendered inside <DropdownMenuContent>.\");\n  return ctx;\n}\n\nconst menuItems = (menu: HTMLElement) =>\n  [...menu.querySelectorAll<HTMLElement>('[data-menu-item]:not([aria-disabled=\"true\"])')].filter((el) => el.closest('[role=\"menu\"]') === menu);\n\nexport interface DropdownMenuProps {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  children?: ReactNode;\n}\n\nexport function DropdownMenu({ open: openProp, defaultOpen = false, onOpenChange, children }: DropdownMenuProps) {\n  const [inner, setInner] = useState(defaultOpen);\n  const [openPath, setOpenPath] = useState<string[]>([]);\n  const open = openProp ?? inner;\n  const pendingFocus = useRef<FocusIntent>(null);\n  const id = useId();\n  const ids = { trigger: `${id}-trigger`, content: `${id}-menu` };\n  const setPendingFocus = (focus: FocusIntent) => {\n    pendingFocus.current = focus;\n  };\n  const takePendingFocus = () => {\n    const f = pendingFocus.current;\n    pendingFocus.current = null;\n    return f;\n  };\n\n  const setOpen = (next: boolean, focus: FocusIntent = null) => {\n    setPendingFocus(focus);\n    if (!next) setOpenPath([]);\n    if (next === open) return;\n    if (openProp === undefined) setInner(next);\n    onOpenChange?.(next);\n  };\n  const closeAll = () => {\n    setOpen(false);\n    document.getElementById(ids.trigger)?.focus({ preventScroll: true });\n  };\n\n  return (\n    <RootContext value={{ open, setOpen, closeAll, openPath, setOpenPath, setPendingFocus, takePendingFocus, ids }}>\n      {children}\n    </RootContext>\n  );\n}\n\nexport function DropdownMenuTrigger({ className, onClick, onKeyDown, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {\n  const { open, setOpen, ids } = useRoot();\n  return (\n    <button\n      type=\"button\"\n      id={ids.trigger}\n      // Registered as the popover's invoker so light dismiss ignores it; the click itself is ours.\n      popoverTarget={ids.content}\n      aria-haspopup=\"menu\"\n      aria-expanded={open}\n      aria-controls={ids.content}\n      {...props}\n      onClick={(e) => {\n        onClick?.(e);\n        e.preventDefault();\n        setOpen(!open, \"content\");\n      }}\n      onKeyDown={(e) => {\n        onKeyDown?.(e);\n        if (e.defaultPrevented) return;\n        if (e.key === \"ArrowDown\" || e.key === \"Enter\" || e.key === \" \") {\n          e.preventDefault();\n          setOpen(true, \"first\");\n        } else if (e.key === \"ArrowUp\") {\n          e.preventDefault();\n          setOpen(true, \"last\");\n        }\n      }}\n      className={cn(\n        \"inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-border bg-card px-4 text-sm font-medium text-foreground shadow-sm outline-none transition-[background-color,scale] hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/60 active:scale-[0.97] aria-expanded:bg-muted [&_svg]:size-4\",\n        className,\n      )}\n    />\n  );\n}\n\ninterface MenuSurfaceProps extends HTMLAttributes<HTMLDivElement> {\n  open: boolean;\n  level: number;\n  /** Id of the element the menu is anchored to. */\n  anchorId: string;\n  side: Side;\n  align: Align;\n  sideOffset: number;\n  alignOffset: number;\n  /** Native close (light dismiss) happened. */\n  onDismiss: () => void;\n  onEscape: () => void;\n  onArrowLeft?: () => void;\n}\n\nfunction MenuSurface({\n  open,\n  level,\n  anchorId,\n  side,\n  align,\n  sideOffset,\n  alignOffset,\n  onDismiss,\n  onEscape,\n  onArrowLeft,\n  className,\n  children,\n  ...props\n}: MenuSurfaceProps) {\n  const root = useRoot();\n  const { takePendingFocus } = root;\n  const take = useRef(takePendingFocus);\n  const ref = useRef<HTMLDivElement>(null);\n  const [highlighted, setHighlighted] = useState<string | null>(null);\n  const layoutId = useId();\n  const typeahead = useRef({ text: \"\", timer: 0 });\n  const mounted = useRef(false);\n  const wasOpen = useRef(false);\n  const dismiss = useRef(onDismiss);\n  useEffect(() => {\n    dismiss.current = onDismiss;\n    take.current = takePendingFocus;\n  });\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const toggle = (e: Event) => {\n      if ((e as ToggleEvent).newState === \"closed\") dismiss.current();\n    };\n    el.addEventListener(\"toggle\", toggle);\n    return () => el.removeEventListener(\"toggle\", toggle);\n  }, []);\n\n  useEffect(() => {\n    const el = ref.current;\n    const firstRun = !mounted.current;\n    const opening = open && !wasOpen.current;\n    mounted.current = true;\n    wasOpen.current = open;\n    if (!el) return;\n    const shown = el.matches(\":popover-open\");\n    if (!open) {\n      if (shown) el.hidePopover();\n      return;\n    }\n    if (!shown) el.showPopover();\n    const position = () => {\n      const a = document.getElementById(anchorId);\n      if (a) place(a.getBoundingClientRect(), el, side, align, sideOffset, alignOffset);\n    };\n    position();\n    const intent = opening ? (take.current() ?? (level === 0 && !firstRun ? \"content\" : null)) : null;\n    if (intent) {\n      const list = menuItems(el);\n      (intent === \"first\" ? list[0] : intent === \"last\" ? list[list.length - 1] : el)?.focus({ preventScroll: true });\n    }\n    window.addEventListener(\"scroll\", position, { capture: true, passive: true });\n    window.addEventListener(\"resize\", position);\n    return () => {\n      window.removeEventListener(\"scroll\", position, { capture: true });\n      window.removeEventListener(\"resize\", position);\n    };\n  }, [open, side, align, sideOffset, alignOffset, anchorId, level]);\n\n  const onKey = (e: KeyboardEvent<HTMLDivElement>) => {\n    props.onKeyDown?.(e);\n    const menu = e.currentTarget;\n    if (e.defaultPrevented || (e.target as HTMLElement).closest('[role=\"menu\"]') !== menu) return;\n    const list = menuItems(menu);\n    const i = list.indexOf(document.activeElement as HTMLElement);\n    const n = list.length;\n    const focus = (k: number) => list[((k % n) + n) % n]?.focus({ preventScroll: true });\n    switch (e.key) {\n      case \"ArrowDown\":\n        focus(i < 0 ? 0 : i + 1);\n        break;\n      case \"ArrowUp\":\n        focus(i < 0 ? n - 1 : i - 1);\n        break;\n      case \"Home\":\n      case \"PageUp\":\n        focus(0);\n        break;\n      case \"End\":\n      case \"PageDown\":\n        focus(n - 1);\n        break;\n      case \"Escape\":\n        onEscape();\n        break;\n      case \"ArrowLeft\":\n        if (!onArrowLeft) return;\n        onArrowLeft();\n        break;\n      case \"Tab\":\n        root.closeAll();\n        break;\n      default: {\n        // Typeahead: jump to the next item whose label starts with what was typed.\n        if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey) return;\n        const t = typeahead.current;\n        clearTimeout(t.timer);\n        t.text += e.key.toLowerCase();\n        t.timer = window.setTimeout(() => (t.text = \"\"), 500);\n        const start = t.text.length === 1 ? i + 1 : Math.max(i, 0);\n        const hit = [...list.slice(start), ...list.slice(0, start)].find((el) =>\n          (el.dataset.text ?? el.textContent ?? \"\").trim().toLowerCase().startsWith(t.text),\n        );\n        hit?.focus({ preventScroll: true });\n      }\n    }\n    e.preventDefault();\n    e.stopPropagation();\n  };\n\n  return (\n    <MenuContext value={{ level, highlighted, setHighlighted, layoutId, menuRef: ref }}>\n      <div\n        {...props}\n        ref={ref}\n        popover=\"auto\"\n        role=\"menu\"\n        aria-orientation=\"vertical\"\n        tabIndex={-1}\n        data-side={side}\n        onKeyDown={onKey}\n        onPointerLeave={(e) => {\n          props.onPointerLeave?.(e);\n          // Clear the highlight when the pointer leaves (unless a submenu owns it now).\n          if (e.pointerType !== \"touch\" && ref.current?.contains(document.activeElement) && document.activeElement !== ref.current) {\n            if (!root.openPath[level]) ref.current.focus({ preventScroll: true });\n          }\n        }}\n        className={cn(\n          \"ui-menu z-50 min-w-56 rounded-xl border border-border bg-popover p-1 text-sm text-popover-foreground shadow-xl shadow-black/20 outline-none!\",\n          className,\n        )}\n      >\n        <style href=\"ui-dropdown-menu\" precedence=\"default\">\n          {css}\n        </style>\n        {children}\n      </div>\n    </MenuContext>\n  );\n}\n\nexport interface DropdownMenuContentProps extends HTMLAttributes<HTMLDivElement> {\n  side?: Side;\n  align?: Align;\n  sideOffset?: number;\n  alignOffset?: number;\n}\n\nexport function DropdownMenuContent({ side = \"bottom\", align = \"start\", sideOffset = 6, alignOffset = 0, ...props }: DropdownMenuContentProps) {\n  const root = useRoot();\n  return (\n    <MenuSurface\n      {...props}\n      id={root.ids.content}\n      aria-labelledby={root.ids.trigger}\n      open={root.open}\n      level={0}\n      anchorId={root.ids.trigger}\n      side={side}\n      align={align}\n      sideOffset={sideOffset}\n      alignOffset={alignOffset}\n      onDismiss={() => root.setOpen(false)}\n      onEscape={root.closeAll}\n    />\n  );\n}\n\ninterface ItemBaseProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n  role: \"menuitem\" | \"menuitemcheckbox\" | \"menuitemradio\";\n  disabled?: boolean;\n  closeOnSelect: boolean;\n  onActivate?: (e: MouseEvent<HTMLDivElement>) => void;\n  /** Leading slot (icon or check indicator). */\n  lead?: ReactNode;\n  /** Trailing slot (shortcut or chevron). */\n  trail?: ReactNode;\n  destructive?: boolean;\n  textValue?: string;\n}\n\nfunction ItemBase({\n  role,\n  disabled = false,\n  closeOnSelect,\n  onActivate,\n  lead,\n  trail,\n  destructive = false,\n  textValue,\n  className,\n  children,\n  onFocus,\n  onBlur,\n  onPointerMove,\n  onKeyDown,\n  onClick,\n  id: idProp,\n  ...props\n}: ItemBaseProps) {\n  const root = useRoot();\n  const menu = useMenu();\n  const reduce = useReducedMotion();\n  const auto = useId();\n  const id = idProp ?? auto;\n  const active = menu.highlighted === id;\n\n  return (\n    <div\n      {...props}\n      id={id}\n      role={role}\n      tabIndex={-1}\n      data-menu-item=\"\"\n      data-text={textValue}\n      data-highlighted={active || undefined}\n      aria-disabled={disabled || undefined}\n      onFocus={(e) => {\n        onFocus?.(e);\n        menu.setHighlighted(id);\n        // Focusing a sibling closes any submenu opened from this level.\n        root.setOpenPath((p) => (p.length > menu.level && p[menu.level] !== id ? p.slice(0, menu.level) : p));\n      }}\n      onBlur={(e) => {\n        onBlur?.(e);\n        menu.setHighlighted((h) => (h === id ? null : h));\n      }}\n      onPointerMove={(e) => {\n        onPointerMove?.(e);\n        if (e.pointerType === \"touch\" || disabled || document.activeElement === e.currentTarget) return;\n        e.currentTarget.focus({ preventScroll: true });\n      }}\n      onKeyDown={(e) => {\n        onKeyDown?.(e);\n        if (!e.defaultPrevented && (e.key === \"Enter\" || e.key === \" \")) {\n          e.preventDefault();\n          e.currentTarget.click();\n        }\n      }}\n      onClick={(e) => {\n        onClick?.(e);\n        if (disabled || e.defaultPrevented) return;\n        onActivate?.(e);\n        if (!e.defaultPrevented && closeOnSelect) root.closeAll();\n      }}\n      className={cn(\n        \"relative flex h-8 cursor-default items-center gap-2 rounded-md px-2 outline-none! select-none\", // the gliding highlight is the focus indicator\n        \"aria-disabled:pointer-events-none aria-disabled:opacity-45 [&_svg]:size-4 [&_svg]:shrink-0\",\n        destructive ? \"text-destructive\" : \"text-foreground\",\n        className,\n      )}\n    >\n      {active && (\n        <motion.span\n          aria-hidden\n          layoutId={menu.layoutId}\n          transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 700, damping: 45 }}\n          className={cn(\"absolute inset-0 rounded-md\", destructive ? \"bg-destructive/12\" : \"bg-accent\")}\n        />\n      )}\n      {lead !== undefined && (\n        <span aria-hidden className={cn(\"relative grid size-4 place-items-center\", !destructive && \"text-muted-foreground\", active && !destructive && \"text-accent-foreground\")}>\n          {lead}\n        </span>\n      )}\n      <span className={cn(\"relative flex-1 truncate\", active && !destructive && \"text-accent-foreground\")}>{children}</span>\n      {trail !== undefined && <span className=\"relative ml-4 flex items-center text-xs tracking-widest text-muted-foreground\">{trail}</span>}\n    </div>\n  );\n}\n\nexport interface DropdownMenuItemProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n  /** Called on click / Enter / Space. Call e.preventDefault() to keep the menu open. */\n  onSelect?: (e: MouseEvent<HTMLDivElement>) => void;\n  disabled?: boolean;\n  icon?: ReactNode;\n  /** Keyboard shortcut hint shown on the right, e.g. \"⌘S\". */\n  shortcut?: string;\n  variant?: \"default\" | \"destructive\";\n  /** Label used for typeahead when children are not plain text. */\n  textValue?: string;\n}\n\nexport function DropdownMenuItem({ onSelect, icon, shortcut, variant = \"default\", ...props }: DropdownMenuItemProps) {\n  return (\n    <ItemBase\n      {...props}\n      role=\"menuitem\"\n      closeOnSelect\n      onActivate={onSelect}\n      lead={icon}\n      trail={shortcut ? <kbd className=\"font-sans\">{shortcut}</kbd> : undefined}\n      destructive={variant === \"destructive\"}\n    />\n  );\n}\n\nexport interface DropdownMenuCheckboxItemProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n  checked?: boolean;\n  onCheckedChange?: (checked: boolean) => void;\n  disabled?: boolean;\n  shortcut?: string;\n  /** Close the menu after toggling. */\n  closeOnSelect?: boolean;\n  textValue?: string;\n}\n\nexport function DropdownMenuCheckboxItem({ checked = false, onCheckedChange, shortcut, closeOnSelect = false, ...props }: DropdownMenuCheckboxItemProps) {\n  return (\n    <ItemBase\n      {...props}\n      role=\"menuitemcheckbox\"\n      aria-checked={checked}\n      closeOnSelect={closeOnSelect}\n      onActivate={() => onCheckedChange?.(!checked)}\n      lead={\n        <Check\n          strokeWidth={2.75}\n          className={cn(\n            \"text-foreground transition-[scale,opacity] duration-200 ease-[cubic-bezier(.3,1.7,.5,1)] motion-reduce:transition-none\",\n            checked ? \"scale-100 opacity-100\" : \"scale-0 opacity-0\",\n          )}\n        />\n      }\n      trail={shortcut ? <kbd className=\"font-sans\">{shortcut}</kbd> : undefined}\n    />\n  );\n}\n\nexport interface DropdownMenuRadioGroupProps extends HTMLAttributes<HTMLDivElement> {\n  value?: string;\n  onValueChange?: (value: string) => void;\n}\n\nexport function DropdownMenuRadioGroup({ value, onValueChange, ...props }: DropdownMenuRadioGroupProps) {\n  return (\n    <RadioContext value={{ value, onValueChange }}>\n      <div role=\"group\" {...props} />\n    </RadioContext>\n  );\n}\n\nexport interface DropdownMenuRadioItemProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n  value: string;\n  disabled?: boolean;\n  closeOnSelect?: boolean;\n  textValue?: string;\n}\n\nexport function DropdownMenuRadioItem({ value, closeOnSelect = false, ...props }: DropdownMenuRadioItemProps) {\n  const radio = useContext(RadioContext);\n  const checked = radio?.value === value;\n  return (\n    <ItemBase\n      {...props}\n      role=\"menuitemradio\"\n      aria-checked={checked}\n      closeOnSelect={closeOnSelect}\n      onActivate={() => radio?.onValueChange?.(value)}\n      lead={\n        <span\n          className={cn(\n            \"size-2 rounded-full bg-foreground transition-[scale] duration-300 ease-[cubic-bezier(.3,1.8,.5,1)] motion-reduce:transition-none\",\n            checked ? \"scale-100\" : \"scale-0\",\n          )}\n        />\n      }\n    />\n  );\n}\n\nexport function DropdownMenuLabel({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n  return <div role=\"presentation\" {...props} className={cn(\"px-2 pt-2 pb-1 text-xs font-medium text-muted-foreground\", className)} />;\n}\n\nexport function DropdownMenuSeparator({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n  return <div role=\"separator\" {...props} className={cn(\"-mx-1 my-1 h-px bg-border\", className)} />;\n}\n\nexport function DropdownMenuSub({ children }: { children?: ReactNode }) {\n  const root = useRoot();\n  const menu = useMenu();\n  const id = useId();\n  const open = root.open && root.openPath[menu.level] === id;\n  return <SubContext value={{ id, level: menu.level, open, contentId: `${id}-sub` }}>{children}</SubContext>;\n}\n\nfunction useSub() {\n  const ctx = useContext(SubContext);\n  if (!ctx) throw new Error(\"Submenu parts must be rendered inside <DropdownMenuSub>.\");\n  return ctx;\n}\n\nexport interface DropdownMenuSubTriggerProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n  icon?: ReactNode;\n  disabled?: boolean;\n  textValue?: string;\n}\n\nexport function DropdownMenuSubTrigger({ icon, className, onKeyDown, onPointerMove, ...props }: DropdownMenuSubTriggerProps) {\n  const root = useRoot();\n  const sub = useSub();\n  const hoverTimer = useRef(0);\n  useEffect(() => () => clearTimeout(hoverTimer.current), []);\n  const openSub = (focus: FocusIntent) => {\n    root.setPendingFocus(focus);\n    root.setOpenPath((p) => [...p.slice(0, sub.level), sub.id]);\n  };\n  return (\n      <ItemBase\n        {...props}\n        id={sub.id}\n        role=\"menuitem\"\n        aria-haspopup=\"menu\"\n        aria-expanded={sub.open}\n        aria-controls={sub.contentId}\n        closeOnSelect={false}\n        onActivate={() => openSub(\"first\")}\n        lead={icon}\n        trail={\n          <ChevronRight\n            className={cn(\"transition-transform duration-200 motion-reduce:transition-none\", sub.open && \"translate-x-0.5 text-foreground\")}\n          />\n        }\n        onKeyDown={(e) => {\n          onKeyDown?.(e);\n          if (e.key === \"ArrowRight\") {\n            e.preventDefault();\n            e.stopPropagation();\n            openSub(\"first\");\n          }\n        }}\n        onPointerMove={(e) => {\n          onPointerMove?.(e);\n          if (e.pointerType === \"touch\" || sub.open) return;\n          clearTimeout(hoverTimer.current);\n          hoverTimer.current = window.setTimeout(() => openSub(null), 120);\n        }}\n        onPointerLeave={() => clearTimeout(hoverTimer.current)}\n        className={cn(sub.open && \"bg-accent/60\", className)}\n      />\n  );\n}\n\nexport interface DropdownMenuSubContentProps extends HTMLAttributes<HTMLDivElement> {\n  sideOffset?: number;\n  alignOffset?: number;\n}\n\nexport function DropdownMenuSubContent({ sideOffset = 6, alignOffset = -5, ...props }: DropdownMenuSubContentProps) {\n  const root = useRoot();\n  const sub = useSub();\n  const back = () => {\n    root.setOpenPath((p) => p.slice(0, sub.level));\n    document.getElementById(sub.id)?.focus({ preventScroll: true });\n  };\n  return (\n    <MenuSurface\n      {...props}\n      id={sub.contentId}\n      aria-labelledby={sub.id}\n      open={sub.open}\n      level={sub.level + 1}\n      anchorId={sub.id}\n      side=\"right\"\n      align=\"start\"\n      sideOffset={sideOffset}\n      alignOffset={alignOffset}\n      onDismiss={() => root.setOpenPath((p) => (p[sub.level] === sub.id ? p.slice(0, sub.level) : p))}\n      onEscape={back}\n      onArrowLeft={back}\n    />\n  );\n}\n"}]}