"use client" import * as React from "react" import { Combobox as ComboboxPrimitive } from "@base-ui/react" import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, } from "@/components/ui/input-group" function isGroupedItems( items: readonly unknown[] ): items is ReadonlyArray<{ items: readonly unknown[] }> { return ( items.length > 0 && typeof items[0] === "object" && items[0] !== null && "items" in items[0] ) } function Combobox({ items, multiple, value, isItemEqualToValue, ...props }: ComboboxPrimitive.Root.Props) { // In multi-select mode, once an option is chosen it's represented as a // chip, so it's removed from the dropdown list instead of staying there // with a checkmark. const filteredItems = React.useMemo(() => { if (!multiple || !items) return items const selected = value as unknown as Value[] | null | undefined if (!Array.isArray(selected) || selected.length === 0) return items const isEqual = isItemEqualToValue ?? ((a: Value, b: Value) => Object.is(a, b)) const isNotSelected = (item: Value) => !selected.some((selectedValue) => isEqual(item, selectedValue)) if (isGroupedItems(items)) { return items.map((group) => ({ ...group, items: group.items.filter((item) => isNotSelected(item as Value)), })) } return (items as readonly Value[]).filter(isNotSelected) }, [items, multiple, value, isItemEqualToValue]) return ( ) } function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { return } function ComboboxTrigger({ className, children, ...props }: ComboboxPrimitive.Trigger.Props) { return ( {children} ) } function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { return ( } className={cn(className)} {...props} > ) } function ComboboxInput({ className, children, disabled = false, showTrigger = true, showClear = false, ...props }: ComboboxPrimitive.Input.Props & { showTrigger?: boolean showClear?: boolean }) { return ( } {...props} /> {showTrigger && ( )} {showClear && } {children} ) } function ComboboxContent({ className, side = "bottom", sideOffset = 6, align = "start", alignOffset = 0, anchor, ...props }: ComboboxPrimitive.Popup.Props & Pick< ComboboxPrimitive.Positioner.Props, "side" | "align" | "sideOffset" | "alignOffset" | "anchor" >) { return ( ) } function ComboboxList({ className, ref, ...props }: ComboboxPrimitive.List.Props & { ref?: React.Ref }) { const listRef = React.useRef(null) return ( { listRef.current = node if (typeof ref === "function") ref(node) else if (ref) ref.current = node }} data-slot="combobox-list" onWheel={(event) => { // Radix Dialog's scroll lock (react-remove-scroll) always cancels // wheel scroll on elements outside its own lock/shards, even if // they're independently scrollable. Since this popup is portaled by // Base UI (a different library not registered with Radix), scroll // it manually instead of relying on the browser's default action. listRef.current?.scrollBy({ top: event.deltaY }) }} className={cn( "max-h-[min(calc(--spacing(96)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto p-1 data-empty:p-0", className )} {...props} /> ) } function ComboboxItem({ className, children, ...props }: ComboboxPrimitive.Item.Props) { return ( {children} } > ) } function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { return ( ) } function ComboboxLabel({ className, ...props }: ComboboxPrimitive.GroupLabel.Props) { return ( ) } function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { return ( ) } function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { return ( ) } function ComboboxSeparator({ className, ...props }: ComboboxPrimitive.Separator.Props) { return ( ) } function ComboboxChips({ className, ...props }: React.ComponentPropsWithRef & ComboboxPrimitive.Chips.Props) { return ( ) } function ComboboxChip({ className, children, showRemove = true, ...props }: ComboboxPrimitive.Chip.Props & { showRemove?: boolean }) { return ( {children} {showRemove && ( } className="-ml-1 opacity-50 hover:opacity-100" data-slot="combobox-chip-remove" > )} ) } function ComboboxChipsInput({ className, children, ...props }: ComboboxPrimitive.Input.Props) { return ( ) } function useComboboxAnchor() { return React.useRef(null) } export { Combobox, ComboboxInput, ComboboxContent, ComboboxList, ComboboxItem, ComboboxGroup, ComboboxLabel, ComboboxCollection, ComboboxEmpty, ComboboxSeparator, ComboboxChips, ComboboxChip, ComboboxChipsInput, ComboboxTrigger, ComboboxValue, useComboboxAnchor, }