Stellen Sie sicher, dass Sie ein Next.js-Projekt eingerichtet haben. Wenn nicht, erstellen Sie eines:
npx create-next-app meine-app --typescriptcd meine-app
Installieren Sie die erforderlichen Shadcn-Komponenten:
npx shadcn@latest init npx shadcn@latest Befehls-Popover-Schaltflächen-Trennzeichen hinzufügen
Erstellen Sie multi-select.tsx
in Ihrem components
:
// src/components/multi-select.tsximport * as React from "react";import { cva, type VariantProps } from "class-variance-authority";import { CheckIcon, XKreis, ChevronDown, XIcon, WandSparkles,} aus „lucide-react“;import { cn } aus „@/lib/utils“;import { Separator } aus „@/components/ui/separator“;import { Button } aus „@/components/ui/ button";import { Badge } from "@/components/ui/badge";import { Popover, PopoverContent, PopoverTrigger,} von „@/components/ui/popover“;import { Befehl, CommandEmpty, Befehlsgruppe, Befehlseingabe, CommandItem, Befehlsliste, CommandSeparator,} from "@/components/ui/command";/** * Varianten für die Mehrfachauswahlkomponente zur Verarbeitung verschiedener Stile. * Verwendet Class-Varianz-Autorität (CVA), um verschiedene Stile basierend auf „Varianten“-Requisiten zu definieren. */const multiSelectVariants = cva( „m-1 Übergang Ease-In-Out Delay-150 Hover:-Translate-Y-1 Hover:Scale-110 Dauer-300“, {variants: { Variante: {default: "border-foreground/10 text-foreground bg-card hover:bg-card/80",secondary: "border-foreground/10 bg-secondary text-secondary-foreground hover:bg- sekundär/80",destruktiv: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",inverted: "inverted", },},defaultVariants: { Variante: "default",}, });/** * Requisiten für die MultiSelect-Komponente */interface MultiSelectProps erweitert React.ButtonHTMLAttributes<HTMLButtonElement>,VariantProps<typeof multiSelectVariants> { /** * Ein Array von Optionsobjekten, die in der Mehrfachauswahlkomponente angezeigt werden sollen. * Jedes Optionsobjekt verfügt über eine Beschriftung, einen Wert und ein optionales Symbol. */ Optionen: {/** Der für die Option anzuzeigende Text. */label: string;/** Der eindeutige Wert, der der Option zugeordnet ist. */value: string;/** Optionale Symbolkomponente, die neben der Option angezeigt wird. */icon?: React.ComponentType<{ className?: string }>; }[]; /** * Rückruffunktion wird ausgelöst, wenn sich die ausgewählten Werte ändern. * Empfängt ein Array der neu ausgewählten Werte. */ onValueChange: (value: string[]) => void; /** Die standardmäßig ausgewählten Werte, wenn die Komponente bereitgestellt wird. */ Standardwert?: string[]; /** * Platzhaltertext, der angezeigt wird, wenn keine Werte ausgewählt sind. * Optional, standardmäßig „Optionen auswählen“. */ Platzhalter?: string; /** * Animationsdauer in Sekunden für die visuellen Effekte (z. B. hüpfende Abzeichen). * Optional, der Standardwert ist 0 (keine Animation). */ Animation?: Zahl; /** * Maximale Anzahl der anzuzeigenden Elemente. Zusätzlich ausgewählte Artikel werden zusammengefasst. * Optional, standardmäßig 3. */ maxCount?: Zahl; /** * Die Modalität des Popovers. Wenn es auf „true“ gesetzt ist, wird die Interaktion mit externen Elementen * deaktiviert und nur Popover-Inhalte werden für Screenreader sichtbar sein. * Optional, der Standardwert ist „false“. */ modalPopover?: boolean; /** * Wenn true, wird die Mehrfachauswahlkomponente als untergeordnetes Element einer anderen Komponente gerendert. * Optional, der Standardwert ist „false“. */ asChild?: boolean; /** * Zusätzliche Klassennamen zum Anwenden benutzerdefinierter Stile auf die Mehrfachauswahlkomponente. * Optional, kann zum Hinzufügen benutzerdefinierter Stile verwendet werden. */ className?: string;}export const MultiSelect = React.forwardRef< HTMLButtonElement, MultiSelectProps>( ({ Optionen, onValueChange, Variante, defaultValue = [], Platzhalter = „Optionen auswählen“, Animation = 0, maxCount = 3, modalPopover = false, asChild = false, className, ...props},ref ) => {const [selectedValues, setSelectedValues] = React.useState<string[]>(defaultValue);const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);const [isAnimating, setIsAnimating] = React.useState(false) ;const handleInputKeyDown = ( Ereignis: React.KeyboardEvent<HTMLInputElement>) => { if (event.key === "Enter") {setIsPopoverOpen(true); } else if (event.key === "Backspace" && !event.currentTarget.value) {const newSelectedValues = [...selectedValues];newSelectedValues.pop();setSelectedValues(newSelectedValues);onValueChange(newSelectedValues); }};const toggleOption = (option: string) => { const newSelectedValues = selectedValues.includes(option)? selectedValues.filter((value) => value !== option): [...selectedValues, option]; setSelectedValues(newSelectedValues); onValueChange(newSelectedValues);};const handleClear = () => { setSelectedValues([]); onValueChange([]);};const handleTogglePopover = () => { setIsPopoverOpen((prev) => !prev);};const clearExtraOptions = () => { const newSelectedValues = selectedValues.slice(0, maxCount); setSelectedValues(newSelectedValues); onValueChange(newSelectedValues);};const toggleAll = () => { if (selectedValues.length === options.length) {handleClear(); } else {const allValues = options.map((option) => option.value);setSelectedValues(allValues);onValueChange(allValues); }};return ( <Popoveropen={isPopoverOpen}onOpenChange={setIsPopoverOpen}modal={modalPopover} ><PopoverTrigger asChild> <Buttonref={ref}{...props}onClick={handleTogglePopover}className={cn( "flex w-full p-1 abgerundet-md-Rand min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit", className)} >{selectedValues.length > 0 ? ( <div className="flex justify-between items-center w-full"><div className="flex flex-wrap items-center"> {selectedValues.slice(0, maxCount).map((value) => {const option = options.find((o) => o.value === value);const IconComponent = option?.icon;return ( <Badgekey={value}className={cn( isAnimating ? "animate-bounce" : "", multiSelectVariants({ variant }))}style={{ animationDuration: `${animation}s` }} >{IconComponent && ( <IconComponent className="h-4 w-4 mr-2" />)}{option?.label}<XCircle className="ml-2 h-4 w-4 Cursorzeiger" onClick={(event) => {event.stopPropagation();toggleOption(value }}/> </Badge>); selectedValues.length > maxCount && (<Badge className={cn("bg-transparent text-foreground border-foreground/1 hover:bg-transparent",isAnimating ? "animate-bounce" : "",multiSelectVariants({ variant }) )} style={{ animationDuration: `${animation}s` }}> {`+ ${selectedValues.length - maxCount} more`} <XCircleclassName= "ml-2 h-4 w-4 Cursorzeiger"onClick={(event) => { event.stopPropagation(); clearExtraOptions();}} /></Badge> )}</div><div className="flex items-center justify-between"> <XIconclassName="h-4 mx-2cursor-pointer text-muted-foreground "onClick={(event) => { event.stopPropagation(); handleClear();}} /> <Separatororientation="vertical"className="flex min-h-6 h-full" /> <ChevronDown className="h-4 mx-2 Cursor-Pointer Text-muted-foreground" / ></div> </div>) : ( <div className="flex items-center justify-between w-full mx-auto"><span className="text-sm text-muted-foreground mx-3"> {placeholder}</span><ChevronDown className="h-4cursor-pointer text-muted-foreground mx-2" /> </div>)} </Button></PopoverTrigger><PopoverContent className="w-auto p-0" align="start" onEscapeKeyDown={() => setIsPopoverOpen(false)}> <Command><CommandInput placeholder="Search..." onKeyDown={handleInputKeyDown}/><CommandList> <CommandEmpty>Keine Ergebnisse gefunden.</CommandEmpty> <CommandGroup><CommandItem key="all" onSelect={toggleAll} className="cursor-pointer"> <divclassName={cn( „mr-2 flex h-4 w-4 items-center justify-centerrounded-sm border border-primary“, selectedValues.length === options.length? "bg-primary text-primary-foreground": "opacity-50 [&_svg]:invisible")} ><CheckIcon className="h-4 w-4" /> </div> < span>(Alle auswählen)</span></CommandItem>{options.map((option) => { const isSelected = selectedValues.includes(option.value); return (<CommandItem key={option.value} onSelect={() => toggleOption(option.value)} className="cursor-pointer"> <divclassName={cn( "mr-2 flex h-4 w-4 items-center justify-centerrounded-sm border border-primary", isSelected? "bg-primary text-primary-foreground": "opacity-50 [&_svg]:invisible")} ><CheckIcon className="h-4 w-4" /> </div> {option.icon && (<option.icon className="mr-2 h-4 w-4 text -muted-foreground" /> )} <span>{option.label}</span></CommandItem> );})} </CommandGroup> <CommandSeparator /> <CommandGroup><div className="flex items-center justify-between"> {selectedValues.length > 0 && (<> <CommandItemonSelect={handleClear}className="flex-1 justify-center Cursor-Pointer" >Clear </CommandItem> <Separatororientation= "vertical"className="flex min-h-6 h-full" /></> )} <CommandItemonSelect={() => setIsPopoverOpen(false)}className="flex-1 justify-center Cursor-Pointer max-w-full" >Schließen </CommandItem></div> </CommandGroup></CommandList> </Command></PopoverContent>{ Animation > 0 && selectedValues.length > 0 && ( <WandSparklesclassName={cn( "cursor-pointer my-2 text-foreground bg-background w-3 h-3", isAnimating ? "" : "text-muted-foreground")}onClick={() => setIsAnimating(!isAnimating)} />)} </Popover>); });MultiSelect.displayName =</sp