{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu",
  "title": "Context-menu",
  "description": "A context-menu component.",
  "dependencies": [
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "@cubby-ui/switch",
    "@cubby-ui/elevated"
  ],
  "files": [
    {
      "path": "registry/default/context-menu/context-menu.tsx",
      "content": "\"use client\";\r\n\r\nimport * as React from \"react\";\r\nimport { ContextMenu as BaseContextMenu } from \"@base-ui/react/context-menu\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n  solidSurface,\r\n  type SurfaceLevel,\r\n} from \"@/registry/default/lib/elevated\";\r\n\r\nimport { HugeiconsIcon } from \"@hugeicons/react\";\r\nimport { ArrowRight01Icon, Tick02Icon } from \"@hugeicons/core-free-icons\";\r\nimport {\r\n  SwitchVisual,\r\n  type SwitchVisualProps,\r\n} from \"@/registry/default/switch/switch\";\r\n\r\n// Shared shell for checkbox and radio items. Padding matches the plain menu\r\n// item so labels line up across every item type; the indicator lives in a\r\n// reserved right-hand column so toggling never shifts the label.\r\nconst toggleItemClasses =\r\n  \"group/switch data-highlighted:bg-surface-hover data-highlighted:text-accent-foreground grid cursor-default items-center rounded-md px-2.5 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-60 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\";\r\n\r\n// The tick draws itself in on check. `pathLength` restates the path as 1 unit\r\n// long, so the dash values are fractions of the stroke and survive a HugeIcons\r\n// reshape. Deriving the icon array is the only way to reach the path.\r\nconst tickIcon = Tick02Icon.map(([tag, attrs]) => [\r\n  tag,\r\n  { ...attrs, pathLength: 1 },\r\n]) as typeof Tick02Icon;\r\n\r\nconst checkmarkClasses =\r\n  \"[&_path]:ease-out-expo [&_path]:transition-[stroke-dashoffset] [&_path]:duration-150 [&_path]:[stroke-dasharray:1] in-data-checked:[&_path]:[stroke-dashoffset:0] in-data-unchecked:[&_path]:[stroke-dashoffset:1] motion-reduce:[&_path]:transition-none\";\r\n\r\nfunction ContextMenu({\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Root>) {\r\n  return <BaseContextMenu.Root data-slot=\"context-menu\" {...props} />;\r\n}\r\n\r\nfunction ContextMenuTrigger({\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Trigger>) {\r\n  return (\r\n    <BaseContextMenu.Trigger data-slot=\"context-menu-trigger\" {...props} />\r\n  );\r\n}\r\n\r\nfunction ContextMenuGroup({\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Group>) {\r\n  return <BaseContextMenu.Group data-slot=\"context-menu-group\" {...props} />;\r\n}\r\n\r\nfunction ContextMenuPortal({\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Portal>) {\r\n  return <BaseContextMenu.Portal data-slot=\"context-menu-portal\" {...props} />;\r\n}\r\n\r\nfunction ContextMenuPositioner({\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Positioner>) {\r\n  return (\r\n    <BaseContextMenu.Positioner\r\n      data-slot=\"context-menu-positioner\"\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuRadioGroup({\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.RadioGroup>) {\r\n  return (\r\n    <BaseContextMenu.RadioGroup\r\n      data-slot=\"context-menu-radio-group\"\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuContent({\r\n  className,\r\n  sideOffset = 4,\r\n  align = \"start\",\r\n  level = 3,\r\n  shadowLevel = 3,\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Popup> & {\r\n  align?: BaseContextMenu.Positioner.Props[\"align\"];\r\n  sideOffset?: BaseContextMenu.Positioner.Props[\"sideOffset\"];\r\n  /** Surface elevation level for the popup bg (1-8). Bump when nesting inside a Dialog or other elevated container. Defaults to 3. */\r\n  level?: SurfaceLevel;\r\n  /** Shadow weight (1-8). Pinned to 3 by default so the menu reads the same regardless of nesting depth. */\r\n  shadowLevel?: SurfaceLevel;\r\n}) {\r\n  return (\r\n    <ContextMenuPortal>\r\n      <ContextMenuPositioner\r\n        className=\"max-h-(--available-height) max-w-(--available-width)\"\r\n        align={align}\r\n        sideOffset={sideOffset}\r\n      >\r\n        <BaseContextMenu.Popup\r\n          data-slot=\"context-menu-content\"\r\n          data-level={level}\r\n          className={cn(\r\n            // No Menu.Viewport here (ContextMenu has no trigger-swap morph to run\r\n            // one for), so the popup is its own scroll container: capped at\r\n            // --available-height, clipping on x for the rounded corners and\r\n            // scrolling on y. Without the cap a tall menu runs off screen.\r\n            \"text-popover-foreground relative z-50 max-h-(--available-height) max-w-(--available-width) min-w-[12rem] origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-xl p-1 outline-none\",\r\n            // Modern enter/exit — scale + fade from the transform origin, matching\r\n            // popover/dropdown-menu (replaces the legacy animate-in/zoom/slide classes)\r\n            \"ease-out-expo transition-[scale,opacity] duration-100\",\r\n            \"data-starting-style:scale-95 data-starting-style:opacity-0\",\r\n            \"data-ending-style:scale-95 data-ending-style:opacity-0\",\r\n            // No data-instant guard, deliberately: 'click' is inferred from\r\n            // `event.detail === 0`, which every right-click matches, so it would\r\n            // kill the enter animation on every open. 'dismiss' must animate or\r\n            // there is no exit, and 'group' / 'trigger-change' cannot fire here.\r\n            \"motion-reduce:transition-none\",\r\n            solidSurface(level, shadowLevel),\r\n            className,\r\n          )}\r\n          {...props}\r\n        />\r\n      </ContextMenuPositioner>\r\n    </ContextMenuPortal>\r\n  );\r\n}\r\n\r\nfunction ContextMenuItem({\r\n  className,\r\n  inset,\r\n  variant = \"default\",\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Item> & {\r\n  inset?: boolean;\r\n  variant?: \"default\" | \"destructive\";\r\n}) {\r\n  return (\r\n    <BaseContextMenu.Item\r\n      data-slot=\"context-menu-item\"\r\n      data-inset={inset || undefined}\r\n      data-variant={variant}\r\n      className={cn(\r\n        \"data-highlighted:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/20 data-[variant=destructive]:data-highlighted:text-destructive-foreground data-[variant=destructive]:*:[svg]:text-destructive! data-highlighted:data-[variant=destructive]:*:[svg]:text-destructive-foreground! [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:bg-surface-hover relative flex cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-60 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n        className,\r\n      )}\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuLinkItem({\r\n  className,\r\n  inset,\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.LinkItem> & {\r\n  inset?: boolean;\r\n}) {\r\n  return (\r\n    <BaseContextMenu.LinkItem\r\n      data-slot=\"context-menu-link-item\"\r\n      data-inset={inset || undefined}\r\n      className={cn(\r\n        \"data-highlighted:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground data-highlighted:bg-surface-hover relative flex cursor-default items-center gap-2 rounded-md px-2.5 py-1.5 text-sm no-underline outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-60 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\r\n        className,\r\n      )}\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuCheckboxItem({\r\n  className,\r\n  children,\r\n  checked,\r\n  inset,\r\n  indicator = \"check\",\r\n  switchColor = \"primary\",\r\n  switchShape = \"circle\",\r\n  switchSize = \"xs\",\r\n  switchMotion = \"default\",\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.CheckboxItem> & {\r\n  inset?: boolean;\r\n  /** Which indicator sits in the right-hand column. `\"switch\"` is visual only — the item keeps the `menuitemcheckbox` role. */\r\n  indicator?: \"check\" | \"switch\";\r\n  /** Checked track colour, when `indicator=\"switch\"`. */\r\n  switchColor?: SwitchVisualProps[\"color\"];\r\n  /** Thumb silhouette, when `indicator=\"switch\"`. */\r\n  switchShape?: SwitchVisualProps[\"shape\"];\r\n  /** Thumb size, when `indicator=\"switch\"`. Defaults to `xs`, which matches the row's text. */\r\n  switchSize?: SwitchVisualProps[\"size\"];\r\n  /** How the thumb travels, when `indicator=\"switch\"`. */\r\n  switchMotion?: SwitchVisualProps[\"motion\"];\r\n}) {\r\n  return (\r\n    <BaseContextMenu.CheckboxItem\r\n      data-slot=\"context-menu-checkbox-item\"\r\n      data-inset={inset || undefined}\r\n      data-indicator={indicator}\r\n      className={cn(\r\n        toggleItemClasses,\r\n        indicator === \"switch\"\r\n          ? \"grid-cols-[1fr_auto] gap-3\"\r\n          : \"grid-cols-[1fr_1rem] gap-2\",\r\n        className,\r\n      )}\r\n      checked={checked}\r\n      {...props}\r\n    >\r\n      <span className=\"col-start-1 flex min-w-0 items-center gap-2\">\r\n        {children}\r\n      </span>\r\n      {indicator === \"switch\" ? (\r\n        // Visual only: the row carries the role and the click target, so a real\r\n        // Switch here would nest a focusable control inside a menuitemcheckbox.\r\n        // Base UI's indicator supplies the aria-hidden.\r\n        <SwitchVisual\r\n          color={switchColor}\r\n          shape={switchShape}\r\n          size={switchSize}\r\n          motion={switchMotion}\r\n          className=\"col-start-2\"\r\n          render={<BaseContextMenu.CheckboxItemIndicator keepMounted />}\r\n        />\r\n      ) : (\r\n        <BaseContextMenu.CheckboxItemIndicator\r\n          keepMounted\r\n          className=\"col-start-2 flex items-center justify-center\"\r\n        >\r\n          <HugeiconsIcon\r\n            icon={tickIcon}\r\n            strokeWidth={2.5}\r\n            className={cn(\"size-4\", checkmarkClasses)}\r\n          />\r\n        </BaseContextMenu.CheckboxItemIndicator>\r\n      )}\r\n    </BaseContextMenu.CheckboxItem>\r\n  );\r\n}\r\n\r\nfunction ContextMenuRadioItem({\r\n  className,\r\n  children,\r\n  inset,\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.RadioItem> & {\r\n  inset?: boolean;\r\n}) {\r\n  return (\r\n    <BaseContextMenu.RadioItem\r\n      data-slot=\"context-menu-radio-item\"\r\n      data-inset={inset || undefined}\r\n      className={cn(toggleItemClasses, \"grid-cols-[1fr_1rem] gap-2\", className)}\r\n      {...props}\r\n    >\r\n      <span className=\"col-start-1 flex min-w-0 items-center gap-2\">\r\n        {children}\r\n      </span>\r\n      <BaseContextMenu.RadioItemIndicator\r\n        keepMounted\r\n        className=\"col-start-2 flex items-center justify-center\"\r\n      >\r\n        <HugeiconsIcon\r\n          icon={tickIcon}\r\n          strokeWidth={2.5}\r\n          className={cn(\"size-4\", checkmarkClasses)}\r\n        />\r\n      </BaseContextMenu.RadioItemIndicator>\r\n    </BaseContextMenu.RadioItem>\r\n  );\r\n}\r\n\r\nfunction ContextMenuLabel({\r\n  className,\r\n  inset,\r\n  ...props\r\n}: React.ComponentProps<\"div\"> & {\r\n  inset?: boolean;\r\n}) {\r\n  return (\r\n    <div\r\n      data-slot=\"context-menu-label\"\r\n      data-inset={inset || undefined}\r\n      className={cn(\r\n        \"px-2.5 py-1.5 text-xs font-medium data-inset:pl-8\",\r\n        className,\r\n      )}\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuGroupLabel({\r\n  className,\r\n  inset,\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.GroupLabel> & {\r\n  inset?: boolean;\r\n}) {\r\n  return (\r\n    <BaseContextMenu.GroupLabel\r\n      data-slot=\"context-menu-group-label\"\r\n      data-inset={inset || undefined}\r\n      className={cn(\r\n        \"px-2.5 py-1.5 text-xs font-medium data-inset:pl-8\",\r\n        className,\r\n      )}\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuSeparator({\r\n  className,\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Separator>) {\r\n  return (\r\n    <BaseContextMenu.Separator\r\n      data-slot=\"context-menu-separator\"\r\n      // Inset to the item label, not the popup edge: mx-2.5 clears the popup's\r\n      // p-1 plus the item's px-2.5, so the rule starts where the text does\r\n      // instead of running into the popup's rounded corners.\r\n      className={cn(\"bg-border mx-2.5 my-1 h-px\", className)}\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuShortcut({\r\n  className,\r\n  ...props\r\n}: React.ComponentProps<\"span\">) {\r\n  return (\r\n    <span\r\n      data-slot=\"context-menu-shortcut\"\r\n      className={cn(\r\n        \"text-muted-foreground ml-auto text-xs tracking-widest\",\r\n        className,\r\n      )}\r\n      {...props}\r\n    />\r\n  );\r\n}\r\n\r\nfunction ContextMenuSub({\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.SubmenuRoot>) {\r\n  return (\r\n    <BaseContextMenu.SubmenuRoot data-slot=\"context-menu-sub\" {...props} />\r\n  );\r\n}\r\n\r\nfunction ContextMenuSubTrigger({\r\n  className,\r\n  inset,\r\n  children,\r\n  delay = 0,\r\n  closeDelay = 0,\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.SubmenuTrigger> & {\r\n  inset?: boolean;\r\n}) {\r\n  return (\r\n    <BaseContextMenu.SubmenuTrigger\r\n      data-slot=\"context-menu-sub-trigger\"\r\n      data-inset={inset || undefined}\r\n      delay={delay}\r\n      closeDelay={closeDelay}\r\n      className={cn(\r\n        \"data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-highlighted:bg-surface-hover data-popup-open:bg-surface-hover flex cursor-default items-center rounded-md px-2.5 py-1.5 text-sm outline-hidden select-none data-inset:pl-8\",\r\n        className,\r\n      )}\r\n      {...props}\r\n    >\r\n      {children}\r\n      <HugeiconsIcon\r\n        icon={ArrowRight01Icon}\r\n        className=\"ml-auto size-4\"\r\n        strokeWidth={2}\r\n      />\r\n    </BaseContextMenu.SubmenuTrigger>\r\n  );\r\n}\r\n\r\nfunction ContextMenuSubContent({\r\n  className,\r\n  // 8px, not 0: the positioner anchors to the sub-trigger, which sits 4px\r\n  // inside the parent popup because of its p-1. A 0 offset therefore overlaps\r\n  // the parent's edge by 4px; 8 leaves a 4px gap, matching Menubar.\r\n  sideOffset = 8,\r\n  align = \"start\",\r\n  level = 5,\r\n  shadowLevel = 3,\r\n  ...props\r\n}: React.ComponentProps<typeof BaseContextMenu.Popup> & {\r\n  align?: BaseContextMenu.Positioner.Props[\"align\"];\r\n  sideOffset?: BaseContextMenu.Positioner.Props[\"sideOffset\"];\r\n  /** Surface elevation level for the submenu bg (1-8). Defaults to 5 — one tier above the parent menu's default of 3. Bump higher when nesting inside a Dialog. */\r\n  level?: SurfaceLevel;\r\n  /** Shadow weight (1-8). Pinned to 3 by default so the submenu reads the same dropdown weight as its parent. */\r\n  shadowLevel?: SurfaceLevel;\r\n}) {\r\n  return (\r\n    <ContextMenuPortal>\r\n      <ContextMenuPositioner\r\n        className=\"max-h-(--available-height) max-w-(--available-width)\"\r\n        sideOffset={sideOffset}\r\n        align={align}\r\n      >\r\n        <BaseContextMenu.Popup\r\n          data-slot=\"context-menu-sub-content\"\r\n          data-level={level}\r\n          className={cn(\r\n            // Its own scroll container, like ContextMenuContent above.\r\n            \"text-popover-foreground relative z-50 max-h-(--available-height) max-w-(--available-width) min-w-[12rem] origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-xl p-1 outline-none\",\r\n            // Modern enter/exit — scale + fade from the transform origin, matching\r\n            // popover/dropdown-menu (replaces the legacy animate-in/zoom/slide classes)\r\n            \"ease-out-expo transition-[scale,opacity] duration-100\",\r\n            \"data-starting-style:scale-95 data-starting-style:opacity-0\",\r\n            \"data-ending-style:scale-95 data-ending-style:opacity-0\",\r\n            // See ContextMenuContent for why there is no data-instant guard.\r\n            \"motion-reduce:transition-none\",\r\n            solidSurface(level, shadowLevel),\r\n            className,\r\n          )}\r\n          {...props}\r\n        />\r\n      </ContextMenuPositioner>\r\n    </ContextMenuPortal>\r\n  );\r\n}\r\n\r\nexport {\r\n  ContextMenu,\r\n  ContextMenuContent,\r\n  ContextMenuItem,\r\n  ContextMenuLinkItem,\r\n  ContextMenuShortcut,\r\n  ContextMenuTrigger,\r\n  ContextMenuSeparator,\r\n  ContextMenuGroup,\r\n  ContextMenuLabel,\r\n  ContextMenuGroupLabel,\r\n  ContextMenuCheckboxItem,\r\n  ContextMenuRadioGroup,\r\n  ContextMenuPortal,\r\n  ContextMenuPositioner,\r\n  ContextMenuRadioItem,\r\n  ContextMenuSub,\r\n  ContextMenuSubTrigger,\r\n  ContextMenuSubContent,\r\n};\r\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/context-menu.tsx"
    }
  ],
  "type": "registry:ui"
}