import { Badge } from '@/components/ui/badge'; import { Command, CommandInput, CommandItem } from '@/components/ui/command'; import { cn } from '@/utils/cn'; import { ChevronsUpDownIcon } from 'lucide-react'; import VirtualList from 'rc-virtual-list'; import * as React from 'react'; import { useOnClickOutside } from 'usehooks-ts'; import { Button, type ButtonProps } from './button'; import { Checkbox, DumpCheckbox } from './checkbox'; import { Popover, PopoverContent, PopoverPortal, PopoverTrigger, } from './popover'; type IValue = any; type IItem = Record<'value' | 'label', IValue>; const sanitize = (value: string) => { return encodeURIComponent(value.replaceAll('"', '"')); }; const desanitize = (value: string) => { return decodeURIComponent(value).replaceAll('"', '"'); }; interface ComboboxAdvancedProps { value: IValue[]; onChange: (value: IValue[]) => void; items: IItem[]; placeholder: string; className?: string; size?: ButtonProps['size']; } export function ComboboxAdvanced({ items, value, onChange, placeholder, className, size, }: ComboboxAdvancedProps) { const [open, setOpen] = React.useState(false); const [inputValue, setInputValue] = React.useState(''); const ref = React.useRef(null); useOnClickOutside(ref as React.RefObject, () => setOpen(false)); const selectables = items .filter((item) => !value.find((s) => s === item.value)) .filter( (item) => (typeof item.label === 'string' && item.label.toLowerCase().includes(inputValue.toLowerCase())) || (typeof item.value === 'string' && item.value.toLowerCase().includes(inputValue.toLowerCase())), ); const renderItem = (item: IItem) => { const checked = !!value.find((s) => s === desanitize(item.value)); return ( { e.preventDefault(); e.stopPropagation(); }} onSelect={() => { setInputValue(''); onChange( value.includes(desanitize(item.value)) ? value.filter((s) => s !== desanitize(item.value)) : [...value, desanitize(item.value)], ); }} className={'flex cursor-pointer items-center gap-2'} value={item.value} > {desanitize(item?.label ?? item?.value)} ); }; const data = React.useMemo(() => { return [ ...(inputValue === '' ? [] : [ { value: inputValue, label: `Pick '${inputValue}'`, }, ]), ...value.map((val) => { const item = items.find((item) => item.value === val); return item ? { value: val, label: item.label, } : { value: val, label: val, }; }), ...selectables, ].filter((item) => item.value); }, [inputValue, selectables, items]); return ( ({ ...item, label: sanitize(item.label), value: sanitize(item.value), }))} itemHeight={32} itemKey="value" > {renderItem} ); }