{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu",
  "type": "registry:ui",
  "title": "Context Menu",
  "description": "Right-click menu with items, submenus, checkboxes, and keyboard shortcuts.",
  "dependencies": [
    "@radix-ui/react-context-menu",
    "@saasflare/ui"
  ],
  "files": [
    {
      "path": "components/ui/context-menu.tsx",
      "type": "registry:ui",
      "content": "// @toreview\n\"use client\"\n\n/**\n * @fileoverview ContextMenu primitive — right-click menu with items, sub-menus, checkboxes, and radio groups.\n * Built on Radix UI ContextMenu. Part of the Saasflare base component layer.\n * @module packages/ui/components/ui/context-menu\n * @layer core\n *\n * @component\n * @example\n * import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem } from '@saasflare/ui';\n * <ContextMenu>\n *   <ContextMenuTrigger>Right-click here</ContextMenuTrigger>\n *   <ContextMenuContent>\n *     <ContextMenuItem>Copy</ContextMenuItem>\n *     <ContextMenuItem>Paste</ContextMenuItem>\n *   </ContextMenuContent>\n * </ContextMenu>\n */\n\nimport * as React from \"react\"\nimport { CheckIcon, CaretRightIcon, CircleIcon } from \"@saasflare/ui\"\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\"\n\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\n\n/**\n * Right-click menu root. Wraps Radix ContextMenu.Root and manages the open\n * state of a menu anchored at the pointer position. Compose with\n * {@link ContextMenuTrigger} and {@link ContextMenuContent}; supports items,\n * checkbox/radio items, labels, separators, and nested sub-menus.\n *\n * @component\n * @layer core\n *\n * @example\n * <ContextMenu>\n *   <ContextMenuTrigger>Right-click here</ContextMenuTrigger>\n *   <ContextMenuContent>\n *     <ContextMenuItem>Copy</ContextMenuItem>\n *     <ContextMenuItem>Paste</ContextMenuItem>\n *   </ContextMenuContent>\n * </ContextMenu>\n */\nfunction ContextMenu({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {\n  return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />\n}\n\n/**\n * Area that opens the menu on right-click (or long-press on touch).\n *\n * @component\n * @layer core\n */\nfunction ContextMenuTrigger({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {\n  return (\n    <ContextMenuPrimitive.Trigger data-slot=\"context-menu-trigger\" {...props} />\n  )\n}\n\n/**\n * Groups related menu items for semantics and accessibility.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuGroup({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {\n  return (\n    <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />\n  )\n}\n\n/**\n * Portals menu content into the document body. {@link ContextMenuContent}\n * already portals itself — reach for this only with custom content trees.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuPortal({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {\n  return (\n    <ContextMenuPrimitive.Portal data-slot=\"context-menu-portal\" {...props} />\n  )\n}\n\n/**\n * Manages the open state of a nested sub-menu. Compose with\n * {@link ContextMenuSubTrigger} and {@link ContextMenuSubContent}.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuSub({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {\n  return <ContextMenuPrimitive.Sub data-slot=\"context-menu-sub\" {...props} />\n}\n\n/**\n * Container for {@link ContextMenuRadioItem}s sharing a single selected value.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuRadioGroup({\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {\n  return (\n    <ContextMenuPrimitive.RadioGroup\n      data-slot=\"context-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\n/**\n * Item that opens a nested sub-menu; renders a trailing caret whose Phosphor\n * weight follows `iconWeight`. Set `inset` to align with items that have\n * indicators.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuSubTrigger({\n  className,\n  inset,\n  iconWeight,\n  children,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {\n  inset?: boolean\n} & Pick<SaasflareComponentProps, \"iconWeight\">) {\n  const sf = useSaasflareProps({ iconWeight })\n  return (\n    <ContextMenuPrimitive.SubTrigger\n      data-slot=\"context-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <CaretRightIcon weight={sf.iconWeight} className=\"ml-auto\" />\n    </ContextMenuPrimitive.SubTrigger>\n  )\n}\n\n/**\n * Panel for a nested sub-menu, positioned against its\n * {@link ContextMenuSubTrigger}.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuSubContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {\n  return (\n    <ContextMenuPrimitive.SubContent\n      data-slot=\"context-menu-sub-content\"\n      className={cn(\n        \"z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Props for {@link ContextMenuContent}. Extends the Radix Content props with\n * `surface`/`radius`/`animated`/`iconWeight` overrides from\n * {@link SaasflareComponentProps}.\n */\ninterface ContextMenuContentProps\n  extends Omit<React.ComponentProps<typeof ContextMenuPrimitive.Content>, keyof SaasflareComponentProps>,\n    SaasflareComponentProps {}\n\n/**\n * Menu panel, portaled and anchored at the pointer position. Honors\n * `surface`/`radius`/`animated` overrides and emits the matching data axes\n * for the design system to style.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuContent({\n  className,\n  surface,\n  radius,\n  animated,\n  iconWeight,\n  ...props\n}: ContextMenuContentProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n\n  return (\n    <ContextMenuPrimitive.Portal>\n      <ContextMenuPrimitive.Content\n        {...props}\n        data-slot=\"context-menu-content\"\n        data-surface={sf.surface}\n        data-radius={sf.radius}\n        data-animated={String(sf.animated)}\n        className={cn(\n          \"z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n          className\n        )}\n      />\n    </ContextMenuPrimitive.Portal>\n  )\n}\n\n/**\n * Selectable menu item. Use `variant=\"destructive\"` for dangerous actions\n * and `inset` to align with items that have indicators.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <ContextMenuPrimitive.Item\n      data-slot=\"context-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Menu item with a togglable checked state, shown via a leading check\n * indicator whose Phosphor weight follows `iconWeight`.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  iconWeight,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem> &\n  Pick<SaasflareComponentProps, \"iconWeight\">) {\n  const sf = useSaasflareProps({ iconWeight })\n  return (\n    <ContextMenuPrimitive.CheckboxItem\n      data-slot=\"context-menu-checkbox-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CheckIcon weight={sf.iconWeight} className=\"size-4\" />\n        </ContextMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.CheckboxItem>\n  )\n}\n\n/**\n * Single-selection item within a {@link ContextMenuRadioGroup}; shows a\n * leading dot indicator when selected.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuRadioItem({\n  className,\n  children,\n  iconWeight,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem> &\n  Pick<SaasflareComponentProps, \"iconWeight\">) {\n  const sf = useSaasflareProps({ iconWeight })\n  return (\n    <ContextMenuPrimitive.RadioItem\n      data-slot=\"context-menu-radio-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n        <ContextMenuPrimitive.ItemIndicator>\n          <CircleIcon weight={sf.iconWeight} className=\"size-2 fill-current\" />\n        </ContextMenuPrimitive.ItemIndicator>\n      </span>\n      {children}\n    </ContextMenuPrimitive.RadioItem>\n  )\n}\n\n/**\n * Non-interactive heading for a group of menu items. Set `inset` to align\n * with items that have indicators.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuLabel({\n  className,\n  inset,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {\n  inset?: boolean\n}) {\n  return (\n    <ContextMenuPrimitive.Label\n      data-slot=\"context-menu-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-1.5 text-sm font-medium text-foreground data-[inset]:pl-8\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Thin divider between menu items or groups.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n  return (\n    <ContextMenuPrimitive.Separator\n      data-slot=\"context-menu-separator\"\n      className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\n/**\n * Right-aligned keyboard shortcut hint inside a menu item. Purely visual —\n * it does not register the key binding.\n *\n * @component\n * @layer core\n */\nfunction ContextMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"context-menu-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  type ContextMenuContentProps,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuGroup,\n  ContextMenuPortal,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuRadioGroup,\n}\n",
      "target": "components/ui/context-menu.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/context-menu.json"
  }
}
