چیدمان آیتم فرم (FormItemLayout)

داربست ردیف فرم — برچسب، توضیح و خطا؛ با چهار حالت چیدمان شامل ردیف toggle

معرفی

FormItemLayout عنصرِ پایهٔ چیدمان یک ردیف فرم است: داربست برچسب/توضیح/خطا را می‌سازد و کنترل (input، switch، select) را به‌عنوان children جای می‌دهد. برگرفته از FormLayout واقعی Supabase و برای RTL بازطراحی شده. عمداً مستقل است و هیچ وابستگی‌ای به react-hook-form ندارد، پس در بارل امن می‌ماند. فیلدی که با react-hook-form اعتبارسنجی می‌شود FormRow است، از render در FormField.

برای فیلدهای صفحه و دیالوگ: FormRow

فیلد یک صفحه یا دیالوگ FormRow است، که روی همین FormItemLayout ساخته شده و چیدمانش را از ظرف می‌گیرد (برچسب بالا در صفحهٔ فرم و دیالوگ، کنار کنترل در بخش تنظیمات). FormItemLayout جزء سطح پایین‌تر است، برای جایی که چیدمان ردیف را خودتان باید انتخاب کنید.

چه زمانی استفاده کنیم:

  • برای یک ردیف فرم استاندارد با برچسب، توضیح و پیام خطا
  • وقتی به شکل‌های مختلف چیدمان نیاز دارید (عمودی، افقی، ردیف toggle)
  • برای یک‌دست‌کردن فاصله‌گذاری و ترتیب برچسب/کنترل/خطا در کل فرم‌ها

چه زمانی استفاده نکنیم:

  • برای مدیریت state و اعتبارسنجی فرم — از Form (react-hook-form) استفاده کنید و FormItemLayout را داخل آن به‌عنوان چیدمان به کار ببرید
  • برای فقط یک برچسب بدون کنترل — از Label استفاده کنید
  • برای سربرگ یک بخش فرم — از FormHeader استفاده کنید

نامی که در گزارش‌ها نمایش داده می‌شود.

آدرس سایت برند را بدون https وارد کنید.

هر هفته یک خلاصه عملکرد ایمیل می‌شود.

استفاده

import { FormItemLayout, Input } from '@partodata/ui'

export default function CampaignNameField() {
  return (
    <FormItemLayout label="نام کمپین" description="نامی که در گزارش‌ها نمایش داده می‌شود." id="campaign-name">
      <Input id="campaign-name" placeholder="کمپین تخفیف فصلی" />
    </FormItemLayout>
  )
}

id (یا name) روی FormItemLayout به‌عنوان htmlFor برچسب استفاده می‌شود؛ همان مقدار را روی کنترل هم بگذارید تا کلیک روی برچسب کنترل را فوکوس کند.

حالت‌ها و انواع

prop layout

چهار شکل ردیف، دقیقاً مطابق FormLayout واقعی Supabase:

  • vertical (پیش‌فرض) — برچسب بالای کنترل، به‌صورت پشته.
  • horizontal — گرید 12 ستونی، برچسب 4 ستون / کنترل 8 ستون.
  • flex — ستون برچسب+توضیح در کنار کنترل.
  • flex-row-reverse — شکل «ردیف toggle»: برچسب در ابتدای خط، کنترل در انتهای خط، روی یک خط.

عمودی (پیش‌فرض)

<FormItemLayout label="نام کمپین" description="نامی که در گزارش‌ها نمایش داده می‌شود." id="name">
  <Input id="name" placeholder="کمپین تخفیف فصلی" />
</FormItemLayout>

افقی

<FormItemLayout
  layout="horizontal"
  label="دامنه برند"
  labelOptional="(اختیاری)"
  description="آدرس سایت برند را بدون https وارد کنید."
  id="domain"
>
  <Input id="domain" dir="ltr" placeholder="brand.example.com" />
</FormItemLayout>

ردیف toggle با flex-row-reverse

این حالت برای ردیف‌های سوییچ/چک‌باکس است: برچسب و توضیح در ابتدای خط، کنترل در انتهای خط.

<FormItemLayout
  layout="flex-row-reverse"
  label="اعلان گزارش هفتگی"
  description="هر هفته یک خلاصه عملکرد ایمیل می‌شود."
  id="weekly"
>
  <Switch id="weekly" defaultChecked />
</FormItemLayout>

چرا نام flex-row-reverse ولی بدون reverse فیزیکی؟

دستور اصلی Supabase در LTR، ردیف را به‌صورت فیزیکی معکوس می‌کند تا برچسب اول و سوییچ آخر بیاید. ما به‌جای آن از جریان منطقی استفاده می‌کنیم: بلوک متن اول در DOM رندر می‌شود و کنترل دوم، با یک ردیف justify-between طبیعی. چون flex-row ساده جهت نوشتار را رعایت می‌کند، برچسب در ابتدای خط و کنترل در انتهای خط در هر دو جهت LTR و RTL قرار می‌گیرد — بدون reverse فیزیکی و بدون left/right. نام prop برای سازگاری drop-in با API سوپابیس، همان flex-row-reverse نگه داشته شده است.

نمایش خطا

با ست‌کردن error، پیام در تینت destructive رندر می‌شود. با hideMessage می‌توانید اسلات خطا را حتی وقتی error ست است پنهان کنید.

<FormItemLayout label="ایمیل" error="ایمیل واردشده معتبر نیست." id="email">
  <Input id="email" dir="ltr" />
</FormItemLayout>

aria-invalid و aria-describedby را دستی ست نکنید — وقتی کنترل تنها فرزندِ ردیف باشد، FormItemLayout خودش پیام خطا و توضیح را با id پایدار رندر می‌کند، آن‌ها را روی کنترل به aria-describedby می‌بندد و در حالت خطا aria-invalid می‌گذارد. اگر خودتان aria-describedby بدهید، حفظ و با شناسه‌های ردیف ادغام می‌شود؛ aria-invalid صریح شما هم بازنویسی نمی‌شود.

برچسب‌های کمکی

<FormItemLayout
  label="کلید API"
  labelOptional="(اختیاری)"
  beforeLabel={<Lock className="size-3.5" />}
  afterLabel="— فقط خواندنی"
  id="api-key"
>
  <Input id="api-key" dir="ltr" />
</FormItemLayout>

راهنمای استفاده

بکنید

  • id/name را روی FormItemLayout و روی کنترل یکسان بگذارید تا برچسب به کنترل متصل شود - برای ردیف‌های سوییچ/چک‌باکس از layout="flex-row-reverse" استفاده کنید نه چیدمان دستی - در فرم‌های react-hook-form، error را از fieldState صریح پاس دهید

نکنید

  • انتظار نداشته باشید FormItemLayout خودش state فرم را مدیریت کند — فقط چیدمان است - برای معکوس‌کردن ردیف toggle از کلاس‌های فیزیکی flex-row-reverse/left/right استفاده نکنید؛ prop layout را به کار ببرید - توضیح و خطا را هم‌زمان به‌عنوان دو پیام متناقض نمایش ندهید؛ هنگام خطا معمولاً پیام خطا کافی است

جدول ویژگی‌ها

FormItemLayout

Prop

Type

دسترسی‌پذیری

  • برچسب یک Label واقعی با htmlFor است؛ اگر id/name را روی کنترل هم بگذارید، کلیک روی برچسب کنترل را فوکوس می‌کند
  • توضیح و پیام خطا هرکدام id پایدار می‌گیرند (<fieldId>-description و <fieldId>-message) و به‌صورت خودکار با aria-describedby به کنترل بسته می‌شوند؛ اگر id/name ندهید، یک شناسه داخلی تولید می‌شود
  • در حالت خطا، کنترل aria-invalid می‌گیرد و پیام خطا با role="alert" رندر می‌شود، پس صفحه‌خوان همان چیزی را اعلام می‌کند که کاربر بینا می‌بیند
  • این اتصال فقط وقتی انجام می‌شود که کنترل تنها فرزند ردیف باشد؛ با چند فرزند، اتصال بر عهدهٔ خودتان است. aria-describedby و aria-invalid که خودتان بدهید حفظ می‌شوند (اولی ادغام می‌شود)
  • با hideMessage متن خطا پنهان می‌شود ولی aria-invalid روی کنترل باقی می‌ماند
  • چیدمان flex-row-reverse از جریان منطقی استفاده می‌کند، پس ترتیب خواندن صفحه‌خوان (برچسب → توضیح → کنترل) در هر دو جهت درست می‌ماند

کامپوننت‌های مرتبط

  • Form — برای مدیریت state و اعتبارسنجی (react-hook-form)، از Form استفاده کنید و FormItemLayout را داخلش برای چیدمان به کار ببرید
  • Field — جایگزین سبک‌تر برای ردیف‌های فرم ساده بدون حالت‌های چیدمان چندگانه
  • FormHeader — برای سربرگ یک بخش از فرم (عنوان + توضیح + اقدامات)، از FormHeader استفاده کنید
  • Label — اگر فقط یک برچسب مستقل می‌خواهید، از Label استفاده کنید