قالب جریان چندمرحلهای (WizardPage)
جریانی که در چند مرحله یک چیز میسازد — نشانگر مراحل، «قبلی» و «بعدی» با اعتبارسنجی هر مرحله، مرحلهٔ بازبینی، و مرحله در نشانی
معرفی
WizardPage قالب صفحهای است که یک چیز را در چند مرحله میسازد: تحلیل تازه (منبع ← برچسبها ← بازبینی)، بولتن تازه،
افزودن منبع. هر تصمیم آن در خود قالب است:
- عرض باریک (768)، مثل هر فرم؛ سرِ صفحه با عنوان (تنها
h1) و پیوند بازگشت؛ - نشانگر مراحل (مرحلهٔ جاری، مراحل گذشته با تیک)؛
- فیلدهای مرحلهٔ جاری در یک کارت، با عنوان مرحله («مرحلهٔ 2 از 3 · برچسبها»)؛
- پاورقی کارت: راه برگشت در ابتدای خط («انصراف» در مرحلهٔ اول، «قبلی» بعد از آن) و راه پیش در انتهای خط («بعدی»، اقدام اصلی؛ در مرحلهٔ آخر دکمهٔ ثبت)؛
- «بعدی» (و Enter در یک فیلد) پیش از رفتن مرحله را بررسی میکند (
onValidateStep)؛ - با رفتن به مرحلهٔ دیگر، فوکوس روی عنوان مرحلهٔ تازه میرود؛
- مرحلهٔ آخر، در جریانی با بیش از دو مرحله، بازبینی است:
WizardReviewهمهٔ پاسخها را به تفکیک مرحله، هر گروه با «ویرایش»، نشان میدهد؛ - با
stepInUrlهر مرحله یک ورودی تاریخچه است (?step=) و «بازگشت» مرورگر یک مرحله عقب میرود.
چه زمانی استفاده کنیم: ساختن چیزی که فیلدهایش به ترتیب به هم وابستهاند یا یکجا زیادند (منبع داده، سپس برچسبها بر اساس همان منبع).
چه زمانی استفاده نکنیم:
- فرمی با یک ثبت که در یک نگاه جا میشود (حتی با گروهها):
FormPage. - کاری کوتاه در دو سه فیلد: یک Dialog.
- مراحل یک کار در حال اجرا (پیشرفت پردازش): دادهٔ زنده.
استفاده
همهٔ مراحل یک فرم react-hook-formاند (useForm روی همهٔ فیلدها)؛ مرحلهٔ جاری وضعیت جزء است و بررسی هر مرحله
form.trigger همان فیلدهای مرحله:
'use client'
import * as React from 'react'
import { useForm } from 'react-hook-form'
import { Input, toast } from '@partodata/ui'
import { Form, FormField } from '@partodata/ui/form'
import { FormRow, WizardPage, WizardReview } from '@partodata/ui/templates'
type NewAnalysis = { name: string; link: string; labels: string }
const STEPS = [
{ id: 'source', label: 'منبع' },
{ id: 'labels', label: 'برچسبها' },
{ id: 'review', label: 'بازبینی' },
]
const FIELDS: Record<string, Array<keyof NewAnalysis>> = { source: ['name', 'link'], labels: ['labels'], review: [] }
async function createAnalysis(values: NewAnalysis): Promise<void> {
const response = await fetch('/api/analyses', { method: 'POST', body: JSON.stringify(values) })
if (!response.ok) throw new Error(`analyses: ${response.status}`)
}
export function NewAnalysisScreen() {
const form = useForm<NewAnalysis>({ defaultValues: { name: '', link: '', labels: '' } })
const [step, setStep] = React.useState('source')
const submit = form.handleSubmit(async (values) => {
try {
await createAnalysis(values)
toast.success('تحلیل ساخته شد')
} catch {
form.setError('root', { message: 'ساخت تحلیل انجام نشد. لحظهای بعد دوباره تلاش کنید.' })
}
})
return (
<Form {...form}>
<WizardPage
title="تحلیل تازه"
back={{ href: '/analyses', label: 'تحلیلها' }}
steps={STEPS}
step={step}
onStepChange={setStep}
onValidateStep={(id) => form.trigger(FIELDS[id], { shouldFocus: true })}
onSubmit={submit}
submitLabel="ساخت تحلیل"
submitting={form.formState.isSubmitting}
error={form.formState.errors.root?.message}
onCancel={() => window.history.back()}
stepInUrl
>
{step === 'source' && (
<>
<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="link"
rules={{ required: 'پیوند پست را وارد کنید' }}
render={({ field, fieldState }) => (
<FormRow label="پیوند پست" required error={fieldState.error?.message}>
<Input kind="url" {...field} />
</FormRow>
)}
/>
</>
)}
{step === 'labels' && (
<FormField
control={form.control}
name="labels"
render={({ field }) => (
<FormRow label="برچسبها" description="با ویرگول جدا کنید">
<Input {...field} />
</FormRow>
)}
/>
)}
{step === 'review' && (
<WizardReview
items={[
{ step: 'source', label: 'نام تحلیل', value: form.getValues('name') },
{ step: 'source', label: 'پیوند پست', value: form.getValues('link') },
{ step: 'labels', label: 'برچسبها', value: form.getValues('labels') || '—' },
]}
/>
)}
</WizardPage>
</Form>
)
}حالتها و انواع
| مرحله | ابتدای پاورقی | انتهای پاورقی |
|---|---|---|
| اول | «انصراف» | «بعدی» (اقدام اصلی) |
| میانی | «قبلی» | «بعدی» |
| آخر | «قبلی» | submitLabel (دکمهٔ ثبت) |
onValidateStepبرای «بعدی» و Enter اجرا میشود؛falseدر همان مرحله میماند (پیامهای فیلدها و فوکوس روی اولین فیلد نامعتبر باshouldFocus). «قبلی» و «ویرایش» بازبینی بی بررسی برمیگردند.stepInUrl: مرحله در?step=(مرحلهٔ اول بی پارامتر)؛ «بازگشت» و «جلو» مرورگر میان مراحلی که رسیدهاید جابهجا میشوند؛ پیوندی به مرحلهای جلوتر، جریان را از مرحلهٔ اول باز میکند (پاسخهای مراحل قبل در نشانی نیستند).- 2 تا 6 مرحله؛ یک مرحله یعنی
FormPageو بیش از شش، جریانی که باید تقسیم شود (در محیط توسعه هشدار).
راهنمای استفاده
بکنید
- همهٔ مراحل را یک
useFormکنید و فقط فیلدهای مرحلهٔ جاری را رندر کنید. - در جریان بیش از دو مرحله، مرحلهٔ آخر را بازبینی (
WizardReview) کنید. - کنش فرعی جریان (مانند «ذخیرهٔ پیشنویس») را در
secondaryActionsبگذارید، نه در بدنهٔ مرحله.
نکنید
- دکمههای «قبلی»، «بعدی» یا ثبت خودتان را نسازید؛ قالب آنها را جای میدهد.
- نشانگر مراحل (
Stepper) یا کارت خودتان را اضافه نکنید. - مرحله را خودتان با
useSearchParamsدر نشانی نگذارید:stepInUrl.
Props
WizardPage
WizardStep
WizardReview
دسترسیپذیری
- نشانگر مراحل فهرستی با نام «مراحل» است و هر مرحله وضعیتش را میگوید (گذشته، جاری، مانده).
- عنوان هر مرحله
h2است و «مرحلهٔ n از m» را با خود دارد؛ با رفتن به مرحلهٔ دیگر فوکوس روی آن میرود، پس صفحهخوان مرحلهٔ تازه را اعلام میکند. - «ویرایش» هر گروه بازبینی نام همان مرحله را دارد («ویرایش منبع»).