قائمة اختيار
قائمة منسدلة لاختيار خيار واحد — حقول البلد، ترتيب الفرز، إعدادات السمة — ذات حدود سميكة على الطراز «النيوبروتاليستي» وظلال حادة.
import {
Select,
SelectContent,يؤدي اختيار هذا العنصر إلى فتح قائمة منبثقة بالخيارات وعرض الخيار المختار في عنصر التشغيل الخاص به — وهو بديل مُصمم وجاهز للاستخدام في النماذج عن العنصر الأصلي <select>. وقد تم بناؤه على أساس مكون Base UI Select وتصميمه وفقًا لأسلوب «النيوبروتالية»: حدود سميكة، وظلال حادة، وخط عريض.
استخدمه عندما:
- حقول النماذج ذات الخيارات الثابتة — البلد، والعملة، والمنطقة الزمنية؛ أي مكان كنت ستستخدم فيه مكون
<select>الأصلي. - عناصر التحكم في الفرز والتصفية — ترتيب شبكة أو جدول المنتجات حسب السعر أو التاريخ أو الشعبية.
- أدوات اختيار التفضيلات — السمة أو اللغة أو تكرار الإشعارات في لوحة الإعدادات.
التثبيت#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/select.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/select.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/select.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/select.json
ثبّت التبعيات التالية:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
انسخ الكود التالي والصقه في مشروعك.
"use client"
import * as React from "react"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { Select as SelectPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return (
<SelectPrimitive.Group
data-slot="select-group"
className={cn("scroll-my-1 p-1", className)}
{...props}
/>
)
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-fit items-center justify-between gap-1.5 rounded border-2 bg-input py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-sm transition-colors outline-none select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "item-aligned",
align = "center",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
data-align-trigger={position === "item-aligned"}
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded border-2 bg-popover text-popover-foreground shadow-md duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
data-position={position}
className={cn(
"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
position === "popper" && ""
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default items-center gap-1.5 rounded-sm py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="pointer-events-none" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}
حدّث مسارات الاستيراد لتتوافق مع بنية مشروعك.
الاستخدام#
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"const items = [
{ label: "Light", value: "light" },
{ label: "Dark", value: "dark" },
{ label: "System", value: "system" },
]
<Select items={items}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>التركيب#
استخدم التركيب التالي لبناء Select:
Select
├── SelectTrigger
│ └── SelectValue
└── SelectContent
├── SelectGroup
│ ├── SelectLabel
│ ├── SelectItem
│ └── SelectItem
├── SelectSeparator
└── SelectGroup
├── SelectLabel
├── SelectItem
└── SelectItemأمثلة#
محاذاة العنصر مع المُشغّل#
استخدم alignItemWithTrigger على SelectContent للتحكم في محاذاة العنصر المحدد مع المُشغّل. عندما تكون القيمة true (الافتراضية)، تُوضع القائمة المنبثقة بحيث يظهر العنصر المحدد فوق المُشغّل. وعندما تكون القيمة false، تُحاذى القائمة المنبثقة مع حافة المُشغّل.
"use client"
import * as React from "react"مجموعات#
استخدم SelectGroup وSelectLabel وSelectSeparator لتنظيم العناصر.
import {
Select,
SelectContent,قابل للتمرير#
عنصر تحديد يحتوي على عناصر كثيرة قابلة للتمرير.
import {
Select,
SelectContent,معطّل#
import {
Select,
SelectContent,غير صالح#
أضف السمة data-invalid إلى المكوّن Field والسمة aria-invalid إلى المكوّن SelectTrigger لإظهار حالة الخطأ.
<Field data-invalid>
<FieldLabel>Fruit</FieldLabel>
<SelectTrigger aria-invalid>
<SelectValue />
</SelectTrigger>
</Field>import { Field, FieldError, FieldLabel } from "@/components/ui/field"
import {
Select,RTL#
لتفعيل RTL في Neobrutalism، راجع دليل إعداد RTL.
"use client"
import * as React from "react"إمكانية الوصول#
تطبق قائمة الاختيار معيار WAI-ARIA Combobox pattern في شكلها المخصص للاختيار فقط: يتم الإعلان عن المشغل على أنه مربع قائمة منسدل باستخدام aria-expanded، ويتم عرض النافذة المنبثقة على أنها listbox من عناصر option، ويقوم aria-selected بتتبع القيمة الحالية — وكل ذلك يتم توصيله بواسطة العنصر الأساسي.
تفاعلات لوحة المفاتيح:
| مفتاح | الإجراء |
|---|---|
Space / Enter | فتح مربع الاختيار؛ وعند فتحه، تحديد العنصر المميز |
ArrowDown / ArrowUp | فتح القائمة عند إغلاقها؛ وعند فتحها، تمييز العنصر التالي / السابق |
Esc | إغلاق القائمة المنبثقة وإعادة التركيز إلى العنصر المشغّل |
| كتابة الأحرف | الكتابة التلقائية — الانتقال إلى أول عنصر مطابق |
مرجع API#
لمزيد من المعلومات، راجع وثائق Base UI.