قالب صفحهٔ فرم (FormPage)
صفحهای که یک فرم است — ساخت یک هشدار، ویرایش یک منبع — با ردیفهای فرم در یک کارت و دکمههای انصراف و ثبت در انتهای خط
معرفی
FormPage قالب صفحهای است که یک فرم است و یک بار ثبت میشود. قالب فرم را میسازد و هر تصمیم آن را میگیرد:
- عرض باریک 768 پیکسل؛
- ردیفها (
FormRow) در یک کارت، برچسب بالای هر کنترل، با جداکننده بین ردیفها (تا 5 فیلد بیگروه، 6 فیلد یا بیشتر در گروههای عنواندارFormSection)؛ - پایین کارت: «انصراف» و دکمهٔ ثبت در انتهای خط، دکمهٔ ثبت آخر (تنها اقدام اصلی صفحه)؛ دکمهها بهاندازهٔ برچسبشاناند و فقط روی موبایل تمامعرض میشوند (دکمهٔ ثبت بالا)؛
- خطای ثبت بهصورت خلاصه بالای فرم، که اعلام و فوکوس میشود؛
- حالت بارگذاری دادهٔ در حال ویرایش بهجای کارت.
چه زمانی استفاده کنیم:
- ساختن یا ویرایش یک چیز با یک دکمهٔ ثبت: «هشدار تازه»، «ویرایش منبع»، «دعوت عضو».
- صفحهای به نام «تنظیمات …» که در واقع یک فرم با یک «ذخیره» و یک «انصراف» است.
چه زمانی استفاده نکنیم:
- تنظیمات در چند گروه مستقل که هر گروه جدا ذخیره میشود:
SettingsPage. - فرم دو سه فیلدی یا تأیید یک کار:
Dialog؛ فرم بلند بدون ترک صفحهٔ جاری:Sheet. (انتخاب قالب صفحه)
استفاده
FormRowها را مستقیم فرزند FormPage کنید (فرم 6 فیلدی یا بلندتر: در FormSectionها، پایینتر). با react-hook-form، Form (از @partodata/ui/form) فرم را فراهم میکند و
handleSubmit به onSubmit میرود:
'use client'
import { useForm } from 'react-hook-form'
import { useRouter } from 'next/navigation'
import { Input, Switch } from '@partodata/ui'
import { Form, FormField } from '@partodata/ui/form'
import { FormPage, FormRow } from '@partodata/ui/templates'
type Values = { name: string; email: boolean }
export default function NewAlertPage() {
const router = useRouter()
const form = useForm<Values>({ defaultValues: { name: '', email: true } })
const save = form.handleSubmit(async () => {
router.push('/mentions')
})
return (
<Form {...form}>
<FormPage
title="هشدار تازه"
back={{ href: '/mentions', label: 'منشنها' }}
onSubmit={save}
onCancel={() => router.push('/mentions')}
submitLabel="ساخت هشدار"
submitting={form.formState.isSubmitting}
>
<FormField
control={form.control}
name="name"
rules={{ required: 'نام هشدار را وارد کنید' }}
render={({ field, fieldState }) => (
<FormRow label="نام هشدار" required error={fieldState.error?.message}>
<Input {...field} />
</FormRow>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormRow label="ارسال ایمیل">
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormRow>
)}
/>
</FormPage>
</Form>
)
}دکمهٔ ثبت را قالب میسازد (submitLabel): دکمهٔ type="submit" دیگری داخل فرم ننویسید (در محیط توسعه هشدار میدهد).
حالتها و انواع
خطای ثبت
error پیام خطای آخرین ثبت است (پیام سرور به زبان کاربر). قالب آن را با عنوان «ذخیره انجام نشد» بالای کارت نشان
میدهد، با role="alert" اعلام میکند و فوکوس را به آن میبرد. خطای هر فیلد در ردیف خودش است (FormRow error).
هر ذخیرهٔ ناموفق — حتی با همان پیام قبلی — خلاصه را دوباره نشان میدهد، اعلام و فوکوس میکند. ثبتی که اعتبارسنجی فیلدها جلویش را میگیرد ذخیرهٔ ناموفق نیست: ذخیره اجرا نشده، پس خلاصه دوباره اعلام نمیشود و فوکوس روی اولین فیلد نامعتبر میماند تا کاربر ببیند چه چیزی را باید درست کند.
با react-hook-form، خطای ثبت خطای ریشهٔ فرم است: در catch ذخیره
form.setError('root', { message: 'ذخیره انجام نشد؛ لحظهای بعد دوباره تلاش کنید.' }) و روی قالب
error={form.formState.errors.root?.message}. ثبت بعدی خودش آن را پاک میکند و useState جداگانهای لازم نیست.
در حال ثبت
submitting دکمهٔ ثبت را به حالت بارگذاری میبرد و هر دو دکمه را غیرفعال میکند (با react-hook-form:
form.formState.isSubmitting). اگر دکمهٔ ثبت با صفحهکلید زده شده باشد، فوکوس در این مدت روی خود فرم میماند (نه
<body>) و وقتی دکمه دوباره فعال شد به آن برمیگردد.
بازگشت و «انصراف»
«انصراف» (onCancel) در هر صفحهٔ فرم هست و الزامی است. کارش به جای صفحه بستگی دارد:
| صفحهٔ فرم | back | onCancel |
|---|---|---|
| مورد منوی خودش را دارد («تنظیمات هشدار») | ندارد | تغییرات را کنار میگذارد: () => form.reset() |
| از صفحهٔ دیگری باز شده (ساخت یا ویرایش، مثلاً از فهرست هشدارها) | پیوند به همان صفحه | به همان صفحه برمیگردد: () => router.push(back.href) |
back و breadcrumbs با هم پذیرفته نمیشوند (خطای نوع)؛ breadcrumbs فقط برای صفحهای دو سطح یا بیشتر عمیق.
اقدامهای ثانوی فرم
اقدامی که مال خود فرم است ولی آن را ثبت نمیکند — «تست اتصال» (کلیدی را پیش از ذخیره بیازمایید)، «ارسال ایمیل
آزمایشی»، «بازگردانی پیشفرضها» — در secondaryActions است: در ابتدای خط پاورقی کارت (ثبت و «انصراف» در انتهای آن)،
هر کدام Button variant="default" با onClick خودش که فرم را ثبت نمیکند (نوع پیشفرض Button همان button است)، پس
دکمهٔ ثبت تنها اقدام اصلی میماند. نتیجهاش: toast («اتصال برقرار است») یا setError روی همان فیلد. روی گوشی زیر
دکمهها و تمامعرض.
فرم ویرایش
در ویرایش، حالت را از بارگذاری دادهٔ فعلی بسازید: state={pageState({ data: settings, isLoading, error, onRetry })}.
تا داده برسد اسکلت ردیفها بهجای کارت مینشیند و سرِ صفحه میماند؛ اگر بارگذاری شکست خورد، ErrorState با «تلاش
مجدد». فرم حالت خالی ندارد (موجودیتی که وجود ندارد UtilityPage نوع 404 است) و فرم ساختن چیز تازه state ندارد.
گروهبندی: یک قاعده
- تا 5 فیلد: ردیفها مستقیم در
FormPage، بدونFormSection. - 6 فیلد یا بیشتر: هر فیلد در یک
FormSectionعنواندار، 2 تا 4 فیلد در هر گروه (پس دستکم دو گروه).
هر فیلدی را که فرم ممکن است نشان دهد بشمارید: فیلدی که فقط با یک شرط دیده میشود (نشانی ایمیل وقتی «ارسال ایمیل» روشن است) هم شمرده میشود و وقتی پنهان است، گروهش میتواند یک ردیف داشته باشد.
گروه داخل همان کارت است و فرم همچنان یک دکمهٔ ثبت دارد. کارت را قالب میسازد: Card یا CardHeader خودتان را
داخل FormPage نگذارید. نمونهٔ بالا (همان فرم §5 راهنمای مصرف و «تنظیمات هشدار» اپ شروع) هفت فیلد دارد، پس سه گروه دارد. در حالت توسعه، Card داخل کارت فرم، FormRow
داخل یک CardContent، و هر گروهبندی بیرون از این قاعده (بیگروه با 6 فیلد یا بیشتر، تنها یک گروه، فیلد بیرون از
گروهها، گروه بیعنوان، گروه بیش از 4 فیلد) یکبار در کنسول هشدار میدهد.
راهنمای استفاده
بکنید
- برای هر صفحهای که یک فرم با یک ثبت است
FormPageرا برگردانید. - برچسب دکمهٔ ثبت را فعل همان کار کنید («ساخت هشدار»، «ذخیره»).
onCancelرا طبق جدول «بازگشت و انصراف» بدهید: در فرم منوform.reset()، در فرمی باbackبازگشت به همان صفحه.
نکنید
- فرم یا گروهی از فیلدها را در
Cardخودتان نپیچید (گروهFormSectionاست)، و دکمهها را خودتان نسازید یا تمامعرض نکنید. - عرض فرم را با
max-w-*تغییر ندهید؛ فرم صفحه همیشه 768 است. - برای تنظیماتی که گروهگروه ذخیره میشوند
FormPageرا به کار نبرید؛ آنSettingsPageاست.
Props
FormPage
دسترسیپذیری
- فرم یک ناحیهٔ نامدار است (
formبا نام عنوان صفحه) وnoValidateدارد تا پیامهای فرم جای پیامهای مرورگر را بگیرند. پسkind="email"چیزی را بررسی نمیکند (فقط صفحهکلید را انتخاب میکند): قالب ایمیل یا نشانی وب را درrulesهمان فیلد بررسی کنید (pattern، مثل «نشانی ایمیل» نمونهٔ بالا). - ترتیب Tab: فیلدها به ترتیب، بعد «انصراف»، بعد دکمهٔ ثبت؛ Enter در یک فیلد فرم را ثبت میکند.
- خلاصهٔ خطای ثبت
role="alert"دارد و فوکوس میگیرد، پس هم خوانده میشود و هم در دید است.
کامپوننتهای مرتبط
FormRow— هر فیلد فرم؛ چیدمانش را ازFormPageمیگیرد.SettingsPage— تنظیمات گروهگروه، هر گروه با ذخیرهٔ خودش.Form— اتصال react-hook-form.Dialog— فرم کوتاه دو سه فیلدی، بدون صفحهٔ جدا.