{
  "schemaVersion": 1,
  "name": "combobox",
  "useFor": "Use to choose from a long list where typing is faster than scrolling (a merchant, a store's region). Pass items to Combobox and render ComboboxItem rows in ComboboxList, with a ComboboxInput search row. Set multiple and use ComboboxChips with ComboboxChipsInput for several values.",
  "constraints": [
    "A combobox for under seven options (use Select)",
    "Animating the highlight",
    "Losing the typed text when the list closes"
  ],
  "documentation": "",
  "source": "Extrapolated from opencommerce (the select field and the command palette)",
  "exports": [
    "Combobox",
    "ComboboxChip",
    "ComboboxChips",
    "ComboboxChipsInput",
    "ComboboxClear",
    "ComboboxCollection",
    "ComboboxContent",
    "ComboboxEmpty",
    "ComboboxGroup",
    "ComboboxInput",
    "ComboboxItem",
    "ComboboxLabel",
    "ComboboxList",
    "ComboboxSeparator",
    "ComboboxTrigger",
    "ComboboxValue",
    "useCombobox",
    "useComboboxAnchor"
  ],
  "client": true,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/combobox",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/combobox.md",
  "examples": [
    {
      "path": "examples/combobox.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Plus, Store } from \"lucide-react\";\n\nimport {\n  Combobox,\n  ComboboxChip,\n  ComboboxChips,\n  ComboboxChipsInput,\n  ComboboxClear,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxGroup,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxList,\n  ComboboxSeparator,\n  ComboboxTrigger,\n  ComboboxValue,\n  useCombobox,\n} from \"@/components/ui/combobox\";\nimport { Label } from \"@/components/ui/label\";\n\nconst MERCHANTS = [\n  { value: \"amazon\", label: \"Amazon\" },\n  { value: \"wayfair\", label: \"Wayfair\" },\n  { value: \"staples\", label: \"Staples\" },\n  { value: \"bestbuy\", label: \"Best Buy\" },\n  { value: \"lowes\", label: \"Lowe's\" },\n  { value: \"target\", label: \"Target\" },\n  { value: \"kohls\", label: \"Kohl's\", disabled: true },\n];\n\nconst CATEGORIES = [\"office\", \"furniture\", \"electronics\", \"groceries\", \"travel\", \"software\", \"hardware\", \"gifts\"];\n\nexport function Site() {\n  const [merchant, setMerchant] = React.useState<string | null>(\"wayfair\");\n  return (\n    <div className=\"grid w-full max-w-80 gap-1.5\">\n      <Label id=\"merchant-label\">Merchant</Label>\n      <Combobox items={MERCHANTS} value={merchant} onValueChange={setMerchant} name=\"merchant\">\n        <ComboboxTrigger aria-labelledby=\"merchant-label\">\n          <ComboboxValue placeholder=\"Choose a merchant\" />\n        </ComboboxTrigger>\n        <ComboboxContent>\n          <ComboboxInput placeholder=\"Search merchants\" />\n          <ComboboxList>\n            <ComboboxEmpty>No merchant by that name.</ComboboxEmpty>\n            {MERCHANTS.map((m) => (\n              <ComboboxItem key={m.value} value={m.value} label={m.label} disabled={m.disabled}>\n                <Store strokeWidth={1.5} />\n                {m.label}\n              </ComboboxItem>\n            ))}\n          </ComboboxList>\n        </ComboboxContent>\n      </Combobox>\n    </div>\n  );\n}\n\nexport function Multiple() {\n  const [values, setValues] = React.useState<string[]>([\"office\", \"electronics\"]);\n  return (\n    <div className=\"grid w-full max-w-100 gap-1.5\">\n      <Label htmlFor=\"categories\">Allowed categories</Label>\n      <Combobox multiple items={CATEGORIES} value={values} onValueChange={setValues} name=\"categories\">\n        <ComboboxChips>\n          {values.map((v) => (\n            <ComboboxChip key={v} value={v} />\n          ))}\n          <ComboboxChipsInput id=\"categories\" placeholder={values.length ? \"\" : \"Add categories\"} />\n          <ComboboxClear />\n        </ComboboxChips>\n        <ComboboxContent>\n          <ComboboxList>\n            {(item) => (\n              <ComboboxItem key={item.value} value={item.value}>\n                {item.label}\n              </ComboboxItem>\n            )}\n          </ComboboxList>\n          <ComboboxEmpty>No such category.</ComboboxEmpty>\n        </ComboboxContent>\n      </Combobox>\n    </div>\n  );\n}\n\nexport function Groups() {\n  const [value, setValue] = React.useState<string | null>(null);\n  return (\n    <div className=\"grid w-full max-w-80 gap-1.5\">\n      <Label id=\"region-label\">Ship from</Label>\n      <Combobox value={value} onValueChange={setValue}>\n        <ComboboxTrigger aria-labelledby=\"region-label\">\n          <ComboboxValue placeholder=\"Choose a warehouse\" />\n        </ComboboxTrigger>\n        <ComboboxContent>\n          <ComboboxInput placeholder=\"Search warehouses\" />\n          <ComboboxList>\n            <ComboboxEmpty>No warehouse found.</ComboboxEmpty>\n            <ComboboxGroup heading=\"United States\">\n              <ComboboxItem value=\"sfo\">San Francisco</ComboboxItem>\n              <ComboboxItem value=\"jfk\">New York</ComboboxItem>\n            </ComboboxGroup>\n            <ComboboxSeparator />\n            <ComboboxGroup heading=\"Europe\">\n              <ComboboxItem value=\"ams\">Amsterdam</ComboboxItem>\n              <ComboboxItem value=\"ber\">Berlin</ComboboxItem>\n            </ComboboxGroup>\n          </ComboboxList>\n        </ComboboxContent>\n      </Combobox>\n    </div>\n  );\n}\n\nfunction CreateRow({ known }: { known: string[] }) {\n  const { search } = useCombobox();\n  const value = search.trim().toLowerCase();\n  if (!value || known.includes(value)) return null;\n  return (\n    <>\n      <ComboboxSeparator />\n      <ComboboxItem value={value} label={value}>\n        <Plus strokeWidth={1.75} />\n        Create &ldquo;{value}&rdquo;\n      </ComboboxItem>\n    </>\n  );\n}\n\nexport function Creatable() {\n  const [tags, setTags] = React.useState([\"urgent\", \"gift\", \"recurring\"]);\n  const [tag, setTag] = React.useState<string | null>(null);\n  return (\n    <div className=\"grid w-full max-w-80 gap-1.5\">\n      <Label id=\"tag-label\">Order tag</Label>\n      <Combobox\n        items={tags}\n        value={tag}\n        onValueChange={(v) => {\n          if (v && !tags.includes(v)) setTags((t) => [...t, v]);\n          setTag(v);\n        }}\n      >\n        <ComboboxTrigger aria-labelledby=\"tag-label\">\n          <ComboboxValue placeholder=\"Add a tag\" />\n        </ComboboxTrigger>\n        <ComboboxContent>\n          <ComboboxInput placeholder=\"Search or create a tag\" />\n          <ComboboxList>\n            {tags.map((t) => (\n              <ComboboxItem key={t} value={t}>\n                {t}\n              </ComboboxItem>\n            ))}\n            <CreateRow known={tags} />\n          </ComboboxList>\n        </ComboboxContent>\n      </Combobox>\n    </div>\n  );\n}\n\nexport function States() {\n  return (\n    <div className=\"grid w-full max-w-80 gap-3\">\n      <Combobox items={MERCHANTS} disabled>\n        <ComboboxTrigger aria-label=\"Disabled merchant\">\n          <ComboboxValue placeholder=\"Disabled\" />\n        </ComboboxTrigger>\n      </Combobox>\n      <Combobox items={MERCHANTS}>\n        <ComboboxTrigger aria-label=\"Required merchant\" aria-invalid>\n          <ComboboxValue placeholder=\"Choose a merchant\" />\n        </ComboboxTrigger>\n        <ComboboxContent>\n          <ComboboxInput placeholder=\"Search merchants\" />\n          <ComboboxList>\n            {(item) => (\n              <ComboboxItem key={item.value} value={item.value} label={item.label}>\n                {item.label}\n              </ComboboxItem>\n            )}\n          </ComboboxList>\n        </ComboboxContent>\n      </Combobox>\n      <p className=\"font-sans text-body-sm text-danger\">Choose a merchant to continue.</p>\n    </div>\n  );\n}\n",
      "exports": [
        "Site",
        "Multiple",
        "Groups",
        "Creatable",
        "States"
      ],
      "requires": [
        "combobox",
        "label"
      ],
      "packages": [
        "lucide-react"
      ],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/combobox/Site"
        },
        {
          "name": "Multiple",
          "url": "https://opencommerce-ds.vercel.app/preview/combobox/Multiple"
        },
        {
          "name": "Groups",
          "url": "https://opencommerce-ds.vercel.app/preview/combobox/Groups"
        },
        {
          "name": "Creatable",
          "url": "https://opencommerce-ds.vercel.app/preview/combobox/Creatable"
        },
        {
          "name": "States",
          "url": "https://opencommerce-ds.vercel.app/preview/combobox/States"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Combobox",
  "description": "The select's field opening a searchable floating panel; chips in the field for several values.",
  "surface": "site",
  "kinds": [
    "input"
  ],
  "requires": [
    "theme",
    "surface",
    "utils"
  ],
  "packages": [
    "@radix-ui/react-popover",
    "cmdk",
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/ui/combobox.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { Check, X } from \"lucide-react\";\n\nimport { usePortalContainer } from \"@/lib/surface\";\nimport { cn } from \"@/lib/utils\";\n\n// A searchable choice (extrapolated from the brand's select and command palette). The trigger is the select's field:\n// 36px, 8px corners, white on the 16 percent ink edge, the value in 14px Inter (the placeholder faint) and the drawn\n// chevron at the right; an open list turns the edge blue. The list is the brand's floating panel (white, a 9 percent ink\n// edge, 8px corners, the soft drop) growing from the trigger: a 42px search row with the drawn search mark, then rows of\n// 14px Inter at 64 percent ink, the highlighted row on the pale blue in ink and a blue tick beside the chosen ones;\n// groups are headed in the platform mono. For several values, ComboboxChips is the field itself: chips on the pill wash\n// with 6px corners and an x, and a text input that filters as you type (Backspace on an empty input removes the last\n// chip). Moving through the rows is instant: it is done by keyboard.\n\ntype Item = { value: string; label: string; disabled?: boolean };\n\ntype ComboboxContextValue = {\n  multiple: boolean;\n  values: string[];\n  toggle: (value: string) => void;\n  remove: (value: string) => void;\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  search: string;\n  setSearch: (search: string) => void;\n  labelOf: (value: string) => string;\n  register: (value: string, label: string) => void;\n  items?: Item[];\n  disabled?: boolean;\n  listRef: React.RefObject<HTMLDivElement | null>;\n  chipsRef: React.RefObject<HTMLDivElement | null>;\n  chips: boolean;\n  setChips: (chips: boolean) => void;\n  contentId: string;\n};\n\nconst ComboboxContext = React.createContext<ComboboxContextValue | null>(null);\n\nfunction useCombobox() {\n  const context = React.useContext(ComboboxContext);\n  if (!context) throw new Error(\"Combobox parts must be used within <Combobox>\");\n  return context;\n}\n\ntype ComboboxBaseProps = {\n  /** The choices, for labels while the list is closed and for ComboboxList's render function. */\n  items?: (Item | string)[];\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  disabled?: boolean;\n  /** The submitted name: one hidden input per chosen value. */\n  name?: string;\n  children?: React.ReactNode;\n};\n\ntype ComboboxSingleProps = ComboboxBaseProps & { multiple?: false; value?: string | null; defaultValue?: string | null; onValueChange?: (value: string | null) => void };\ntype ComboboxMultipleProps = ComboboxBaseProps & { multiple: true; value?: string[]; defaultValue?: string[]; onValueChange?: (value: string[]) => void };\n\nconst normalize = (items?: (Item | string)[]) => items?.map((i) => (typeof i === \"string\" ? { value: i, label: i } : i));\n\nfunction Combobox(props: ComboboxSingleProps | ComboboxMultipleProps) {\n  const { open: openProp, defaultOpen = false, onOpenChange, disabled, name, children } = props;\n  const multiple = props.multiple === true;\n  const items = React.useMemo(() => normalize(props.items), [props.items]);\n  const initial = props.defaultValue == null ? [] : Array.isArray(props.defaultValue) ? props.defaultValue : [props.defaultValue];\n  const [inner, setInner] = React.useState<string[]>(initial);\n  const controlled = props.value !== undefined;\n  const values = controlled ? (props.value == null ? [] : Array.isArray(props.value) ? props.value : [props.value]) : inner;\n  const [openInner, setOpenInner] = React.useState(defaultOpen);\n  const open = openProp ?? openInner;\n  const [search, setSearch] = React.useState(\"\");\n  const [chips, setChips] = React.useState(false);\n  const labels = React.useRef(new Map<string, string>());\n  const listRef = React.useRef<HTMLDivElement>(null);\n  const chipsRef = React.useRef<HTMLDivElement>(null);\n  const contentId = React.useId();\n\n  const setOpen = React.useCallback(\n    (next: boolean) => {\n      if (openProp === undefined) setOpenInner(next);\n      onOpenChange?.(next);\n      if (!next) setSearch(\"\");\n    },\n    [openProp, onOpenChange],\n  );\n  const commit = (next: string[]) => {\n    if (!controlled) setInner(next);\n    if (multiple) (props as ComboboxMultipleProps).onValueChange?.(next);\n    else (props as ComboboxSingleProps).onValueChange?.(next[0] ?? null);\n  };\n  const toggle = (value: string) => {\n    if (multiple) {\n      commit(values.includes(value) ? values.filter((v) => v !== value) : [...values, value]);\n      setSearch(\"\");\n    } else {\n      // Picking the chosen value again keeps it; ComboboxClear empties the field.\n      commit([value]);\n      setOpen(false);\n    }\n  };\n  const remove = (value: string) => commit(values.filter((v) => v !== value));\n  const labelOf = (value: string) => labels.current.get(value) ?? items?.find((i) => i.value === value)?.label ?? value;\n  const register = React.useCallback((value: string, label: string) => labels.current.set(value, label), []);\n\n  return (\n    <ComboboxContext.Provider value={{ multiple, values, toggle, remove, open, setOpen, search, setSearch, labelOf, register, items, disabled, listRef, chipsRef, chips, setChips, contentId }}>\n      <PopoverPrimitive.Root data-slot=\"combobox\" open={open} onOpenChange={setOpen}>\n        {children}\n      </PopoverPrimitive.Root>\n      {name && values.map((v) => <input key={v} type=\"hidden\" name={name} value={v} />)}\n    </ComboboxContext.Provider>\n  );\n}\n\nconst FIELD =\n  \"flex min-h-9 w-full min-w-0 items-center gap-2 rounded-control border border-hairline bg-surface px-3 font-sans text-body-sm leading-body text-fg outline-none transition-[border-color] duration-160 ease-out hover:border-line-field-hover data-[state=open]:border-line-brand aria-invalid:border-danger motion-reduce:transition-none\";\n\n/** The closed field: the chosen value (ComboboxValue) and the caret. Opens the list. */\nfunction ComboboxTrigger({ className, children, ...props }: React.ComponentProps<\"button\">) {\n  const { open, disabled, values, contentId } = useCombobox();\n  return (\n    <PopoverPrimitive.Trigger asChild>\n      <button\n        type=\"button\"\n        role=\"combobox\"\n        aria-expanded={open}\n        aria-controls={contentId}\n        data-slot=\"combobox-trigger\"\n        data-empty={!values.length}\n        disabled={disabled}\n        className={cn(FIELD, \"h-9 cursor-pointer text-left focus-visible:border-line-brand disabled:pointer-events-none disabled:opacity-50 data-[empty=true]:text-fg-faint\", className)}\n        {...props}\n      >\n        <span className=\"flex min-w-0 flex-1 items-center gap-2 truncate\">{children ?? <ComboboxValue />}</span>\n        <svg aria-hidden viewBox=\"0 0 10 6\" className=\"h-1.5 w-2.5 shrink-0 fill-none stroke-current text-fg-muted [stroke-linecap:round] [stroke-linejoin:round] [stroke-width:1.5]\">\n          <path d=\"M1 1l4 4 4-4\" />\n        </svg>\n      </button>\n    </PopoverPrimitive.Trigger>\n  );\n}\n\n/** The chosen value's label, the chosen labels joined, or the placeholder. */\nfunction ComboboxValue({ placeholder = \"Select\", children }: { placeholder?: string; children?: (values: string[]) => React.ReactNode }) {\n  const { values, labelOf } = useCombobox();\n  if (children) return <>{children(values)}</>;\n  return <span data-slot=\"combobox-value\">{values.length ? values.map(labelOf).join(\", \") : placeholder}</span>;\n}\n\n/** Clears every chosen value. Put it inside ComboboxChips or beside a trigger. */\nfunction ComboboxClear({ className, ...props }: React.ComponentProps<\"button\">) {\n  const { values, remove, disabled } = useCombobox();\n  if (!values.length) return null;\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"combobox-clear\"\n      aria-label=\"Clear\"\n      disabled={disabled}\n      onClick={(e) => {\n        e.stopPropagation();\n        values.forEach(remove);\n      }}\n      className={cn(\"inline-flex size-6 shrink-0 items-center justify-center rounded-sm text-fg-faint outline-none transition-colors duration-160 ease-out hover:text-fg focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-focus-ring motion-reduce:transition-none\", className)}\n      {...props}\n    >\n      <X strokeWidth={1.75} className=\"size-3.5\" />\n    </button>\n  );\n}\n\nfunction ComboboxContent({ className, children, align = \"start\", sideOffset = 6, anchor, onInteractOutside, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content> & { anchor?: React.RefObject<HTMLElement | null> }) {\n  const container = usePortalContainer();\n  const { search, chips, listRef, chipsRef, contentId, open } = useCombobox();\n  // With chips cmdk does not filter, so it would keep a hidden row highlighted: highlight the first match instead.\n  const [active, setActive] = React.useState(\"\");\n  React.useEffect(() => {\n    if (!chips || !open) return;\n    const frame = requestAnimationFrame(() => setActive(listRef.current?.querySelector(\"[cmdk-item]:not([data-disabled=true])\")?.getAttribute(\"data-value\") ?? \"\"));\n    return () => cancelAnimationFrame(frame);\n  }, [chips, open, search, listRef]);\n  return (\n    <>\n    {anchor && <PopoverPrimitive.Anchor virtualRef={anchor as React.RefObject<HTMLElement>} />}\n    <PopoverPrimitive.Portal container={container}>\n      <PopoverPrimitive.Content\n        id={contentId}\n        data-slot=\"combobox-content\"\n        align={align}\n        sideOffset={sideOffset}\n        // With chips the typing stays in the field; the list follows it.\n        onOpenAutoFocus={chips ? (e) => e.preventDefault() : undefined}\n        onInteractOutside={(e) => {\n          onInteractOutside?.(e);\n          // A press in the chips field keeps the list open: the field is where the typing happens.\n          if (chips && chipsRef.current?.contains(e.target as Node)) e.preventDefault();\n        }}\n        className={cn(\n          \"z-50 w-(--radix-popover-trigger-width) min-w-56 max-w-[calc(100vw-2rem)] origin-(--radix-popover-content-transform-origin) animate-pop-in overflow-hidden rounded-control border border-line bg-surface text-fg shadow-floating outline-none motion-reduce:animate-none\",\n          className,\n        )}\n        {...props}\n      >\n        <CommandPrimitive ref={listRef} shouldFilter={!chips} {...(chips ? { value: active, onValueChange: setActive } : {})} className=\"flex flex-col\">\n          {children}\n        </CommandPrimitive>\n      </PopoverPrimitive.Content>\n    </PopoverPrimitive.Portal>\n    </>\n  );\n}\n\n/** The search row at the top of the list. */\nfunction ComboboxInput({ className, placeholder = \"Search\", ...props }: Omit<React.ComponentProps<typeof CommandPrimitive.Input>, \"value\" | \"onValueChange\">) {\n  const { search, setSearch } = useCombobox();\n  return (\n    <div data-slot=\"combobox-input-wrapper\" className=\"flex h-10.5 shrink-0 items-center gap-2.5 border-b border-line px-3 text-fg-muted\">\n      <svg aria-hidden viewBox=\"0 0 16 16\" className=\"size-3.75 shrink-0 fill-none stroke-current [stroke-linecap:round] [stroke-width:1.5]\">\n        <circle cx=\"7\" cy=\"7\" r=\"4.75\" />\n        <path d=\"M10.5 10.5 14 14\" />\n      </svg>\n      <CommandPrimitive.Input\n        data-slot=\"combobox-input\"\n        value={search}\n        onValueChange={setSearch}\n        placeholder={placeholder}\n        className={cn(\"h-full w-full min-w-0 bg-transparent font-sans text-body-sm leading-body text-fg caret-primary outline-none placeholder:text-fg-faint\", className)}\n        {...props}\n      />\n    </div>\n  );\n}\n\n/** The rows. Pass items as children, or a function that renders each of the Combobox's items. */\nfunction ComboboxList({ className, children, ...props }: Omit<React.ComponentProps<typeof CommandPrimitive.List>, \"children\"> & { children?: React.ReactNode | ((item: Item) => React.ReactNode) }) {\n  const { items } = useCombobox();\n  return (\n    <CommandPrimitive.List data-slot=\"combobox-list\" className={cn(\"max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto overscroll-contain p-1\", className)} {...props}>\n      {typeof children === \"function\" ? items?.map((item) => <React.Fragment key={item.value}>{children(item)}</React.Fragment>) : children}\n    </CommandPrimitive.List>\n  );\n}\n\n/** Renders a group's own items with a function, inside a ComboboxGroup. */\nfunction ComboboxCollection({ items, children }: { items: (Item | string)[]; children: (item: Item) => React.ReactNode }) {\n  return <>{normalize(items)?.map((item) => <React.Fragment key={item.value}>{children(item)}</React.Fragment>)}</>;\n}\n\nfunction ComboboxItem({ value, label, className, children, disabled, ...props }: Omit<React.ComponentProps<typeof CommandPrimitive.Item>, \"value\" | \"onSelect\"> & { value: string; label?: string }) {\n  const { values, toggle, register, chips, search } = useCombobox();\n  const text = label ?? (typeof children === \"string\" ? children : value);\n  React.useEffect(() => {\n    register(value, text);\n  }, [register, value, text]);\n  // With chips, cmdk does not filter (the typing is outside it): hide rows that do not match here.\n  if (chips && search && !text.toLowerCase().includes(search.toLowerCase())) return null;\n  const selected = values.includes(value);\n  return (\n    <CommandPrimitive.Item\n      data-slot=\"combobox-item\"\n      value={`${text} ${value}`}\n      disabled={disabled}\n      data-checked={selected || undefined}\n      onSelect={() => toggle(value)}\n      className={cn(\n        \"relative flex min-h-8.5 cursor-default items-center gap-2 rounded-menu py-2 pr-8 pl-2.5 font-sans text-body-sm leading-snug text-fg-muted outline-none select-none data-[checked]:text-fg data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-40 data-[selected=true]:bg-search-selected data-[selected=true]:text-fg [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-fg-brand\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? text}\n      {selected && <Check aria-hidden strokeWidth={1.75} className=\"absolute right-2.5 size-3.5 text-fg-brand\" />}\n    </CommandPrimitive.Item>\n  );\n}\n\nfunction ComboboxEmpty({ className, children = \"No results.\", ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  const { chips, search, items, labelOf, values } = useCombobox();\n  // cmdk's own empty state only knows its filter; with chips, decide here.\n  if (chips) {\n    const any = (items ?? values.map((v) => ({ value: v, label: labelOf(v) }))).some((i) => !search || i.label.toLowerCase().includes(search.toLowerCase()));\n    if (any) return null;\n    return (\n      <div data-slot=\"combobox-empty\" className={cn(\"px-3 py-4.5 font-sans text-body-sm text-fg-muted\", className)}>\n        {children}\n      </div>\n    );\n  }\n  return (\n    <CommandPrimitive.Empty data-slot=\"combobox-empty\" className={cn(\"px-3 py-4.5 font-sans text-body-sm text-fg-muted\", className)} {...props}>\n      {children}\n    </CommandPrimitive.Empty>\n  );\n}\n\nfunction ComboboxGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      data-slot=\"combobox-group\"\n      className={cn(\"overflow-hidden text-fg [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:pt-2 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:font-mono [&_[cmdk-group-heading]]:text-bar [&_[cmdk-group-heading]]:leading-body [&_[cmdk-group-heading]]:text-fg-muted\", className)}\n      {...props}\n    />\n  );\n}\n\n/** A heading inside the list that is not a group's (a note above the rows). */\nfunction ComboboxLabel({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"combobox-label\" className={cn(\"px-2.5 pt-2 pb-1 font-mono text-bar leading-body text-fg-muted\", className)} {...props} />;\n}\n\nfunction ComboboxSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return <CommandPrimitive.Separator data-slot=\"combobox-separator\" className={cn(\"-mx-1 my-1 h-px bg-line\", className)} {...props} />;\n}\n\n/** The field for several values: chips and a text input, anchoring the list. */\nfunction ComboboxChips({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  const { setChips, setOpen, disabled, chipsRef } = useCombobox();\n  React.useEffect(() => {\n    setChips(true);\n    return () => setChips(false);\n  }, [setChips]);\n  return (\n    <PopoverPrimitive.Anchor asChild>\n      <div\n        ref={chipsRef}\n        data-slot=\"combobox-chips\"\n        data-disabled={disabled || undefined}\n        onMouseDown={(e) => {\n          if (disabled || e.target !== e.currentTarget) return;\n          e.preventDefault();\n          e.currentTarget.querySelector(\"input\")?.focus();\n          setOpen(true);\n        }}\n        className={cn(FIELD, \"flex-wrap gap-1.5 py-1 has-[input:focus-visible]:border-line-brand data-disabled:opacity-50\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </PopoverPrimitive.Anchor>\n  );\n}\n\nfunction ComboboxChip({ value, className, children, ...props }: React.ComponentProps<\"span\"> & { value: string }) {\n  const { remove, labelOf, disabled } = useCombobox();\n  return (\n    <span data-slot=\"combobox-chip\" className={cn(\"inline-flex h-6.5 max-w-full items-center gap-1 rounded-sm bg-wash-pill pr-1 pl-2 font-sans text-caption leading-tight text-fg\", className)} {...props}>\n      <span className=\"truncate\">{children ?? labelOf(value)}</span>\n      <button\n        type=\"button\"\n        aria-label={`Remove ${labelOf(value)}`}\n        disabled={disabled}\n        onClick={() => remove(value)}\n        className=\"inline-flex size-4.5 items-center justify-center rounded-menu text-fg-muted outline-none transition-[background-color,color] duration-160 ease-out hover:bg-wash-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-focus-ring motion-reduce:transition-none\"\n      >\n        <X strokeWidth={1.75} className=\"size-3\" />\n      </button>\n    </span>\n  );\n}\n\n/** The typing part of ComboboxChips: filters the list; arrows and Enter work the list; Backspace removes the last chip. */\nfunction ComboboxChipsInput({ className, onKeyDown, ...props }: React.ComponentProps<\"input\">) {\n  const { search, setSearch, setOpen, open, values, remove, listRef, disabled, contentId } = useCombobox();\n  return (\n    <input\n      data-slot=\"combobox-chips-input\"\n      role=\"combobox\"\n      aria-expanded={open}\n      aria-controls={contentId}\n      aria-autocomplete=\"list\"\n      disabled={disabled}\n      value={search}\n      onChange={(e) => {\n        setSearch(e.target.value);\n        if (!open) setOpen(true);\n      }}\n      onFocus={() => setOpen(true)}\n      onKeyDown={(e) => {\n        onKeyDown?.(e);\n        if (e.defaultPrevented) return;\n        if (e.key === \"Backspace\" && !search && values.length) remove(values[values.length - 1]);\n        else if ([\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Home\", \"End\"].includes(e.key) && open && listRef.current) {\n          e.preventDefault();\n          // Hand the key to the list, which moves the highlight or picks the highlighted row.\n          listRef.current.dispatchEvent(new KeyboardEvent(\"keydown\", { key: e.key, bubbles: true, cancelable: true }));\n        } else if (e.key === \"ArrowDown\" && !open) setOpen(true);\n      }}\n      className={cn(\"h-6.5 min-w-24 flex-1 bg-transparent font-sans text-body-sm leading-body text-fg caret-primary outline-none placeholder:text-fg-faint disabled:cursor-not-allowed\", className)}\n      {...props}\n    />\n  );\n}\n\n/** A ref for an element the list should open from, when it is not the trigger (pass it to ComboboxContent's anchor). */\nfunction useComboboxAnchor<T extends HTMLElement = HTMLDivElement>() {\n  return React.useRef<T>(null);\n}\n\nexport {\n  Combobox,\n  ComboboxChip,\n  ComboboxChips,\n  ComboboxChipsInput,\n  ComboboxClear,\n  ComboboxCollection,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxGroup,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxLabel,\n  ComboboxList,\n  ComboboxSeparator,\n  ComboboxTrigger,\n  ComboboxValue,\n  useCombobox,\n  useComboboxAnchor,\n};\n"
    }
  ]
}
