{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command-menu",
  "title": "Command Menu",
  "description": "A composable ⌘K command palette with fuzzy search, custom groups, built-in theme switcher, and animated content reveal.",
  "dependencies": [
    "next",
    "next-themes",
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/components/unlumen/command-menu/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { useTheme } from \"next-themes\";\nimport { Moon, Sun, Monitor, Search } from \"lucide-react\";\n\nimport {\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n} from \"@/components/ui/command\";\nimport { Kbd, KbdGroup } from \"@/components/ui/kbd\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CommandMenuItemDef = {\n  /** Display label */\n  label: string;\n  /** Lucide or any icon component */\n  icon?: React.ComponentType<{ className?: string }>;\n  /** Route to navigate to (uses next/navigation router.push) */\n  href?: string;\n  /** Custom action — used instead of href when provided */\n  action?: () => void;\n  /** Extra keywords for matching */\n  keywords?: string[];\n};\n\nexport type CommandMenuGroupDef = {\n  /** Heading rendered above the group */\n  heading: string;\n  items: CommandMenuItemDef[];\n};\n\nexport interface CommandMenuTriggerProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** Label text inside the trigger button */\n  label?: string;\n  /** Keyboard shortcut hint shown on the right */\n  shortcut?: string;\n  /** Whether to show the keyboard shortcut badge */\n  showShortcut?: boolean;\n}\n\nexport interface CommandMenuProps {\n  /** CommandGroup definitions rendered in the dialog */\n  groups?: CommandMenuGroupDef[];\n  /** Whether to include the built-in Theme group */\n  showThemeGroup?: boolean;\n  /** Placeholder text inside the search input */\n  placeholder?: string;\n  /** Key portion of the keyboard shortcut (⌘ / Ctrl + key) */\n  shortcutKey?: string;\n  /** Delay in ms before the dialog content becomes visible (avoids layout pop) */\n  contentDelay?: number;\n  /** Custom trigger element. When provided the default button is NOT rendered. */\n  trigger?: React.ReactNode;\n  /** Props forwarded to the default trigger button */\n  triggerProps?: CommandMenuTriggerProps;\n  /** Extra className on the root CommandDialog */\n  className?: string;\n}\n\nfunction CommandMenuTrigger({\n  label = \"Search…\",\n  shortcut = \"K\",\n  showShortcut = true,\n  className,\n  onClick,\n  ...props\n}: CommandMenuTriggerProps) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      className={cn(\n        \"flex items-center gap-2 rounded-lg border bg-background/60 backdrop-blur-sm px-3 py-2 text-sm text-muted-foreground hover:bg-accent/50 transition-colors w-full max-w-sm cursor-pointer\",\n        className,\n      )}\n      {...props}\n    >\n      <Search className=\"size-4 shrink-0\" />\n      <span className=\"flex-1 text-left\">{label}</span>\n      {showShortcut && (\n        <KbdGroup>\n          <Kbd>⌘</Kbd>\n          <Kbd>{shortcut}</Kbd>\n        </KbdGroup>\n      )}\n    </button>\n  );\n}\n\nfunction CommandMenu({\n  groups = [],\n  showThemeGroup = true,\n  placeholder = \"Search components, pages, actions…\",\n  shortcutKey = \"k\",\n  contentDelay = 150,\n  trigger,\n  triggerProps,\n  className,\n}: CommandMenuProps) {\n  const router = useRouter();\n  const { setTheme } = useTheme();\n\n  const [open, setOpen] = React.useState(false);\n  const [showContent, setShowContent] = React.useState(false);\n\n  // Reveal content after dialog open transition\n  React.useEffect(() => {\n    if (open) {\n      const id = setTimeout(() => setShowContent(true), contentDelay);\n      return () => clearTimeout(id);\n    } else {\n      setShowContent(false);\n    }\n  }, [open, contentDelay]);\n\n  React.useEffect(() => {\n    const down = (e: KeyboardEvent) => {\n      if (\n        e.key.toLowerCase() === shortcutKey.toLowerCase() &&\n        (e.metaKey || e.ctrlKey)\n      ) {\n        e.preventDefault();\n        e.stopPropagation();\n        setOpen((prev) => !prev);\n      }\n    };\n    document.addEventListener(\"keydown\", down, { capture: true });\n    return () =>\n      document.removeEventListener(\"keydown\", down, { capture: true });\n  }, [shortcutKey]);\n\n  const run = React.useCallback((fn: () => void) => {\n    setOpen(false);\n    fn();\n  }, []);\n\n  const handleItemSelect = React.useCallback(\n    (item: CommandMenuItemDef) => {\n      if (item.action) {\n        run(item.action);\n      } else if (item.href) {\n        run(() => router.push(item.href!));\n      }\n    },\n    [run, router],\n  );\n\n  return (\n    <>\n      {trigger ? (\n        <span onClick={() => setOpen(true)} className=\"cursor-pointer\">\n          {trigger}\n        </span>\n      ) : (\n        <CommandMenuTrigger\n          shortcut={shortcutKey.toUpperCase()}\n          {...triggerProps}\n          onClick={() => setOpen(true)}\n        />\n      )}\n\n      <CommandDialog open={open} onOpenChange={setOpen} className={className}>\n        <CommandInput placeholder={placeholder} />\n\n        <div\n          data-lenis-prevent=\"command-menu\"\n          className=\"transition-all duration-300 ease-out overflow-hidden\"\n          style={{\n            maxHeight: showContent ? \"400px\" : \"0px\",\n            opacity: showContent ? 1 : 0,\n          }}\n        >\n          <CommandList>\n            <CommandEmpty>\n              <span className=\"text-sm font-mono text-muted-foreground\">\n                No results found.\n              </span>\n            </CommandEmpty>\n\n            {groups.map((group, gi) => (\n              <React.Fragment key={`g-${gi}`}>\n                {gi > 0 && <CommandSeparator />}\n                <CommandGroup heading={group.heading}>\n                  {group.items.map((item, ii) => (\n                    <CommandItem\n                      key={`i-${gi}-${ii}`}\n                      keywords={item.keywords}\n                      onSelect={() => handleItemSelect(item)}\n                    >\n                      {item.icon && (\n                        <item.icon className=\"mr-2 size-4 shrink-0\" />\n                      )}\n                      {item.label}\n                    </CommandItem>\n                  ))}\n                </CommandGroup>\n              </React.Fragment>\n            ))}\n\n            {showThemeGroup && (\n              <>\n                {groups.length > 0 && <CommandSeparator />}\n                <CommandGroup heading=\"Theme\">\n                  <CommandItem\n                    keywords={[\"light\", \"bright\", \"white\", \"day\"]}\n                    onSelect={() => run(() => setTheme(\"light\"))}\n                  >\n                    <Sun className=\"mr-2 size-4\" />\n                    Light Mode\n                  </CommandItem>\n                  <CommandItem\n                    keywords={[\"dark\", \"night\", \"black\"]}\n                    onSelect={() => run(() => setTheme(\"dark\"))}\n                  >\n                    <Moon className=\"mr-2 size-4\" />\n                    Dark Mode\n                  </CommandItem>\n                  <CommandItem\n                    keywords={[\"system\", \"auto\", \"os\", \"default\"]}\n                    onSelect={() => run(() => setTheme(\"system\"))}\n                  >\n                    <Monitor className=\"mr-2 size-4\" />\n                    System Theme\n                  </CommandItem>\n                </CommandGroup>\n              </>\n            )}\n          </CommandList>\n        </div>\n      </CommandDialog>\n    </>\n  );\n}\n\nexport { CommandMenu, CommandMenuTrigger };\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/command-menu.tsx"
    }
  ],
  "type": "registry:ui"
}