{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "title": "Combobox",
  "description": "A searchable select for long lists, single or multiple, with people rows, section headers, chips and loading built in.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json",
    "https://miseui.junsu4621.workers.dev/r/popover.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/combobox.tsx",
      "content": "import type * as React from \"react\"\nimport { useId, useRef } from \"react\"\nimport { Search } from \"lucide-react\"\nimport { LayoutGroup, useReducedMotion } from \"motion/react\"\nimport { Popover, PopoverContent } from \"@/registry/miseui/ui/popover\"\nimport { ComboboxList, optionId } from \"@/registry/miseui/ui/combobox-list\"\nimport { useComboboxState, type ComboboxOption } from \"@/registry/miseui/ui/combobox-state\"\nimport { ComboboxTags, ComboboxTrigger } from \"@/registry/miseui/ui/combobox-trigger\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype ComboboxVariant = \"plain\" | \"avatar\" | \"grouped\" | \"tags\"\ntype ComboboxEffect = \"spring\" | \"materialize\"\n\ntype TriggerAttributes = Omit<React.ComponentProps<\"button\">, \"value\" | \"defaultValue\" | \"onChange\" | \"children\">\n\ninterface ComboboxBaseProps extends TriggerAttributes {\n  options: readonly ComboboxOption[]\n  /** `plain` rows, `avatar` media and description, `grouped` section headers, `tags` chips in the trigger. */\n  variant?: ComboboxVariant\n  /** `spring` slides one highlight between rows; `materialize` grows the panel out of the trigger. */\n  effect?: ComboboxEffect\n  placeholder?: string\n  searchPlaceholder?: string\n  emptyText?: React.ReactNode\n  loading?: boolean\n  loadingText?: string\n  /** Hands filtering to you: fires on every keystroke and with \"\" on close. Debounce it on your side. */\n  onSearch?: (query: string) => void\n  /** Posts the value with the surrounding form. */\n  name?: string\n}\n\ntype ComboboxSingleProps = ComboboxBaseProps & {\n  multiple?: false\n  value?: string | null\n  defaultValue?: string | null\n  onValueChange?: (value: string | null) => void\n}\n\ntype ComboboxMultipleProps = ComboboxBaseProps & {\n  multiple: true\n  value?: readonly string[]\n  defaultValue?: readonly string[]\n  onValueChange?: (value: string[]) => void\n}\n\ntype ComboboxProps = ComboboxSingleProps | ComboboxMultipleProps\n\n/** Everything else on the props is a button attribute and goes to the trigger, which is what a Field needs. */\nconst OWN_PROPS: ReadonlySet<string> = new Set([\n  \"options\", \"variant\", \"effect\", \"placeholder\", \"searchPlaceholder\", \"emptyText\", \"loading\", \"loadingText\",\n  \"onSearch\", \"name\", \"multiple\", \"value\", \"defaultValue\", \"onValueChange\", \"className\",\n])\n\nfunction triggerAttributes(props: ComboboxProps) {\n  return Object.fromEntries(Object.entries(props).filter(([key]) => !OWN_PROPS.has(key))) as TriggerAttributes\n}\n\nfunction toArray(value: string | readonly string[] | null | undefined) {\n  if (value === null || value === undefined) return []\n  return typeof value === \"string\" ? [value] : value\n}\n\nfunction useCombobox(props: ComboboxProps) {\n  const { options, variant, value, defaultValue, onSearch } = props\n  return useComboboxState({\n    options,\n    controlled: value === undefined ? undefined : toArray(value),\n    initial: toArray(defaultValue),\n    multiple: props.multiple === true,\n    grouped: variant === \"grouped\",\n    removeOnBackspace: variant === \"tags\",\n    serverFiltered: onSearch !== undefined,\n    onSearch,\n    emit: (next) => {\n      if (props.multiple) props.onValueChange?.([...next])\n      else props.onValueChange?.(next[0] ?? null)\n    },\n  })\n}\n\ntype ComboboxState = ReturnType<typeof useCombobox>\n\ninterface ComboboxPanelProps {\n  state: ComboboxState\n  props: ComboboxProps\n  variant: ComboboxVariant\n}\n\nfunction ComboboxPanel({ state, props, variant }: ComboboxPanelProps) {\n  const { effect, searchPlaceholder = \"Search\", emptyText = \"No results.\", loading = false, loadingText = \"Loading\" } = props\n  const inputRef = useRef<HTMLInputElement>(null)\n  const reduceMotion = useReducedMotion() ?? false\n  const activeIndex = state.active ? state.visible.indexOf(state.active) : -1\n  return (\n    <PopoverContent\n      effect={effect === \"materialize\" ? \"materialize\" : undefined}\n      align=\"start\"\n      aria-label={props[\"aria-label\"]}\n      className=\"w-(--radix-popover-trigger-width) max-w-(--radix-popover-content-available-width) min-w-64 p-0\"\n      onOpenAutoFocus={(event) => {\n        event.preventDefault()\n        inputRef.current?.focus()\n      }}\n    >\n      <div className=\"flex items-center gap-2 border-b px-3\">\n        <Search aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n        <input\n          ref={inputRef}\n          role=\"combobox\"\n          aria-label={searchPlaceholder}\n          aria-expanded={state.open}\n          aria-controls={state.listId}\n          aria-autocomplete=\"list\"\n          aria-activedescendant={activeIndex >= 0 ? optionId(state.listId, activeIndex) : undefined}\n          autoComplete=\"off\"\n          placeholder={searchPlaceholder}\n          value={state.query}\n          className=\"h-10 w-full min-w-0 bg-transparent text-base outline-none placeholder:text-muted-foreground md:text-sm\"\n          onChange={(event) => state.onQueryChange(event.target.value)}\n          onKeyDown={state.onInputKeyDown}\n        />\n      </div>\n      <ComboboxList\n        {...{ loading, loadingText, emptyText, reduceMotion }}\n        {...{ listId: state.listId, groups: state.groups, visible: state.visible, active: state.active, selected: state.selected }}\n        label={props[\"aria-label\"]}\n        multiple={props.multiple === true}\n        rich={variant === \"avatar\"}\n        spring={effect === \"spring\"}\n        onHover={state.setActiveValue}\n        onPick={state.toggle}\n      />\n    </PopoverContent>\n  )\n}\n\nfunction Combobox(props: ComboboxProps) {\n  const { variant = \"plain\", effect, placeholder = \"Select\", name, className } = props\n  const state = useCombobox(props)\n  // layoutId is global to the page; without a per-instance group two open lists would share one highlight.\n  const groupId = useId()\n  const trigger = triggerAttributes(props)\n  return (\n    <div data-slot=\"combobox\" data-variant={variant} data-effect={effect} className={cn(\"w-full min-w-0\", className)}>\n      {name ? state.selected.map((entry) => <input key={entry} type=\"hidden\" name={name} value={entry} />) : null}\n      <Popover open={state.open} onOpenChange={state.setOpen}>\n        {variant === \"tags\" ? (\n          <ComboboxTags selected={state.selectedOptions} placeholder={placeholder} onRemove={state.remove} {...trigger} />\n        ) : (\n          <ComboboxTrigger selected={state.selectedOptions} placeholder={placeholder} showMedia={variant === \"avatar\"} {...trigger} />\n        )}\n        <LayoutGroup id={groupId}>\n          <ComboboxPanel state={state} props={props} variant={variant} />\n        </LayoutGroup>\n      </Popover>\n    </div>\n  )\n}\n\nexport { Combobox, type ComboboxProps, type ComboboxOption, type ComboboxVariant, type ComboboxEffect }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/combobox-list.tsx",
      "content": "import type * as React from \"react\"\nimport { useEffect, useId, useRef } from \"react\"\nimport { Check, Loader2 } from \"lucide-react\"\nimport { motion } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport type { ComboboxGroup, ComboboxOption } from \"@/registry/miseui/ui/combobox-state\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ninterface RowContext {\n  listId: string\n  rich: boolean\n  spring: boolean\n  reduceMotion: boolean\n  activeValue: string | null\n  selected: readonly string[]\n  visible: readonly ComboboxOption[]\n  onHover: (value: string) => void\n  onPick: (option: ComboboxOption) => void\n}\n\nconst optionId = (listId: string, index: number) => `${listId}-option-${index}`\n\nfunction RowHighlight({ reduceMotion }: { reduceMotion: boolean }) {\n  return (\n    <motion.span\n      aria-hidden=\"true\"\n      data-slot=\"combobox-highlight\"\n      data-reduced={reduceMotion ? \"true\" : \"false\"}\n      layoutId={reduceMotion ? undefined : \"combobox-highlight\"}\n      transition={SPRING_DEFAULT}\n      className=\"absolute inset-0 -z-10 rounded-sm bg-mise-soft\"\n    />\n  )\n}\n\nfunction ComboboxRow({ option, context }: { option: ComboboxOption; context: RowContext }) {\n  const { listId, rich, spring, reduceMotion, activeValue, selected, visible, onHover, onPick } = context\n  const ref = useRef<HTMLDivElement>(null)\n  const active = option.value === activeValue\n  const isSelected = selected.includes(option.value)\n  useEffect(() => {\n    // Arrow keys can move past the fold; the pointer never can, and \"nearest\" does nothing when already visible.\n    if (active) ref.current?.scrollIntoView({ block: \"nearest\" })\n  }, [active])\n  return (\n    <div\n      ref={ref}\n      id={optionId(listId, visible.indexOf(option))}\n      role=\"option\"\n      aria-selected={isSelected}\n      aria-disabled={option.disabled ? true : undefined}\n      data-slot=\"combobox-option\"\n      data-active={active ? \"true\" : \"false\"}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-sm px-2 text-sm select-none\",\n        rich ? \"min-h-12 py-1.5\" : \"min-h-9 py-1.5 pointer-coarse:min-h-10\",\n        \"aria-disabled:pointer-events-none aria-disabled:opacity-50\",\n        !spring && \"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground\",\n      )}\n      onPointerMove={() => {\n        if (!option.disabled && !active) onHover(option.value)\n      }}\n      // The search input keeps focus, and with it aria-activedescendant, while the pointer works the list.\n      onMouseDown={(event) => event.preventDefault()}\n      onClick={() => onPick(option)}\n    >\n      {spring && active ? <RowHighlight reduceMotion={reduceMotion} /> : null}\n      {rich && option.media ? (\n        <span className=\"flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted\">\n          {option.media}\n        </span>\n      ) : null}\n      <span className=\"grid min-w-0 flex-1\">\n        <span className=\"truncate\">{option.label}</span>\n        {rich && option.description ? (\n          <span className=\"truncate text-[13px] text-muted-foreground\">{option.description}</span>\n        ) : null}\n      </span>\n      {isSelected ? <Check aria-hidden=\"true\" className=\"size-4 shrink-0\" /> : null}\n    </div>\n  )\n}\n\nfunction ComboboxSection({ group, context }: { group: ComboboxGroup; context: RowContext }) {\n  const headerId = useId()\n  const rows = group.options.map((option) => <ComboboxRow key={option.value} option={option} context={context} />)\n  if (!group.name) return rows\n  return (\n    <div role=\"group\" aria-labelledby={headerId} className=\"not-first:mt-1\">\n      <div id={headerId} role=\"presentation\" className=\"px-2 py-1.5 text-[13px] text-muted-foreground\">\n        {group.name}\n      </div>\n      {rows}\n    </div>\n  )\n}\n\ninterface ComboboxListProps extends Omit<RowContext, \"activeValue\" | \"visible\"> {\n  groups: readonly ComboboxGroup[]\n  visible: readonly ComboboxOption[]\n  active: ComboboxOption | null\n  multiple: boolean\n  loading: boolean\n  loadingText: string\n  emptyText: React.ReactNode\n  label: string | undefined\n}\n\n/** The status sits outside the listbox, whose children may only be options and groups. */\nfunction ComboboxList({ groups, visible, active, multiple, loading, loadingText, emptyText, label, ...row }: ComboboxListProps) {\n  const context: RowContext = { ...row, visible, activeValue: active?.value ?? null }\n  const empty = visible.length === 0\n  return (\n    <>\n      <div role=\"status\" className={cn(!loading && !empty && \"sr-only\")}>\n        {loading ? (\n          <span className=\"flex items-center gap-2 px-3 py-2 text-[13px] text-muted-foreground\">\n            <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin motion-reduce:animate-none\" />\n            {loadingText}\n          </span>\n        ) : empty ? (\n          <span className=\"block px-3 py-6 text-center text-sm text-muted-foreground\">{emptyText}</span>\n        ) : null}\n      </div>\n      {/* layoutScroll: without it the highlight measures rows against the page and jumps once the list has scrolled. */}\n      <motion.div\n        layoutScroll\n        id={row.listId}\n        role=\"listbox\"\n        aria-label={label}\n        aria-busy={loading ? true : undefined}\n        aria-multiselectable={multiple ? true : undefined}\n        // One stacking context for the list keeps a flying highlight behind every label, not just its own.\n        className={cn(\"isolate max-h-72 overflow-y-auto\", !empty && \"p-1\")}\n      >\n        {groups.map((group) => (\n          <ComboboxSection key={group.name ?? \"all\"} group={group} context={context} />\n        ))}\n      </motion.div>\n    </>\n  )\n}\n\nexport { ComboboxList, optionId }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/combobox-state.ts",
      "content": "import type * as React from \"react\"\nimport { useId, useMemo, useState } from \"react\"\n\ninterface ComboboxOption {\n  value: string\n  label: string\n  /** A second line, drawn by `avatar` and always searched. */\n  description?: string\n  /** Leading picture or icon, drawn by `avatar`. */\n  media?: React.ReactNode\n  /** Section name, drawn by `grouped`. */\n  group?: string\n  disabled?: boolean\n}\n\ninterface ComboboxGroup {\n  name: string | null\n  options: readonly ComboboxOption[]\n}\n\nfunction filterOptions(options: readonly ComboboxOption[], query: string) {\n  const needle = query.trim().toLowerCase()\n  if (!needle) return options\n  return options.filter((option) => `${option.label} ${option.description ?? \"\"}`.toLowerCase().includes(needle))\n}\n\n/** Sections in first-seen order, so the eye and the arrow keys travel the list the same way. */\nfunction groupOptions(options: readonly ComboboxOption[], grouped: boolean): readonly ComboboxGroup[] {\n  if (!grouped) return [{ name: null, options }]\n  const names = [...new Set(options.map((option) => option.group ?? \"\"))]\n  return names.map((name) => ({\n    name: name || null,\n    options: options.filter((option) => (option.group ?? \"\") === name),\n  }))\n}\n\ninterface ComboboxStateOptions {\n  options: readonly ComboboxOption[]\n  /** Normalised to an array so single and multiple share one code path. */\n  controlled: readonly string[] | undefined\n  initial: readonly string[]\n  multiple: boolean\n  grouped: boolean\n  /** Backspace in an empty search removes the last chip; only meaningful where chips are visible. */\n  removeOnBackspace: boolean\n  serverFiltered: boolean\n  emit: (next: readonly string[]) => void\n  onSearch?: (query: string) => void\n}\n\nfunction useSelection({ options, controlled, initial, multiple, emit }: ComboboxStateOptions) {\n  const [uncontrolled, setUncontrolled] = useState<readonly string[]>(initial)\n  // Async results replace `options`, so a chosen option has to be remembered to keep its label in the trigger.\n  const [remembered, setRemembered] = useState<Readonly<Record<string, ComboboxOption>>>({})\n  const selected = controlled ?? uncontrolled\n\n  const update = (next: readonly string[]) => {\n    setUncontrolled(next)\n    emit(next)\n  }\n  const known = (value: string) =>\n    options.find((option) => option.value === value) ?? remembered[value] ?? { value, label: value }\n\n  return {\n    selected,\n    selectedOptions: selected.map(known),\n    remove: (value: string) => update(selected.filter((entry) => entry !== value)),\n    removeLast: () => update(selected.slice(0, -1)),\n    toggle: (option: ComboboxOption) => {\n      setRemembered((previous) => ({ ...previous, [option.value]: option }))\n      const isSelected = selected.includes(option.value)\n      if (multiple) {\n        update(isSelected ? selected.filter((value) => value !== option.value) : [...selected, option.value])\n      } else {\n        update(isSelected ? [] : [option.value])\n      }\n    },\n  }\n}\n\nfunction useVisibleOptions({ options, grouped, serverFiltered }: ComboboxStateOptions, query: string, activeValue: string | null) {\n  const groups = useMemo(\n    () => groupOptions(serverFiltered ? options : filterOptions(options, query), grouped),\n    [options, query, grouped, serverFiltered],\n  )\n  const visible = useMemo(() => groups.flatMap((group) => group.options), [groups])\n  const enabled = useMemo(() => visible.filter((option) => !option.disabled), [visible])\n  // Derived, not stored: when results change under the highlight it falls to the first row instead of pointing at nothing.\n  const active = enabled.find((option) => option.value === activeValue) ?? enabled[0] ?? null\n  const neighbour = (step: 1 | -1) => {\n    if (enabled.length === 0) return null\n    const index = active ? enabled.indexOf(active) : -1\n    return enabled[(index + step + enabled.length) % enabled.length].value\n  }\n  return { groups, visible, active, neighbour }\n}\n\nfunction useComboboxState(config: ComboboxStateOptions) {\n  const { multiple, removeOnBackspace, onSearch } = config\n  const listId = useId()\n  const [open, setOpenState] = useState(false)\n  const [query, setQuery] = useState(\"\")\n  const [activeValue, setActiveValue] = useState<string | null>(null)\n  const selection = useSelection(config)\n  const list = useVisibleOptions(config, query, activeValue)\n\n  const setOpen = (next: boolean) => {\n    setOpenState(next)\n    if (next) setActiveValue(selection.selected[0] ?? null)\n    if (next) return\n    if (query) onSearch?.(\"\")\n    setQuery(\"\")\n  }\n  const toggle = (option: ComboboxOption) => {\n    if (option.disabled) return\n    selection.toggle(option)\n    if (!multiple) setOpen(false)\n  }\n  const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n    if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n      event.preventDefault()\n      setActiveValue(list.neighbour(event.key === \"ArrowDown\" ? 1 : -1))\n    } else if (event.key === \"Enter\") {\n      // Inside a form, Enter on a search box must pick a row, not submit.\n      event.preventDefault()\n      if (list.active) toggle(list.active)\n    } else if (event.key === \"Backspace\" && !query && removeOnBackspace && selection.selected.length > 0) {\n      selection.removeLast()\n    }\n  }\n  const onQueryChange = (next: string) => {\n    setQuery(next)\n    onSearch?.(next)\n  }\n  return { ...selection, ...list, listId, open, setOpen, query, onQueryChange, setActiveValue, toggle, onInputKeyDown }\n}\n\nexport { useComboboxState, type ComboboxGroup, type ComboboxOption }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/combobox-trigger.tsx",
      "content": "import type * as React from \"react\"\nimport { useId } from \"react\"\nimport { ChevronsUpDown, X } from \"lucide-react\"\nimport { PopoverAnchor, PopoverTrigger } from \"@/registry/miseui/ui/popover\"\nimport type { ComboboxOption } from \"@/registry/miseui/ui/combobox-state\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst FIELD_CLASS = [\n  \"rounded-md border border-input bg-transparent text-sm shadow-xs\",\n  \"transition-[color,border-color,box-shadow] duration-(--duration-fast) ease-(--ease-mise)\",\n  \"aria-invalid:border-destructive aria-invalid:ring-destructive/20\",\n]\nconst FOCUS_CLASS = \"outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\"\nconst DISABLED_CLASS = \"disabled:cursor-not-allowed disabled:opacity-50\"\n\ntype TriggerProps = React.ComponentProps<typeof PopoverTrigger>\n\nfunction summary(selected: readonly ComboboxOption[]) {\n  if (selected.length < 2) return selected[0]?.label\n  return `${selected[0].label} +${selected.length - 1}`\n}\n\ninterface ComboboxTriggerProps extends TriggerProps {\n  selected: readonly ComboboxOption[]\n  placeholder: string\n  showMedia: boolean\n}\n\nfunction ComboboxTrigger({\n  selected,\n  placeholder,\n  showMedia,\n  className,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": labelledBy,\n  ...props\n}: ComboboxTriggerProps) {\n  const valueId = useId()\n  const label = summary(selected)\n  const media = showMedia ? selected[0]?.media : null\n  return (\n    <PopoverTrigger\n      data-slot=\"combobox-trigger\"\n      // A name replaces a button's content, so the chosen value has to be added to it or it is never announced.\n      aria-label={ariaLabel && label ? `${ariaLabel}, ${label}` : ariaLabel}\n      aria-labelledby={labelledBy && label ? `${labelledBy} ${valueId}` : labelledBy}\n      className={cn(\n        FIELD_CLASS,\n        FOCUS_CLASS,\n        DISABLED_CLASS,\n        \"flex h-9 w-full items-center justify-between gap-2 px-3 text-left pointer-coarse:h-10\",\n        className,\n      )}\n      {...props}\n    >\n      {media ? (\n        <span className=\"flex size-5 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted\">{media}</span>\n      ) : null}\n      <span id={valueId} className={cn(\"min-w-0 flex-1 truncate\", !label && \"text-muted-foreground\")}>\n        {label ?? placeholder}\n      </span>\n      <ChevronsUpDown aria-hidden=\"true\" className=\"size-4 shrink-0 opacity-50\" />\n    </PopoverTrigger>\n  )\n}\n\ninterface ComboboxTagsProps extends TriggerProps {\n  selected: readonly ComboboxOption[]\n  placeholder: string\n  onRemove: (value: string) => void\n}\n\nfunction RemovableTag({ option, disabled, onRemove }: { option: ComboboxOption; disabled?: boolean; onRemove: (value: string) => void }) {\n  return (\n    <span className=\"flex h-7 max-w-full items-center rounded-sm bg-secondary pl-2 text-[13px] text-secondary-foreground\">\n      <span className=\"min-w-0 truncate\">{option.label}</span>\n      <button\n        type=\"button\"\n        disabled={disabled}\n        aria-label={`Remove ${option.label}`}\n        className=\"relative flex size-7 shrink-0 items-center justify-center rounded-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 pointer-coarse:after:absolute pointer-coarse:after:-inset-1.5\"\n        onClick={() => onRemove(option.value)}\n      >\n        <X aria-hidden=\"true\" className=\"size-3.5\" />\n      </button>\n    </span>\n  )\n}\n\n/** Remove buttons cannot nest inside the trigger button, so the frame is an anchor holding both as siblings. */\nfunction ComboboxTags({ selected, placeholder, onRemove, className, disabled, ...props }: ComboboxTagsProps) {\n  return (\n    <PopoverAnchor asChild>\n      <div\n        data-slot=\"combobox-tags\"\n        className={cn(\n          FIELD_CLASS,\n          \"flex min-h-9 w-full flex-wrap items-center gap-1 p-1 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 pointer-coarse:min-h-10\",\n          disabled && \"cursor-not-allowed opacity-50\",\n          className,\n        )}\n      >\n        {selected.map((option) => (\n          <RemovableTag key={option.value} option={option} disabled={disabled} onRemove={onRemove} />\n        ))}\n        <PopoverTrigger\n          data-slot=\"combobox-trigger\"\n          disabled={disabled}\n          className=\"flex h-7 min-w-24 flex-1 items-center justify-between gap-2 rounded-sm px-2 text-left text-muted-foreground outline-none disabled:cursor-not-allowed\"\n          {...props}\n        >\n          <span className=\"truncate\">{placeholder}</span>\n          <ChevronsUpDown aria-hidden=\"true\" className=\"size-4 shrink-0 opacity-50\" />\n        </PopoverTrigger>\n      </div>\n    </PopoverAnchor>\n  )\n}\n\nexport { ComboboxTags, ComboboxTrigger }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}