'use client'; import { useMemo, useState } from 'react'; import { Check, ChevronDown } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; import { cn } from '@/lib/utils'; export interface MultiSelectOption { value: string; label: string; disabled?: boolean; } interface MultiSelectPopoverProps { label: string; icon?: React.ReactNode; options: MultiSelectOption[]; values: string[]; onValuesChange: (values: string[]) => void; searchPlaceholder?: string; emptyMessage?: string; emptySelectionLabel?: string; align?: 'start' | 'center' | 'end'; } function checkboxClass(checked: boolean) { return cn( 'flex size-4 shrink-0 items-center justify-center rounded-lg border', checked && 'border-primary bg-primary text-primary-foreground', ); } export function MultiSelectPopover({ label, icon, options, values, onValuesChange, searchPlaceholder = 'Search', emptyMessage = 'No options found.', emptySelectionLabel, align = 'end', }: MultiSelectPopoverProps) { const [search, setSearch] = useState(''); const selectedValues = useMemo(() => new Set(values), [values]); const selectableOptions = options.filter((option) => !option.disabled); const areAllSelected = selectableOptions.length > 0 && selectableOptions.every((option) => selectedValues.has(option.value)); const filteredOptions = useMemo(() => { const term = search.trim().toLowerCase(); if (!term) return options; return options.filter((option) => option.label.toLowerCase().includes(term), ); }, [options, search]); const toggleValue = (value: string) => { const nextValues = new Set(values); if (nextValues.has(value)) { nextValues.delete(value); } else { nextValues.add(value); } onValuesChange(Array.from(nextValues)); }; const toggleAll = () => { const nextValues = new Set(values); selectableOptions.forEach((option) => { if (areAllSelected) { nextValues.delete(option.value); } else { nextValues.add(option.value); } }); onValuesChange(Array.from(nextValues)); }; return (
setSearch(event.target.value)} placeholder={searchPlaceholder} className="flex-1" />
{filteredOptions.length ? ( filteredOptions.map((option) => { const checked = selectedValues.has(option.value); return (
); }) ) : (
{emptyMessage}
)}
); }