{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "context-menu-check",
  "title": "Menu-check context",
  "description": "Styled context: the Menu-check set. 5 decorative context variations (CheckContextMenu, RadioContextMenu, ToggleContextMenu, MixedContextMenu, MultiContextMenu) 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-check.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Circle } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Check context menu family: 5 menus with selection controls. The shell is the same\n   as 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 difference: the rows carry real working selection state.\n   Checkbox rows use menuitemcheckbox + aria-checked, radio rows use menuitemradio +\n   aria-checked; the state is held in React state and survives the menu closing and\n   reopening. NO radix. Color comes ONLY from tokens. */\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  /* \"check\" and \"toggle\" are independently on or off; \"radio\" is a single choice within a group; when undefined it is a plain row (selecting closes the menu). */\n  kind?: \"check\" | \"radio\" | \"toggle\"\n  value?: string\n  defaultChecked?: 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-2 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 [&_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-2 py-2 text-left text-sm text-danger outline-none transition-colors 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-[3px] focus-visible:ring-danger/40 [&>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 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/* Sol taraftaki isaret kutusu: checkbox kare, radio yuvarlak. */\nconst markBox =\n  \"inline-flex size-4 shrink-0 items-center justify-center rounded border border-border text-primary-foreground transition-colors\"\n\nconst markBoxOn =\n  \"border-[color-mix(in_oklab,var(--color-primary)_60%,transparent)] bg-primary\"\n\nconst markDot =\n  \"inline-flex size-4 shrink-0 items-center justify-center rounded-full border border-border transition-colors\"\n\nconst markDotOn = \"border-[color-mix(in_oklab,var(--color-primary)_60%,transparent)]\"\n\n/* Toggle satirlarinin sagindaki minik anahtar. */\nconst switchTrack =\n  \"ml-auto inline-flex h-4 w-7 shrink-0 items-center rounded-full border border-border bg-[color-mix(in_oklab,var(--color-foreground)_10%,transparent)] p-0.5 transition-colors\"\n\nconst switchTrackOn = \"border-[color-mix(in_oklab,var(--color-primary)_60%,transparent)] bg-primary\"\n\nconst switchThumb = \"size-3 rounded-full bg-background transition-transform\"\n\nconst checkItems: StyledContextMenuItem[] = [\n  { label: \"Show toolbar\", kind: \"check\", value: \"toolbar\", defaultChecked: true },\n  { label: \"Show sidebar\", kind: \"check\", value: \"sidebar\" },\n  { label: \"Show status bar\", kind: \"check\", value: \"status\", defaultChecked: true },\n]\n\nconst radioItems: StyledContextMenuItem[] = [\n  { label: \"Small\", kind: \"radio\", value: \"sm\", group: \"density\" },\n  { label: \"Medium\", kind: \"radio\", value: \"md\", group: \"density\", defaultChecked: true },\n  { label: \"Large\", kind: \"radio\", value: \"lg\", group: \"density\" },\n]\n\nconst toggleItems: StyledContextMenuItem[] = [\n  { label: \"Word wrap\", kind: \"toggle\", value: \"wrap\", defaultChecked: true },\n  { label: \"Minimap\", kind: \"toggle\", value: \"minimap\" },\n  { label: \"Line numbers\", kind: \"toggle\", value: \"lines\", defaultChecked: true },\n]\n\nconst mixedItems: StyledContextMenuItem[] = [\n  { label: \"Refresh\", shortcut: \"Ctrl R\" },\n  { label: \"Show grid\", kind: \"check\", value: \"grid\", defaultChecked: true },\n  { label: \"Snap to grid\", kind: \"check\", value: \"snap\" },\n  { label: \"List\", kind: \"radio\", value: \"list\", group: \"view\", defaultChecked: true },\n  { label: \"Board\", kind: \"radio\", value: \"board\", group: \"view\" },\n]\n\nconst multiItems: StyledContextMenuItem[] = [\n  { label: \"Design\", kind: \"check\", value: \"design\", defaultChecked: true },\n  { label: \"Engineering\", kind: \"check\", value: \"eng\", defaultChecked: true },\n  { label: \"Marketing\", kind: \"check\", value: \"mkt\" },\n  { label: \"Support\", kind: \"check\", value: \"support\" },\n  { label: \"Sales\", kind: \"check\", value: \"sales\" },\n]\n\nfunction keyOf(item: StyledContextMenuItem, index: number) {\n  return item.value ?? `item-${index}`\n}\n\n/* Baslangic secim durumu itemlarin defaultChecked degerinden turer, rastgelelik yok. */\nfunction useSelection(list: StyledContextMenuItem[]) {\n  const [checks, setChecks] = React.useState<Record<string, boolean>>(() => {\n    const next: Record<string, boolean> = {}\n    list.forEach((item, i) => {\n      if (item.kind === \"check\" || item.kind === \"toggle\") {\n        next[keyOf(item, i)] = item.defaultChecked ?? false\n      }\n    })\n    return next\n  })\n\n  const [radios, setRadios] = React.useState<Record<string, string>>(() => {\n    const next: Record<string, string> = {}\n    list.forEach((item, i) => {\n      if (item.kind === \"radio\" && item.defaultChecked) {\n        next[item.group ?? \"default\"] = keyOf(item, i)\n      }\n    })\n    return next\n  })\n\n  const toggle = React.useCallback((key: string) => {\n    setChecks((prev) => ({ ...prev, [key]: !prev[key] }))\n  }, [])\n\n  const pick = React.useCallback((group: string, key: string) => {\n    setRadios((prev) => ({ ...prev, [group]: key }))\n  }, [])\n\n  return { checks, radios, toggle, pick }\n}\n\n/* Shared shell: the target area plus a fixed AnimatePresence menu at the cursor position. render(close) produces the menu body. */\nfunction CheckContextMenuShell({\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  const menuId = React.useId()\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  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, 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/* Row rendering: menuitem / menuitemcheckbox / menuitemradio depending on kind. Selection rows leave the menu open (so the state is visible); plain rows close it. */\nfunction SelectionRows({\n  list,\n  close,\n  variant,\n  selection,\n}: {\n  list: StyledContextMenuItem[]\n  close: () => void\n  variant: \"mark\" | \"switch\"\n  selection: ReturnType<typeof useSelection>\n}) {\n  const { checks, radios, toggle, pick } = selection\n\n  return (\n    <div className=\"flex flex-col\">\n      {list.map((item, i) => {\n        const key = keyOf(item, i)\n\n        if (item.kind === \"check\" || item.kind === \"toggle\") {\n          const on = checks[key] ?? false\n          const asSwitch = variant === \"switch\" || item.kind === \"toggle\"\n          return (\n            <button\n              key={key}\n              type=\"button\"\n              role=\"menuitemcheckbox\"\n              aria-checked={on}\n              data-menu-item=\"true\"\n              onClick={() => {\n                toggle(key)\n                item.onSelect?.()\n              }}\n              className={item.danger ? itemDanger : itemBase}\n            >\n              {asSwitch ? null : (\n                <span className={cn(markBox, on && markBoxOn)} aria-hidden>\n                  {on ? <Check className=\"size-3\" /> : null}\n                </span>\n              )}\n              <span className=\"flex-1 truncate\">{item.label}</span>\n              {asSwitch ? (\n                <span className={cn(switchTrack, on && switchTrackOn)} aria-hidden>\n                  <span className={cn(switchThumb, on && \"translate-x-3\")} />\n                </span>\n              ) : null}\n              {item.shortcut ? <kbd className={shortcutChip}>{item.shortcut}</kbd> : null}\n            </button>\n          )\n        }\n\n        if (item.kind === \"radio\") {\n          const group = item.group ?? \"default\"\n          const on = radios[group] === key\n          return (\n            <button\n              key={key}\n              type=\"button\"\n              role=\"menuitemradio\"\n              aria-checked={on}\n              data-menu-item=\"true\"\n              onClick={() => {\n                pick(group, key)\n                item.onSelect?.()\n              }}\n              className={item.danger ? itemDanger : itemBase}\n            >\n              <span className={cn(markDot, on && markDotOn)} aria-hidden>\n                {on ? <Circle className=\"size-2 fill-primary text-primary\" /> : null}\n              </span>\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        return (\n          <button\n            key={key}\n            type=\"button\"\n            role=\"menuitem\"\n            data-menu-item=\"true\"\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    </div>\n  )\n}\n\n/* Check: bagimsiz onay kutulari, isaretliyken primary kare + tik. */\nexport function CheckContextMenu({ className, size, children, items }: ContextMenuProps) {\n  const list = items ?? checkItems\n  const selection = useSelection(list)\n  return (\n    <CheckContextMenuShell\n      className={className}\n      size={size}\n      render={(close) => (\n        <SelectionRows list={list} close={close} variant=\"mark\" selection={selection} />\n      )}\n    >\n      {children}\n    </CheckContextMenuShell>\n  )\n}\n\n/* Radio: grup icinde tekli secim, yuvarlak isaret. */\nexport function RadioContextMenu({ className, size, children, items }: ContextMenuProps) {\n  const list = items ?? radioItems\n  const selection = useSelection(list)\n  return (\n    <CheckContextMenuShell\n      className={className}\n      size={size}\n      render={(close) => (\n        <SelectionRows list={list} close={close} variant=\"mark\" selection={selection} />\n      )}\n    >\n      {children}\n    </CheckContextMenuShell>\n  )\n}\n\n/* Toggle: a tiny right-aligned switch instead of a checkbox. */\nexport function ToggleContextMenu({ className, size, children, items }: ContextMenuProps) {\n  const list = items ?? toggleItems\n  const selection = useSelection(list)\n  return (\n    <CheckContextMenuShell\n      className={className}\n      size={size}\n      render={(close) => (\n        <SelectionRows list={list} close={close} variant=\"switch\" selection={selection} />\n      )}\n    >\n      {children}\n    </CheckContextMenuShell>\n  )\n}\n\n/* Mixed: plain rows, checkboxes and a radio group together. */\nexport function MixedContextMenu({ className, size, children, items }: ContextMenuProps) {\n  const list = items ?? mixedItems\n  const selection = useSelection(list)\n  return (\n    <CheckContextMenuShell\n      className={className}\n      size={size}\n      render={(close) => (\n        <SelectionRows list={list} close={close} variant=\"mark\" selection={selection} />\n      )}\n    >\n      {children}\n    </CheckContextMenuShell>\n  )\n}\n\n/* Multi: a long multi-select list where everything can be checked at once. */\nexport function MultiContextMenu({ className, size, children, items }: ContextMenuProps) {\n  const list = items ?? multiItems\n  const selection = useSelection(list)\n  return (\n    <CheckContextMenuShell\n      className={className}\n      size={size}\n      render={(close) => (\n        <SelectionRows list={list} close={close} variant=\"mark\" selection={selection} />\n      )}\n    >\n      {children}\n    </CheckContextMenuShell>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/context-menu-check.tsx"
    }
  ],
  "categories": [
    "styled",
    "context"
  ],
  "type": "registry:component"
}