'use client'; import * as React from 'react'; import type { MessageDescriptor } from '@lingui/core'; import { Trans } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { AnimatePresence } from 'framer-motion'; import { Check, ChevronsUpDown, Loader, XIcon } from 'lucide-react'; import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out'; import { cn } from '../lib/utils'; import { Button } from './button'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from './command'; import { Popover, PopoverContent, PopoverTrigger } from './popover'; type OptionValue = string | number | boolean | null; type ComboBoxOption = { label: string; value: T; disabled?: boolean; }; type MultiSelectComboboxProps = { emptySelectionPlaceholder?: React.ReactNode | string; enableClearAllButton?: boolean; loading?: boolean; inputPlaceholder?: MessageDescriptor; onChange: (_values: T[]) => void; options: ComboBoxOption[]; selectedValues: T[]; }; /** * Multi select combo box component which supports: * * - Label/value pairs * - Loading state * - Clear all button */ export function MultiSelectCombobox({ emptySelectionPlaceholder = 'Select values...', enableClearAllButton, inputPlaceholder, loading, onChange, options, selectedValues, }: MultiSelectComboboxProps) { const { _ } = useLingui(); const [open, setOpen] = React.useState(false); const handleSelect = (selectedOption: T) => { let newSelectedOptions = [...selectedValues, selectedOption]; if (selectedValues.includes(selectedOption)) { newSelectedOptions = selectedValues.filter((v) => v !== selectedOption); } onChange(newSelectedOptions); setOpen(false); }; const selectedOptions = React.useMemo(() => { return selectedValues.map((value): ComboBoxOption => { const foundOption = options.find((option) => option.value === value); if (foundOption) { return foundOption; } let label = ''; if (typeof value === 'string' || typeof value === 'number') { label = value.toString(); } return { label, value, }; }); }, [selectedValues, options]); const buttonLabel = React.useMemo(() => { if (loading) { return ''; } if (selectedOptions.length === 0) { return emptySelectionPlaceholder; } return selectedOptions.map((option) => option.label).join(', '); }, [selectedOptions, emptySelectionPlaceholder, loading]); const showClearButton = enableClearAllButton && selectedValues.length > 0; return (
{/* This is placed outside the trigger since we can't have nested buttons. */} {showClearButton && !loading && (
)}
No value found. {options.map((option, i) => ( handleSelect(option.value)}> {option.label} ))}
); }