{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu-submenu",
  "title": "Menu-submenu context",
  "description": "Styled context: the Menu-submenu set. 5 decorative context variations (NestedContextMenu, FlyoutContextMenu, InlineContextMenu, DeepContextMenu, WideContextMenu) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/context-menu-submenu.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDown, ChevronRight, ClipboardPaste, Copy, Share2 } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Submenu context menu family: 5 nested menus. The shell is the same as\n   context-menu-styled - the target area opens a fixed menu at the cursor via\n   onContextMenu (preventDefault), and it closes on an outside pointerdown / Escape /\n   item selection. The difference: rows can carry a `sub`; the submenu opens on hover\n   or with ArrowRight and closes with ArrowLeft. There are two modes: flyout (a panel\n   opening to the side) and inline (a list expanding beneath the row). NO radix.\n   Color comes ONLY from tokens, via alpha color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nexport type StyledContextMenuItem = {\n  label: React.ReactNode\n  icon?: React.ReactNode\n  onSelect?: () => void\n  shortcut?: React.ReactNode\n  group?: string\n  danger?: boolean\n  sub?: StyledContextMenuItem[]\n}\n\ntype ContextMenuProps = {\n  className?: string\n  size?: StyledSize\n  children?: React.ReactNode\n  items?: StyledContextMenuItem[]\n}\n\nconst menuSize: Record<StyledSize, string> = {\n  sm: \"w-48\",\n  md: \"w-56\",\n  lg: \"w-64\",\n  xl: \"w-72\",\n}\n\nconst targetBox =\n  \"flex h-28 w-full select-none items-center justify-center rounded-xl border border-dashed border-border bg-[color-mix(in_oklab,var(--color-foreground)_3%,transparent)] px-6 text-center text-sm text-muted-foreground\"\n\n/* The submenu can extend outside the panel, so there is NO overflow-hidden here. */\nconst menuBase =\n  \"fixed z-50 origin-top-left rounded-xl border border-border bg-popover p-1.5 text-sm text-popover-foreground shadow-lg outline-none\"\n\nconst subPanel =\n  \"absolute top-0 z-50 rounded-xl border border-border bg-popover p-1.5 text-sm text-popover-foreground shadow-lg outline-none\"\n\nconst itemBase =\n  \"flex w-full cursor-default items-center gap-2 rounded-md px-3 py-2 text-left text-sm text-popover-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&>svg]:size-4 [&>svg]:shrink-0 [&>i]:text-base [&>i]:leading-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst itemDanger =\n  \"flex w-full cursor-default items-center gap-2 rounded-md px-3 py-2 text-left text-sm text-danger outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-danger)_12%,transparent)] focus-visible:bg-[color-mix(in_oklab,var(--color-danger)_12%,transparent)] focus-visible:ring-[3px] focus-visible:ring-danger/40 [&>svg]:size-4 [&>svg]:shrink-0 [&>i]:text-base [&>i]:leading-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\nconst shortcutChip =\n  \"ml-auto inline-flex h-5 items-center rounded border border-border bg-[color-mix(in_oklab,var(--color-foreground)_5%,transparent)] px-1.5 font-mono text-[11px] text-muted-foreground\"\n\nconst defaultItems: StyledContextMenuItem[] = [\n  { label: \"Copy\", icon: <Copy />, shortcut: \"Ctrl C\" },\n  { label: \"Paste\", icon: <ClipboardPaste />, shortcut: \"Ctrl V\" },\n  {\n    label: \"Share\",\n    icon: <Share2 />,\n    sub: [{ label: \"Copy link\" }, { label: \"Email\" }, { label: \"Embed\" }],\n  },\n  {\n    label: \"Move to\",\n    sub: [{ label: \"Inbox\" }, { label: \"Archive\" }, { label: \"Trash\" }],\n  },\n]\n\nconst deepItems: StyledContextMenuItem[] = [\n  { label: \"Open\", icon: <Copy /> },\n  {\n    label: \"Export as\",\n    sub: [\n      { label: \"Image\" },\n      {\n        label: \"Document\",\n        sub: [{ label: \"PDF\" }, { label: \"Word\" }, { label: \"Markdown\" }],\n      },\n      {\n        label: \"Data\",\n        sub: [{ label: \"CSV\" }, { label: \"JSON\" }],\n      },\n    ],\n  },\n  { label: \"Rename\" },\n]\n\nfunction focusFirstIn(node: HTMLElement | null) {\n  const first = node?.querySelector<HTMLElement>('[data-menu-item=\"true\"]')\n  first?.focus()\n}\n\nfunction MenuItemButton({\n  item,\n  close,\n}: {\n  item: StyledContextMenuItem\n  close: () => void\n}) {\n  return (\n    <button\n      type=\"button\"\n      role=\"menuitem\"\n      data-menu-item=\"true\"\n      onClick={() => {\n        item.onSelect?.()\n        close()\n      }}\n      className={item.danger ? itemDanger : itemBase}\n    >\n      {item.icon ? (\n        <span className=\"inline-flex shrink-0\" aria-hidden>\n          {item.icon}\n        </span>\n      ) : null}\n      <span className=\"flex-1 truncate\">{item.label}</span>\n      {item.shortcut ? <kbd className={shortcutChip}>{item.shortcut}</kbd> : null}\n    </button>\n  )\n}\n\n/* A row carrying a submenu. Hover or ArrowRight opens it, ArrowLeft closes it and returns focus to the trigger. With mode=\"inline\" it expands under the row instead of in a panel. */\nfunction SubmenuRow({\n  item,\n  close,\n  mode,\n  subWidth,\n  gap,\n}: {\n  item: StyledContextMenuItem\n  close: () => void\n  mode: \"flyout\" | \"inline\"\n  subWidth: string\n  gap: string\n}) {\n  const [open, setOpen] = React.useState(false)\n  const reduce = useReducedMotion()\n  const triggerRef = React.useRef<HTMLButtonElement>(null)\n  const subRef = React.useRef<HTMLDivElement>(null)\n  const subId = React.useId()\n\n  const closeSub = React.useCallback(() => {\n    setOpen(false)\n    triggerRef.current?.focus()\n  }, [])\n\n  const onTriggerKey = (e: React.KeyboardEvent) => {\n    if (e.key === \"ArrowRight\") {\n      e.preventDefault()\n      e.stopPropagation()\n      setOpen(true)\n      window.requestAnimationFrame(() => focusFirstIn(subRef.current))\n    }\n    if (e.key === \"ArrowLeft\") {\n      e.preventDefault()\n      e.stopPropagation()\n      setOpen(false)\n    }\n  }\n\n  const onSubKey = (e: React.KeyboardEvent) => {\n    if (e.key === \"ArrowLeft\") {\n      e.preventDefault()\n      e.stopPropagation()\n      closeSub()\n    }\n  }\n\n  const anim = reduce\n    ? { initial: { opacity: 1 }, animate: { opacity: 1 }, exit: { opacity: 1 } }\n    : {\n        initial: { opacity: 0, x: mode === \"flyout\" ? -6 : 0, y: mode === \"inline\" ? -4 : 0 },\n        animate: { opacity: 1, x: 0, y: 0 },\n        exit: { opacity: 0, x: mode === \"flyout\" ? -6 : 0, y: mode === \"inline\" ? -4 : 0 },\n      }\n\n  return (\n    <div\n      className=\"relative\"\n      onMouseEnter={mode === \"flyout\" ? () => setOpen(true) : undefined}\n      onMouseLeave={mode === \"flyout\" ? () => setOpen(false) : undefined}\n    >\n      <button\n        ref={triggerRef}\n        type=\"button\"\n        role=\"menuitem\"\n        data-menu-item=\"true\"\n        aria-haspopup=\"menu\"\n        aria-expanded={open}\n        aria-controls={open ? subId : undefined}\n        onKeyDown={onTriggerKey}\n        onClick={() => setOpen((o) => !o)}\n        className={itemBase}\n      >\n        {item.icon ? (\n          <span className=\"inline-flex shrink-0\" aria-hidden>\n            {item.icon}\n          </span>\n        ) : null}\n        <span className=\"flex-1 truncate\">{item.label}</span>\n        <span className=\"ml-auto inline-flex text-muted-foreground\" aria-hidden>\n          {mode === \"inline\" ? (\n            <ChevronDown className={cn(\"transition-transform\", open && \"rotate-180\")} />\n          ) : (\n            <ChevronRight />\n          )}\n        </span>\n      </button>\n\n      <AnimatePresence>\n        {open ? (\n          <motion.div\n            ref={subRef}\n            id={subId}\n            role=\"menu\"\n            data-slot=\"styled-context-menu-sub\"\n            onKeyDown={onSubKey}\n            className={\n              mode === \"inline\"\n                ? \"mt-0.5 flex flex-col border-l border-border pl-2\"\n                : cn(subPanel, subWidth, \"left-full\", gap)\n            }\n            initial={anim.initial}\n            animate={anim.animate}\n            exit={anim.exit}\n            transition={reduce ? { duration: 0 } : { duration: 0.12, ease: \"easeOut\" }}\n          >\n            <MenuRows\n              items={item.sub ?? []}\n              close={close}\n              mode={mode}\n              subWidth={subWidth}\n              gap={gap}\n            />\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\nfunction MenuRows({\n  items,\n  close,\n  mode,\n  subWidth,\n  gap,\n}: {\n  items: StyledContextMenuItem[]\n  close: () => void\n  mode: \"flyout\" | \"inline\"\n  subWidth: string\n  gap: string\n}) {\n  return (\n    <div className=\"flex flex-col\">\n      {items.map((item, i) =>\n        item.sub && item.sub.length > 0 ? (\n          <SubmenuRow key={i} item={item} close={close} mode={mode} subWidth={subWidth} gap={gap} />\n        ) : (\n          <MenuItemButton key={i} item={item} close={close} />\n        )\n      )}\n    </div>\n  )\n}\n\n/* Shared shell: the target area plus a fixed AnimatePresence menu at the cursor position. */\nfunction SubmenuContextMenuShell({\n  className,\n  size = \"md\",\n  children,\n  items,\n  mode,\n  subWidth,\n  gap,\n}: ContextMenuProps & {\n  items: StyledContextMenuItem[]\n  mode: \"flyout\" | \"inline\"\n  subWidth: string\n  gap: string\n}) {\n  const [open, setOpen] = React.useState(false)\n  const [coords, setCoords] = React.useState({ x: 0, y: 0 })\n  const reduce = useReducedMotion()\n  const menuRef = React.useRef<HTMLDivElement>(null)\n  const menuId = React.useId()\n\n  const close = React.useCallback(() => setOpen(false), [])\n\n  const onContextMenu = (e: React.MouseEvent) => {\n    e.preventDefault()\n    setCoords({ x: e.clientX, y: e.clientY })\n    setOpen(true)\n  }\n\n  React.useEffect(() => {\n    if (!open) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        setOpen(false)\n        return\n      }\n      if (e.key !== \"ArrowDown\" && e.key !== \"ArrowUp\") return\n      const node = menuRef.current\n      if (!node) return\n      const rows = Array.from(\n        node.querySelectorAll<HTMLElement>('[data-menu-item=\"true\"]')\n      )\n      if (rows.length === 0) return\n      e.preventDefault()\n      const current = rows.indexOf(document.activeElement as HTMLElement)\n      const next =\n        e.key === \"ArrowDown\"\n          ? (current + 1) % rows.length\n          : current <= 0\n            ? rows.length - 1\n            : current - 1\n      rows[next]?.focus()\n    }\n    const onPointer = (e: PointerEvent) => {\n      const node = menuRef.current\n      if (node && e.target instanceof Node && !node.contains(e.target)) {\n        setOpen(false)\n      }\n    }\n    window.addEventListener(\"keydown\", onKey)\n    window.addEventListener(\"pointerdown\", onPointer)\n    return () => {\n      window.removeEventListener(\"keydown\", onKey)\n      window.removeEventListener(\"pointerdown\", onPointer)\n    }\n  }, [open])\n\n  const anim = reduce\n    ? { initial: { opacity: 1 }, animate: { opacity: 1 }, exit: { opacity: 1 } }\n    : {\n        initial: { opacity: 0, scale: 0.96 },\n        animate: { opacity: 1, scale: 1 },\n        exit: { opacity: 0, scale: 0.96 },\n      }\n\n  return (\n    <div data-slot=\"styled-context-menu\" className=\"w-full\">\n      <div\n        data-slot=\"styled-context-menu-target\"\n        onContextMenu={onContextMenu}\n        className={cn(targetBox, className)}\n      >\n        {children ?? \"Right-click here\"}\n      </div>\n\n      <AnimatePresence>\n        {open ? (\n          <motion.div\n            ref={menuRef}\n            id={menuId}\n            role=\"menu\"\n            data-slot=\"styled-context-menu-content\"\n            style={{ left: coords.x, top: coords.y }}\n            className={cn(menuBase, menuSize[size])}\n            initial={anim.initial}\n            animate={anim.animate}\n            exit={anim.exit}\n            transition={reduce ? { duration: 0 } : { duration: 0.14, ease: \"easeOut\" }}\n          >\n            <MenuRows items={items} close={close} mode={mode} subWidth={subWidth} gap={gap} />\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n/* Nested: klasik yandan acilan alt menu, panele bitisik. */\nexport function NestedContextMenu({ className, size, children, items }: ContextMenuProps) {\n  return (\n    <SubmenuContextMenuShell\n      className={className}\n      size={size}\n      items={items ?? defaultItems}\n      mode=\"flyout\"\n      subWidth=\"w-48\"\n      gap=\"ml-0.5\"\n    >\n      {children}\n    </SubmenuContextMenuShell>\n  )\n}\n\n/* Flyout: alt menu belirgin bosluk ve derin golge ile yana ucar. */\nexport function FlyoutContextMenu({ className, size, children, items }: ContextMenuProps) {\n  return (\n    <SubmenuContextMenuShell\n      className={className}\n      size={size}\n      items={items ?? defaultItems}\n      mode=\"flyout\"\n      subWidth=\"w-52 shadow-xl\"\n      gap=\"ml-2\"\n    >\n      {children}\n    </SubmenuContextMenuShell>\n  )\n}\n\n/* Inline: alt menu ucmaz, satirin altinda girintili olarak genisler. */\nexport function InlineContextMenu({ className, size, children, items }: ContextMenuProps) {\n  return (\n    <SubmenuContextMenuShell\n      className={className}\n      size={size}\n      items={items ?? defaultItems}\n      mode=\"inline\"\n      subWidth=\"w-full\"\n      gap=\"\"\n    >\n      {children}\n    </SubmenuContextMenuShell>\n  )\n}\n\n/* Deep: alt menunun alt menusu, uc kademe derinlik. */\nexport function DeepContextMenu({ className, size, children, items }: ContextMenuProps) {\n  return (\n    <SubmenuContextMenuShell\n      className={className}\n      size={size}\n      items={items ?? deepItems}\n      mode=\"flyout\"\n      subWidth=\"w-44\"\n      gap=\"ml-0.5\"\n    >\n      {children}\n    </SubmenuContextMenuShell>\n  )\n}\n\n/* Wide: wide submenu panels, for long labels. */\nexport function WideContextMenu({ className, size, children, items }: ContextMenuProps) {\n  return (\n    <SubmenuContextMenuShell\n      className={className}\n      size={size}\n      items={items ?? defaultItems}\n      mode=\"flyout\"\n      subWidth=\"w-72\"\n      gap=\"ml-1\"\n    >\n      {children}\n    </SubmenuContextMenuShell>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/context-menu-submenu.tsx"
    }
  ],
  "categories": [
    "styled",
    "context"
  ],
  "type": "registry:component"
}