{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command-palette",
  "title": "Command Palette",
  "description": "A ⌘K command menu with fuzzy filtering, keyboard navigation, and a spring-tracked active highlight.",
  "dependencies": ["framer-motion"],
  "registryDependencies": ["@godui/godui-theme"],
  "files": [
    {
      "path": "packages/components/src/command-palette/command-palette.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nexport type CommandItem = {\n  /** Stable id. */\n  id: string;\n  /** Visible label. */\n  label: string;\n  /** Optional leading icon. */\n  icon?: React.ReactNode;\n  /** Optional shortcut hint rendered on the right (e.g. \"⌘P\"). */\n  shortcut?: string;\n  /** Extra terms to match against when filtering. */\n  keywords?: string[];\n  /** Invoked when the item is chosen. */\n  onSelect?: () => void;\n};\n\nexport type CommandGroup = {\n  /** Optional group heading. */\n  heading?: string;\n  items: CommandItem[];\n};\n\nexport type CommandPaletteProps = {\n  /** Controlled open state. */\n  open: boolean;\n  /** Called when the palette opens or closes. */\n  onOpenChange: (open: boolean) => void;\n  /** Grouped commands to show. */\n  groups: CommandGroup[];\n  /** Input placeholder. */\n  placeholder?: string;\n  /** Toggle the palette with ⌘K / Ctrl+K. */\n  enableShortcut?: boolean;\n};\n\nfunction matches(item: CommandItem, query: string) {\n  if (!query) return true;\n  const q = query.toLowerCase();\n  return (\n    item.label.toLowerCase().includes(q) ||\n    item.keywords?.some((k) => k.toLowerCase().includes(q)) === true\n  );\n}\n\nfunction useMounted() {\n  const [mounted, setMounted] = React.useState(false);\n  React.useEffect(() => setMounted(true), []);\n  return mounted;\n}\n\nconst CommandPalette = React.forwardRef<HTMLDivElement, CommandPaletteProps>(\n  (\n    {\n      open,\n      onOpenChange,\n      groups,\n      placeholder = \"Type a command or search…\",\n      enableShortcut = true,\n    },\n    ref,\n  ) => {\n    const mounted = useMounted();\n    const inputRef = React.useRef<HTMLInputElement>(null);\n    const [query, setQuery] = React.useState(\"\");\n    const [activeIndex, setActiveIndex] = React.useState(0);\n\n    const filteredGroups = React.useMemo(\n      () =>\n        groups\n          .map((g) => ({\n            ...g,\n            items: g.items.filter((i) => matches(i, query)),\n          }))\n          .filter((g) => g.items.length > 0),\n      [groups, query],\n    );\n    const flat = React.useMemo(\n      () => filteredGroups.flatMap((g) => g.items),\n      [filteredGroups],\n    );\n\n    React.useEffect(() => {\n      if (open) {\n        setQuery(\"\");\n        setActiveIndex(0);\n        const id = requestAnimationFrame(() => inputRef.current?.focus());\n        const prevOverflow = document.body.style.overflow;\n        document.body.style.overflow = \"hidden\";\n        return () => {\n          cancelAnimationFrame(id);\n          document.body.style.overflow = prevOverflow;\n        };\n      }\n    }, [open]);\n\n    React.useEffect(() => {\n      if (!enableShortcut) return;\n      const onKey = (e: KeyboardEvent) => {\n        if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === \"k\") {\n          e.preventDefault();\n          onOpenChange(!open);\n        }\n      };\n      document.addEventListener(\"keydown\", onKey);\n      return () => document.removeEventListener(\"keydown\", onKey);\n    }, [enableShortcut, open, onOpenChange]);\n\n    const select = (item: CommandItem) => {\n      item.onSelect?.();\n      onOpenChange(false);\n    };\n\n    const handleKeyDown = (e: React.KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        e.preventDefault();\n        onOpenChange(false);\n      } else if (e.key === \"ArrowDown\") {\n        e.preventDefault();\n        setActiveIndex((i) => (i + 1) % Math.max(flat.length, 1));\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault();\n        setActiveIndex(\n          (i) => (i - 1 + Math.max(flat.length, 1)) % Math.max(flat.length, 1),\n        );\n      } else if (e.key === \"Enter\") {\n        e.preventDefault();\n        const item = flat[activeIndex];\n        if (item) select(item);\n      }\n    };\n\n    if (!mounted) return null;\n\n    let runningIndex = -1;\n\n    return createPortal(\n      <AnimatePresence>\n        {open ? (\n          <div className=\"fixed inset-0 z-modal flex items-start justify-center p-4 pt-[12vh]\">\n            <motion.div\n              aria-hidden\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              onClick={() => onOpenChange(false)}\n              className=\"absolute inset-0 bg-foreground/40 backdrop-blur-sm\"\n            />\n            <motion.div\n              ref={ref}\n              role=\"dialog\"\n              aria-modal=\"true\"\n              aria-label=\"Command palette\"\n              data-slot=\"command-palette\"\n              initial={{ opacity: 0, scale: 0.96, y: -8, filter: \"blur(8px)\" }}\n              animate={{ opacity: 1, scale: 1, y: 0, filter: \"blur(0px)\" }}\n              exit={{ opacity: 0, scale: 0.97, y: -6, filter: \"blur(6px)\" }}\n              transition={{\n                type: \"spring\",\n                stiffness: 320,\n                damping: 32,\n                mass: 0.9,\n              }}\n              onKeyDown={handleKeyDown}\n              className=\"relative z-raised flex max-h-[60vh] w-full max-w-xl flex-col overflow-hidden rounded-2xl border border-border bg-card text-card-foreground shadow-xl\"\n            >\n              <div className=\"flex items-center gap-3 border-b border-border px-4\">\n                <span aria-hidden className=\"text-muted-foreground\">\n                  ⌕\n                </span>\n                <input\n                  ref={inputRef}\n                  value={query}\n                  onChange={(e) => {\n                    setQuery(e.target.value);\n                    setActiveIndex(0);\n                  }}\n                  placeholder={placeholder}\n                  className=\"w-full bg-transparent py-4 text-sm text-foreground outline-none placeholder:text-muted-foreground\"\n                />\n              </div>\n\n              <div className=\"overflow-y-auto p-2\">\n                {flat.length === 0 ? (\n                  <div className=\"px-3 py-8 text-center text-sm text-muted-foreground\">\n                    No results found.\n                  </div>\n                ) : (\n                  filteredGroups.map((group) => (\n                    <div key={group.heading ?? \"group\"} className=\"mb-1\">\n                      {group.heading ? (\n                        <div className=\"px-3 py-1.5 text-xs font-medium text-muted-foreground\">\n                          {group.heading}\n                        </div>\n                      ) : null}\n                      {group.items.map((item) => {\n                        runningIndex += 1;\n                        const isActive = runningIndex === activeIndex;\n                        const itemIndex = runningIndex;\n                        return (\n                          <button\n                            key={item.id}\n                            type=\"button\"\n                            onClick={() => select(item)}\n                            onMouseMove={() => setActiveIndex(itemIndex)}\n                            className=\"relative flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm text-foreground\"\n                          >\n                            {isActive ? (\n                              <motion.span\n                                layoutId=\"command-active\"\n                                transition={{\n                                  type: \"spring\",\n                                  stiffness: 500,\n                                  damping: 35,\n                                }}\n                                className=\"absolute inset-0 rounded-lg bg-accent\"\n                              />\n                            ) : null}\n                            {item.icon ? (\n                              <span className=\"relative text-muted-foreground\">\n                                {item.icon}\n                              </span>\n                            ) : null}\n                            <span className=\"relative flex-1\">\n                              {item.label}\n                            </span>\n                            {item.shortcut ? (\n                              <kbd className=\"relative rounded border border-border px-1.5 py-0.5 text-xs text-muted-foreground\">\n                                {item.shortcut}\n                              </kbd>\n                            ) : null}\n                          </button>\n                        );\n                      })}\n                    </div>\n                  ))\n                )}\n              </div>\n            </motion.div>\n          </div>\n        ) : null}\n      </AnimatePresence>,\n      document.body,\n    );\n  },\n);\nCommandPalette.displayName = \"CommandPalette\";\n\nexport { CommandPalette };\n",
      "type": "registry:ui",
      "target": "components/godui/command-palette.tsx"
    }
  ],
  "type": "registry:ui"
}
