דילוג לתוכן

בחירה

תפריט נפתח לבחירת אפשרות אחת — שדות מדינה, סדר מיון, הגדרות ערכת נושא — עם מסגרות עבות בסגנון ניאו-ברוטליסטי וצללים חדים.

import {
  Select,
  SelectContent,

ה-select פותח רשימת אפשרויות קופצת ומציג את הנבחרת בטריגר שלו — תחליף מעוצב ומוכן לטפסים ל-<select> המקורי. נבנה על גבי רכיב Base UI Select ומעוצב לפי המתכון הנאו-ברוטליסטי: מסגרות עבות, צללים חדים וטיפוגרפיה מודגשת.

השתמשו בו כש:

  • שדות טופס עם בחירות קבועות — מדינה, מטבע, אזור זמן; בכל מקום שהייתם משתמשים אחרת ב-<select> מקורי.
  • בקרות מיון וסינון — מסדרים רשת מוצרים או טבלה לפי מחיר, תאריך או פופולריות.
  • בוחרי העדפות — ערכת נושא, שפה או תדירות התראות בפאנל הגדרות.

התקנה

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/select.json
npx shadcn@latest add https://neobrutalism.com/r/base/select.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/select.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/select.json

התקינו את התלויות הבאות:

pnpm add @base-ui/react
npm install @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react

העתיקו והדביקו את הקוד הבא לפרויקט שלכם.

components/ui/select.tsx
"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,

ניתן לגלילה

רכיב select עם פריטים רבים שניתן לגלול בו.

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"

נגישות

ה-select מממש את דפוס Combobox של WAI-ARIA בצורת select-only: הטריגר מוכרז כ-combobox מכווץ עם aria-expanded, ה-popup מרנדר כ-listbox של פריטי option, ו-aria-selected עוקב אחרי הערך הנוכחי — הכול מחווט על ידי הפרימיטיב.

אינטראקציות מקלדת:

מקשפעולה
Space / Enterפותחים את ה-select; כשפתוח, בוחרים את הפריט המודגש
ArrowDown / ArrowUpפותחים כשסגור; כשפתוח, מדגישים את הפריט הבא / הקודם
Escסוגרים את ה-popup ומחזירים מוקד לטריגר
מקלידים תוויםTypeahead — קופצים לפריט התואם הראשון

תיעוד ה-API

למידע נוסף, עיינו בתיעוד של Base UI.