{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "command-palette",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": [
    "@radix-ui/react-dialog",
    "lucide-react",
    "motion",
    "next-themes"
  ],
  "devDependencies": [],
  "files": [
    {
      "path": "command-palette.tsx",
      "content": "\"use client\";\n\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport {\n  ArrowDown,\n  ArrowUp,\n  Command,\n  CornerDownLeft,\n  Search,\n  X,\n} from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { usePathname, useRouter } from \"next/navigation\";\nimport { useTheme } from \"next-themes\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst QUERY_SPLIT_REGEX = /\\s+/;\nconst RECENT_STORAGE_KEY = \"iconiq-command-palette-recent\";\nconst MAC_PLATFORM_REGEX = /mac/i;\nconst EXTERNAL_HREF_REGEX = /^https?:\\/\\//i;\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)]\";\n\nconst commandItemHighlightClassName =\n  \"absolute inset-0 -z-10 rounded-lg bg-accent/60\";\n\nconst dialogClassName =\n  \"fixed inset-x-4 top-[calc(var(--nav-stack-height-mobile,0px)+0.75rem+env(safe-area-inset-top,0px))] z-[401] flex w-auto min-h-[12rem] max-h-[min(560px,calc(100dvh-var(--nav-stack-height-mobile,0px)-1.5rem-env(safe-area-inset-top,0px)-env(safe-area-inset-bottom,0px)))] flex-col overflow-hidden rounded-2xl border border-border/80 bg-background shadow-[0_28px_90px_rgba(10,10,10,0.12)] outline-none sm:inset-x-6 lg:inset-x-auto lg:top-[calc(var(--nav-stack-height-desktop,0px)+1rem+env(safe-area-inset-top,0px))] lg:left-1/2 lg:w-[min(680px,calc(100vw-2rem))] lg:max-h-[min(560px,calc(100dvh-var(--nav-stack-height-desktop,0px)-2rem-env(safe-area-inset-top,0px)-env(safe-area-inset-bottom,0px)))] lg:-translate-x-1/2\";\n\nconst resultsClassName = \"max-h-[min(420px,50dvh)]\";\n\ntype StoredRecentItem = {\n  description?: string;\n  href?: string;\n  id?: string;\n  keywords?: string[];\n  label: string;\n  value?: string;\n};\n\ntype DisplaySection = {\n  heading: string;\n  items: CommandMenuItemDef[];\n};\n\nexport type CommandMenuItemDef = {\n  action?: () => void;\n  description?: string;\n  disabled?: boolean;\n  external?: boolean;\n  href?: string;\n  icon?: React.ComponentType<{ className?: string }>;\n  id?: string;\n  keywords?: string[];\n  label: string;\n  replace?: boolean;\n  shortcut?: string;\n  value?: string;\n};\n\nexport type CommandMenuGroupDef = {\n  heading: string;\n  items: CommandMenuItemDef[];\n};\n\nexport interface CommandMenuTriggerProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  label?: string;\n  shortcut?: string;\n  showShortcut?: boolean;\n}\n\nexport interface CommandPaletteProps {\n  className?: string;\n  closeOnRouteChange?: boolean;\n  contentDelay?: number;\n  currentPath?: string;\n  emptyMessage?: string;\n  enableGlobalShortcut?: boolean;\n  filter?: (item: CommandMenuItemDef, query: string) => boolean;\n  groups?: CommandMenuGroupDef[];\n  loadingMessage?: string;\n  maxRecentItems?: number;\n  noQueryMessage?: string;\n  onNavigate?: (href: string, item: CommandMenuItemDef) => void;\n  onOpenChange?: (open: boolean) => void;\n  onSearch?: (query: string) => Promise<CommandMenuGroupDef[]>;\n  onSelect?: (item: CommandMenuItemDef) => void;\n  open?: boolean;\n  overlayClassName?: string;\n  placeholder?: string;\n  positionClassName?: string;\n  recentItems?: CommandMenuItemDef[];\n  searchDebounceMs?: number;\n  shortcutKey?: string;\n  showFooterHints?: boolean;\n  showRecentGroup?: boolean;\n  showThemeGroup?: boolean;\n  themeGroup?: CommandMenuGroupDef;\n  themeGroupHeading?: string;\n  themed?: boolean;\n  trigger?: React.ReactNode;\n  triggerProps?: CommandMenuTriggerProps;\n}\n\nfunction useDebouncedValue<T>(value: T, delayMs: number): T {\n  const [debounced, setDebounced] = React.useState(value);\n\n  React.useEffect(() => {\n    if (delayMs <= 0) {\n      setDebounced(value);\n      return;\n    }\n\n    const id = window.setTimeout(() => setDebounced(value), delayMs);\n    return () => window.clearTimeout(id);\n  }, [delayMs, value]);\n\n  return debounced;\n}\n\nfunction useIsMac() {\n  const [isMac, setIsMac] = React.useState(true);\n\n  React.useEffect(() => {\n    if (typeof navigator === \"undefined\") {\n      return;\n    }\n\n    const navigatorWithUserAgentData = navigator as Navigator & {\n      userAgentData?: { platform?: string };\n    };\n    const platform =\n      navigatorWithUserAgentData.userAgentData?.platform ??\n      navigator.platform ??\n      \"\";\n\n    setIsMac(MAC_PLATFORM_REGEX.test(platform));\n  }, []);\n\n  return isMac;\n}\n\nfunction escapeRegExp(value: string) {\n  return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction highlightText(text: string, query: string) {\n  const rawTerms = query.trim().split(QUERY_SPLIT_REGEX).filter(Boolean);\n\n  if (rawTerms.length === 0) {\n    return text;\n  }\n\n  const parts = text.split(\n    new RegExp(`(${rawTerms.map(escapeRegExp).join(\"|\")})`, \"gi\")\n  );\n\n  return parts.map((part, index) => {\n    if (rawTerms.some((term) => part.toLowerCase() === term.toLowerCase())) {\n      return (\n        <mark\n          className=\"rounded-sm bg-amber-200/90 px-0.5 text-foreground dark:bg-amber-400/40\"\n          key={`${part}-${index}`}\n        >\n          {part}\n        </mark>\n      );\n    }\n\n    return <React.Fragment key={`${part}-${index}`}>{part}</React.Fragment>;\n  });\n}\n\nfunction Kbd({ className, ...props }: React.ComponentProps<\"kbd\">) {\n  return (\n    <kbd\n      className={cn(\n        \"pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-[6px] border border-border bg-background px-1.5 font-medium font-sans text-[11px] text-muted-foreground\",\n        className\n      )}\n      data-slot=\"kbd\"\n      {...props}\n    />\n  );\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\"inline-flex items-center gap-1\", className)}\n      data-slot=\"kbd-group\"\n      {...props}\n    />\n  );\n}\n\nfunction isEditableTarget(target: EventTarget | null) {\n  return (\n    target instanceof HTMLInputElement ||\n    target instanceof HTMLTextAreaElement ||\n    target instanceof HTMLSelectElement ||\n    (target instanceof HTMLElement && target.isContentEditable)\n  );\n}\n\nfunction getItemKey(item: CommandMenuItemDef) {\n  return item.id ?? item.value ?? item.href ?? item.label;\n}\n\nfunction matchesQuery(\n  item: CommandMenuItemDef,\n  query: string,\n  customFilter?: CommandPaletteProps[\"filter\"]\n) {\n  if (customFilter) {\n    return customFilter(item, query);\n  }\n\n  const normalized = query.trim().toLowerCase();\n\n  if (!normalized) {\n    return true;\n  }\n\n  const haystack = [item.label, item.description, ...(item.keywords ?? [])]\n    .filter(Boolean)\n    .join(\" \")\n    .toLowerCase();\n\n  return normalized\n    .split(QUERY_SPLIT_REGEX)\n    .filter(Boolean)\n    .every((term) => haystack.includes(term));\n}\n\nfunction filterGroupItems(\n  items: CommandMenuItemDef[],\n  query: string,\n  customFilter?: CommandPaletteProps[\"filter\"]\n) {\n  return items.filter((item) => matchesQuery(item, query, customFilter));\n}\n\nfunction loadStoredRecentItems(): StoredRecentItem[] {\n  if (typeof window === \"undefined\") {\n    return [];\n  }\n\n  try {\n    const raw = window.localStorage.getItem(RECENT_STORAGE_KEY);\n    if (!raw) {\n      return [];\n    }\n\n    const parsed = JSON.parse(raw) as StoredRecentItem[];\n    return Array.isArray(parsed) ? parsed : [];\n  } catch {\n    return [];\n  }\n}\n\nfunction persistRecentItem(item: CommandMenuItemDef, maxRecentItems: number) {\n  if (typeof window === \"undefined\") {\n    return;\n  }\n\n  const nextEntry: StoredRecentItem = {\n    description: item.description,\n    href: item.href,\n    id: item.id,\n    keywords: item.keywords,\n    label: item.label,\n    value: item.value,\n  };\n\n  const existing = loadStoredRecentItems().filter(\n    (entry) => getItemKey(entry as CommandMenuItemDef) !== getItemKey(item)\n  );\n\n  try {\n    window.localStorage.setItem(\n      RECENT_STORAGE_KEY,\n      JSON.stringify([nextEntry, ...existing].slice(0, maxRecentItems))\n    );\n  } catch {\n    // Ignore quota errors.\n  }\n}\n\nfunction findNextSelectableIndex(\n  items: CommandMenuItemDef[],\n  current: number,\n  direction: 1 | -1\n) {\n  if (items.length === 0) {\n    return 0;\n  }\n\n  let next = current;\n\n  for (const _item of items) {\n    next = (next + direction + items.length) % items.length;\n\n    if (!items[next]?.disabled) {\n      return next;\n    }\n  }\n\n  return current;\n}\n\nfunction SearchShortcutBadge({\n  className,\n  isMac,\n  shortcutKey,\n}: {\n  className?: string;\n  isMac: boolean;\n  shortcutKey: string;\n}) {\n  return (\n    <KbdGroup className={cn(\"shrink-0\", className)}>\n      {isMac ? (\n        <>\n          <Kbd className=\"px-1\">\n            <Command className=\"size-3\" />\n          </Kbd>\n          <Kbd>{shortcutKey.toUpperCase()}</Kbd>\n        </>\n      ) : (\n        <>\n          <Kbd className=\"px-1.5 normal-case\">Ctrl</Kbd>\n          <Kbd>{shortcutKey.toUpperCase()}</Kbd>\n        </>\n      )}\n    </KbdGroup>\n  );\n}\n\nfunction CommandMenuTrigger({\n  className,\n  label = \"Search…\",\n  onClick,\n  shortcut = \"K\",\n  showShortcut = true,\n  ...props\n}: CommandMenuTriggerProps) {\n  const isMac = useIsMac();\n\n  return (\n    <button\n      className={cn(\n        \"flex w-full max-w-sm items-center gap-2 rounded-lg border border-border/80 bg-background/60 px-3 py-2 text-left text-muted-foreground text-sm backdrop-blur-sm transition-colors hover:bg-accent/50\",\n        className\n      )}\n      data-slot=\"command-palette-trigger\"\n      onClick={onClick}\n      type=\"button\"\n      {...props}\n    >\n      <Search className=\"size-4 shrink-0\" />\n      <span className=\"flex-1 truncate\">{label}</span>\n      {showShortcut ? (\n        <SearchShortcutBadge\n          className=\"hidden sm:inline-flex\"\n          isMac={isMac}\n          shortcutKey={shortcut}\n        />\n      ) : null}\n    </button>\n  );\n}\n\nfunction createDefaultThemeGroup(\n  setTheme: (theme: string) => void,\n  heading: string\n): CommandMenuGroupDef {\n  return {\n    heading,\n    items: [\n      {\n        id: \"theme-light\",\n        label: \"Light Mode\",\n        action: () => setTheme(\"light\"),\n        keywords: [\"light\", \"bright\", \"white\", \"day\"],\n      },\n      {\n        id: \"theme-dark\",\n        label: \"Dark Mode\",\n        action: () => setTheme(\"dark\"),\n        keywords: [\"dark\", \"night\", \"black\"],\n      },\n      {\n        id: \"theme-system\",\n        label: \"System Theme\",\n        action: () => setTheme(\"system\"),\n        keywords: [\"system\", \"auto\", \"os\", \"default\"],\n      },\n    ],\n  };\n}\n\ntype CommandPaletteViewProps = CommandPaletteProps & {\n  setTheme?: (theme: string) => void;\n};\n\nfunction CommandPaletteView({\n  className,\n  closeOnRouteChange = true,\n  contentDelay = 0,\n  currentPath,\n  emptyMessage = \"No results found.\",\n  enableGlobalShortcut = true,\n  filter: customFilter,\n  groups = [],\n  loadingMessage = \"Searching…\",\n  maxRecentItems = 5,\n  noQueryMessage = \"Start typing to search commands.\",\n  onNavigate,\n  onOpenChange,\n  onSearch,\n  onSelect,\n  open: openProp,\n  overlayClassName,\n  placeholder = \"Search components, pages, actions…\",\n  positionClassName,\n  recentItems,\n  searchDebounceMs = 200,\n  shortcutKey = \"k\",\n  showFooterHints = true,\n  showRecentGroup = false,\n  showThemeGroup = false,\n  themeGroup,\n  themeGroupHeading = \"Theme\",\n  themed = false,\n  trigger,\n  triggerProps,\n  setTheme,\n}: CommandPaletteViewProps) {\n  const router = useRouter();\n  const pathname = usePathname();\n  const resolvedPath = currentPath ?? pathname ?? \"\";\n  const isMac = useIsMac();\n  const prefersReducedMotion = useReducedMotion() === true;\n  const paletteId = React.useId().replace(/:/g, \"\");\n  const listboxId = `${paletteId}-listbox`;\n  const dialogId = `${paletteId}-dialog`;\n\n  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);\n  const [resultsVisible, setResultsVisible] = React.useState(contentDelay <= 0);\n  const [query, setQuery] = React.useState(\"\");\n  const [activeIndex, setActiveIndex] = React.useState(0);\n  const [storedRecentItems, setStoredRecentItems] = React.useState<\n    CommandMenuItemDef[]\n  >([]);\n  const [asyncGroups, setAsyncGroups] = React.useState<CommandMenuGroupDef[]>(\n    []\n  );\n  const [isSearching, setIsSearching] = React.useState(false);\n\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const itemRefs = React.useRef<Array<HTMLButtonElement | null>>([]);\n  const previousPathnameRef = React.useRef(pathname);\n\n  const isControlled = openProp !== undefined;\n  const open = isControlled ? openProp : uncontrolledOpen;\n  const debouncedQuery = useDebouncedValue(\n    query,\n    onSearch ? searchDebounceMs : 0\n  );\n  const searchQuery = onSearch ? debouncedQuery : query;\n\n  const setOpen = React.useCallback(\n    (nextOpen: boolean | ((current: boolean) => boolean)) => {\n      if (isControlled) {\n        const resolved =\n          typeof nextOpen === \"function\"\n            ? nextOpen(Boolean(openProp))\n            : nextOpen;\n        onOpenChange?.(resolved);\n        return;\n      }\n\n      setUncontrolledOpen((current) => {\n        const resolved =\n          typeof nextOpen === \"function\" ? nextOpen(current) : nextOpen;\n        onOpenChange?.(resolved);\n        return resolved;\n      });\n    },\n    [isControlled, onOpenChange, openProp]\n  );\n\n  React.useEffect(() => {\n    if (!showRecentGroup) {\n      return;\n    }\n\n    setStoredRecentItems(recentItems ?? loadStoredRecentItems());\n  }, [recentItems, showRecentGroup]);\n\n  React.useEffect(() => {\n    if (!open) {\n      setResultsVisible(false);\n      setQuery(\"\");\n      setActiveIndex(0);\n      setAsyncGroups([]);\n      setIsSearching(false);\n      return;\n    }\n\n    if (contentDelay > 0) {\n      const contentId = window.setTimeout(\n        () => setResultsVisible(true),\n        contentDelay\n      );\n      const focusId = window.setTimeout(() => inputRef.current?.focus(), 10);\n\n      return () => {\n        window.clearTimeout(contentId);\n        window.clearTimeout(focusId);\n      };\n    }\n\n    setResultsVisible(true);\n    const focusId = window.setTimeout(() => inputRef.current?.focus(), 10);\n\n    return () => {\n      window.clearTimeout(focusId);\n    };\n  }, [contentDelay, open]);\n\n  React.useEffect(() => {\n    if (!(closeOnRouteChange && pathname)) {\n      previousPathnameRef.current = pathname;\n      return;\n    }\n\n    if (previousPathnameRef.current === pathname) {\n      return;\n    }\n\n    previousPathnameRef.current = pathname;\n    setOpen(false);\n  }, [closeOnRouteChange, pathname, setOpen]);\n\n  React.useEffect(() => {\n    if (!enableGlobalShortcut) {\n      return;\n    }\n\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key.toLowerCase() === shortcutKey.toLowerCase() &&\n        (event.metaKey || event.ctrlKey) &&\n        !event.altKey &&\n        !event.shiftKey &&\n        !isEditableTarget(event.target)\n      ) {\n        event.preventDefault();\n        event.stopPropagation();\n        setOpen((current) => !current);\n      }\n    };\n\n    document.addEventListener(\"keydown\", handleKeyDown, { capture: true });\n\n    return () => {\n      document.removeEventListener(\"keydown\", handleKeyDown, { capture: true });\n    };\n  }, [enableGlobalShortcut, setOpen, shortcutKey]);\n\n  React.useEffect(() => {\n    if (!(open && onSearch)) {\n      return;\n    }\n\n    let cancelled = false;\n    setIsSearching(true);\n\n    onSearch(debouncedQuery)\n      .then((nextGroups) => {\n        if (!cancelled) {\n          setAsyncGroups(nextGroups);\n          setIsSearching(false);\n        }\n      })\n      .catch(() => {\n        if (!cancelled) {\n          setAsyncGroups([]);\n          setIsSearching(false);\n        }\n      });\n\n    return () => {\n      cancelled = true;\n    };\n  }, [debouncedQuery, onSearch, open]);\n\n  const themeSection = React.useMemo<CommandMenuGroupDef | null>(() => {\n    if (!(showThemeGroup && setTheme)) {\n      return null;\n    }\n\n    return themeGroup ?? createDefaultThemeGroup(setTheme, themeGroupHeading);\n  }, [setTheme, showThemeGroup, themeGroup, themeGroupHeading]);\n\n  const displaySections = React.useMemo<DisplaySection[]>(() => {\n    const sections: DisplaySection[] = [];\n\n    if (showRecentGroup) {\n      const recent = filterGroupItems(\n        storedRecentItems,\n        searchQuery,\n        customFilter\n      );\n\n      if (recent.length > 0) {\n        sections.push({ heading: \"Recent\", items: recent });\n      }\n    }\n\n    for (const group of [...groups, ...asyncGroups]) {\n      const items = filterGroupItems(group.items, searchQuery, customFilter);\n\n      if (items.length > 0) {\n        sections.push({ heading: group.heading, items });\n      }\n    }\n\n    if (themeSection) {\n      const items = filterGroupItems(\n        themeSection.items,\n        searchQuery,\n        customFilter\n      );\n\n      if (items.length > 0) {\n        sections.push({ heading: themeSection.heading, items });\n      }\n    }\n\n    return sections;\n  }, [\n    asyncGroups,\n    customFilter,\n    groups,\n    searchQuery,\n    showRecentGroup,\n    storedRecentItems,\n    themeSection,\n  ]);\n\n  const flatItems = React.useMemo(\n    () => displaySections.flatMap((section) => section.items),\n    [displaySections]\n  );\n\n  React.useEffect(() => {\n    itemRefs.current = itemRefs.current.slice(0, flatItems.length);\n\n    if (flatItems.length === 0) {\n      setActiveIndex(0);\n      return;\n    }\n\n    setActiveIndex(findNextSelectableIndex(flatItems, -1, 1));\n  }, [flatItems]);\n\n  React.useEffect(() => {\n    itemRefs.current[activeIndex]?.scrollIntoView({ block: \"nearest\" });\n  }, [activeIndex]);\n\n  const activeOptionId =\n    flatItems.length > 0 ? `${listboxId}-opt-${activeIndex}` : undefined;\n\n  const run = React.useCallback(\n    (fn: () => void) => {\n      setOpen(false);\n      fn();\n    },\n    [setOpen]\n  );\n\n  const rememberRecentItem = React.useCallback(\n    (item: CommandMenuItemDef) => {\n      if (!showRecentGroup) {\n        return;\n      }\n\n      persistRecentItem(item, maxRecentItems);\n      setStoredRecentItems((current) => {\n        const nextEntry: CommandMenuItemDef = {\n          description: item.description,\n          href: item.href,\n          id: item.id,\n          keywords: item.keywords,\n          label: item.label,\n          value: item.value,\n        };\n\n        return [\n          nextEntry,\n          ...current.filter((entry) => getItemKey(entry) !== getItemKey(item)),\n        ].slice(0, maxRecentItems);\n      });\n    },\n    [maxRecentItems, showRecentGroup]\n  );\n\n  const handleItemSelect = React.useCallback(\n    (item: CommandMenuItemDef) => {\n      if (item.disabled) {\n        return;\n      }\n\n      onSelect?.(item);\n      rememberRecentItem(item);\n\n      if (item.action) {\n        run(item.action);\n        return;\n      }\n\n      if (!item.href) {\n        return;\n      }\n\n      const href = item.href;\n      const isExternal =\n        item.external === true || EXTERNAL_HREF_REGEX.test(href);\n\n      if (isExternal) {\n        run(() => {\n          window.open(href, \"_blank\", \"noopener,noreferrer\");\n        });\n        return;\n      }\n\n      if (onNavigate) {\n        run(() => onNavigate(href, item));\n        return;\n      }\n\n      run(() => {\n        if (item.replace) {\n          router.replace(href);\n          return;\n        }\n\n        router.push(href);\n      });\n    },\n    [onNavigate, onSelect, rememberRecentItem, router, run]\n  );\n\n  const handleListKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n    if (event.key === \"ArrowDown\") {\n      event.preventDefault();\n      setActiveIndex((current) =>\n        flatItems.length === 0\n          ? 0\n          : findNextSelectableIndex(flatItems, current, 1)\n      );\n      return;\n    }\n\n    if (event.key === \"ArrowUp\") {\n      event.preventDefault();\n      setActiveIndex((current) =>\n        flatItems.length === 0\n          ? 0\n          : findNextSelectableIndex(flatItems, current, -1)\n      );\n      return;\n    }\n\n    if (event.key === \"Home\") {\n      event.preventDefault();\n      setActiveIndex(findNextSelectableIndex(flatItems, -1, 1));\n      return;\n    }\n\n    if (event.key === \"End\") {\n      event.preventDefault();\n      setActiveIndex(findNextSelectableIndex(flatItems, flatItems.length, -1));\n      return;\n    }\n\n    if (event.key === \"Enter\") {\n      const activeItem = flatItems[activeIndex];\n\n      if (!activeItem || activeItem.disabled) {\n        return;\n      }\n\n      event.preventDefault();\n      handleItemSelect(activeItem);\n    }\n  };\n\n  const renderTrigger = () => {\n    const triggerAria = {\n      \"aria-controls\": dialogId,\n      \"aria-expanded\": open,\n      \"aria-haspopup\": \"dialog\" as const,\n    };\n\n    if (trigger) {\n      if (\n        React.isValidElement<{\n          onClick?: (event: React.MouseEvent) => void;\n        }>(trigger)\n      ) {\n        return React.cloneElement(trigger, {\n          ...triggerAria,\n          onClick: (event: React.MouseEvent) => {\n            trigger.props.onClick?.(event);\n            setOpen(true);\n          },\n        });\n      }\n\n      return (\n        <button\n          className=\"cursor-pointer\"\n          onClick={() => setOpen(true)}\n          type=\"button\"\n          {...triggerAria}\n        >\n          {trigger}\n        </button>\n      );\n    }\n\n    const { onClick: triggerOnClick, ...restTriggerProps } = triggerProps ?? {};\n\n    return (\n      <CommandMenuTrigger\n        shortcut={shortcutKey.toUpperCase()}\n        {...restTriggerProps}\n        {...triggerAria}\n        onClick={(event) => {\n          triggerOnClick?.(event);\n          setOpen(true);\n        }}\n      />\n    );\n  };\n\n  const renderItem = (item: CommandMenuItemDef, index: number, key: string) => {\n    const isActive = index === activeIndex;\n    const optionId = `${listboxId}-opt-${index}`;\n    const ItemIcon = item.icon;\n    const isCurrentPage =\n      Boolean(item.href) &&\n      Boolean(resolvedPath) &&\n      (item.href === resolvedPath ||\n        (item.href !== \"/\" && resolvedPath.startsWith(`${item.href}/`)));\n\n    return (\n      <button\n        aria-selected={isActive}\n        className={cn(\n          \"relative isolate flex w-full items-start gap-3 rounded-lg px-3 py-2.5 text-left text-foreground text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n          item.disabled && \"cursor-not-allowed opacity-50\"\n        )}\n        data-slot=\"command-palette-option\"\n        disabled={item.disabled}\n        id={optionId}\n        key={key}\n        onClick={() => handleItemSelect(item)}\n        onMouseEnter={() => {\n          if (!item.disabled) {\n            setActiveIndex(index);\n          }\n        }}\n        ref={(node) => {\n          itemRefs.current[index] = node;\n        }}\n        role=\"option\"\n        type=\"button\"\n      >\n        {isActive ? (\n          prefersReducedMotion ? (\n            <span aria-hidden className={commandItemHighlightClassName} />\n          ) : (\n            <motion.span\n              aria-hidden\n              className={commandItemHighlightClassName}\n              layoutId={`${paletteId}-active-item`}\n              transition={{ type: \"spring\", stiffness: 600, damping: 38 }}\n            />\n          )\n        ) : null}\n        {ItemIcon ? (\n          <ItemIcon className=\"relative z-10 mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n        ) : null}\n        <div className=\"relative z-10 min-w-0 flex-1\">\n          <div className=\"flex items-center gap-2\">\n            <div className=\"truncate font-medium text-sm\">\n              {highlightText(item.label, searchQuery)}\n            </div>\n            {isCurrentPage ? (\n              <span className=\"shrink-0 rounded-full bg-accent px-2 py-0.5 font-medium text-[10px] text-muted-foreground uppercase tracking-[0.12em]\">\n                Current\n              </span>\n            ) : null}\n          </div>\n          {item.description ? (\n            <p className=\"mt-0.5 line-clamp-2 text-muted-foreground text-xs leading-5\">\n              {highlightText(item.description, searchQuery)}\n            </p>\n          ) : null}\n        </div>\n        {item.shortcut ? (\n          <Kbd className=\"relative z-10 mt-0.5 shrink-0\">{item.shortcut}</Kbd>\n        ) : null}\n      </button>\n    );\n  };\n\n  const indexedSections = React.useMemo(() => {\n    let index = 0;\n\n    return displaySections.map((section) => ({\n      heading: section.heading,\n      items: section.items.map((item) => {\n        const currentIndex = index;\n        index += 1;\n\n        return { index: currentIndex, item };\n      }),\n    }));\n  }, [displaySections]);\n\n  const hasQuery = searchQuery.trim().length > 0;\n  const showEmptyState = !isSearching && flatItems.length === 0;\n  const emptyStateMessage = hasQuery ? emptyMessage : noQueryMessage;\n\n  return (\n    <>\n      {renderTrigger()}\n\n      <DialogPrimitive.Root onOpenChange={setOpen} open={open}>\n        <DialogPrimitive.Portal>\n          <DialogPrimitive.Overlay\n            className={cn(\n              \"fixed inset-0 z-[400] bg-black/40 backdrop-blur-sm dark:bg-black/55\",\n              overlayClassName\n            )}\n          />\n          <DialogPrimitive.Content\n            className={cn(\n              dialogClassName,\n              themed && componentThemeClassName,\n              className,\n              positionClassName\n            )}\n            data-slot=\"command-palette-content\"\n            id={dialogId}\n            onOpenAutoFocus={(event) => {\n              event.preventDefault();\n              inputRef.current?.focus();\n            }}\n          >\n            <DialogPrimitive.Title className=\"sr-only\">\n              Command palette\n            </DialogPrimitive.Title>\n            <DialogPrimitive.Description className=\"sr-only\">\n              Search commands and navigate results with the keyboard.\n            </DialogPrimitive.Description>\n\n            <div className=\"flex shrink-0 items-center gap-3 border-border/70 border-b px-4 py-3\">\n              <Search className=\"size-4 shrink-0 text-muted-foreground\" />\n              <input\n                aria-activedescendant={activeOptionId}\n                aria-autocomplete=\"list\"\n                aria-controls={listboxId}\n                aria-expanded={open}\n                autoCapitalize=\"off\"\n                autoCorrect=\"off\"\n                className=\"w-full min-w-0 touch-manipulation bg-transparent text-[16px] text-foreground leading-normal outline-none placeholder:text-muted-foreground md:text-sm\"\n                data-slot=\"command-palette-input\"\n                onChange={(event) => setQuery(event.target.value)}\n                onKeyDown={handleListKeyDown}\n                placeholder={placeholder}\n                ref={inputRef}\n                role=\"combobox\"\n                spellCheck={false}\n                type=\"text\"\n                value={query}\n              />\n              <SearchShortcutBadge\n                className=\"hidden md:inline-flex\"\n                isMac={isMac}\n                shortcutKey={shortcutKey}\n              />\n              <DialogPrimitive.Close asChild>\n                <button\n                  aria-label=\"Close command palette\"\n                  className=\"inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n                  type=\"button\"\n                >\n                  <X className=\"size-4\" />\n                </button>\n              </DialogPrimitive.Close>\n            </div>\n\n            <div\n              className={cn(\n                \"min-h-0 overflow-hidden transition-[max-height,opacity] duration-200 ease-out\",\n                resultsVisible\n                  ? cn(\"opacity-100\", resultsClassName)\n                  : \"max-h-0 opacity-0\"\n              )}\n            >\n              <div\n                aria-busy={isSearching}\n                className=\"h-full max-h-[inherit] overflow-y-auto overscroll-contain p-2 [-webkit-overflow-scrolling:touch]\"\n                id={listboxId}\n                onKeyDown={handleListKeyDown}\n                role=\"listbox\"\n              >\n                {isSearching ? (\n                  <div className=\"px-3 py-10 text-center text-muted-foreground text-sm\">\n                    {loadingMessage}\n                  </div>\n                ) : showEmptyState ? (\n                  <div className=\"px-3 py-10 text-center text-muted-foreground text-sm\">\n                    {emptyStateMessage}\n                  </div>\n                ) : (\n                  indexedSections.map((section, sectionIndex) => (\n                    <React.Fragment key={section.heading}>\n                      {sectionIndex > 0 ? (\n                        <div className=\"mx-2 my-2 h-px bg-border/60\" />\n                      ) : null}\n                      <div className=\"px-2 py-1\">\n                        <div className=\"px-1 pb-1.5 font-medium text-[11px] text-muted-foreground uppercase tracking-[0.16em]\">\n                          {section.heading}\n                        </div>\n                        <div className=\"space-y-1\">\n                          {section.items.map(({ index, item }) =>\n                            renderItem(\n                              item,\n                              index,\n                              `${section.heading}-${getItemKey(item)}-${index}`\n                            )\n                          )}\n                        </div>\n                      </div>\n                    </React.Fragment>\n                  ))\n                )}\n              </div>\n            </div>\n\n            {showFooterHints ? (\n              <div\n                className=\"flex shrink-0 flex-wrap items-center gap-x-3 gap-y-1 border-border/70 border-t px-4 py-2 text-[11px] text-muted-foreground\"\n                data-slot=\"command-palette-footer\"\n              >\n                <span className=\"inline-flex items-center gap-1\">\n                  <Kbd>\n                    <ArrowUp className=\"size-3\" />\n                  </Kbd>\n                  <Kbd>\n                    <ArrowDown className=\"size-3\" />\n                  </Kbd>\n                  Navigate\n                </span>\n                <span className=\"inline-flex items-center gap-1\">\n                  <Kbd>\n                    <CornerDownLeft className=\"size-3\" />\n                  </Kbd>\n                  Select\n                </span>\n                <span className=\"inline-flex items-center gap-1\">\n                  <Kbd className=\"px-2\">esc</Kbd>\n                  Close\n                </span>\n              </div>\n            ) : null}\n          </DialogPrimitive.Content>\n        </DialogPrimitive.Portal>\n      </DialogPrimitive.Root>\n    </>\n  );\n}\n\nfunction CommandPaletteWithTheme(props: CommandPaletteProps) {\n  const { setTheme } = useTheme();\n\n  return <CommandPaletteView {...props} setTheme={setTheme} />;\n}\n\nfunction CommandPalette(props: CommandPaletteProps) {\n  if (props.showThemeGroup) {\n    return <CommandPaletteWithTheme {...props} />;\n  }\n\n  return <CommandPaletteView {...props} />;\n}\n\nexport {\n  CommandMenuTrigger,\n  CommandPalette,\n  CommandPalette as CommandMenu,\n  Kbd,\n  KbdGroup,\n};\n",
      "type": "registry:ui"
    }
  ],
  "title": "Command Palette",
  "description": "Radix dialog command menu with grouped search, keyboard shortcuts, navigation items, custom triggers, and optional theme switching."
}
