{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "title": "Combobox",
  "description": "A searchable, accessible combobox built on Base UI.",
  "dependencies": [
    "@base-ui/react",
    "react-virtuoso"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.persian-labs.ir/r/scroll-area.json"
  ],
  "files": [
    {
      "path": "registry/base/ui/combobox.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { Combobox as ComboboxPrimitive } from \"@base-ui/react/combobox\"\nimport { CheckIcon, ChevronDownIcon, XIcon } from \"lucide-react\"\nimport { Virtuoso, type VirtuosoHandle } from \"react-virtuoso\"\n\nimport { Hitbox } from \"@/components/ui/hitbox\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * ComboboxContent renders through a Portal, so it doesn't inherit `dir` from\n * a nearby wrapper (only from document.documentElement). ComboboxInputGroup\n * measures the ambient direction where it's actually rendered and pushes it\n * here so ComboboxContent can apply it explicitly to the portaled popup.\n */\nconst ComboboxDirContext = React.createContext<{\n  dir: \"ltr\" | \"rtl\"\n  setDir: (dir: \"ltr\" | \"rtl\") => void\n}>({ dir: \"ltr\", setDir: () => {} })\n\n/**\n * The scrollable viewport element behind ComboboxContent's ScrollArea.\n * ComboboxVirtualList reads this to drive react-virtuoso's\n * `customScrollParent`, so it needs the real DOM node, not ScrollArea's\n * Radix-style wrapper.\n */\nconst ComboboxViewportContext = React.createContext<HTMLDivElement | null>(null)\n\ninterface ComboboxVirtualizerHandle {\n  scrollToIndex: (index: number) => void\n}\n\n/**\n * Bridges keyboard highlight events on the (possibly distant) Combobox Root\n * down to whichever ComboboxVirtualList is currently mounted inside it, so\n * arrowing onto an item that's scrolled out of the virtualized window still\n * scrolls it into view. A ref (not state) on purpose: it's written by a\n * descendant after Root has already rendered, and reading it happens inside\n * an event callback, not during render.\n */\nconst ComboboxVirtualizerContext =\n  React.createContext<React.RefObject<ComboboxVirtualizerHandle | null> | null>(\n    null\n  )\n\nfunction Combobox<Value, Multiple extends boolean | undefined = false>({\n  onItemHighlighted,\n  ...props\n}: ComboboxPrimitive.Root.Props<Value, Multiple>) {\n  const [dir, setDir] = React.useState<\"ltr\" | \"rtl\">(\"ltr\")\n  const contextValue = React.useMemo(() => ({ dir, setDir }), [dir])\n  const virtualizerHandleRef = React.useRef<ComboboxVirtualizerHandle | null>(\n    null\n  )\n\n  return (\n    <ComboboxDirContext.Provider value={contextValue}>\n      <ComboboxVirtualizerContext.Provider value={virtualizerHandleRef}>\n        <ComboboxPrimitive.Root\n          data-slot=\"combobox\"\n          onItemHighlighted={(value, eventDetails) => {\n            onItemHighlighted?.(value, eventDetails)\n            if (eventDetails.reason === \"keyboard\") {\n              virtualizerHandleRef.current?.scrollToIndex(eventDetails.index)\n            }\n          }}\n          {...props}\n        />\n      </ComboboxVirtualizerContext.Provider>\n    </ComboboxDirContext.Provider>\n  )\n}\n\nfunction ComboboxInputGroup({\n  className,\n  ...props\n}: ComboboxPrimitive.InputGroup.Props) {\n  const ref = React.useRef<HTMLDivElement>(null)\n  const { setDir } = React.useContext(ComboboxDirContext)\n\n  React.useEffect(() => {\n    function update() {\n      if (!ref.current) return\n      setDir(getComputedStyle(ref.current).direction === \"rtl\" ? \"rtl\" : \"ltr\")\n    }\n\n    update()\n\n    const observer = new MutationObserver(update)\n    observer.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: [\"dir\"],\n      subtree: true,\n    })\n\n    return () => observer.disconnect()\n  }, [setDir])\n\n  return (\n    <ComboboxPrimitive.InputGroup\n      ref={ref}\n      data-slot=\"combobox-input-group\"\n      className={cn(\n        \"relative flex h-8 w-full items-center gap-1.5 rounded-lg border border-border bg-background px-2.5 focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxInput({ className, ...props }: ComboboxPrimitive.Input.Props) {\n  return (\n    <ComboboxPrimitive.Input\n      data-slot=\"combobox-input\"\n      className={cn(\n        \"w-full min-w-0 truncate bg-transparent text-sm outline-none placeholder:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxIcon({ className, ...props }: ComboboxPrimitive.Icon.Props) {\n  return (\n    <ComboboxPrimitive.Icon\n      data-slot=\"combobox-icon\"\n      className={cn(\n        \"flex shrink-0 items-center justify-center text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      <ChevronDownIcon className=\"size-4\" />\n    </ComboboxPrimitive.Icon>\n  )\n}\n\nfunction ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {\n  return (\n    <ComboboxPrimitive.Clear\n      data-slot=\"combobox-clear\"\n      className={cn(\n        \"shrink-0 text-muted-foreground hover:text-foreground\",\n        className\n      )}\n      {...props}\n    >\n      <XIcon className=\"size-3.5\" />\n    </ComboboxPrimitive.Clear>\n  )\n}\n\nfunction ComboboxContent({\n  className,\n  sideOffset = 6,\n  children,\n  ...props\n}: ComboboxPrimitive.Positioner.Props & {\n  sideOffset?: number\n}) {\n  const { dir } = React.useContext(ComboboxDirContext)\n  const [viewportElement, setViewportElement] =\n    React.useState<HTMLDivElement | null>(null)\n\n  return (\n    <ComboboxPrimitive.Portal>\n      <ComboboxPrimitive.Positioner\n        dir={dir}\n        data-slot=\"combobox-positioner\"\n        sideOffset={sideOffset}\n        className=\"z-50 outline-none\"\n        {...props}\n      >\n        <ComboboxPrimitive.Popup\n          data-slot=\"combobox-content\"\n          className={cn(\n            \"flex max-h-80 w-[var(--anchor-width)] min-w-40 origin-[var(--transform-origin)] flex-col overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md duration-150 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n            className\n          )}\n        >\n          <ScrollArea\n            className=\"min-h-0 flex-1\"\n            viewportRef={setViewportElement}\n          >\n            <ComboboxViewportContext.Provider value={viewportElement}>\n              {children}\n            </ComboboxViewportContext.Provider>\n          </ScrollArea>\n        </ComboboxPrimitive.Popup>\n      </ComboboxPrimitive.Positioner>\n    </ComboboxPrimitive.Portal>\n  )\n}\n\nfunction ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {\n  return (\n    <ComboboxPrimitive.List\n      data-slot=\"combobox-list\"\n      className={cn(\"flex flex-col gap-0.5\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxCollection(props: ComboboxPrimitive.Collection.Props) {\n  return <ComboboxPrimitive.Collection {...props} />\n}\n\n/**\n * A windowed alternative to `ComboboxList`, for lists with enough items that\n * rendering them all as DOM nodes becomes a real cost (see the \"virtualize\n * lists exceeding 50 items\" guideline). Opt in per-usage by swapping\n * `ComboboxList` for this component — everything else about `Combobox`\n * composition stays the same.\n *\n * Must be used with `<Combobox virtualized>` on the root so Base UI hands\n * scroll-position and typeahead bookkeeping over to the virtualizer instead\n * of assuming every item is mounted.\n *\n * Reads the currently filtered items directly from Base UI's internal\n * filtering (`Combobox.useFilteredItems`) rather than taking an `items` prop,\n * so it always windows exactly what the root would otherwise render — typing\n * a search query re-windows automatically.\n */\nfunction ComboboxVirtualList<T>({\n  estimateSize = 36,\n  overscan = 8,\n  className,\n  children,\n  ...props\n}: {\n  /** Estimated row height in pixels, used before an item's real height is measured on mount. @default 36 */\n  estimateSize?: number\n  /** Extra pixels rendered outside the visible viewport, in each direction, to reduce blank flashes while scrolling fast. @default 8 */\n  overscan?: number\n  children: (item: T, index: number) => React.ReactNode\n} & Omit<ComboboxPrimitive.List.Props, \"children\">) {\n  const items = ComboboxPrimitive.useFilteredItems<T>()\n  const viewportElement = React.useContext(ComboboxViewportContext)\n  const virtualizerHandleRef = React.useContext(ComboboxVirtualizerContext)\n  const virtuosoRef = React.useRef<VirtuosoHandle>(null)\n\n  React.useEffect(() => {\n    if (!virtualizerHandleRef) return\n    virtualizerHandleRef.current = {\n      scrollToIndex: (index) =>\n        virtuosoRef.current?.scrollIntoView({ index, behavior: \"auto\" }),\n    }\n    return () => {\n      virtualizerHandleRef.current = null\n    }\n  }, [virtualizerHandleRef])\n\n  return (\n    <ComboboxPrimitive.List\n      data-slot=\"combobox-list\"\n      className={cn(\"relative\", className)}\n      {...props}\n    >\n      {/* customScrollParent falls back to Virtuoso's own self-contained\n          scrolling until the outer ScrollArea's viewport DOM node is\n          available, instead of rendering nothing while it waits — gating\n          the whole list on that ref could leave it permanently empty if\n          the ref never resolved in time. `style` height is only used in\n          that fallback case; Virtuoso ignores it once customScrollParent\n          is set. */}\n      <Virtuoso\n        ref={virtuosoRef}\n        customScrollParent={viewportElement ?? undefined}\n        style={{ height: \"20rem\" }}\n        totalCount={items.length}\n        overscan={overscan}\n        defaultItemHeight={estimateSize}\n        itemContent={(index) => children(items[index] as T, index)}\n      />\n    </ComboboxPrimitive.List>\n  )\n}\n\nfunction ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {\n  return (\n    <ComboboxPrimitive.Empty\n      data-slot=\"combobox-empty\"\n      className={cn(\n        \"py-6 text-center text-sm text-muted-foreground empty:m-0 empty:p-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxItem({\n  className,\n  children,\n  ...props\n}: ComboboxPrimitive.Item.Props) {\n  return (\n    <ComboboxPrimitive.Item\n      data-slot=\"combobox-item\"\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-md py-1.5 ps-7 pe-2 text-sm text-muted-foreground outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[selected]:text-foreground\",\n        className\n      )}\n      {...props}\n    >\n      <ComboboxPrimitive.ItemIndicator className=\"absolute start-2 inline-flex items-center justify-center\">\n        <CheckIcon className=\"size-3.5\" />\n      </ComboboxPrimitive.ItemIndicator>\n      {children}\n    </ComboboxPrimitive.Item>\n  )\n}\n\nfunction ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {\n  return (\n    <ComboboxPrimitive.Group\n      data-slot=\"combobox-group\"\n      className={cn(\"flex flex-col gap-0.5\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxGroupLabel({\n  className,\n  ...props\n}: ComboboxPrimitive.GroupLabel.Props) {\n  return (\n    <ComboboxPrimitive.GroupLabel\n      data-slot=\"combobox-group-label\"\n      className={cn(\n        \"px-2 py-1.5 text-xs font-medium text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxSeparator({\n  className,\n  ...props\n}: ComboboxPrimitive.Separator.Props) {\n  return (\n    <ComboboxPrimitive.Separator\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({ className, ...props }: ComboboxPrimitive.Chips.Props) {\n  const ref = React.useRef<HTMLDivElement>(null)\n  const { setDir } = React.useContext(ComboboxDirContext)\n\n  React.useEffect(() => {\n    function update() {\n      if (!ref.current) return\n      setDir(getComputedStyle(ref.current).direction === \"rtl\" ? \"rtl\" : \"ltr\")\n    }\n\n    update()\n\n    const observer = new MutationObserver(update)\n    observer.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: [\"dir\"],\n      subtree: true,\n    })\n\n    return () => observer.disconnect()\n  }, [setDir])\n\n  return (\n    <ComboboxPrimitive.Chips\n      ref={ref}\n      data-slot=\"combobox-chips\"\n      className={cn(\n        \"flex min-h-8 w-full flex-wrap items-center gap-1 rounded-lg border border-border bg-background px-2 py-1 focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ComboboxValue(props: ComboboxPrimitive.Value.Props) {\n  return <ComboboxPrimitive.Value {...props} />\n}\n\nfunction ComboboxChip({\n  className,\n  children,\n  ...props\n}: ComboboxPrimitive.Chip.Props) {\n  return (\n    <ComboboxPrimitive.Chip\n      data-slot=\"combobox-chip\"\n      className={cn(\n        \"inline-flex items-center gap-1 rounded-md bg-muted py-0.5 ps-2 pe-1 text-xs text-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <Hitbox size=\"sm\" radius=\"sm\">\n        <ComboboxPrimitive.ChipRemove className=\"inline-flex size-4 items-center justify-center rounded-sm hover:bg-background/60\">\n          <XIcon className=\"size-3\" />\n        </ComboboxPrimitive.ChipRemove>\n      </Hitbox>\n    </ComboboxPrimitive.Chip>\n  )\n}\n\nfunction ComboboxChipsInput({\n  className,\n  ...props\n}: ComboboxPrimitive.Input.Props) {\n  return (\n    <ComboboxPrimitive.Input\n      data-slot=\"combobox-chips-input\"\n      className={cn(\n        \"min-w-16 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Combobox,\n  ComboboxChip,\n  ComboboxChips,\n  ComboboxChipsInput,\n  ComboboxClear,\n  ComboboxCollection,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxGroup,\n  ComboboxGroupLabel,\n  ComboboxIcon,\n  ComboboxInput,\n  ComboboxInputGroup,\n  ComboboxItem,\n  ComboboxList,\n  ComboboxSeparator,\n  ComboboxValue,\n  ComboboxVirtualList,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}