تأكد من إعداد مشروع Next.js. إذا لم يكن الأمر كذلك، قم بإنشاء واحد:
npx create-next-app my-app --typescriptcd my-app
تثبيت مكونات shadcn المطلوبة:
npx shadcn@latest init npx shadcn@latest إضافة شارة فاصل الزر المنبثق لأمر الإضافة
قم بإنشاء multi-select.tsx
في دليل components
الخاص بك:
// src/components/multi-select.tsximport * كرد فعل من "react";import {cva، اكتب VariantProps} من "class-variance-authority";import { أيقونة التحقق, إكس سيركل, شيفرون داون, إكسيكون, WandSparkles،} من "lucide-react"؛ استيراد { cn } من "@/lib/utils"؛ استيراد { Separator } من "@/components/ui/separator"؛ استيراد { Button } من "@/components/ui/ زر";استيراد {شارة} من "@/components/ui/badge";استيراد { بوبوفر, المحتوى المنبثق, PopoverTrigger,} من "@/components/ui/popover";import { يأمر، كوماند فارغ, مجموعة الأوامر, إدخال الأوامر, عنصر الأوامر, قائمة الأوامر, CommandSeparator,} from "@/components/ui/command";/** * متغيرات للمكون متعدد التحديد للتعامل مع الأنماط المختلفة. * يستخدم سلطة تباين الفئة (cva) لتحديد أنماط مختلفة بناءً على الدعامة "المتغيرة". */const multiSelectVariants = cva( "م-1 تأخير سهولة الدخول والخروج-150 تحويم:-ترجمة-y-1 تحويم: مقياس-110 مدة-300"، {المتغيرات: { البديل: {افتراضي: "border-foreground/10 text-foreground bg-card hover:bg-card/80"، ثانوي: "border-foreground/10 bg- Secondary text- Secondary-foreground hover: bg- ثانوي/80"، مدمر: "نص مدمر بحدود شفافة، مدمر للمقدمة، تحويم: bg-مدمر/80"، مقلوب: "مقلوب"، }،}،المتغيرات الافتراضية: { البديل: "افتراضي"،}، });/** * الدعائم لمكون MultiSelect */interface MultiSelectProps يمتد React.ButtonHTMLattributes<HTMLButtonElement>,VariantProps<typeof multiSelectVariants> { /** * مصفوفة من كائنات الخيارات التي سيتم عرضها في مكون التحديد المتعدد. * يحتوي كل كائن خيار على تسمية وقيمة ورمز اختياري. */ الخيارات: {/** النص الذي سيتم عرضه للخيار. */label: string;/** القيمة الفريدة المرتبطة بالخيار. */value: string;/** مكون رمز اختياري للعرض بجانب الخيار. */icon?: React.ComponentType<{ className?: string }>; []; /** * يتم تشغيل وظيفة رد الاتصال عند تغيير القيم المحددة. * يتلقى مجموعة من القيم المحددة الجديدة. */ onValueChange: (value: string[]) => void; /** القيم الافتراضية المحددة عند تركيب المكون. */ defaultValue ؟: string[]; /** * سيتم عرض نص العنصر النائب عند عدم تحديد أي قيم. * اختياري، الإعدادات الافتراضية هي "تحديد الخيارات". */ العنصر النائب؟: سلسلة؛ /** * مدة الرسوم المتحركة بالثواني للمؤثرات المرئية (مثل الشارات المرتدة). * اختياري، القيمة الافتراضية هي 0 (بدون رسوم متحركة). */ الرسوم المتحركة؟: الرقم؛ /** * الحد الأقصى لعدد العناصر المعروضة. سيتم تلخيص العناصر الإضافية المحددة. * اختياري، القيمة الافتراضية هي 3. */ maxCount ؟: الرقم؛ /** * طريقة العنصر المنبثق. عند التعيين على "صحيح"، سيتم تعطيل التفاعل مع العناصر الخارجية * وسيكون المحتوى المنبثق فقط مرئيًا لقارئات الشاشة. * اختياري، القيمة الافتراضية خاطئة. */ modalPopover ؟: boolean; /** * إذا كان صحيحًا، فسيتم عرض مكون التحديد المتعدد باعتباره تابعًا لمكون آخر. * اختياري، القيمة الافتراضية خاطئة. */ asChild ؟: منطقي؛ /** * أسماء فئات إضافية لتطبيق الأنماط المخصصة على مكون التحديد المتعدد. * اختياري، يمكن استخدامه لإضافة أنماط مخصصة. */ className?: string;}export const MultiSelect = React.forwardRef< HTMLButtonElement, الدعائم المتعددة التحديد>( ({خيارات، onValueChange، متغير، defaultValue = []، العنصر النائب = "حدد الخيارات"، الرسوم المتحركة = 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 = (الحدث: 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 ث-كامل p-1 مدور-md حد أدنى-h-10 h-auto مركز العناصر ضبط-بين bg-inherit hover:bg-inherit"، className)} >{selectedValues.length > 0 ? ( <div className="flex justify-between items-center w-full"><div className=" فليكس 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 cursor-pointer" onClick={(event) = > {event.stopPropagation();toggleOption(value); (<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 cursor-pointer"onClick={(event) => { Event.stopPropagation(); ClearExtraOptions();}} /></Badge> )</div><div className="flex items-center justify-between"> <XIconclassName="h-4 mx-2 cursor-pointer text-muted-foreground "onClick={(event) => { events.stopPropagation(); HandleClear();}} /> <Separatororientation="vertical"className="flex min-h-6 h-full" /> <ChevronDown className="h-4 mx-2 مؤشر المؤشر نص كتم المقدمة" / ></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-4 cursor-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>لم يتم العثور على نتائج.</CommandEmpty> <CommandGroup><CommandItem key="all" onSelect={toggleAll} className="cursor-pointer"> <divclassName={cn( "mr-2 flex h-4 w-4 items-center justify-center rounded-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>(تحديد الكل)</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-center rounded-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" > مسح </CommandItem> <Separatororientation="vertical"className="flex min-h-6 h-full" /></> )} <CommandItemonSelect={() => setIsPopoverOpen(false)}className="flex-1 justify-center cursor- المؤشر max-w-full" >إغلاق </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