پرتوپرتو

الگوهای بارگذاری

راهنمای طراحی حالت‌های loading در پرتو — Skeleton، Spinner، و Progressive Loading

چه زمانی از چه الگویی استفاده کنیم؟

حالتابزارمثال
بارگذاری اولیه صفحهSkeletonلیست اینفلوئنسرها هنگام fetch اولیه
عملیات کوتاه (< ۲ ثانیه)Spinner در دکمهذخیره فرم، اعمال فیلتر
بارگذاری نمودارprop isLoadingنمودار روند
بارگذاری تدریجیSkeleton per itemلیست بی‌نهایت

نمونه بصری

بارگذاری اولیه صفحه — Skeleton

بارگذاری لیست

عملیات کوتاه — Spinner در دکمه


Skeleton

کاربرد اصلی

import { Skeleton } from '@parto-system-design/ui'

// Skeleton ساده
<Skeleton className="h-4 w-48" />

// جایگزین کارت
<div className="bg-surface-100 p-4 rounded-lg border border-default space-y-3">
  <div className="flex items-center gap-3">
    <Skeleton className="h-10 w-10 rounded-full" />
    <div className="space-y-2">
      <Skeleton className="h-4 w-32" />
      <Skeleton className="h-3 w-24" />
    </div>
  </div>
  <Skeleton className="h-4 w-full" />
  <Skeleton className="h-4 w-3/4" />
</div>

جایگزین لیست

function InfluencerListSkeleton() {
  return (
    <div className="space-y-3">
      {Array.from({ length: 5 }).map((_, i) => (
        <div key={i} className="flex items-center gap-3 p-4 border border-default rounded-lg">
          <Skeleton className="h-12 w-12 rounded-full" />
          <div className="flex-1 space-y-2">
            <Skeleton className="h-4 w-1/3" />
            <Skeleton className="h-3 w-1/4" />
          </div>
          <Skeleton className="h-8 w-20" />
        </div>
      ))}
    </div>
  )
}

// استفاده
{isLoading ? <InfluencerListSkeleton /> : <InfluencerList data={data} />}

جایگزین MetricCard

function MetricCardSkeleton() {
  return (
    <div className="bg-surface-100 p-4 rounded-lg border border-default space-y-2">
      <Skeleton className="h-3 w-24" />
      <Skeleton className="h-8 w-32" />
      <Skeleton className="h-3 w-16" />
    </div>
  )
}

Spinner در دکمه

برای عملیات‌های کوتاه (submit، ذخیره)، Spinner را داخل دکمه قرار دهید:

import { Button, Spinner } from '@parto-system-design/ui'

<Button disabled={isSubmitting}>
  {isSubmitting && <Spinner className="ms-2 h-4 w-4" />}
  {isSubmitting ? 'در حال ذخیره...' : 'ذخیره'}
</Button>

Skeleton نمودار

همه نمودارهای پرتو از isLoading پشتیبانی می‌کنند:

<LineChart
  data={data}
  isLoading={isLoading}
  // وقتی isLoading=true، Skeleton با ابعاد نمودار نمایش داده می‌شود
/>

Progressive Loading (بارگذاری تدریجی)

برای لیست‌های بی‌نهایت یا صفحه‌بندی:

const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useInfiniteQuery(...)

return (
  <div>
    {/* داده‌های بارگذاری شده */}
    {data?.pages.map(page =>
      page.items.map(item => <InfluencerCard key={item.id} data={item} />)
    )}

    {/* بارگذاری آیتم‌های جدید */}
    {isFetchingNextPage && (
      <div className="space-y-3 mt-3">
        {Array.from({ length: 3 }).map((_, i) => (
          <InfluencerCardSkeleton key={i} />
        ))}
      </div>
    )}

    {/* دکمه بارگذاری بیشتر */}
    {hasNextPage && !isFetchingNextPage && (
      <Button variant="outline" className="w-full mt-4" onClick={fetchNextPage}>
        بارگذاری بیشتر
      </Button>
    )}
  </div>
)

اصول Skeleton خوب

  • ابعاد واقعی — Skeleton باید شبیه محتوای واقعی باشد. اگر کارت ۸۰px ارتفاع دارد، Skeleton هم همین ارتفاع داشته باشد
  • بدون پرجزئیاتی — Skeleton نباید هر جزء را شبیه‌سازی کند — فقط ساختار کلی
  • انیمیشن یکنواخت — Skeleton پرتو انیمیشن شیمری دارد که نشان می‌دهد در حال بارگذاری است
  • تعداد مناسب — ۳ تا ۵ item برای لیست، نه ۲۰

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

// اطلاع‌رسانی به screen reader
<div aria-busy={isLoading} aria-label="در حال بارگذاری...">
  {isLoading ? (
    <InfluencerListSkeleton />
  ) : (
    <InfluencerList data={data} />
  )}
</div>

// یا با aria-live
<div aria-live="polite">
  {isLoading && <span className="sr-only">در حال بارگذاری داده‌ها...</span>}
</div>

صفحات مرتبط