'use client' import * as React from 'react' import { Button } from '@/components/ui/button' import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover' import { ChevronDown, CheckIcon } from 'lucide-react' import { cn } from '@/lib/utils' export interface MultiSelectPropOption { value: string label: string icon?: React.ReactNode } interface MultiSelectProps { options: MultiSelectPropOption[] value: string[] onValueChange: (value: string[]) => void placeholder?: string emptyMessage?: string selectionName?: { singular: string plural: string } className?: string triggerClassName?: string triggerIcon?: React.ReactNode 'aria-label'?: string size?: 'sm' | 'default' } export function MultiSelect({ options, value, onValueChange, placeholder = 'Select items...', selectionName = { singular: 'item', plural: 'items', }, className, triggerClassName, triggerIcon, 'aria-label': ariaLabel, }: MultiSelectProps) { const [open, setOpen] = React.useState(false) function handleToggle(optionValue: string, checked: boolean) { const newValue = checked ? [...value, optionValue] : value.filter((v) => v !== optionValue) if (newValue.length > 0) { onValueChange(newValue) } } let displayText: string if (value.length === options.length) { displayText = `All ${selectionName.plural}` } else if (value.length === 0) { displayText = placeholder } else { displayText = `${value.length} ${value.length === 1 ? selectionName.singular : selectionName.plural}` } return ( { if (e.key === 'Escape') { e.preventDefault() setOpen(false) return } const checkboxes = Array.from( e.currentTarget.querySelectorAll('input[type="checkbox"]') ) as HTMLInputElement[] const currentIndex = checkboxes.indexOf( document.activeElement as HTMLInputElement ) if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault() if (currentIndex === -1) { checkboxes[0]?.focus() } else { const nextIndex = e.key === 'ArrowDown' ? (currentIndex + 1) % checkboxes.length : (currentIndex - 1 + checkboxes.length) % checkboxes.length checkboxes[nextIndex]?.focus() } } }} >
{ariaLabel || placeholder} {options.map((option) => ( handleToggle(option.value, checked)} /> ))}
) } function MultiSelectOption({ label, icon, checked, onChange, }: { label: string icon?: React.ReactNode checked: boolean onChange: (checked: boolean) => void }) { return ( ) }