"use client"; import React, { useState, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { CheckIcon, ChevronDownIcon, XIcon } from "@/components/icons"; import { cn } from "@/lib/utils"; interface MultiSelectOption { value: string | number; label: string; description?: string; } interface MultiSelectProps { label: string; placeholder?: string; options: MultiSelectOption[]; value: (string | number)[]; onChange: (value: (string | number)[]) => void; error?: string; required?: boolean; disabled?: boolean; searchable?: boolean; maxSelections?: number; className?: string; helpText?: string; } export const MultiSelect: React.FC = ({ label, placeholder = "Select options...", options, value = [], onChange, error, required = false, disabled = false, searchable = true, maxSelections, className = "", helpText, }) => { const [open, setOpen] = useState(false); const [searchValue, setSearchValue] = useState(""); const hasError = !!error; const filteredOptions = useMemo(() => { if (!searchable || !searchValue) return options; return options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase()) || (option.description && option.description.toLowerCase().includes(searchValue.toLowerCase())) ); }, [options, searchValue, searchable]); const selectedOptions = useMemo(() => { return options.filter(option => value.includes(option.value)); }, [options, value]); const handleSelect = (optionValue: string | number) => { if (disabled) return; const newValue = value.includes(optionValue) ? value.filter(v => v !== optionValue) : maxSelections && value.length >= maxSelections ? value : [...value, optionValue]; onChange(newValue); }; const handleRemove = (optionValue: string | number) => { if (disabled) return; onChange(value.filter(v => v !== optionValue)); }; const handleClearAll = () => { if (disabled) return; onChange([]); }; return (
{searchable && ( )} {searchable && searchValue ? "No options found." : "No options available."} {filteredOptions.map((option) => ( handleSelect(option.value)} className="flex items-center justify-between" >
{option.label}
{option.description && (
{option.description}
)}
))}
{/* Selected Items Display */} {selectedOptions.length > 0 && (
Selected: {!disabled && ( )}
{selectedOptions.map((option) => ( {option.label} {!disabled && ( )} ))}
)} {helpText && (

{helpText}

)} {hasError && (

{error}

)}
); };