{
  "schemaVersion": 1,
  "name": "combobox",
  "useFor": "Use instead of Select when there are more than about ten choices or people know what they are looking for (an agent, a contact, webhook events). Wrap ComboboxTrigger (or ComboboxChips with ComboboxChip and ComboboxChipsInput for several values) and ComboboxContent with ComboboxInput, ComboboxList, ComboboxItem rows and ComboboxEmpty in a Combobox with value and onValueChange (multiple for several). Pass items so the closed field can show labels. To let people add a new value, render an extra item for the search text from useCombobox().",
  "constraints": [
    "A combobox for five or fewer choices (use Select or a radio group)",
    "Animating the highlight between rows",
    "A list without an empty state",
    "Chips in the purple"
  ],
  "documentation": "",
  "source": "Extrapolated from the Inkbox console (Figma 427:2715): the select trigger (Any State), the command menu and the lilac handle chip",
  "exports": [
    "Combobox",
    "ComboboxChip",
    "ComboboxChips",
    "ComboboxChipsInput",
    "ComboboxClear",
    "ComboboxCollection",
    "ComboboxContent",
    "ComboboxEmpty",
    "ComboboxGroup",
    "ComboboxInput",
    "ComboboxItem",
    "ComboboxLabel",
    "ComboboxList",
    "ComboboxSeparator",
    "ComboboxTrigger",
    "ComboboxValue",
    "useCombobox",
    "useComboboxAnchor"
  ],
  "client": true,
  "docsUrl": "https://inkbox-ds.vercel.app/components/combobox",
  "markdownUrl": "https://inkbox-ds.vercel.app/r/combobox.md",
  "examples": [
    {
      "path": "examples/combobox.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { PlusIcon, RobotIcon } from \"@phosphor-icons/react/ssr\";\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 AGENTS = [\n  { value: \"acme\", label: \"@acme\" },\n  { value: \"acme-support\", label: \"@acme-support\" },\n  { value: \"support-desk\", label: \"@support-desk\" },\n  { value: \"ops-runner\", label: \"@ops-runner\" },\n  { value: \"test-agent-233\", label: \"@test-agent-233\" },\n  { value: \"billing-bot\", label: \"@billing-bot\", disabled: true },\n];\n\nconst EVENTS = [\"message.received\", \"message.sent\", \"message.delivered\", \"message.bounced\", \"call.started\", \"call.ended\", \"imessage.received\", \"tunnel.connected\", \"tunnel.disconnected\"];\n\nexport function Preview() {\n  const [agent, setAgent] = React.useState<string | null>(\"acme\");\n  return (\n    <div className=\"grid w-full max-w-80 gap-1\">\n      <Label id=\"agent-label\">Agent</Label>\n      <Combobox items={AGENTS} value={agent} onValueChange={setAgent}>\n        <ComboboxTrigger aria-labelledby=\"agent-label\">\n          <ComboboxValue placeholder=\"Choose an agent\" />\n        </ComboboxTrigger>\n        <ComboboxContent>\n          <ComboboxInput placeholder=\"Search agents\" />\n          <ComboboxList>\n            <ComboboxEmpty>No agent with that handle.</ComboboxEmpty>\n            {AGENTS.map((a) => (\n              <ComboboxItem key={a.value} value={a.value} label={a.label} disabled={a.disabled}>\n                <RobotIcon weight=\"fill\" />\n                {a.label}\n              </ComboboxItem>\n            ))}\n          </ComboboxList>\n        </ComboboxContent>\n      </Combobox>\n    </div>\n  );\n}\n\nexport function Multiple() {\n  const [events, setEvents] = React.useState<string[]>([\"message.received\", \"message.delivered\"]);\n  return (\n    <div className=\"grid w-full max-w-100 gap-1\">\n      <Label htmlFor=\"events\">Events</Label>\n      <Combobox multiple items={EVENTS} value={events} onValueChange={setEvents} name=\"events\">\n        <ComboboxChips>\n          {events.map((e) => (\n            <ComboboxChip key={e} value={e} className=\"font-mono text-body-sm\" />\n          ))}\n          <ComboboxChipsInput id=\"events\" placeholder={events.length ? \"\" : \"Add events\"} />\n          <ComboboxClear />\n        </ComboboxChips>\n        <ComboboxContent>\n          <ComboboxList>\n            {(item) => (\n              <ComboboxItem value={item.value} className=\"font-mono text-body-sm\">\n                {item.label}\n              </ComboboxItem>\n            )}\n          </ComboboxList>\n          <ComboboxEmpty>No such event.</ComboboxEmpty>\n        </ComboboxContent>\n      </Combobox>\n    </div>\n  );\n}\n\n// The \"Create\" row reads the search from the combobox and offers it as a new value; choosing it calls onValueChange.\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        <PlusIcon />\n        Create &ldquo;{value}&rdquo;\n      </ComboboxItem>\n    </>\n  );\n}\n\nexport function Creatable() {\n  const [tags, setTags] = React.useState([\"vip\", \"customer\", \"vendor\"]);\n  const [tag, setTag] = React.useState<string | null>(null);\n  return (\n    <div className=\"grid w-full max-w-80 gap-1\">\n      <Label id=\"tag-label\">Contact tag</Label>\n      <Combobox items={tags} value={tag} onValueChange={(v) => {\n        if (v && !tags.includes(v)) setTags((t) => [...t, v]);\n        setTag(v);\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 Groups() {\n  const [line, setLine] = React.useState<string | null>(null);\n  return (\n    <div className=\"grid w-full max-w-80 gap-1\">\n      <Label id=\"line-label\">Route to</Label>\n      <Combobox\n        value={line}\n        onValueChange={setLine}\n        items={[\n          { value: \"+16504749720\", label: \"+1 650 474 9720\" },\n          { value: \"+14155550134\", label: \"+1 415 555 0134\" },\n          { value: \"shared\", label: \"Shared iMessage line\" },\n        ]}\n      >\n        <ComboboxTrigger aria-labelledby=\"line-label\">\n          <ComboboxValue placeholder=\"Choose a line\" />\n        </ComboboxTrigger>\n        <ComboboxContent>\n          <ComboboxInput placeholder=\"Search lines\" />\n          <ComboboxList>\n            <ComboboxEmpty />\n            <ComboboxGroup heading=\"Phone\">\n              <ComboboxItem value=\"+16504749720\">+1 650 474 9720</ComboboxItem>\n              <ComboboxItem value=\"+14155550134\">+1 415 555 0134</ComboboxItem>\n            </ComboboxGroup>\n            <ComboboxSeparator />\n            <ComboboxGroup heading=\"iMessage\">\n              <ComboboxItem value=\"shared\">Shared iMessage line</ComboboxItem>\n            </ComboboxGroup>\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={AGENTS} defaultValue=\"acme\" disabled>\n        <ComboboxTrigger aria-label=\"Disabled agent\" />\n      </Combobox>\n      <div className=\"grid gap-1\">\n        <Combobox items={AGENTS}>\n          <ComboboxTrigger aria-label=\"Required agent\" aria-invalid>\n            <ComboboxValue placeholder=\"Choose an agent\" />\n          </ComboboxTrigger>\n          <ComboboxContent>\n            <ComboboxInput placeholder=\"Search agents\" />\n            <ComboboxList>\n              {(a) => <ComboboxItem value={a.value}>{a.label}</ComboboxItem>}\n            </ComboboxList>\n          </ComboboxContent>\n        </Combobox>\n        <p className=\"text-caption leading-body text-danger\">Choose the agent that owns this mailbox.</p>\n      </div>\n    </div>\n  );\n}\n",
      "exports": [
        "Preview",
        "Multiple",
        "Creatable",
        "Groups",
        "States"
      ],
      "requires": [
        "combobox",
        "label"
      ],
      "packages": [
        "@phosphor-icons/react"
      ],
      "previews": [
        {
          "name": "Preview",
          "url": "https://inkbox-ds.vercel.app/preview/combobox/Preview"
        },
        {
          "name": "Multiple",
          "url": "https://inkbox-ds.vercel.app/preview/combobox/Multiple"
        },
        {
          "name": "Creatable",
          "url": "https://inkbox-ds.vercel.app/preview/combobox/Creatable"
        },
        {
          "name": "Groups",
          "url": "https://inkbox-ds.vercel.app/preview/combobox/Groups"
        },
        {
          "name": "States",
          "url": "https://inkbox-ds.vercel.app/preview/combobox/States"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Combobox",
  "description": "The select trigger that opens a searchable list, for one value or several shown as lilac chips.",
  "surface": "site",
  "kinds": [],
  "requires": [
    "theme",
    "surface",
    "utils"
  ],
  "packages": [
    "radix-ui",
    "cmdk",
    "@phosphor-icons/react"
  ],
  "files": [
    {
      "path": "components/ui/combobox.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { CaretDownIcon, CheckIcon, MagnifyingGlassIcon, XIcon } from \"@phosphor-icons/react/ssr\";\n\nimport { usePortalContainer } from \"@/lib/surface\";\nimport { cn } from \"@/lib/utils\";\n\n// A searchable choice (extrapolated from the Inkbox console, Figma 427:2715: the select trigger \"Any State\" and the\n// command menu). The trigger is the select's white 40px key with 8px corners and the Shadow/S ring, the value in 14px ink\n// (the placeholder in the light purple) and the bold caret at the right. It opens the menu panel (white, 8px corners,\n// the floating shadow) from the trigger: a 44px search row with a magnifying glass, then 36px rows of 14px ink with the\n// highlighted row on the lilac and a purple check beside the chosen ones; groups are headed in 13px purple. For several\n// values, ComboboxChips is the field itself: lilac chips with 6px corners and an x, and a text input that filters as you\n// type (Backspace on an empty input removes the last 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-10 w-full min-w-0 items-center gap-2 rounded-control bg-surface pr-3 pl-3.5 font-sans text-body leading-tight text-fg shadow-s outline-none transition-[box-shadow] duration-150 ease-out aria-invalid:shadow-[0_0_0_1px_var(--ds-color-status-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-10 cursor-pointer text-left focus-visible:shadow-[var(--ds-shadow-s),var(--ds-shadow-focus)] disabled:cursor-not-allowed disabled:bg-subtle disabled:text-fg-muted data-[empty=true]:text-fg-subtle\", className)}\n        {...props}\n      >\n        <span className=\"flex min-w-0 flex-1 items-center gap-2 truncate\">{children ?? <ComboboxValue />}</span>\n        <CaretDownIcon aria-hidden weight=\"bold\" className=\"size-4 shrink-0 text-fg\" />\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-150 ease-out hover:text-fg-muted focus-visible:outline-2 focus-visible:outline-focus-ring motion-reduce:transition-none\", className)}\n      {...props}\n    >\n      <XIcon weight=\"bold\" className=\"size-4\" />\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) overflow-hidden rounded-control bg-surface text-fg shadow-floating outline-none data-[state=closed]:animate-pop-out data-[state=open]:animate-pop-in\",\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-11 shrink-0 items-center gap-2.5 border-b border-line-subtle px-3.5\">\n      <MagnifyingGlassIcon aria-hidden className=\"size-5.5 shrink-0 text-fg-muted\" />\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 leading-tight text-fg outline-none placeholder:text-fg-subtle\", 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 h-9 cursor-default items-center gap-2.5 rounded-control pr-8 pl-2.5 font-sans text-body leading-tight text-fg outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-subtle [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5.5 [&_svg:not([class*='text-'])]:text-icon-secondary\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? text}\n      {selected && <CheckIcon aria-hidden weight=\"bold\" className=\"absolute right-2.5 size-4 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(\"py-6 text-center text-body text-fg-muted\", className)}>\n        {children}\n      </div>\n    );\n  }\n  return (\n    <CommandPrimitive.Empty data-slot=\"combobox-empty\" className={cn(\"py-6 text-center text-body 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]]:text-body-sm [&_[cmdk-group-heading]]:leading-tight [&_[cmdk-group-heading]]:text-fg-label\", 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 text-body-sm leading-tight text-fg-label\", 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-subtle\", 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.5 has-[input:focus-visible]:shadow-[var(--ds-shadow-s),var(--ds-shadow-focus)] data-disabled:bg-subtle data-disabled:text-fg-muted\", 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-7 max-w-full items-center gap-1 rounded-sm bg-subtle pr-1 pl-2 text-body 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-5 items-center justify-center rounded-xs text-fg-muted outline-none transition-colors duration-150 ease-out hover:bg-subtle-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-focus-ring motion-reduce:transition-none\"\n      >\n        <XIcon weight=\"bold\" className=\"size-3.5\" />\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-7 min-w-24 flex-1 bg-transparent font-sans text-body leading-tight text-fg outline-none placeholder:text-fg-subtle 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"
    }
  ]
}
