{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu-compact",
  "title": "Menu-compact context",
  "description": "Styled context: the Menu-compact set. 5 decorative context variations (DenseContextMenu, MinimalContextMenu, PillContextMenu, BareContextMenu, NarrowContextMenu) 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-compact.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ClipboardPaste, Copy, Scissors, Share2, Star } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Compact context menu family: 5 dense 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 only difference is the DENSITY: dense (tight rows), minimal\n   (plain, no border), pill (a pill-shaped highlight), bare (no frame or shadow, a\n   ruled separator), narrow (a very narrow panel + small typography). 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}\n\ntype ContextMenuProps = {\n  className?: string\n  size?: StyledSize\n  children?: React.ReactNode\n  items?: StyledContextMenuItem[]\n}\n\n/* Yogun aile daha dar panel olcekleri kullanir. */\nconst menuSize: Record<StyledSize, string> = {\n  sm: \"w-40\",\n  md: \"w-48\",\n  lg: \"w-56\",\n  xl: \"w-64\",\n}\n\nconst narrowSize: Record<StyledSize, string> = {\n  sm: \"w-32\",\n  md: \"w-36\",\n  lg: \"w-40\",\n  xl: \"w-48\",\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\nconst menuBase =\n  \"fixed z-50 origin-top-left overflow-hidden text-popover-foreground outline-none\"\n\nconst iconCompat =\n  \"[&>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 rowBase =\n  \"flex w-full cursor-default items-center gap-2 text-left outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst dangerTone =\n  \"text-danger 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-danger/40\"\n\nconst shortcutText = \"ml-auto font-mono text-[10px] text-muted-foreground\"\n\nconst defaultItems: StyledContextMenuItem[] = [\n  { label: \"Copy\", icon: <Copy />, shortcut: \"Ctrl C\" },\n  { label: \"Cut\", icon: <Scissors />, shortcut: \"Ctrl X\" },\n  { label: \"Paste\", icon: <ClipboardPaste />, shortcut: \"Ctrl V\" },\n  { label: \"Share\", icon: <Share2 /> },\n  { label: \"Favorite\", icon: <Star /> },\n]\n\ntype CompactSkin = {\n  /* Panel yuzeyi. */\n  surface: string\n  /* Satir gorunumu (yogunluk + hover vurgusu). */\n  row: string\n  /* Satirlar arasi ayirici cizilsin mi. */\n  divided?: boolean\n  /* Whether icons are hidden (for the densest variants). */\n  hideIcons?: boolean\n  /* Kisayol metni gosterilsin mi. */\n  hideShortcuts?: boolean\n  sizes?: Record<StyledSize, string>\n}\n\n/* Shared shell: the target area plus a fixed AnimatePresence menu at the cursor position. skin carries the variant's density language. */\nfunction CompactContextMenuShell({\n  className,\n  size = \"md\",\n  children,\n  items,\n  skin,\n}: ContextMenuProps & { skin: CompactSkin }) {\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  const list = items ?? defaultItems\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  const widths = skin.sizes ?? menuSize\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, skin.surface, widths[size])}\n            initial={anim.initial}\n            animate={anim.animate}\n            exit={anim.exit}\n            transition={reduce ? { duration: 0 } : { duration: 0.12, ease: \"easeOut\" }}\n          >\n            <div className=\"flex flex-col\">\n              {list.map((item, i) => (\n                <React.Fragment key={i}>\n                  {skin.divided && i > 0 ? (\n                    <div role=\"separator\" className=\"h-px bg-border\" />\n                  ) : null}\n                  <button\n                    type=\"button\"\n                    role=\"menuitem\"\n                    data-menu-item=\"true\"\n                    onClick={() => {\n                      item.onSelect?.()\n                      close()\n                    }}\n                    className={cn(\n                      rowBase,\n                      iconCompat,\n                      skin.row,\n                      item.danger && dangerTone\n                    )}\n                  >\n                    {!skin.hideIcons && 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                    {!skin.hideShortcuts && item.shortcut ? (\n                      <kbd className={shortcutText}>{item.shortcut}</kbd>\n                    ) : null}\n                  </button>\n                </React.Fragment>\n              ))}\n            </div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n/* Dense: the standard shell with noticeably shortened rows. */\nexport function DenseContextMenu(props: ContextMenuProps) {\n  return (\n    <CompactContextMenuShell\n      {...props}\n      skin={{\n        surface: \"rounded-lg border border-border bg-popover p-1 shadow-lg\",\n        row: \"rounded-sm px-2 py-1 text-xs hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground [&_svg]:size-3.5 [&_i]:text-sm\",\n      }}\n    />\n  )\n}\n\n/* Minimal: ikonsuz, sade metin listesi, cok hafif yuzey. */\nexport function MinimalContextMenu(props: ContextMenuProps) {\n  return (\n    <CompactContextMenuShell\n      {...props}\n      skin={{\n        surface:\n          \"rounded-lg border border-[color-mix(in_oklab,var(--color-border)_60%,transparent)] bg-popover p-1 shadow-md\",\n        row: \"rounded-sm px-2.5 py-1.5 text-xs text-muted-foreground hover:text-foreground focus-visible:text-foreground\",\n        hideIcons: true,\n      }}\n    />\n  )\n}\n\n/* Pill: hap seklinde tam yuvarlak vurgu, yuvarlak panel. */\nexport function PillContextMenu(props: ContextMenuProps) {\n  return (\n    <CompactContextMenuShell\n      {...props}\n      skin={{\n        surface: \"rounded-2xl border border-border bg-popover p-1.5 shadow-lg\",\n        row: \"rounded-full px-3 py-1.5 text-xs hover:bg-[color-mix(in_oklab,var(--color-primary)_14%,transparent)] focus-visible:bg-[color-mix(in_oklab,var(--color-primary)_14%,transparent)] [&_svg]:size-3.5 [&_i]:text-sm\",\n      }}\n    />\n  )\n}\n\n/* Bare: cerceve/golge yok, satirlar ince cizgilerle ayrilir. */\nexport function BareContextMenu(props: ContextMenuProps) {\n  return (\n    <CompactContextMenuShell\n      {...props}\n      skin={{\n        surface: \"rounded-md bg-popover p-0\",\n        row: \"px-3 py-1.5 text-xs hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] focus-visible:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] [&_svg]:size-3.5 [&_i]:text-sm\",\n        divided: true,\n        hideShortcuts: true,\n      }}\n    />\n  )\n}\n\n/* Narrow: en dar panel, kisayolsuz, kucuk tipografi. */\nexport function NarrowContextMenu(props: ContextMenuProps) {\n  return (\n    <CompactContextMenuShell\n      {...props}\n      skin={{\n        surface: \"rounded-lg border border-border bg-popover p-1 shadow-lg\",\n        row: \"rounded-sm px-2 py-1 text-[11px] hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground [&_svg]:size-3 [&_i]:text-xs\",\n        hideShortcuts: true,\n        sizes: narrowSize,\n      }}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/context-menu-compact.tsx"
    }
  ],
  "categories": [
    "styled",
    "context"
  ],
  "type": "registry:component"
}