{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu-styled",
  "title": "Menu-styled context",
  "description": "Styled context: the Menu-styled set. 5 decorative context variations (SimpleContextMenu, IconContextMenu, ShortcutContextMenu, SectionContextMenu, DangerContextMenu) 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-styled.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/* Context menu family: 5 decorative self-contained menus. NO radix or portal. Each\n   export renders a target area (children, by default a dashed token box saying\n   \"Right-click here\"); onContextMenu (preventDefault) opens a fixed menu at the\n   cursor (clientX/clientY written to state). It closes on an outside click (window\n   pointerdown), on Escape, and on an item selection. AnimatePresence fade+scale;\n   instant under reduced motion. Color comes ONLY from tokens, via alpha color-mix;\n   hover is bg-accent. The coordinates come from the contextmenu event, with no\n   randomness. */\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\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\nconst menuBase =\n  \"fixed z-50 origin-top-left overflow-hidden 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\"\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)] hover:text-danger 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\"\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\n/* One row: icon plus label plus an optional shortcut chip; the danger tone is styled separately. onSelect runs on choice and the menu closes. */\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      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/* Shared shell: the target area plus a fixed AnimatePresence menu at the cursor position. render(close) produces the menu body. It closes on outside pointerdown, Escape or item selection. */\nfunction ContextMenuShell({\n  className,\n  size = \"md\",\n  children,\n  render,\n}: {\n  className?: string\n  size?: StyledSize\n  children?: React.ReactNode\n  render: (close: () => void) => React.ReactNode\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\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\") setOpen(false)\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            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            {render(close)}\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n\n/* Simple: sade dikey liste. */\nexport function SimpleContextMenu({ className, size = \"md\", children, items }: ContextMenuProps) {\n  const list = items ?? [\n    { label: \"Back\" },\n    { label: \"Reload\" },\n    { label: \"Save as...\" },\n    { label: \"Print\" },\n  ]\n  return (\n    <ContextMenuShell className={className} size={size} render={(close) => (\n      <div className=\"flex flex-col\">\n        {list.map((item, i) => (\n          <MenuItemButton key={i} item={item} close={close} />\n        ))}\n      </div>\n    )}>\n      {children}\n    </ContextMenuShell>\n  )\n}\n\n/* Icon: her satirin basinda token ikon. */\nexport function IconContextMenu({ className, size = \"md\", children, items }: ContextMenuProps) {\n  const list = items ?? [\n    { label: \"Copy\", icon: <Copy /> },\n    { label: \"Cut\", icon: <Scissors /> },\n    { label: \"Paste\", icon: <ClipboardPaste /> },\n    { label: \"Share\", icon: <Share2 /> },\n    { label: \"Add to favorites\", icon: <Star /> },\n  ]\n  return (\n    <ContextMenuShell className={className} size={size} render={(close) => (\n      <div className=\"flex flex-col\">\n        {list.map((item, i) => (\n          <MenuItemButton key={i} item={item} close={close} />\n        ))}\n      </div>\n    )}>\n      {children}\n    </ContextMenuShell>\n  )\n}\n\n/* Shortcut: every row shows a right-aligned token keyboard-shortcut chip. */\nexport function ShortcutContextMenu({ className, size = \"md\", children, items }: ContextMenuProps) {\n  const list = items ?? [\n    { label: \"Cut\", shortcut: \"Ctrl X\" },\n    { label: \"Copy\", shortcut: \"Ctrl C\" },\n    { label: \"Paste\", shortcut: \"Ctrl V\" },\n    { label: \"Select all\", shortcut: \"Ctrl A\" },\n  ]\n  return (\n    <ContextMenuShell className={className} size={size} render={(close) => (\n      <div className=\"flex flex-col\">\n        {list.map((item, i) => (\n          <MenuItemButton key={i} item={item} close={close} />\n        ))}\n      </div>\n    )}>\n      {children}\n    </ContextMenuShell>\n  )\n}\n\n/* Section: label baslikli + ayirici cizgili gruplanmis satirlar. */\nexport function SectionContextMenu({ className, size = \"md\", children, items }: ContextMenuProps) {\n  const list = items ?? [\n    { label: \"Undo\", group: \"Edit\" },\n    { label: \"Redo\", group: \"Edit\" },\n    { label: \"Cut\", group: \"Clipboard\" },\n    { label: \"Copy\", group: \"Clipboard\" },\n    { label: \"Paste\", group: \"Clipboard\" },\n  ]\n  const groups: { name: string; rows: StyledContextMenuItem[] }[] = []\n  for (const item of list) {\n    const name = item.group ?? \"\"\n    const last = groups[groups.length - 1]\n    if (last && last.name === name) last.rows.push(item)\n    else groups.push({ name, rows: [item] })\n  }\n  return (\n    <ContextMenuShell className={className} size={size} render={(close) => (\n      <div className=\"flex flex-col\">\n        {groups.map((g, gi) => (\n          <div key={gi} className=\"flex flex-col\">\n            {gi > 0 ? <div role=\"separator\" className=\"my-1 h-px bg-border\" /> : null}\n            {g.name ? (\n              <div className=\"px-3 pb-1 pt-2 text-xs font-medium text-muted-foreground\">\n                {g.name}\n              </div>\n            ) : null}\n            {g.rows.map((item, i) => (\n              <MenuItemButton key={i} item={item} close={close} />\n            ))}\n          </div>\n        ))}\n      </div>\n    )}>\n      {children}\n    </ContextMenuShell>\n  )\n}\n\n/* Danger: normal liste + ayirici ile ayrilmis son danger-token \"Delete\" satiri. */\nexport function DangerContextMenu({ className, size = \"md\", children, items }: ContextMenuProps) {\n  const list = items ?? [\n    { label: \"Open\" },\n    { label: \"Rename\" },\n    { label: \"Duplicate\" },\n    { label: \"Delete\", danger: true },\n  ]\n  const normal = list.filter((i) => !i.danger)\n  const danger = list.filter((i) => i.danger)\n  return (\n    <ContextMenuShell className={className} size={size} render={(close) => (\n      <div className=\"flex flex-col\">\n        {normal.map((item, i) => (\n          <MenuItemButton key={i} item={item} close={close} />\n        ))}\n        {danger.length > 0 ? (\n          <>\n            <div role=\"separator\" className=\"my-1 h-px bg-border\" />\n            {danger.map((item, i) => (\n              <MenuItemButton key={i} item={item} close={close} />\n            ))}\n          </>\n        ) : null}\n      </div>\n    )}>\n      {children}\n    </ContextMenuShell>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/context-menu-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "context"
  ],
  "type": "registry:component"
}