{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "title": "Combobox",
  "description": "A type-ahead autocomplete with staggered result reveal, highlighted matches, keyboard navigation, and an async loading state.",
  "dependencies": ["framer-motion"],
  "registryDependencies": ["@godui/godui-theme"],
  "files": [
    {
      "path": "packages/components/src/combobox/combobox.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport * as React from \"react\";\n\nexport type ComboboxOption = {\n  label: string;\n  value: string;\n  description?: string;\n};\n\n/** A non-option affordance rendered inside the listbox (e.g. a pinned top row or\n *  an empty-state CTA). `onSelect` receives the current (trimmed) query. */\nexport type ComboboxAction = {\n  label: React.ReactNode;\n  onSelect: (query: string) => void;\n};\n\nexport type ComboboxProps = Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  \"onChange\" | \"defaultValue\" | \"onToggle\"\n> & {\n  /** Static option list. Ignored when `onSearch` is provided. */\n  options?: ComboboxOption[];\n  /** Async resolver. Return options for a query. */\n  onSearch?: (query: string) => Promise<ComboboxOption[]>;\n  value?: string;\n  defaultValue?: string;\n  placeholder?: string;\n  emptyMessage?: string;\n  /** Disable the input and prevent opening the listbox. */\n  disabled?: boolean;\n  onChange?: (value: string, option: ComboboxOption) => void;\n  /** Multi-select mode: chosen options render as chips in the control and\n   *  picking one keeps the list open. Drive it with `values` + `onToggle`. */\n  multiple?: boolean;\n  /** Selected values (multi-select). */\n  values?: string[];\n  /** Toggle handler (multi-select). */\n  onToggle?: (value: string, option: ComboboxOption) => void;\n  /** Offer the typed value as an \"Add …\" row when it isn't already an option, so\n   *  free entry works alongside the suggestions. Implies a searchable input. */\n  creatable?: boolean;\n  /** Persist a newly-typed value instead of committing a synthetic option.\n   *  Receives the trimmed label. */\n  onCreate?: (label: string) => void | Promise<void>;\n  /** Spinner + disabled state on the create row while a create is in flight. */\n  creating?: boolean;\n  /** When false, render a plain click-to-open dropdown (no type-ahead filtering)\n   *  — a drop-in for a fixed-enum `<select>`. Defaults smart: searchable once the\n   *  option count exceeds `searchableThreshold`. Ignored when a feature that needs\n   *  a text query is on (`onSearch`, `creatable`, `emptyAction`, `multiple`). */\n  searchable?: boolean;\n  /** Option count above which search auto-enables when `searchable` is unset.\n   *  Defaults to `COMBOBOX_SEARCHABLE_THRESHOLD` (5). */\n  searchableThreshold?: number;\n  /** A persistent row shown at the top of the list — a place for a \"manage\" or\n   *  \"create\" affordance that's always reachable, whatever the query. */\n  pinnedAction?: ComboboxAction;\n  /** A call-to-action button shown in the empty state — e.g. to create or invite\n   *  the thing the user was searching for. Implies a searchable input. */\n  emptyAction?: ComboboxAction;\n};\n\n/** Lists longer than this auto-enable the type-ahead input; shorter lists render\n *  as a plain click-to-open dropdown. Override per-instance with\n *  `searchableThreshold`, or change here to retune globally. */\nexport const COMBOBOX_SEARCHABLE_THRESHOLD = 5;\n\nfunction highlight(label: string, query: string) {\n  if (!query) return label;\n  const idx = label.toLowerCase().indexOf(query.toLowerCase());\n  if (idx === -1) return label;\n  return (\n    <>\n      {label.slice(0, idx)}\n      <mark className=\"bg-transparent font-semibold text-foreground\">\n        {label.slice(idx, idx + query.length)}\n      </mark>\n      {label.slice(idx + query.length)}\n    </>\n  );\n}\n\nconst Spinner = (\n  <svg\n    aria-hidden=\"true\"\n    viewBox=\"0 0 24 24\"\n    className=\"h-4 w-4 animate-spin text-muted-foreground\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2.5\"\n    strokeLinecap=\"round\"\n  >\n    <path d=\"M21 12a9 9 0 1 1-6.2-8.6\" />\n  </svg>\n);\n\nconst SearchIcon = (\n  <svg\n    aria-hidden=\"true\"\n    viewBox=\"0 0 24 24\"\n    className=\"h-4 w-4 text-muted-foreground\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n  >\n    <path d=\"M21 21l-4.3-4.3M11 18a7 7 0 1 0 0-14 7 7 0 0 0 0 14z\" />\n  </svg>\n);\n\nconst CheckIcon = (\n  <svg\n    aria-hidden=\"true\"\n    viewBox=\"0 0 24 24\"\n    className=\"h-4 w-4 text-primary\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2.5\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n  >\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n);\n\nconst Combobox = React.forwardRef<HTMLDivElement, ComboboxProps>(\n  (\n    {\n      options: staticOptions,\n      onSearch,\n      value: valueProp,\n      defaultValue,\n      placeholder = \"Search…\",\n      emptyMessage = \"No results\",\n      disabled = false,\n      onChange,\n      multiple = false,\n      values,\n      onToggle,\n      creatable = false,\n      onCreate,\n      creating = false,\n      searchable,\n      searchableThreshold = COMBOBOX_SEARCHABLE_THRESHOLD,\n      pinnedAction,\n      emptyAction,\n      className,\n      ...props\n    },\n    ref,\n  ) => {\n    const reduceMotion = useReducedMotion();\n    const listboxId = React.useId();\n    const inputId = React.useId();\n    const isControlled = valueProp !== undefined;\n    const [internal, setInternal] = React.useState(defaultValue ?? \"\");\n    const value = isControlled ? valueProp : internal;\n\n    const selectedValues = React.useMemo(() => values ?? [], [values]);\n\n    const allOptions = React.useMemo(\n      () => staticOptions ?? [],\n      [staticOptions],\n    );\n    const selectedOption = allOptions.find((o) => o.value === value);\n\n    // Smart default: features that need a typed query force the input; otherwise\n    // honor an explicit `searchable`, else auto-enable past the threshold.\n    const isSearchable =\n      onSearch != null || multiple || creatable || emptyAction != null\n        ? true\n        : (searchable ?? allOptions.length > searchableThreshold);\n\n    const [query, setQuery] = React.useState(\"\");\n    const [open, setOpen] = React.useState(false);\n    const [active, setActive] = React.useState(0);\n    const [loading, setLoading] = React.useState(false);\n    const [asyncResults, setAsyncResults] = React.useState<ComboboxOption[]>(\n      [],\n    );\n    // Drive the create-row spinner from the pending `onCreate` promise even when\n    // the parent doesn't wire the `creating` prop, so the click always reacts.\n    const [creatingInternal, setCreatingInternal] = React.useState(false);\n    const isCreating = creating || creatingInternal;\n    // Polite live-region text so screen readers hear the create/select outcome.\n    const [liveMessage, setLiveMessage] = React.useState(\"\");\n    // Brief success flash: the trailing search icon morphs to a check, then back.\n    const [createdFlash, setCreatedFlash] = React.useState(false);\n    const flashTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined);\n    const flashCreated = () => {\n      setCreatedFlash(true);\n      clearTimeout(flashTimer.current);\n      flashTimer.current = setTimeout(() => setCreatedFlash(false), 1300);\n    };\n    React.useEffect(() => () => clearTimeout(flashTimer.current), []);\n    const rootRef = React.useRef<HTMLDivElement>(null);\n    const inputRef = React.useRef<HTMLInputElement>(null);\n    const reqId = React.useRef(0);\n    // The query the current asyncResults belong to, so reopening the popup with\n    // the same query doesn't reflash the spinner or refetch.\n    const fetchedQuery = React.useRef<string | null>(null);\n    const typeahead = React.useRef({ buf: \"\", at: 0 });\n\n    React.useImperativeHandle(ref, () => rootRef.current as HTMLDivElement);\n\n    React.useEffect(() => {\n      if (!open) return;\n      const onDown = (e: MouseEvent) => {\n        if (rootRef.current && !rootRef.current.contains(e.target as Node)) {\n          setOpen(false);\n        }\n      };\n      document.addEventListener(\"mousedown\", onDown);\n      return () => document.removeEventListener(\"mousedown\", onDown);\n    }, [open]);\n\n    // async search\n    React.useEffect(() => {\n      if (!onSearch || !open) return;\n      // Results for this query are already loaded — reopening must not reflash\n      // the spinner or refetch.\n      if (fetchedQuery.current === query) return;\n      const id = ++reqId.current;\n      setLoading(true);\n      const t = setTimeout(() => {\n        onSearch(query).then((res) => {\n          if (id === reqId.current) {\n            setAsyncResults(res);\n            fetchedQuery.current = query;\n            setLoading(false);\n            setActive(0);\n          }\n        });\n      }, 180);\n      return () => clearTimeout(t);\n    }, [query, onSearch, open]);\n\n    const matches = onSearch\n      ? asyncResults\n      : allOptions.filter((o) =>\n          o.label.toLowerCase().includes(query.toLowerCase()),\n        );\n\n    // Creatable: offer the typed value as a \"create\" row when it isn't already\n    // an option, so free entry works alongside the suggestions.\n    const trimmedQuery = query.trim();\n    const canCreate =\n      creatable &&\n      trimmedQuery.length > 0 &&\n      !matches.some(\n        (o) => o.label.toLowerCase() === trimmedQuery.toLowerCase(),\n      );\n    const createRow: ComboboxOption = {\n      value: trimmedQuery,\n      label: trimmedQuery,\n    };\n    // Create row goes last so the default highlight (and Enter) prefer a real\n    // match over creating.\n    const results: ComboboxOption[] = canCreate\n      ? [...matches, createRow]\n      : matches;\n\n    // Keep the active index in range as the result list changes size.\n    React.useEffect(() => {\n      setActive((a) => Math.min(a, Math.max(0, results.length - 1)));\n    }, [results.length]);\n\n    // Chip labels resolve from options ∪ a small cache of chosen items, so a\n    // selection still names itself when the query filters it out.\n    const labelCacheRef = React.useRef(new Map<string, string>());\n    React.useEffect(() => {\n      for (const o of results) labelCacheRef.current.set(o.value, o.label);\n    }, [results]);\n    const chipLabel = (v: string) =>\n      allOptions.find((o) => o.value === v)?.label ??\n      labelCacheRef.current.get(v) ??\n      v;\n    // The selected option's label — resolved from the cache too, so an async\n    // pick (whose option never lives in `options`) still names itself.\n    const selectedLabel =\n      selectedOption?.label ??\n      (value ? labelCacheRef.current.get(value) : undefined);\n\n    const runCreate = async () => {\n      if (isCreating) return;\n      const created = trimmedQuery;\n      if (onCreate) {\n        // Keep the popup open with an in-row spinner while the create is in\n        // flight; only close once it resolves. On error the row simply returns\n        // to its idle state (the popup never silently vanishes).\n        try {\n          setCreatingInternal(true);\n          await onCreate(created);\n          setLiveMessage(`Created “${created}”`);\n          flashCreated();\n          setQuery(\"\");\n          setOpen(false);\n        } finally {\n          setCreatingInternal(false);\n        }\n      } else {\n        // No handler: commit the typed value as a synthetic option.\n        setLiveMessage(`Created “${created}”`);\n        flashCreated();\n        commit({ value: created, label: created });\n      }\n    };\n\n    const commit = (opt: ComboboxOption) => {\n      // Identity check (not a magic value) so a real option whose value happens\n      // to match the query can't be mistaken for the create row.\n      if (opt === createRow) {\n        void runCreate();\n        return;\n      }\n      if (multiple) {\n        labelCacheRef.current.set(opt.value, opt.label);\n        onToggle?.(opt.value, opt);\n        setQuery(\"\");\n        setActive(0);\n        inputRef.current?.focus();\n        return;\n      }\n      if (!isControlled) setInternal(opt.value);\n      onChange?.(opt.value, opt);\n      setQuery(\"\");\n      setOpen(false);\n    };\n\n    const onInputKeyDown = (e: React.KeyboardEvent) => {\n      if (e.key === \"ArrowDown\") {\n        e.preventDefault();\n        setOpen(true);\n        setActive((a) => Math.min(a + 1, results.length - 1));\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault();\n        setActive((a) => Math.max(a - 1, 0));\n      } else if (e.key === \"Enter\" && open && results[active]) {\n        e.preventDefault();\n        commit(results[active]);\n      } else if (e.key === \"Escape\") {\n        setOpen(false);\n      }\n    };\n\n    // Keyboard for the non-searchable trigger button (a plain <select>-like control).\n    const onButtonKeyDown = (e: React.KeyboardEvent) => {\n      if (!open) {\n        if ([\"ArrowDown\", \"ArrowUp\", \"Enter\", \" \"].includes(e.key)) {\n          e.preventDefault();\n          setActive(\n            Math.max(\n              0,\n              results.findIndex((o) => o.value === value),\n            ),\n          );\n          setOpen(true);\n        }\n        return;\n      }\n      if (e.key === \"ArrowDown\") {\n        e.preventDefault();\n        setActive((a) => Math.min(a + 1, results.length - 1));\n      } else if (e.key === \"ArrowUp\") {\n        e.preventDefault();\n        setActive((a) => Math.max(a - 1, 0));\n      } else if (e.key === \"Home\") {\n        e.preventDefault();\n        setActive(0);\n      } else if (e.key === \"End\") {\n        e.preventDefault();\n        setActive(results.length - 1);\n      } else if (e.key === \"Enter\" || e.key === \" \") {\n        e.preventDefault();\n        if (results[active]) commit(results[active]);\n      } else if (e.key === \"Escape\") {\n        e.preventDefault();\n        setOpen(false);\n      } else if (e.key.length === 1) {\n        // First-letter type-ahead, matching a native <select>.\n        const recent = Date.now() - typeahead.current.at < 600;\n        const buf = recent ? typeahead.current.buf + e.key : e.key;\n        typeahead.current = { buf, at: Date.now() };\n        const idx = results.findIndex((o) =>\n          o.label.toLowerCase().startsWith(buf.toLowerCase()),\n        );\n        if (idx >= 0) setActive(idx);\n      }\n    };\n\n    const spring = reduceMotion\n      ? { duration: 0 }\n      : ({ type: \"spring\", stiffness: 520, damping: 32 } as const);\n\n    return (\n      <div\n        ref={rootRef}\n        className={`relative w-72 ${className ?? \"\"}`}\n        {...props}\n      >\n        <span aria-live=\"polite\" role=\"status\" className=\"sr-only\">\n          {liveMessage}\n        </span>\n        {multiple ? (\n          // A <label> tied to the input by `htmlFor` so clicking the empty chip\n          // area focuses the input (opening the list via onFocus). The explicit\n          // association is required: a bare <label> proxies clicks to its first\n          // labelable descendant — a chip's remove button — silently toggling it off.\n          <label\n            htmlFor={inputId}\n            className=\"flex min-h-[2.75rem] w-full flex-wrap items-center gap-1.5 rounded-xl border border-border bg-background px-2.5 py-2 text-sm focus-within:ring-2 focus-within:ring-ring\"\n          >\n            <AnimatePresence mode=\"popLayout\" initial={false}>\n              {selectedValues.map((v) => (\n                <motion.span\n                  key={v}\n                  layout\n                  initial={reduceMotion ? false : { opacity: 0, scale: 0.7 }}\n                  animate={{ opacity: 1, scale: 1 }}\n                  exit={\n                    reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.7 }\n                  }\n                  transition={spring}\n                  className=\"inline-flex items-center gap-1 rounded-md bg-accent py-1 pr-1 pl-2 font-medium text-accent-foreground text-xs\"\n                >\n                  {chipLabel(v)}\n                  <button\n                    type=\"button\"\n                    disabled={disabled}\n                    aria-label={`Remove ${chipLabel(v)}`}\n                    onClick={(e) => {\n                      e.stopPropagation();\n                      if (!disabled)\n                        onToggle?.(v, { value: v, label: chipLabel(v) });\n                    }}\n                    className=\"-mr-0.5 inline-flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground [transition:color_120ms_ease,background-color_120ms_ease,scale_120ms_ease] hover:scale-110 hover:bg-foreground/10 hover:text-foreground active:scale-90 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:scale-100\"\n                  >\n                    <svg\n                      aria-hidden=\"true\"\n                      viewBox=\"0 0 24 24\"\n                      className=\"size-3\"\n                      fill=\"none\"\n                      stroke=\"currentColor\"\n                      strokeWidth=\"2.5\"\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                    >\n                      <path d=\"M18 6 6 18M6 6l12 12\" />\n                    </svg>\n                  </button>\n                </motion.span>\n              ))}\n            </AnimatePresence>\n            <input\n              ref={inputRef}\n              id={inputId}\n              role=\"combobox\"\n              aria-expanded={open}\n              aria-controls={listboxId}\n              aria-autocomplete=\"list\"\n              disabled={disabled}\n              value={query}\n              placeholder={selectedValues.length === 0 ? placeholder : \"\"}\n              onChange={(e) => {\n                setQuery(e.target.value);\n                setActive(0);\n                setOpen(true);\n              }}\n              onFocus={() => setOpen(true)}\n              onKeyDown={onInputKeyDown}\n              className=\"min-w-[6rem] flex-1 bg-transparent text-foreground outline-none placeholder:text-muted-foreground\"\n            />\n          </label>\n        ) : isSearchable ? (\n          <div className=\"relative\">\n            <input\n              ref={inputRef}\n              role=\"combobox\"\n              aria-expanded={open}\n              aria-controls={listboxId}\n              aria-autocomplete=\"list\"\n              disabled={disabled}\n              value={\n                open\n                  ? query\n                  : (selectedLabel ?? (creatable ? (value ?? \"\") : query))\n              }\n              placeholder={placeholder}\n              onChange={(e) => {\n                setQuery(e.target.value);\n                setActive(0);\n                setOpen(true);\n              }}\n              onFocus={() => setOpen(true)}\n              onKeyDown={onInputKeyDown}\n              className=\"w-full rounded-xl border border-border bg-background px-3.5 py-2.5 pr-9 text-foreground text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\"\n            />\n            <span className=\"-translate-y-1/2 absolute top-1/2 right-3 flex\">\n              <AnimatePresence mode=\"wait\" initial={false}>\n                <motion.span\n                  key={loading ? \"spin\" : createdFlash ? \"check\" : \"search\"}\n                  initial={reduceMotion ? false : { opacity: 0, scale: 0.5 }}\n                  animate={{ opacity: 1, scale: 1 }}\n                  exit={\n                    reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.5 }\n                  }\n                  transition={\n                    reduceMotion ? { duration: 0 } : { duration: 0.14 }\n                  }\n                  className=\"flex\"\n                >\n                  {loading ? Spinner : createdFlash ? CheckIcon : SearchIcon}\n                </motion.span>\n              </AnimatePresence>\n            </span>\n          </div>\n        ) : (\n          // Non-searchable: a plain click-to-open dropdown (drop-in for <select>).\n          <button\n            type=\"button\"\n            role=\"combobox\"\n            aria-haspopup=\"listbox\"\n            aria-expanded={open}\n            aria-controls={listboxId}\n            disabled={disabled}\n            onClick={() => !disabled && setOpen((o) => !o)}\n            onKeyDown={onButtonKeyDown}\n            className=\"flex w-full items-center justify-between gap-2 rounded-xl border border-border bg-background px-3.5 py-2.5 text-left text-foreground text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\"\n          >\n            <span\n              className={`truncate ${selectedLabel ? \"text-foreground\" : \"text-muted-foreground\"}`}\n            >\n              {selectedLabel ?? placeholder}\n            </span>\n            <svg\n              aria-hidden=\"true\"\n              viewBox=\"0 0 24 24\"\n              className={`h-4 w-4 shrink-0 text-muted-foreground transition-transform ${open ? \"rotate-180\" : \"\"}`}\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <path d=\"m6 9 6 6 6-6\" />\n            </svg>\n          </button>\n        )}\n\n        <AnimatePresence>\n          {open && (\n            <motion.ul\n              id={listboxId}\n              role=\"listbox\"\n              aria-multiselectable={multiple || undefined}\n              aria-busy={loading || undefined}\n              initial={\n                reduceMotion\n                  ? { opacity: 0 }\n                  : { opacity: 0, scale: 0.97, y: -4 }\n              }\n              animate={{ opacity: 1, scale: 1, y: 0 }}\n              exit={\n                reduceMotion\n                  ? { opacity: 0 }\n                  : { opacity: 0, scale: 0.97, y: -4 }\n              }\n              transition={spring}\n              className=\"absolute top-full left-0 z-popover mt-2 max-h-72 w-full origin-top overflow-y-auto rounded-xl border border-border bg-background p-1 shadow-xl\"\n            >\n              {pinnedAction && (\n                <li className=\"mb-1 border-border border-b pb-1\">\n                  <button\n                    type=\"button\"\n                    onClick={() => pinnedAction.onSelect(query.trim())}\n                    className=\"w-full rounded-lg px-3 py-2 text-left font-medium text-primary text-sm hover:bg-accent\"\n                  >\n                    {pinnedAction.label}\n                  </button>\n                </li>\n              )}\n              {loading && results.length === 0 && (\n                <li className=\"flex items-center justify-center gap-2 px-3 py-6 text-muted-foreground text-sm\">\n                  {Spinner}\n                  <span>Searching…</span>\n                </li>\n              )}\n              {!loading && results.length === 0 && (\n                <li className=\"px-3 py-6 text-center text-muted-foreground text-sm\">\n                  <div>{emptyMessage}</div>\n                  {emptyAction && (\n                    <button\n                      type=\"button\"\n                      onClick={() => emptyAction.onSelect(query.trim())}\n                      className=\"mt-2 rounded-lg px-3 py-1.5 font-medium text-primary text-sm hover:bg-accent\"\n                    >\n                      {emptyAction.label}\n                    </button>\n                  )}\n                </li>\n              )}\n              {results.map((opt, i) => {\n                const isActive = i === active;\n                const isCreate = opt === createRow;\n                const isSelected = isCreate\n                  ? false\n                  : multiple\n                    ? selectedValues.includes(opt.value)\n                    : opt.value === value;\n                return (\n                  <motion.li\n                    key={isCreate ? \"__create_row__\" : opt.value}\n                    initial={reduceMotion ? false : { opacity: 0, y: 4 }}\n                    animate={{ opacity: 1, y: 0 }}\n                    transition={{ delay: reduceMotion ? 0 : i * 0.02 }}\n                    role=\"option\"\n                    aria-label={isCreate ? `Add ${opt.label}` : opt.label}\n                    aria-selected={isSelected}\n                    onMouseEnter={() => setActive(i)}\n                    onClick={() => commit(opt)}\n                    className={`flex cursor-pointer items-start gap-2 rounded-lg px-3 py-2 text-sm [transition:background-color_120ms_ease] ${\n                      isActive ? \"bg-accent\" : \"\"\n                    }`}\n                  >\n                    {isCreate ? (\n                      <span className=\"flex flex-1 items-center gap-1.5 text-primary\">\n                        {isCreating ? (\n                          Spinner\n                        ) : (\n                          <svg\n                            aria-hidden=\"true\"\n                            viewBox=\"0 0 24 24\"\n                            className=\"size-4 shrink-0\"\n                            fill=\"none\"\n                            stroke=\"currentColor\"\n                            strokeWidth=\"2\"\n                            strokeLinecap=\"round\"\n                            strokeLinejoin=\"round\"\n                          >\n                            <path d=\"M12 5v14M5 12h14\" />\n                          </svg>\n                        )}\n                        <span className=\"block truncate\">\n                          {isCreating ? \"Adding\" : \"Create\"}{\" \"}\n                          <span className=\"font-semibold\">\n                            &ldquo;{opt.label}&rdquo;\n                          </span>\n                        </span>\n                      </span>\n                    ) : (\n                      <span className=\"flex-1\">\n                        <span className=\"block text-foreground\">\n                          {highlight(opt.label, query)}\n                        </span>\n                        {opt.description && (\n                          <span className=\"block text-muted-foreground text-xs\">\n                            {opt.description}\n                          </span>\n                        )}\n                      </span>\n                    )}\n                    {isSelected && (\n                      <svg\n                        aria-hidden=\"true\"\n                        viewBox=\"0 0 24 24\"\n                        className=\"mt-0.5 h-4 w-4 shrink-0 text-foreground\"\n                        fill=\"none\"\n                        stroke=\"currentColor\"\n                        strokeWidth=\"3\"\n                        strokeLinecap=\"round\"\n                        strokeLinejoin=\"round\"\n                      >\n                        <path d=\"M20 6 9 17l-5-5\" />\n                      </svg>\n                    )}\n                  </motion.li>\n                );\n              })}\n            </motion.ul>\n          )}\n        </AnimatePresence>\n      </div>\n    );\n  },\n);\nCombobox.displayName = \"Combobox\";\n\nexport { Combobox };\n",
      "type": "registry:ui",
      "target": "components/godui/combobox.tsx"
    }
  ],
  "type": "registry:ui"
}
