import * as React from "react" import { Check, ChevronsUpDown } from "lucide-react" import { cn } from "@/lib/utils" export interface MultiSelectOption { value: string label: string color?: string } interface MultiSelectComboboxProps { options: MultiSelectOption[] selected: Set onSelectionChange: (selected: Set) => void placeholder?: string searchPlaceholder?: string emptyText?: string disabled?: boolean className?: string renderOption?: (option: MultiSelectOption, isSelected: boolean) => React.ReactNode } export function MultiSelectCombobox({ options, selected, onSelectionChange, placeholder = "Select...", searchPlaceholder = "Search...", emptyText = "No results found.", disabled = false, className, renderOption, }: MultiSelectComboboxProps) { const [open, setOpen] = React.useState(false) const [search, setSearch] = React.useState("") const wrapperRef = React.useRef(null) const filtered = search ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()) ) : options React.useEffect(() => { if (!open) return const onMouseDown = (e: MouseEvent) => { if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { setOpen(false) setSearch("") } } const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { e.stopPropagation() setOpen(false) setSearch("") } } document.addEventListener("mousedown", onMouseDown) document.addEventListener("keydown", onKeyDown, true) return () => { document.removeEventListener("mousedown", onMouseDown) document.removeEventListener("keydown", onKeyDown, true) } }, [open]) const handleToggle = (option: MultiSelectOption) => { const next = new Set(selected) if (next.has(option.value)) { next.delete(option.value) } else { next.add(option.value) } onSelectionChange(next) } const triggerLabel = selected.size > 0 ? `${selected.size} tag${selected.size !== 1 ? 's' : ''}` : placeholder return (
{open && (
{options.length > 5 && (
setSearch(e.target.value)} placeholder={searchPlaceholder} className="h-7 w-full bg-transparent px-2 text-xs outline-none placeholder:text-muted-foreground" autoFocus />
)}
{filtered.length === 0 ? (
{emptyText}
) : ( filtered.map((option) => { const isSelected = selected.has(option.value) return ( ) }) )}
{selected.size > 0 && (
)}
)}
) }