import type { ButtonProps } from '@/components/ui/button'; import { Button } from '@/components/ui/button'; import { Command, CommandEmpty, CommandInput, CommandItem, } from '@/components/ui/command'; import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { cn } from '@/utils/cn'; import { PopoverPortal } from '@radix-ui/react-popover'; import type { LucideIcon } from 'lucide-react'; import { Check, ChevronsUpDown } from 'lucide-react'; import VirtualList from 'rc-virtual-list'; import * as React from 'react'; export interface ComboboxProps { placeholder: string; items: { value: T; label: string; disabled?: boolean; }[]; value: T | null | undefined; onChange: (value: T) => void; children?: React.ReactNode; onCreate?: (value: T) => void; className?: string; searchable?: boolean; icon?: LucideIcon; size?: ButtonProps['size']; label?: string; align?: 'start' | 'end' | 'center'; portal?: boolean; error?: string; disabled?: boolean; } export type ExtendedComboboxProps = Omit< ComboboxProps, 'items' | 'placeholder' > & { placeholder?: string; }; export function Combobox({ placeholder, items, value, onChange, children, onCreate, className, searchable, icon: Icon, size, align = 'start', portal, error, disabled, }: ComboboxProps) { const [open, setOpen] = React.useState(false); const [search, setSearch] = React.useState(''); function find(value: string) { return items.find( (item) => item.value.toLowerCase() === value.toLowerCase(), ); } return ( {children ?? ( )} {searchable === true && ( )} {typeof onCreate === 'function' && search ? ( ) : ( Nothing selected )} { if (search === '') return true; return item.label.toLowerCase().includes(search.toLowerCase()); })} itemHeight={32} itemKey="value" className="min-w-60" > {(item) => ( { const value = find(currentValue)?.value ?? currentValue; onChange(value as T); setOpen(false); }} {...(item.disabled && { disabled: true })} > {item.label} )} ); }