{
  "$schema": "https://ethereal-ui.saefulrohman.dev/schema/registry-item.json",
  "name": "command-palette",
  "title": "Command Palette",
  "type": "registry:primitives",
  "description": "Keyboard-first Cmd+K search modal with categorized results and shortcut hints.",
  "dependencies": [
    "@radix-ui/react-dialog",
    "lucide-react"
  ],
  "usage": "import * as React from \"react\"\nimport { CommandPalette, Button } from \"@etherealui/react\"\n\nexport function CommandPaletteDemo() {\n  const [open, setOpen] = React.useState(false)\n  return (\n    <>\n      <Button variant=\"outline\" onClick={() => setOpen(true)}>\n        Open Command Palette (⌘K)\n      </Button>\n      <CommandPalette open={open} onOpenChange={setOpen} />\n    </>\n  )\n}",
  "files": [
    {
      "path": "packages/ui/src/primitives/command-palette.tsx",
      "target": "components/ui/command-palette.tsx",
      "type": "registry:primitives",
      "content": "import * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { Search, CornerDownLeft, FileText, Settings, Users, Terminal, BarChart3 } from \"lucide-react\"\nimport { cn } from \"../utils/cn\"\n\nexport interface CommandItem {\n  id: string\n  label: string\n  category: string\n  shortcut?: string\n  icon?: React.ReactNode\n  onSelect?: () => void\n}\n\nexport interface CommandPaletteProps {\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\n  items?: CommandItem[]\n  placeholder?: string\n  enableGlobalShortcut?: boolean\n}\n\nconst defaultCommandItems: CommandItem[] = [\n  {\n    id: \"overview\",\n    label: \"Go to Analytics Overview\",\n    category: \"Navigation\",\n    shortcut: \"G A\",\n    icon: <BarChart3 className=\"h-4 w-4 text-blue-600 dark:text-blue-400\" />,\n  },\n  {\n    id: \"customers\",\n    label: \"Search Customer Directory\",\n    category: \"Navigation\",\n    shortcut: \"G C\",\n    icon: <Users className=\"h-4 w-4 text-zinc-500\" />,\n  },\n  {\n    id: \"docs-cli\",\n    label: \"Copy CLI Installation Command\",\n    category: \"Developer\",\n    shortcut: \"⌘ C\",\n    icon: <Terminal className=\"h-4 w-4 text-zinc-500\" />,\n  },\n  {\n    id: \"invoices\",\n    label: \"Export Monthly Invoice PDF\",\n    category: \"Billing\",\n    shortcut: \"⌘ E\",\n    icon: <FileText className=\"h-4 w-4 text-zinc-500\" />,\n  },\n  {\n    id: \"settings\",\n    label: \"Open Workspace Preferences\",\n    category: \"Settings\",\n    shortcut: \"⌘ ,\",\n    icon: <Settings className=\"h-4 w-4 text-zinc-500\" />,\n  },\n]\n\nexport const CommandPalette: React.FC<CommandPaletteProps> = ({\n  open: controlledOpen,\n  onOpenChange,\n  items = defaultCommandItems,\n  placeholder = \"Type a command or search...\",\n  enableGlobalShortcut = true,\n}) => {\n  const [internalOpen, setInternalOpen] = React.useState(false)\n  const [query, setQuery] = React.useState(\"\")\n  const [activeIndex, setActiveIndex] = React.useState(0)\n\n  const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen\n  const setOpen = onOpenChange || setInternalOpen\n\n  React.useEffect(() => {\n    if (!enableGlobalShortcut) return\n    const onKeyDown = (e: KeyboardEvent) => {\n      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === \"k\") {\n        e.preventDefault()\n        setOpen(!isOpen)\n      }\n    }\n    window.addEventListener(\"keydown\", onKeyDown)\n    return () => window.removeEventListener(\"keydown\", onKeyDown)\n  }, [enableGlobalShortcut, isOpen, setOpen])\n\n  const filtered = React.useMemo(() => {\n    if (!query.trim()) return items\n    const q = query.toLowerCase()\n    return items.filter(\n      (i) =>\n        i.label.toLowerCase().includes(q) ||\n        i.category.toLowerCase().includes(q)\n    )\n  }, [items, query])\n\n  const grouped = React.useMemo(() => {\n    const map = new Map<string, CommandItem[]>()\n    for (const item of filtered) {\n      const list = map.get(item.category) || []\n      list.push(item)\n      map.set(item.category, list)\n    }\n    return Array.from(map.entries())\n  }, [filtered])\n\n  React.useEffect(() => {\n    setActiveIndex(0)\n  }, [query])\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"ArrowDown\") {\n      e.preventDefault()\n      setActiveIndex((prev) => (filtered.length ? (prev + 1) % filtered.length : 0))\n    } else if (e.key === \"ArrowUp\") {\n      e.preventDefault()\n      setActiveIndex((prev) =>\n        filtered.length ? (prev - 1 + filtered.length) % filtered.length : 0\n      )\n    } else if (e.key === \"Enter\" && filtered[activeIndex]) {\n      e.preventDefault()\n      filtered[activeIndex].onSelect?.()\n      setOpen(false)\n    }\n  }\n\n  let flatCursor = -1\n\n  return (\n    <DialogPrimitive.Root open={isOpen} onOpenChange={setOpen}>\n      <DialogPrimitive.Portal>\n        <DialogPrimitive.Overlay className=\"fixed inset-0 z-50 bg-black/50 backdrop-blur-xs\" />\n        <DialogPrimitive.Content\n          onKeyDown={handleKeyDown}\n          className=\"fixed left-1/2 top-[18%] z-50 w-full max-w-xl -translate-x-1/2 overflow-hidden rounded-2xl border border-zinc-200 bg-white text-zinc-900 shadow-2xl focus:outline-none dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100\"\n        >\n          <DialogPrimitive.Title className=\"sr-only\">\n            Command Palette\n          </DialogPrimitive.Title>\n          <div className=\"flex items-center gap-3 border-b border-zinc-200 px-4 py-3 dark:border-zinc-800\">\n            <Search className=\"h-4 w-4 text-zinc-400 shrink-0\" />\n            <input\n              type=\"text\"\n              autoFocus\n              value={query}\n              onChange={(e) => setQuery(e.target.value)}\n              placeholder={placeholder}\n              className=\"w-full bg-transparent text-sm text-zinc-900 placeholder:text-zinc-400 focus:outline-none dark:text-zinc-100\"\n            />\n            <kbd className=\"rounded border border-zinc-200 bg-zinc-100 px-1.5 py-0.5 font-mono text-[10px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400\">\n              ESC\n            </kbd>\n          </div>\n\n          <div className=\"max-h-80 overflow-y-auto p-2 space-y-3\">\n            {grouped.length === 0 ? (\n              <div className=\"py-10 text-center text-xs text-zinc-400\">\n                No matching results for &ldquo;{query}&rdquo;\n              </div>\n            ) : (\n              grouped.map(([category, groupItems]) => (\n                <div key={category} className=\"space-y-1\">\n                  <div className=\"px-2.5 py-1 text-[10px] font-semibold uppercase tracking-wider text-zinc-400\">\n                    {category}\n                  </div>\n                  {groupItems.map((item) => {\n                    flatCursor += 1\n                    const idx = flatCursor\n                    const isActive = idx === activeIndex\n                    return (\n                      <button\n                        key={item.id}\n                        type=\"button\"\n                        onMouseEnter={() => setActiveIndex(idx)}\n                        onClick={() => {\n                          item.onSelect?.()\n                          setOpen(false)\n                        }}\n                        className={cn(\n                          \"flex w-full items-center justify-between rounded-xl px-3 py-2 text-xs transition-colors cursor-pointer\",\n                          isActive\n                            ? \"bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-white\"\n                            : \"text-zinc-600 hover:bg-zinc-50 dark:text-zinc-300 dark:hover:bg-zinc-800/50\"\n                        )}\n                      >\n                        <span className=\"flex items-center gap-2.5\">\n                          {item.icon}\n                          <span className=\"font-medium\">{item.label}</span>\n                        </span>\n                        {item.shortcut && (\n                          <kbd className=\"rounded border border-zinc-200 bg-white px-1.5 py-0.5 font-mono text-[10px] text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-400\">\n                            {item.shortcut}\n                          </kbd>\n                        )}\n                      </button>\n                    )\n                  })}\n                </div>\n              ))\n            )}\n          </div>\n\n          <div className=\"flex items-center justify-between border-t border-zinc-200 bg-zinc-50 px-4 py-2 text-[11px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950/50 dark:text-zinc-400\">\n            <div className=\"flex items-center gap-3\">\n              <span className=\"flex items-center gap-1\">\n                <CornerDownLeft className=\"h-3 w-3\" /> Select\n              </span>\n              <span>↑↓ Navigate</span>\n            </div>\n            <span className=\"font-mono text-[10px]\">Quick Search</span>\n          </div>\n        </DialogPrimitive.Content>\n      </DialogPrimitive.Portal>\n    </DialogPrimitive.Root>\n  )\n}\n"
    }
  ]
}