{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "title": "Combobox",
  "author": "@fma-ui/ui",
  "description": "A searchable, filterable select combining a text input with a listbox popover. Supports single selection (ComboboxValue) or a multi-select tag/chip input (ComboboxChips + ComboboxChip).",
  "dependencies": [
    "cn@^0.4.0",
    "lucide-react@^1.52.0",
    "react-aria-components@^1.21.1"
  ],
  "registryDependencies": [
    "@fma-ui/button",
    "@fma-ui/input-group"
  ],
  "files": [
    {
      "path": "src/core/combobox/combobox.tsx",
      "content": "import { cn } from \"cn\";\nimport { CheckIcon, ChevronDownIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport {\n  Button as ButtonPrimitive,\n  type ButtonProps,\n  Collection,\n  ComboBox as ComboBoxPrimitive,\n  ComboBoxStateContext,\n  ComboBoxValue as ComboBoxValuePrimitive,\n  type ComboBoxValueProps,\n  composeRenderProps,\n  Group,\n  type GroupProps,\n  Header as HeaderPrimitive,\n  type HeaderProps,\n  Input as InputPrimitive,\n  type InputProps,\n  ListBoxItem as ListBoxItemPrimitive,\n  type ListBoxItemProps,\n  ListBox as ListBoxPrimitive,\n  type ListBoxProps,\n  ListBoxSection as ListBoxSectionPrimitive,\n  type ListBoxSectionProps,\n  Popover as PopoverPrimitive,\n  Separator as SeparatorPrimitive,\n  type SeparatorProps,\n  TagGroup as TagGroupPrimitive,\n  TagList as TagListPrimitive,\n  type TagListProps,\n  Tag as TagPrimitive,\n  type TagProps,\n} from \"react-aria-components\";\nimport { Button } from \"@/core/button/button\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"@/core/input-group/input-group\";\n\nfunction ComboboxValue<T>({ ...props }: ComboBoxValueProps<T>) {\n  return <ComboBoxValuePrimitive data-slot=\"combobox-value\" {...props} />;\n}\n\nfunction ComboboxTrigger({\n  className,\n  children,\n  ...props\n}: Omit<ButtonProps, \"children\"> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <ButtonPrimitive\n      data-slot=\"combobox-trigger\"\n      className={cn(\"[&_svg:not([class*='size-'])]:size-4\", className)}\n      {...props}\n    >\n      {children}\n      <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n    </ButtonPrimitive>\n  );\n}\n\nfunction ComboboxClear({ className, ...props }: React.ComponentProps<typeof InputGroupButton>) {\n  const state = React.useContext(ComboBoxStateContext);\n  if (state?.inputValue === \"\") {\n    return null;\n  }\n\n  return (\n    <InputGroupButton\n      data-slot=\"combobox-clear\"\n      variant=\"ghost\"\n      size=\"icon-xs\"\n      aria-label=\"Clear\"\n      className={cn(className)}\n      onPress={() => {\n        state?.setValue(null);\n      }}\n      slot={null}\n      {...props}\n    >\n      <XIcon className=\"pointer-events-none\" />\n    </InputGroupButton>\n  );\n}\n\nfunction ComboboxInput({\n  className,\n  children,\n  disabled = false,\n  showTrigger = true,\n  showClear = false,\n  ...props\n}: React.ComponentProps<\"input\"> & {\n  showTrigger?: boolean;\n  showClear?: boolean;\n}) {\n  return (\n    <InputGroup className={cn(\"w-auto\", className)}>\n      <InputGroupInput disabled={disabled} {...props} />\n      <InputGroupAddon align=\"inline-end\">\n        {showTrigger && (\n          <InputGroupButton\n            size=\"icon-xs\"\n            variant=\"ghost\"\n            data-slot=\"combobox-trigger\"\n            className=\"group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent [&_svg:not([class*='size-'])]:size-4\"\n            isDisabled={disabled}\n          >\n            <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n          </InputGroupButton>\n        )}\n        {showClear && <ComboboxClear isDisabled={disabled} />}\n      </InputGroupAddon>\n      {children}\n    </InputGroup>\n  );\n}\n\nfunction ComboboxContent({\n  className,\n  placement = \"bottom\",\n  offset = 6,\n  crossOffset = 0,\n  anchor,\n  ...props\n}: Omit<React.ComponentProps<typeof PopoverPrimitive>, \"className\" | \"children\"> & {\n  className?: string;\n  children?: React.ReactNode;\n  anchor?: React.RefObject<HTMLDivElement | null>;\n}) {\n  return (\n    <PopoverPrimitive\n      data-slot=\"combobox-content\"\n      placement={placement}\n      offset={offset}\n      crossOffset={crossOffset}\n      triggerRef={anchor}\n      className={cn(\n        \"relative isolate z-50 max-h-72 w-(--trigger-width) min-w-36 origin-(--trigger-anchor-point) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-entering:animate-in data-entering:fade-in-0 data-entering:zoom-in-95 data-exiting:animate-out data-exiting:fade-out-0 data-exiting:zoom-out-95 data-[placement=bottom]:slide-in-from-top-2 data-[placement=left]:slide-in-from-right-2 data-[placement=right]:slide-in-from-left-2 data-[placement=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxList<T extends object>({ className, ...props }: ListBoxProps<T>) {\n  return (\n    <ListBoxPrimitive\n      data-slot=\"combobox-list\"\n      className={cn(\n        \"group/combobox-content no-scrollbar max-h-[inherit] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxItem<T extends object>({ className, children, ...props }: ListBoxItemProps<T>) {\n  return (\n    <ListBoxItemPrimitive\n      data-slot=\"combobox-item\"\n      textValue={typeof children === \"string\" ? children : undefined}\n      className={cn(\n        \"relative flex w-full cursor-default items-center gap-2 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-focused:bg-accent data-focused:text-accent-foreground not-data-[variant=destructive]:data-focused:**:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className,\n      )}\n      {...props}\n    >\n      {composeRenderProps(children, (children, { isSelected }) => (\n        <>\n          {children}\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\">\n            {isSelected ? <CheckIcon className=\"pointer-events-none\" /> : null}\n          </span>\n        </>\n      ))}\n    </ListBoxItemPrimitive>\n  );\n}\n\nfunction ComboboxGroup<T extends object>({ className, ...props }: ListBoxSectionProps<T>) {\n  return (\n    <ListBoxSectionPrimitive data-slot=\"combobox-group\" className={cn(className)} {...props} />\n  );\n}\n\nfunction ComboboxLabel({ className, ...props }: HeaderProps) {\n  return (\n    <HeaderPrimitive\n      data-slot=\"combobox-label\"\n      className={cn(\"px-2 py-1.5 text-xs text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxEmpty({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"combobox-empty\"\n      className={cn(\n        \"hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxSeparator({ className, ...props }: SeparatorProps) {\n  return (\n    <SeparatorPrimitive\n      data-slot=\"combobox-separator\"\n      className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxChips({ children, className, ...props }: GroupProps) {\n  return (\n    <Group\n      data-slot=\"combobox-chips\"\n      className={cn(\n        \"flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent bg-clip-padding px-2.5 py-1 text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </Group>\n  );\n}\n\nfunction ComboboxChipList<T extends object>({\n  className,\n  ...props\n}: Omit<TagListProps<T>, \"className\" | \"items\"> & {\n  className?: string;\n}) {\n  return (\n    <ComboBoxValuePrimitive<T> className=\"contents\">\n      {({ selectedItems, state }) => (\n        <TagGroupPrimitive\n          data-slot=\"combobox-chip-list\"\n          className={cn(\"contents\", className)}\n          onRemove={(keys) => {\n            if (Array.isArray(state.value)) {\n              state.setValue(state.value.filter((k) => !keys.has(k)));\n            }\n          }}\n        >\n          <TagListPrimitive\n            className=\"contents\"\n            items={selectedItems.filter((item) => item != null)}\n            {...props}\n          />\n        </TagGroupPrimitive>\n      )}\n    </ComboBoxValuePrimitive>\n  );\n}\n\nfunction ComboboxChip({\n  className,\n  children,\n  showRemove = true,\n  ...props\n}: Omit<TagProps, \"children\"> & {\n  showRemove?: boolean;\n  children?: React.ReactNode;\n}) {\n  return (\n    <TagPrimitive\n      data-slot=\"combobox-chip\"\n      className={cn(\n        \"flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      {showRemove && (\n        <Button\n          slot=\"remove\"\n          variant=\"ghost\"\n          size=\"icon-xs\"\n          className=\"-ml-1 opacity-50 hover:opacity-100\"\n          data-slot=\"combobox-chip-remove\"\n        >\n          <XIcon className=\"pointer-events-none\" />\n        </Button>\n      )}\n    </TagPrimitive>\n  );\n}\n\nfunction ComboboxChipsInput({ className, ...props }: InputProps) {\n  const state = React.useContext(ComboBoxStateContext);\n  return (\n    <InputPrimitive\n      data-slot=\"combobox-chip-input\"\n      className={cn(\"min-w-16 flex-1 outline-none\", className)}\n      onKeyDown={(e) => {\n        if (\n          e.key === \"Backspace\" &&\n          e.currentTarget.value === \"\" &&\n          Array.isArray(state?.value) &&\n          state.value.length > 0\n        ) {\n          e.preventDefault();\n          state.setValue(state.value.slice(0, -1));\n        }\n      }}\n      {...props}\n    />\n  );\n}\n\nfunction useComboboxAnchor() {\n  return React.useRef<HTMLDivElement | null>(null);\n}\n\nexport {\n  Collection as ComboboxCollection,\n  ComboBoxPrimitive as Combobox,\n  ComboboxChip,\n  ComboboxChipList,\n  ComboboxChips,\n  ComboboxChipsInput,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxGroup,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxLabel,\n  ComboboxList,\n  ComboboxSeparator,\n  ComboboxTrigger,\n  ComboboxValue,\n  useComboboxAnchor,\n};\n",
      "type": "registry:component",
      "target": "components/fma-ui/combobox.tsx"
    }
  ],
  "meta": {
    "status": "stable",
    "source": "customized",
    "tags": [
      "form",
      "react-aria",
      "selection",
      "autocomplete"
    ]
  },
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}