{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command-menu",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/command-menu.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type SlashCommand = {\n  id: string;\n  /** Name without the leading slash, e.g. \"summarize\". */\n  name: string;\n  description?: string;\n  /** Extra search terms. */\n  keywords?: string[];\n  /** If set, selecting replaces the input with this text instead of running the command. */\n  insert?: string;\n  /** Optional leading icon. */\n  icon?: React.ReactNode;\n};\n\n/**\n * Ranks name-prefix matches first, then name substrings (2+ characters), then\n * keywords or description words that start with the query.\n */\nexport function filterCommands(commands: SlashCommand[], query: string) {\n  const q = query.trim().toLowerCase();\n  if (!q) return commands;\n  const scored: [SlashCommand, number][] = [];\n  for (const c of commands) {\n    const name = c.name.toLowerCase();\n    if (name.startsWith(q)) scored.push([c, 0]);\n    else if (q.length > 1 && name.includes(q)) scored.push([c, 1]);\n    else if (\n      [...(c.keywords ?? []), ...(c.description ?? \"\").split(/\\s+/)].some((w) => w.toLowerCase().startsWith(q))\n    ) scored.push([c, 2]);\n  }\n  return scored.sort((a, b) => a[1] - b[1]).map(([c]) => c);\n}\n\nexport function optionId(menuId: string, index: number) {\n  return `${menuId}-option-${index}`;\n}\n\n/** The listbox. PromptComposer wires keyboard handling and ARIA; use this directly for custom inputs. */\nexport function CommandMenu({\n  id,\n  commands,\n  activeIndex,\n  onActiveChange,\n  onSelect,\n  className,\n}: {\n  id: string;\n  commands: SlashCommand[];\n  activeIndex: number;\n  onActiveChange: (index: number) => void;\n  onSelect: (command: SlashCommand) => void;\n  className?: string;\n}) {\n  const listRef = React.useRef<HTMLUListElement>(null);\n\n  // Keep the active option visible inside the list. scrollIntoView would also scroll the page\n  // (including on first render), so adjust the list's own scroll position instead.\n  React.useEffect(() => {\n    const list = listRef.current;\n    const option = list?.querySelector<HTMLElement>(`[id=\"${optionId(id, activeIndex)}\"]`);\n    if (!list || !option) return;\n    const top = option.offsetTop;\n    const bottom = top + option.offsetHeight;\n    if (top < list.scrollTop) list.scrollTop = top;\n    else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;\n  }, [activeIndex, id]);\n\n  return (\n    <div className={cn(\"overflow-hidden rounded border bg-background shadow-lg\", className)}>\n    <ul\n      ref={listRef}\n      id={id}\n      role=\"listbox\"\n      aria-label=\"Commands\"\n      className=\"relative max-h-64 overflow-y-auto p-1\"\n    >\n      {commands.map((c, i) => (\n        <li\n          key={c.id}\n          id={optionId(id, i)}\n          role=\"option\"\n          aria-selected={i === activeIndex}\n          onMouseMove={() => i !== activeIndex && onActiveChange(i)}\n          // Keep focus in the textarea while clicking.\n          onMouseDown={(e) => e.preventDefault()}\n          onClick={() => onSelect(c)}\n          className={cn(\n            \"flex cursor-default items-center gap-2.5 rounded-sm px-2 py-1.5 text-sm\",\n            i === activeIndex && \"bg-muted shadow-[inset_2px_0_0_currentColor]\",\n          )}\n        >\n          {c.icon && <span className=\"flex size-5 shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4\">{c.icon}</span>}\n          <span className=\"font-mono text-[13px]\">/{c.name}</span>\n          {c.description && <span className=\"min-w-0 truncate text-xs text-muted-foreground\">{c.description}</span>}\n        </li>\n      ))}\n    </ul>\n    <div aria-hidden className=\"flex gap-3 border-t px-3 py-1.5 font-mono text-[11px] text-muted-foreground\">\n      <span>↑↓ navigate</span>\n      <span>↵ select</span>\n      <span>esc close</span>\n    </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}