حالت بلوک (PageState)
حالتهای بارگذاری، خطا و خالیِ یک بلوک از صفحه — اسکلتی همشکل محتوا، خطا با «تلاش مجدد» و حالت خالی، همه بهجای بلوک
معرفی
PageState حالتهای یک بلوک از صفحه را نشان میدهد: وقتی حالت «آماده» است محتوای بلوک رندر میشود و در غیر این
صورت یک عنصر بهجای آن مینشیند:
- بارگذاری — اسکلتی همشکل همان محتوا (ردیفهای جدول، کارتها، کاشیهای شاخص، ردیفهای فرم، بخشها، نمودار)؛
- خطا —
ErrorStateبا دکمهٔ «تلاش مجدد»؛ - خالی —
Emptyبا عنوان، توضیح و یک اقدام؛ یا وقتی فیلتری فعال است، «نتیجهای یافت نشد» با «پاک کردن فیلترها».
هیچکدام داخل محتوا رندر نمیشود، پس حالت خالی هرگز در سلول جدول یا داخل کارت نمیافتد. حالت خطا و حالت خالی
دستکم به ارتفاع توکن --layout-state-min-height (240 پیکسل) هستند و اسکلت شکل خود محتوا را دارد، تا جابهجایی
بین حالتها صفحه را کمتر تکان دهد.
حالت را همیشه با تابع pageState() از خروجی درخواست میسازید؛ این تابع یک قاعده است که همهٔ صفحهها را یکسان
میکند. قالبهای صفحه (ListPage، DetailPage، FormPage، SettingsPage، DashboardPage) همان حالت را با ویژگی
state میگیرند و خودشان جایش را تعیین میکنند؛ سرِ صفحه در هیچ حالتی ناپدید نمیشود.
چه زمانی استفاده کنیم:
- یک بلوک داخل صفحه که دادهاش جدا بارگذاری میشود: فهرست یک زبانه، فیدی در یک بخش.
- هر جایی که همان قاعدهٔ «بهجای بلوک، نه داخل آن» لازم است و قالب صفحه آن را برایتان انجام نمیدهد.
چه زمانی استفاده نکنیم:
- محتوای اصلی صفحهای که با قالب ساخته شده است:
stateهمان قالب را بدهید. بخشی از صفحهٔ جزئیات همstateخودDetailSectionرا دارد. - دور کل صفحه یا دور سرِ صفحه: سرِ صفحه همیشه روی صفحه میماند.
- داخل
Card: جدول یا فهرستی که در کارت داشبورد استDashboardChart height="content"باstateخودش است.
| منبع | پلتفرم | منشنها |
|---|---|---|
| صفحهٔ رسمی برند | اینستاگرام | 1,240 |
| کانال خبری فروشگاه | تلگرام | 860 |
| وبلاگ بررسی محصول | وبسایت | 312 |
استفاده
PageState و pageState را از @partodata/ui/templates وارد کنید؛ isLoading و error همان خروجی هوک داده است
(useAsync سیستم طراحی، هوک همهٔ نمونهها) و data خودِ فهرست است. اینجا هوک خودِ فهرست نظرها
(Comment[]) را برمیگرداند؛ اگر هوک شیء صفحه ({ items, total }) برگرداند، data: data?.items را بدهید:
'use client'
import * as React from 'react'
import { useAsync } from '@partodata/ui'
import { Comment, EntityCollection, type SocialComment } from '@partodata/ui/social'
import { PageState, pageState } from '@partodata/ui/templates'
type ApiComment = { id: string; text: string; sentiment: 'positive' | 'negative' | 'neutral' }
// نظر API در مدل اجتماعی (یک Comment همهٔ نظرها را رسم میکند)
const toComment = (c: ApiComment): SocialComment => ({ id: c.id, text: c.text, signals: { sentiment: c.sentiment } })
// درخواست API اپ، در سطح ماژول (پس `load` پایین میان رندرها همان تابع میماند)
async function getComments(postId: string): Promise<ApiComment[]> {
const response = await fetch(`/api/posts/${postId}/comments`)
if (!response.ok) throw new Error(`comments: ${response.status}`)
return response.json()
}
export function CommentsBlock({ postId }: { postId: string }) {
const { data, isLoading, error, run } = useAsync<ApiComment[]>()
// مقدار درخواست در خود فراخوانی، همان مقدار در وابستگیها — هرگز شیء یا تابعی که هنگام رندر ساخته شود
const load = React.useCallback(() => run(() => getComments(postId)), [run, postId])
React.useEffect(() => {
load()
}, [load])
return (
<PageState
state={pageState({ data, isLoading, error, onRetry: load, emptyCopy: { title: 'هنوز نظری ثبت نشده است' } })}
skeleton="list"
>
<EntityCollection
entity="comment"
items={(data ?? []).map(toComment)}
getId={(comment) => comment.id}
renderItem={(comment, item) => <Comment comment={comment} {...item} />}
/>
</PageState>
)
}حالتها و انواع
قاعدهٔ pageState
pageState({ data, isLoading, error, onRetry, filtered?, onClearFilters?, isEmpty?, emptyCopy? }) به ترتیب:
| وضعیت درخواست | حالت |
|---|---|
هنوز چیزی بارگذاری نشده (data تعریفنشده یا null)، یا فهرست خالی در حال بارگذاری | بارگذاری |
| آخرین درخواست شکست خورده (حتی اگر دادهٔ قبلی روی صفحه باشد) | خطا با «تلاش مجدد» که onRetry را صدا میزند |
فهرست بارگذاریشده خالی است و فیلتری فعال است (filtered خود ListPage، یا filtered اینجا) | «نتیجهای یافت نشد» با «پاک کردن فیلترها» (متن خود سیستم طراحی) |
| فهرست بارگذاریشده خالی است | متن اولین استفاده از emptyCopy |
| ردیفهایی روی صفحه است و درخواست تازهای در جریان است (صفحهٔ بعد، جستوجو، فیلتر) | آماده و در حال تازهشدن: همان ردیفها، کمرنگ |
با live: تازهشدن همان درخواست در جریان است، یا شکست خورده و دادهای روی صفحه هست | آماده (یا خالی) با live: همان محتوا، بیتغییر؛ اعلان شکست در قالب |
| بقیه | آماده: خود محتوا |
dataبرای فهرست خودِ فهرست است (result?.items)، نه شیء صفحهای که فهرست در آن است؛ برای صفحهٔ جزئیات یا فرم ویرایش، خود موجودیت، که هیچوقت خالی نیست مگرisEmptyبگوید. دادن شیء صفحه ({ items, total }) بهdataخطای نوع است: آن شیء هیچوقت خالی نیست و جدول ردیف «دادهای نیست» خودش را میکشید.- هوک را از «هیچ» شروع کنید (
useAsync()بی دادهٔ اولیه،useState<T[] | undefined>()): آرایهٔ خالی اولیه نتیجهٔ خالی خوانده میشود و پیش از اولین درخواست حالت خالی را نشان میدهد. - ردیفهایی که روی صفحهاند تا رسیدن صفحهٔ بعد یا نتیجهٔ جستوجو و فیلتر تازه میمانند، کمرنگ، و ناحیهٔ وضعیت بلوک
«در حال بارگذاری» را اعلام میکند (
{ status: 'ready', refreshing: true })؛ اسکلت فقط وقتی است که چیزی برای نمایش نیست. نشانهٔ بارگذاری دیگری (اسپینر، کمرنگ کردن دستی،isLoadingجدول) لازم نیست. - دادهٔ زنده (صفحهای که خودش تازه میشود):
liveخروجیuseLiveRefreshاست. تازهشدن همان درخواست محتوا را همانطور که هست نگه میدارد (نه اسکلت، نه کمرنگ شدن، نه اعلام «در حال بارگذاری») و شکستش هم داده را پاک نمیکند: قالب اعلان هشدار با «تلاش مجدد» نشان میدهد.errorآنجا فقط برای وقتی است که هیچ دادهای نیست (هوک اولین بارگذاری را بیصدا دوباره میکوشد و خطا تا پاسخ میماند) یا خود درخواست عوض شده و شکست خورده است. هرliveفقط به یکstateمیرود: صفحه، یا همان یک بخشی که جدا بارگذاری میشود؛ فرم،EntityDrawerو فهرست باloadMoreهرگز زنده نیستند. جزئیات: دادهٔ زنده. errorوonRetryالزامیاند: خطای بارگذاری همیشه نشان داده میشود و همیشه از همانجا دوباره تلاش میشود. خطایی که با تلاش دوباره حل نمیشود (دسترسی نداشتن) صفحهٔUtilityPageنوع403است. پیام فنی خطا به کاربر نشان داده نمیشود و متن حالت خطا همیشه متن خود سیستم طراحی است («بارگذاری انجام نشد»).emptyCopyفقط متن «هنوز دادهای نیست» است (اولین استفاده، بی فیلتر):title،descriptionو یکactionبه شکل داده —{ label, onClick }یا{ label, href }— که سیستم طراحی آن را دکمهٔ ثانوی (default) رندر میکند؛ اقدام اصلی صفحه جای خودش را دارد. در حالت «نتیجهای یافت نشد» به کار نمیرود.- در
ListPage،filteredوonClearFiltersرا به خود صفحه بدهید، نه بهpageState: صفحه با همان مقدار دکمهٔ پاک کردن نوارابزار را هم نشان میدهد (اگر حالت باfilteredساخته شده باشد، در محیط توسعه هشدار میدهد).filteredوonClearFiltersدرpageState— همیشه با هم — برایPageStateیاDetailSectionای است که فهرستش فیلتر خودش را دارد؛ دکمهٔ «پاک کردن فیلترها»ی حالت «نتیجهای یافت نشد» همانonClearFiltersرا صدا میزند.
مقداری که pageState برمیگرداند
status | چه چیزی بهجای بلوک میآید | فیلدها |
|---|---|---|
ready | خود محتوا (بدون هیچ پوششی)؛ با refreshing کمرنگ، تا رسیدن دادهٔ تازه | refreshing?، live? |
loading | اسکلت به شکل skeleton | live? (نشانگر سر جایش میماند) |
error | ErrorState (اندازهٔ md) با دکمهٔ «تلاش مجدد» | onRetry (متن همیشه از سیستم طراحی)؛ live? |
empty | Empty با آیکون، عنوان، توضیح و یک اقدام | reason?: 'no-data'، title?، description?، action? — یا reason: 'no-results' با onClearFilters؛ live? |
این شیء را دستی ننویسید؛ تنها جاهای نوشتن آن state={{ status: 'loading' }} در فایل loading.tsx مسیر و
state={{ status: 'ready' }} در ListPageای است که ردیفهایش در خود کد نوشته شده است (هرگز بارگذاری نمیشود).
شکل اسکلت
skeleton نوع محتوایی است که اسکلت جایش را میگیرد:
skeleton | شکل |
|---|---|
table | سرِ جدول و هشت ردیف |
list | ردیفهای آواتار و متن (فید، نظرها) |
cards | شبکهٔ کارت با عرض کمینهٔ --layout-tile-min-width |
kpis | ردیف کاشیهای شاخص |
form | ردیفهای فرم در یک قاب |
sections | دو بخش، هر کدام عنوان و یک بلوک |
chart | ناحیهٔ نمودار |
راهنمای استفاده
بکنید
- حالت را با
pageState({ data: result?.items, isLoading, error, onRetry })از خروجی هوک داده بسازید و بهPageStateیاstateقالب بدهید؛dataخودِ فهرست است (یا موجودیت). - در
ListPagefilteredوonClearFiltersرا به صفحه بدهید؛ برای فهرست یکPageStateیاDetailSectionکه فیلتر خودش را دارد، هر دو را با هم بهpageState. skeletonرا همنوع محتوا انتخاب کنید تا جای خالی هنگام بارگذاری شکل همان محتوا باشد.
نکنید
- شیء حالت یا شرط سهتایی
isLoading ? … : error ? …را دستی ننویسید؛ ترتیب و قاعده راpageStateتعیین میکند. - شیء صفحهٔ هوک (
{ items, total }) را بهdataندهید؛ خودِ فهرست را بدهید. - برای بارگذاری دوباره، وقتی ردیفها روی صفحهاند، اسپینر، کمرنگ کردن دستی یا
isLoadingجدول اضافه نکنید؛ حالت خودش ردیفها را کمرنگ میکند. Emptyرا درemptyStateجدول یا داخلCardنگذارید؛ حالت خالی جای بلوک را میگیرد (قرار گرفتنPageStateداخل کارت در محیط توسعه هشدار میدهد).- اسپینر یا
PageLoaderرا بهجای اسکلت بلوک به کار نبرید. PageStateرا دورPageHeaderیا کل صفحه نپیچید؛ سرِ صفحه در هیچ حالتی ناپدید نمیشود.- برای خطای بارگذاری
toastنشان ندهید؛ خطا جای بلوک را میگیرد و دکمهٔ تلاش مجدد دارد.
Props
PageState
PageStateInput
ورودی pageState():
دسترسیپذیری
- هر بلوک یک ناحیهٔ زندهٔ
role="status"پنهان دارد که در همهٔ حالتها (آماده هم) روی صفحه میماند، پس هر تغییر اعلام میشود: «در حال بارگذاری» و سپس عنوان حالت خالی («نتیجهای یافت نشد»). شکلهای اسکلت از فناوری کمکی پنهاناند، پس صفحهخوان یک بار اعلام میکند، نه یک بار برای هر ردیف. حالت خطاrole="alert"خودش را دارد. - هنگام تازهشدن (ردیفها روی صفحه، درخواست تازه در جریان) همان ناحیه «در حال بارگذاری» را اعلام میکند و محتوا پس از
150 میلیثانیه کمرنگ میشود (پاسخ سریع چشمک نمیزند) و با رسیدن داده بیدرنگ برمیگردد.
aria-busyروی محتوا گذاشته نمیشود: محتوا عنصر خود صفحه است و والد مشغول، اعلام ناحیهٔ وضعیت را نگه میداشت. - دادهٔ زنده: تازهشدن همان درخواست اعلام نمیشود و محتوا کمرنگ نمیشود؛ شکستش را نشانگر «آخرین بهروزرسانی» قالب
(یا، در
PageStateتنها، ناحیهٔ وضعیت همین بلوک) یک بار و مؤدبانه اعلام میکند. - فوکوس در بلوک میماند: وقتی «تلاش مجدد» یا «پاک کردن فیلترها» با تغییر حالت از صفحه برداشته میشود، فوکوس به خود بلوک میرود (همان عنصر در بارگذاری، خطا و خالی) و وقتی محتوا برگشت، به ابتدای بلوک؛ با Tab به اولین کنترل محتوا میرسید. فوکوسی که جای دیگری است جابهجا نمیشود.
- سرعنوان حالت خطا و حالت خالی ترتیب سرعنوانهای صفحه را ادامه میدهد: مستقیم زیر عنوان صفحه
h2و زیر عنوان یک بخشh3. در کارت نمودار، عنوان کارت نام نمودار است و عنوان حالت پاراگراف است.
کامپوننتهای مرتبط
ListPageو بقیهٔ قالبهای صفحه — همین شیء را با ویژگیstateمیگیرند و جایش را خودشان تعیین میکنند.ErrorStateوEmpty— اجزایی که حالتها با آنها ساخته میشوند.Skeleton— پیشتنظیمهای اسکلتی کهskeletonاز آنها استفاده میکند.- دادهٔ زنده —
useLiveRefresh: تازهشدن بی اسکلت، زمان آخرین بهروزرسانی، و شکستی که داده را نگه میدارد.
نوارابزار صفحه (PageToolbar)
جزء سطح پایین ردیف فیلتر و اقدامی که ListPage میسازد — فیلترها در ابتدای خط، اقدامها در انتهای خط، یک اندازه و عرضهای ذاتی
دادهٔ زنده (useLiveRefresh)
صفحهای که دادهاش خودبهخود تازه میشود — تازهشدن بی اسکلت و بی جابهجایی، زمان آخرین بهروزرسانی، و شکستی که هرگز داده را پاک نمیکند