{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu",
  "title": "Context Menu",
  "description": "The menu that opens where you press: right-click, long press or the Menu key, with sub menus, check and radio items and shortcuts.",
  "dependencies": [
    "lucide-react",
    "radix-ui"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/context-menu.tsx",
      "content": "import type * as React from \"react\"\nimport { useContext, useMemo } from \"react\"\nimport { ContextMenu as ContextMenuPrimitive } from \"radix-ui\"\nimport {\n  MATERIALIZE_CLASS,\n  PANEL_BASE_CLASS,\n  PANEL_CLASS,\n  PLAIN_MOTION_CLASS,\n  TRIGGER_CLASS,\n  type ContextMenuEffect,\n  type ContextMenuVariant,\n} from \"@/registry/miseui/ui/context-menu-classes\"\nimport {\n  ContextMenuCheckboxItem,\n  ContextMenuContext,\n  ContextMenuGroup,\n  ContextMenuItem,\n  ContextMenuLabel,\n  ContextMenuQuickItem,\n  ContextMenuQuickRow,\n  ContextMenuRadioGroup,\n  ContextMenuRadioItem,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuSub,\n  ContextMenuSubTrigger,\n  type ContextMenuItemProps,\n  type ContextMenuQuickItemProps,\n} from \"@/registry/miseui/ui/context-menu-items\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst SUB_SIDE_OFFSET_PX = 4\n/** Keeps menus off the screen edge on a phone. */\nconst COLLISION_PADDING_PX = 8\n\ntype ContextMenuProps = React.ComponentProps<typeof ContextMenuPrimitive.Root> & {\n  /** `plain` standard rows, `compact` tight rows for dense tools, `grouped` labelled sections, `icon` a column of icons and a quick row. */\n  variant?: ContextMenuVariant\n  /** `materialize` brings the menu into focus from the point that was pressed. */\n  effect?: ContextMenuEffect\n}\n\nfunction ContextMenu({ variant = \"plain\", effect, ...props }: ContextMenuProps) {\n  const context = useMemo(() => ({ variant, effect }), [variant, effect])\n  return (\n    <ContextMenuContext.Provider value={context}>\n      <ContextMenuPrimitive.Root {...props} />\n    </ContextMenuContext.Provider>\n  )\n}\n\n/** Focusable by default: the keyboard opens a context menu from whatever has focus, with Shift F10 or the Menu key. */\nfunction ContextMenuTrigger({ className, disabled, tabIndex, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {\n  const { variant, effect } = useContext(ContextMenuContext)\n  return (\n    <ContextMenuPrimitive.Trigger\n      data-slot=\"context-menu\"\n      data-variant={variant}\n      data-effect={effect}\n      disabled={disabled}\n      tabIndex={disabled ? undefined : (tabIndex ?? 0)}\n      className={cn(TRIGGER_CLASS, className)}\n      {...props}\n    />\n  )\n}\n\nfunction useMotionClass() {\n  const { effect } = useContext(ContextMenuContext)\n  return effect === \"materialize\" ? MATERIALIZE_CLASS : PLAIN_MOTION_CLASS\n}\n\nfunction ContextMenuContent({\n  className,\n  collisionPadding = COLLISION_PADDING_PX,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {\n  const { variant, effect } = useContext(ContextMenuContext)\n  const motionClass = useMotionClass()\n  return (\n    <ContextMenuPrimitive.Portal>\n      <ContextMenuPrimitive.Content\n        data-slot=\"context-menu-content\"\n        data-variant={variant}\n        data-effect={effect}\n        collisionPadding={collisionPadding}\n        className={cn(PANEL_BASE_CLASS, PANEL_CLASS[variant], motionClass, className)}\n        {...props}\n      />\n    </ContextMenuPrimitive.Portal>\n  )\n}\n\nfunction ContextMenuSubContent({\n  className,\n  sideOffset = SUB_SIDE_OFFSET_PX,\n  collisionPadding = COLLISION_PADDING_PX,\n  ...props\n}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {\n  const { variant, effect } = useContext(ContextMenuContext)\n  const motionClass = useMotionClass()\n  return (\n    <ContextMenuPrimitive.Portal>\n      <ContextMenuPrimitive.SubContent\n        data-slot=\"context-menu-sub-content\"\n        data-effect={effect}\n        sideOffset={sideOffset}\n        collisionPadding={collisionPadding}\n        className={cn(PANEL_BASE_CLASS, PANEL_CLASS[variant], variant === \"grouped\" && \"p-1\", motionClass, className)}\n        {...props}\n      />\n    </ContextMenuPrimitive.Portal>\n  )\n}\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioGroup,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuGroup,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuQuickRow,\n  ContextMenuQuickItem,\n  ContextMenuSub,\n  ContextMenuSubTrigger,\n  ContextMenuSubContent,\n  type ContextMenuProps,\n  type ContextMenuItemProps,\n  type ContextMenuQuickItemProps,\n  type ContextMenuVariant,\n  type ContextMenuEffect,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/context-menu-classes.ts",
      "content": "type ContextMenuVariant = \"plain\" | \"compact\" | \"grouped\" | \"icon\"\ntype ContextMenuEffect = \"materialize\"\n\nconst TRIGGER_CLASS = [\n  \"rounded-md outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n  // Without this a long press on a phone selects the words under the finger before the menu opens.\n  \"pointer-coarse:select-none\",\n  \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50\",\n]\n\n/**\n * Menus only animate in. A right-click somewhere else closes the menu and opens it again in the same gesture,\n * and a panel still fading out would be brought back at the old point before it moves.\n */\nconst PLAIN_MOTION_CLASS = [\n  \"duration-(--duration-fast) ease-(--ease-mise)\",\n  \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 motion-reduce:data-[state=open]:zoom-in-100\",\n]\n\n/** The enter values stay unqualified so the media overrides below win on source order. */\nconst MATERIALIZE_CLASS = [\n  \"duration-(--duration-base) ease-(--ease-mise-out) will-change-[transform,opacity,filter]\",\n  \"data-[state=open]:animate-in\",\n  \"fade-in-0 zoom-in-90 blur-in-sm\",\n  \"motion-reduce:zoom-in-100 motion-reduce:blur-in-0\",\n  \"[@media(prefers-reduced-transparency:reduce)]:blur-in-0\",\n]\n\nconst PANEL_BASE_CLASS = [\n  \"z-50 max-w-[calc(100vw-1rem)] overflow-x-hidden overflow-y-auto border bg-popover text-popover-foreground shadow-md\",\n  \"max-h-(--radix-context-menu-content-available-height) origin-(--radix-context-menu-content-transform-origin)\",\n]\n\nconst PANEL_CLASS: Record<ContextMenuVariant, string> = {\n  plain: \"min-w-48 rounded-md p-1\",\n  compact: \"min-w-40 rounded-sm p-1\",\n  // No panel padding: each group brings its own, so the dividers run from edge to edge.\n  grouped: \"min-w-56 rounded-lg\",\n  icon: \"min-w-52 rounded-lg p-1\",\n}\n\nconst ITEM_BASE_CLASS = [\n  \"relative flex cursor-default items-center gap-2 rounded-sm outline-hidden select-none\",\n  \"transition-[color,background-color] duration-(--duration-fast) ease-(--ease-mise)\",\n  \"focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n  \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n]\n\nconst ITEM_CLASS: Record<ContextMenuVariant, string> = {\n  plain: \"px-2 py-1.5 text-sm pointer-coarse:py-2.5\",\n  compact: \"px-2 py-1 text-[13px] pointer-coarse:py-2.5\",\n  grouped: \"px-2 py-1.5 text-sm pointer-coarse:py-2.5\",\n  icon: \"px-2 py-2 text-sm pointer-coarse:py-2.5\",\n}\n\nconst DESTRUCTIVE_ITEM_CLASS = \"text-destructive focus:bg-destructive/10 focus:text-destructive dark:focus:bg-destructive/20\"\n\n/** Check and radio rows keep a fixed slot on the left, the same width as an icon, so all labels line up. */\nconst INDICATOR_ITEM_CLASS = \"pl-8\"\n\nconst LABEL_CLASS: Record<ContextMenuVariant, string> = {\n  plain: \"px-2 py-1.5 text-[13px] text-muted-foreground\",\n  compact: \"px-2 py-1 text-xs text-muted-foreground\",\n  grouped: \"px-2 pt-1 pb-1 text-[13px] font-medium text-foreground\",\n  icon: \"px-2 py-1.5 text-[13px] text-muted-foreground\",\n}\n\nconst GROUP_CLASS: Record<ContextMenuVariant, string> = {\n  plain: \"\",\n  compact: \"\",\n  grouped: \"border-t p-1 first:border-t-0\",\n  icon: \"\",\n}\n\nconst QUICK_ITEM_CLASS = [\n  \"flex size-8 flex-1 cursor-default items-center justify-center rounded-sm text-muted-foreground outline-hidden select-none pointer-coarse:h-10\",\n  \"transition-[color,background-color] duration-(--duration-fast) ease-(--ease-mise)\",\n  \"focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n  \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]\n\nexport {\n  DESTRUCTIVE_ITEM_CLASS,\n  GROUP_CLASS,\n  INDICATOR_ITEM_CLASS,\n  ITEM_BASE_CLASS,\n  ITEM_CLASS,\n  LABEL_CLASS,\n  MATERIALIZE_CLASS,\n  PANEL_BASE_CLASS,\n  PANEL_CLASS,\n  PLAIN_MOTION_CLASS,\n  QUICK_ITEM_CLASS,\n  TRIGGER_CLASS,\n  type ContextMenuEffect,\n  type ContextMenuVariant,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/context-menu-items.tsx",
      "content": "import type * as React from \"react\"\nimport { createContext, useContext } from \"react\"\nimport { ContextMenu as ContextMenuPrimitive } from \"radix-ui\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\nimport {\n  DESTRUCTIVE_ITEM_CLASS,\n  GROUP_CLASS,\n  INDICATOR_ITEM_CLASS,\n  ITEM_BASE_CLASS,\n  ITEM_CLASS,\n  LABEL_CLASS,\n  QUICK_ITEM_CLASS,\n  type ContextMenuEffect,\n  type ContextMenuVariant,\n} from \"@/registry/miseui/ui/context-menu-classes\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst ContextMenuContext = createContext<{ variant: ContextMenuVariant; effect?: ContextMenuEffect }>({ variant: \"plain\" })\n\ntype ContextMenuItemProps = React.ComponentProps<typeof ContextMenuPrimitive.Item> & {\n  /** Destructive items read in the destructive colour so the eye slows down before pressing. */\n  tone?: \"default\" | \"destructive\"\n  /** Sits in a fixed column in `icon`. Beside the label in `plain` and `grouped`. `compact` has no room and leaves it out. */\n  icon?: React.ReactNode\n}\n\nfunction ItemIcon({ icon, variant }: { icon: React.ReactNode; variant: ContextMenuVariant }) {\n  if (variant === \"compact\") return null\n  // `icon` always keeps the slot, or labels would start at two different places down the column.\n  if (variant !== \"icon\" && !icon) return null\n  return (\n    <span aria-hidden=\"true\" data-slot=\"context-menu-icon\" className=\"flex size-4 shrink-0 items-center justify-center text-muted-foreground\">\n      {icon}\n    </span>\n  )\n}\n\nfunction ContextMenuItem({ className, tone = \"default\", icon, children, ...props }: ContextMenuItemProps) {\n  const { variant } = useContext(ContextMenuContext)\n  return (\n    <ContextMenuPrimitive.Item\n      data-slot=\"context-menu-item\"\n      data-tone={tone}\n      className={cn(ITEM_BASE_CLASS, ITEM_CLASS[variant], tone === \"destructive\" && DESTRUCTIVE_ITEM_CLASS, className)}\n      {...props}\n    >\n      <ItemIcon icon={icon} variant={variant} />\n      {children}\n    </ContextMenuPrimitive.Item>\n  )\n}\n\nfunction Indicator({ children }: { children: React.ReactNode }) {\n  return (\n    <span className=\"pointer-events-none absolute left-2 flex size-4 items-center justify-center\">\n      <ContextMenuPrimitive.ItemIndicator>{children}</ContextMenuPrimitive.ItemIndicator>\n    </span>\n  )\n}\n\nfunction ContextMenuCheckboxItem({ className, children, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {\n  const { variant } = useContext(ContextMenuContext)\n  return (\n    <ContextMenuPrimitive.CheckboxItem\n      data-slot=\"context-menu-checkbox-item\"\n      className={cn(ITEM_BASE_CLASS, ITEM_CLASS[variant], INDICATOR_ITEM_CLASS, className)}\n      {...props}\n    >\n      <Indicator>\n        <Check aria-hidden=\"true\" />\n      </Indicator>\n      {children}\n    </ContextMenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction ContextMenuRadioGroup(props: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {\n  return <ContextMenuPrimitive.RadioGroup data-slot=\"context-menu-radio-group\" {...props} />\n}\n\nfunction ContextMenuRadioItem({ className, children, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {\n  const { variant } = useContext(ContextMenuContext)\n  return (\n    <ContextMenuPrimitive.RadioItem\n      data-slot=\"context-menu-radio-item\"\n      className={cn(ITEM_BASE_CLASS, ITEM_CLASS[variant], INDICATOR_ITEM_CLASS, className)}\n      {...props}\n    >\n      <Indicator>\n        <Circle aria-hidden=\"true\" className=\"size-2 fill-current\" />\n      </Indicator>\n      {children}\n    </ContextMenuPrimitive.RadioItem>\n  )\n}\n\nfunction ContextMenuLabel({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Label>) {\n  const { variant } = useContext(ContextMenuContext)\n  return <ContextMenuPrimitive.Label data-slot=\"context-menu-label\" className={cn(LABEL_CLASS[variant], className)} {...props} />\n}\n\nfunction ContextMenuGroup({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {\n  const { variant } = useContext(ContextMenuContext)\n  return <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" className={cn(GROUP_CLASS[variant], className)} {...props} />\n}\n\n/** `grouped` draws its dividers on the groups, so a separator there would double the line. */\nfunction ContextMenuSeparator({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {\n  const { variant } = useContext(ContextMenuContext)\n  if (variant === \"grouped\") return null\n  return <ContextMenuPrimitive.Separator data-slot=\"context-menu-separator\" className={cn(\"-mx-1 my-1 h-px bg-border\", className)} {...props} />\n}\n\n/** On touch there is no keyboard to press it with, so the hint would only take room from the label. */\nfunction ContextMenuShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n  return <span data-slot=\"context-menu-shortcut\" className={cn(\"ml-auto pl-4 text-xs text-muted-foreground tabular-nums pointer-coarse:hidden\", className)} {...props} />\n}\n\nfunction ContextMenuQuickRow({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {\n  return <ContextMenuPrimitive.Group data-slot=\"context-menu-quick-row\" className={cn(\"-mx-1 mb-1 flex items-center gap-1 border-b px-1 pb-1\", className)} {...props} />\n}\n\ntype ContextMenuQuickItemProps = React.ComponentProps<typeof ContextMenuPrimitive.Item> & {\n  /** Required: this row shows only a glyph, so the word has to reach assistive tech some other way. */\n  \"aria-label\": string\n}\n\nfunction ContextMenuQuickItem({ className, ...props }: ContextMenuQuickItemProps) {\n  return <ContextMenuPrimitive.Item data-slot=\"context-menu-quick-item\" title={props[\"aria-label\"]} className={cn(QUICK_ITEM_CLASS, className)} {...props} />\n}\n\nfunction ContextMenuSub(props: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {\n  return <ContextMenuPrimitive.Sub {...props} />\n}\n\nfunction ContextMenuSubTrigger({ className, children, icon, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & { icon?: React.ReactNode }) {\n  const { variant } = useContext(ContextMenuContext)\n  return (\n    <ContextMenuPrimitive.SubTrigger\n      data-slot=\"context-menu-sub-trigger\"\n      className={cn(ITEM_BASE_CLASS, ITEM_CLASS[variant], \"data-[state=open]:bg-accent\", className)}\n      {...props}\n    >\n      <ItemIcon icon={icon} variant={variant} />\n      {children}\n      <ChevronRight aria-hidden=\"true\" className=\"ml-auto text-muted-foreground\" />\n    </ContextMenuPrimitive.SubTrigger>\n  )\n}\n\nexport {\n  ContextMenuContext,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioGroup,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuGroup,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuQuickRow,\n  ContextMenuQuickItem,\n  ContextMenuSub,\n  ContextMenuSubTrigger,\n  type ContextMenuItemProps,\n  type ContextMenuQuickItemProps,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "overlays"
  ],
  "type": "registry:ui"
}