| | "use client"; |
| |
|
| | 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 { cn } from "@/lib/utils"; |
| | import { Check, ChevronsUpDown } from "lucide-react"; |
| | import * as React from "react"; |
| |
|
| | export function MultiSelect({ |
| | options, |
| | value, |
| | onChange, |
| | placeholder = 'Select...', |
| | }: { |
| | options: { label: string; value: string }[]; |
| | value: string[]; |
| | onChange: (values: string[]) => void; |
| | placeholder?: string; |
| | }) { |
| | const [open, setOpen] = React.useState(false); |
| |
|
| | const toggleValue = (val: string) => { |
| | if (value.includes(val)) { |
| | onChange(value.filter((v) => v !== val)); |
| | } else { |
| | onChange([...value, val]); |
| | } |
| | |
| | }; |
| |
|
| | return ( |
| | <Popover open={open} onOpenChange={setOpen}> |
| | <PopoverTrigger asChild> |
| | <div |
| | className={cn( |
| | "border w-full min-h-[40px] px-3 py-2 rounded-md flex items-center justify-between cursor-pointer gap-2", |
| | value.length === 0 && "text-muted-foreground" |
| | )} |
| | > |
| | <div className="flex flex-wrap gap-1 flex-1"> |
| | {value.length === 0 ? ( |
| | <span>{placeholder}</span> |
| | ) : ( |
| | value.map((val) => ( |
| | <Badge |
| | key={val} |
| | variant="secondary" |
| | className="text-xs px-2 py-0.5 gap-1" |
| | > |
| | {val} |
| | </Badge> |
| | )) |
| | )} |
| | </div> |
| | <ChevronsUpDown className="h-4 w-4 opacity-50 shrink-0" /> |
| | </div> |
| | </PopoverTrigger> |
| | |
| | <PopoverContent className="p-0 w-[250px]"> |
| | <Command> |
| | <CommandInput placeholder="Search..." /> |
| | <CommandList> |
| | <CommandEmpty>No results found.</CommandEmpty> |
| | |
| | <CommandGroup> |
| | {options.map((option) => { |
| | const selected = value.includes(option.value); |
| | |
| | return ( |
| | <CommandItem |
| | key={option.value} |
| | onSelect={() => toggleValue(option.value)} |
| | className="flex items-center gap-2 cursor-pointer" |
| | > |
| | <div |
| | className={cn( |
| | "h-4 w-4 border rounded-sm flex items-center justify-center", |
| | selected ? "bg-primary border-primary" : "border-input" |
| | )} |
| | > |
| | {selected && <Check className="h-3 w-3 text-primary-foreground" />} |
| | </div> |
| | {option.label} |
| | </CommandItem> |
| | ); |
| | })} |
| | </CommandGroup> |
| | </CommandList> |
| | </Command> |
| | </PopoverContent> |
| | </Popover> |
| | ); |
| | } |