حقل
مساحات التسميات وعناصر التحكم ونصوص المساعدة ورسائل الخطأ الخاصة بلوحات الإعدادات ونماذج الدفع وعمليات التسجيل — مع تصميم النماذج بأسلوب «النيوبروتاليستي».
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {يجمع هذا الحقل بين تسمية وعنصر تحكم ونص مساعد ورسالة خطأ في كتلة واحدة مُحاذاة، ويمكن توسيعه ليشمل أقسامًا مجمعة باستخدام FieldGroup وFieldSet. ولا يوجد أي عنصر أساسي تحته — حيث تتكون دلالات fieldset، وlegend وrole="group" المركبة مع Label وSeparator، والمصممة وفقًا لنهج «النيوبروتاليةي»: خط عناوين غامق في التوضيحات والتسميات، بينما تتميز عناصر التحكم المُجمَّعة بحدود سميكة وظلال قوية.
استخدمه عندما:
- صفحات الإعدادات — قم بتكديس أقسام الملف الشخصي والإشعارات والفوترة باستخدام توضيحات
FieldSetوFieldSeparatorبين المجموعات. - نماذج الدفع والتسجيل — حافظ على اتساق محاذاة التسميات والأوصاف والأخطاء عبر حقول الإدخال وقوائم الاختيار ومفاتيح التبديل.
- بطاقات الاختيار — قم بتضمين أزرار الاختيار أو مربعات الاختيار أو مفاتيح التبديل في بطاقات
FieldLabelالقابلة للتحديد لاختيار الخطط ولوحات التفضيلات.
التثبيت#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/field.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/field.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/field.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/field.json
انسخ والصق الكود التالي في مشروعك.
"use client"
import { useMemo } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn(
"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
className
)}
{...props}
/>
)
}
function FieldLegend({
className,
variant = "legend",
...props
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
return (
<legend
data-slot="field-legend"
data-variant={variant}
className={cn(
"mb-1.5 font-head font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
className
)}
{...props}
/>
)
}
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className={cn(
"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
className
)}
{...props}
/>
)
}
const fieldVariants = cva(
"group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
{
variants: {
orientation: {
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
horizontal:
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
responsive:
"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
},
},
defaultVariants: {
orientation: "vertical",
},
}
)
function Field({
className,
orientation = "vertical",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
data-slot="field"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
{...props}
/>
)
}
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-content"
className={cn(
"group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
className
)}
{...props}
/>
)
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded has-[>[data-slot=field]]:border-2 *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
className
)}
{...props}
/>
)
}
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-label"
className={cn(
"flex w-fit items-center gap-2 text-sm font-head font-medium group-data-[disabled=true]/field:opacity-50",
className
)}
{...props}
/>
)
}
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn(
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
"last:mt-0 nth-last-2:-mt-1",
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<"div"> & {
children?: React.ReactNode
}) {
return (
<div
data-slot="field-separator"
data-content={!!children}
className={cn(
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
className
)}
{...props}
>
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
data-slot="field-separator-content"
>
{children}
</span>
)}
</div>
)
}
function FieldError({
className,
children,
errors,
...props
}: React.ComponentProps<"div"> & {
errors?: Array<{ message?: string } | undefined>
}) {
const content = useMemo(() => {
if (children) {
return children
}
if (!errors?.length) {
return null
}
const uniqueErrors = [
...new Map(errors.map((error) => [error?.message, error])).values(),
]
if (uniqueErrors?.length == 1) {
return uniqueErrors[0]?.message
}
return (
<ul className="ml-4 flex list-disc flex-col gap-1">
{uniqueErrors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>
)}
</ul>
)
}, [children, errors])
if (!content) {
return null
}
return (
<div
role="alert"
data-slot="field-error"
className={cn("text-sm font-normal text-destructive", className)}
{...props}
>
{content}
</div>
)
}
export {
Field,
FieldLabel,
FieldDescription,
FieldError,
FieldGroup,
FieldLegend,
FieldSeparator,
FieldSet,
FieldContent,
FieldTitle,
}
حدِّث مسارات الاستيراد لتطابق إعداد مشروعك.
الاستخدام#
import {
Field,
FieldContent,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSeparator,
FieldSet,
FieldTitle,
} from "@/components/ui/field"<FieldSet>
<FieldLegend>Profile</FieldLegend>
<FieldDescription>This appears on invoices and emails.</FieldDescription>
<FieldGroup>
<Field>
<FieldLabel htmlFor="name">Full name</FieldLabel>
<Input id="name" autoComplete="off" placeholder="Evil Rabbit" />
<FieldDescription>This appears on invoices and emails.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="username">Username</FieldLabel>
<Input id="username" autoComplete="off" aria-invalid />
<FieldError>Choose another username.</FieldError>
</Field>
<Field orientation="horizontal">
<Switch id="newsletter" />
<FieldLabel htmlFor="newsletter">Subscribe to the newsletter</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>التركيب#
Field#
عنصر تحكم واحد مع تسمية ونص مساعدة وتحقّق.
Field
├── FieldLabel
├── Input / Textarea / Switch / Select
├── FieldDescription
└── FieldErrorFieldGroup#
حقول مرتبطة في مجموعة واحدة. استخدم FieldSeparator بين الأقسام عند الحاجة.
FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
├── FieldSeparator
└── Field
├── FieldLabel
└── Input / Textarea / Switch / SelectFieldSet#
تجميع دلالي مع عنوان (legend) ووصف، يحتوي عادةً على FieldGroup.
FieldSet
├── FieldLegend
├── FieldDescription
└── FieldGroup
├── Field
│ ├── FieldLabel
│ ├── Input / Textarea / Switch / Select
│ ├── FieldDescription
│ └── FieldError
└── Field
├── FieldLabel
└── Input / Textarea / Switch / Selectالتشريح#
عائلة Field مصمّمة لتكوين نماذج سهلة الوصول. يُبنى الحقل النموذجي على النحو التالي:
<Field>
<FieldLabel htmlFor="input-id">Label</FieldLabel>
{/* Input, Select, Switch, etc. */}
<FieldDescription>Optional helper text.</FieldDescription>
<FieldError>Validation message.</FieldError>
</Field>Fieldهو الغلاف الأساسي لحقل واحد.FieldContentهو عمود flex يجمع التسمية والوصف. غير مطلوب إذا لم يكن لديك وصف.- غلّف الحقول المرتبطة بـ
FieldGroup، واستخدمFieldSetمعFieldLegendللتجميع الدلالي.
النموذج#
راجع وثائق Form لبناء النماذج باستخدام مكوّن Field ومع React Hook Form أو Tanstack Form أو Formisch.
أمثلة#
Input#
import {
Field,
FieldDescription,Textarea#
import {
Field,
FieldDescription,Select#
import {
Field,
FieldDescription,Slider#
"use client"
import * as React from "react"Fieldset#
import {
Field,
FieldDescription,Checkbox#
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,Radio#
import {
Field,
FieldDescription,Switch#
import { Field, FieldLabel } from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"
بطاقة اختيار#
غلّف مكوّنات Field داخل FieldLabel لإنشاء مجموعات حقول قابلة للاختيار. يعمل هذا مع مكوّنات RadioItem وCheckbox وSwitch.
import {
Field,
FieldContent,مجموعة حقول#
كدّس مكوّنات Field باستخدام FieldGroup. أضف FieldSeparator للفصل بينها.
import { Checkbox } from "@/components/ui/checkbox"
import {
Field,RTL#
لتفعيل RTL في Neobrutalism، راجع دليل إعداد RTL.
"use client"
import * as React from "react"التخطيط المتجاوب#
- الحقول العمودية: الاتجاه الافتراضي يكدّس التسمية وعنصر التحكم ونص المساعدة — مثالي للتخطيطات التي تُصمَّم للأجهزة المحمولة أولًا.
- الحقول الأفقية: اضبط
orientation="horizontal"علىFieldلمحاذاة التسمية وعنصر التحكم جنبًا إلى جنب. اقرنها بـFieldContentللحفاظ على محاذاة الأوصاف. - الحقول المتجاوبة: اضبط
orientation="responsive"للحصول على تخطيطات أعمدة تلقائية داخل العناصر الأصل المدركة للحاوية. طبِّق فئات@container/field-groupعلىFieldGroupلتبديل الاتجاهات عند نقاط توقّف محدّدة.
import { Button } from "@/components/ui/button"
import {
Field,التحقق والأخطاء#
- أضف
data-invalidإلىFieldلتحويل الكتلة بأكملها إلى حالة خطأ. - أضف
aria-invalidعلى الإدخال نفسه من أجل التقنيات المساعِدة. - اعرض
FieldErrorمباشرةً بعد عنصر التحكم أو داخلFieldContentللحفاظ على محاذاة رسائل الخطأ مع الحقل.
<Field data-invalid>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" aria-invalid />
<FieldError>Enter a valid email address.</FieldError>
</Field>إمكانية الوصول#
FieldSetوFieldLegendيُبقيان عناصر التحكم المرتبطة مجمّعة لمستخدمي لوحة المفاتيح والتقنيات المساعِدة.- يُخرج
Fieldالقيمةrole="group"بحيث ترث عناصر التحكم المتداخلة التسمية منFieldLabelوFieldLegendعند دمجها. - طبِّق
FieldSeparatorباعتدال لضمان أن يواجه قارئو الشاشة حدود أقسام واضحة.
إمكانية الوصول#
تتبع عائلة الحقول نهج «الدلالة أولاً»: يعرض FieldSet و FieldLegend fieldset/legend الأصليين، ويعرض Field role="group"، ويعرض FieldError role="alert"، بحيث يتم الإعلان عن رسائل التحقق من الصحة فور ظهورها.
- قم بإقران كل
FieldLabelبعنصر التحكم الخاص به عبرhtmlFor/id— يؤدي النقر على التسمية إلى تركيز عنصر التحكم، وتقوم برامج قراءة الشاشة بالإعلان عن الارتباط. - في حالة الإدخال غير الصحيح، قم بتعيين
aria-invalidعلى عنصر التحكم نفسه؛ أماdata-invalidعلىFieldفيقوم فقط بتحديد نمط الخطأ. - سلوك لوحة المفاتيح يخص عناصر التحكم الموجودة بالداخل — لا تضيف العناصر المغلقة أي نقاط توقف للتركيز أو ارتباطات مفاتيح خاصة بها.
مرجع API#
FieldSet#
حاوية تعرض fieldset دلاليًا مع إعدادات تباعد مسبقة.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSet>
<FieldLegend>Delivery</FieldLegend>
<FieldGroup>{/* Fields */}</FieldGroup>
</FieldSet>FieldLegend#
عنصر legend لـ FieldSet. بدِّل إلى النمط label للتوافق مع حجم التسمية.
| Prop | Type | Default |
|---|---|---|
variant | "legend" | "label" | "legend" |
className | string |
<FieldLegend variant="label">Notification Preferences</FieldLegend>يحتوي FieldLegend على نمطين: legend وlabel. يطبِّق النمط label حجم التسمية ومحاذاتها. مفيد إذا كان لديك FieldSet متداخل.
FieldGroup#
غلاف تخطيط يكدّس مكوّنات Field ويُفعّل استعلامات الحاوية للاتجاهات المتجاوبة.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldGroup className="@container/field-group flex flex-col gap-6">
<Field>{/* ... */}</Field>
<Field>{/* ... */}</Field>
</FieldGroup>Field#
الغلاف الأساسي لحقل واحد. يوفّر التحكم في الاتجاه وتنسيق الحالة غير الصالحة والتباعد.
| Prop | Type | Default |
|---|---|---|
orientation | "vertical" | "horizontal" | "responsive" | "vertical" |
className | string | |
data-invalid | boolean |
<Field orientation="horizontal">
<FieldLabel htmlFor="remember">Remember me</FieldLabel>
<Switch id="remember" />
</Field>FieldContent#
عمود flex يجمع عنصر التحكم والأوصاف عندما تكون التسمية بجانب عنصر التحكم. غير مطلوب إذا لم يكن لديك وصف.
| Prop | Type | Default |
|---|---|---|
className | string |
<Field>
<Checkbox id="notifications" />
<FieldContent>
<FieldLabel htmlFor="notifications">Notifications</FieldLabel>
<FieldDescription>Email, SMS, and push options.</FieldDescription>
</FieldContent>
</Field>FieldLabel#
تسمية مصمّمة لكل من عناصر الإدخال المباشرة وعناصر Field الأبناء المتداخلة.
| Prop | Type | Default |
|---|---|---|
className | string | |
asChild | boolean | false |
<FieldLabel htmlFor="email">Email</FieldLabel>FieldTitle#
يعرض عنوانًا بتنسيق تسمية داخل FieldContent.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldContent>
<FieldTitle>Enable Touch ID</FieldTitle>
<FieldDescription>Unlock your device faster.</FieldDescription>
</FieldContent>FieldDescription#
فتحة نص مساعدة توازن تلقائيًا الأسطر الطويلة في التخطيطات الأفقية.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldDescription>We never share your email with anyone.</FieldDescription>FieldSeparator#
فاصل بصري لفصل الأقسام داخل FieldGroup. يقبل محتوى مضمّنًا اختياريًا.
| Prop | Type | Default |
|---|---|---|
className | string |
<FieldSeparator>Or continue with</FieldSeparator>FieldError#
حاوية أخطاء سهلة الوصول تقبل عناصر أبناء أو مصفوفة errors (مثلًا من react-hook-form).
| Prop | Type | Default |
|---|---|---|
errors | Array<{ message?: string } | undefined> | |
className | string |
<FieldError errors={errors.username} />عندما تحتوي مصفوفة errors على رسائل متعدّدة، يعرض المكوّن قائمة تلقائيًا.
يقبل FieldError أيضًا المشكلات (issues) التي يُنتجها أي أداة تحقّق تُطبّق Standard Schema، بما في ذلك Zod وValibot وArkType. مرِّر مصفوفة issues من نتيجة المخطّط مباشرةً لعرض قائمة أخطاء موحّدة بين المكتبات.