بنر محدودیت نرخ (RateLimitBanner)

Banner برای هشدار فعال بودن rate-limit — شمارش معکوس live، دکمه retry اختیاری، و ترکیب با actionType برای context.

معرفی

RateLimitBanner به کاربر اعلام می‌کند که یک عملیات در حال حاضر توسط پلتفرم rate-limit شده و باید تا زمان مشخصی صبر کرد. شمارش معکوس live (هر ثانیه به‌روز می‌شود) + دکمه retry اختیاری + context از ActionTypeKey.

برخلاف QuotaProgressBar که «نزدیک شدن به سقف» را نشان می‌دهد، این banner بعد از برخورد با سقف می‌آید: «پلتفرم گفت صبر کن، X دقیقه دیگر تلاش کن.»

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

  • وقتی backend گزارش می‌دهد worker با rate-limit از سمت Instagram مواجه شده
  • در داشبورد وقتی یک عملیات متوقف شده و resumeAt مشخص است
  • بالای کارت worker یا در top-of-page برای هشدار جدی
  • در admin panel برای نمایش rate-limit سمت API

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

  • برای هشدار فقط «نزدیک به سقف» — از QuotaProgressBar استفاده کنید
  • برای خطاهای عمومی (نه rate-limit) — از Banner یا Callout استفاده کنید
  • برای toast موقت — از sonner استفاده کنید

زمین بازی

با تغییر تنظیمات زیر، پیش‌نمایش زنده را مشاهده کنید.

زمین بازی
تنظیمات
محتوا
کد این نمونه به‌صورت خودکار قابل تولید نیست — برای کد آماده‌ی copy/paste به بخش «استفاده» در بالای صفحه مراجعه کنید.

استفاده

'use client'
import { RateLimitBanner } from '@partodata/ui'

export function WorkerFeed({ worker }) {
  if (!worker.rateLimit?.isActive) return null
  return (
    <RateLimitBanner
      resumeAt={worker.rateLimit.resumeAt}
      actionType="like"
      reason="پلتفرم نرخ پسند این حساب را موقتاً کاهش داده است."
      onRetryNow={() => retryWorker(worker.id)}
    />
  )
}

شمارش معکوس live

countdown هر ثانیه به‌روز می‌شود:

با live={true} (پیش‌فرض)، یک setInterval هر ثانیه state را re-render می‌کند و formatTimeRemaining دوباره محاسبه می‌کند. در صفحاتی که banner طولانی مدت باز می‌ماند، این اطلاعات دقیق به کاربر می‌دهد. اگر کاربر prefers-reduced-motion: reduce فعال کرده باشد، این tick به‌جای هر ثانیه به هر 30 ثانیه کند می‌شود تا از churn بصری کم شود بدون اینکه شمارش معکوس از دست برود.

اگر نیاز به performance بالا دارید یا در تست‌ها، live={false} کنید.

پایان cooldown

وقتی resumeAt می‌گذرد، banner به یک حالت جداگانه سوئیچ می‌کند: عنوان به «محدودیت نرخ برطرف شد» تغییر می‌کند، آیکن Pause با یک آیکن تیک جایگزین می‌شود، شمارش معکوس (که دیگر معنا ندارد) حذف می‌شود و tick هر ثانیه متوقف می‌شود — banner دیگر برای همیشه «هم‌اکنون» نشان نمی‌دهد. اگر onExpire بدهید، دقیقاً یک‌بار وقتی resumeAt می‌گذرد صدا زده می‌شود (یا بلافاصله، اگر banner با resumeAt گذشته mount شود) — برای refetch کردن state واقعی یا auto-dismiss کردن banner.

ترکیب با actionType

با actionType، عنوان به «محدودیت نرخ فعال — پسندیدن» (مثلاً) تغییر می‌کند و data-action-type روی root می‌آید — برای CSS یا telemetry.

variant warning (کمتر جدی)

برای سناریوهایی که rate-limit موقت و کم‌خطر است (مثلاً backend gateway با 40 req/h)، variant="warning" رنگ کهربایی می‌دهد به جای قرمز.

با دکمه retry

کلیک روی «تلاش مجدد» تعداد را افزایش می‌دهد: 0

وقتی onRetryNow تنظیم شود، دکمه «تلاش مجدد» در سمت end banner ظاهر می‌شود. کاربر می‌تواند override کند و سعی کند قبل از پایان cooldown.

Props

Prop

Type

helper formatTimeRemaining

در کنار کامپوننت، تابع formatTimeRemaining(target, locale) هم export می‌شود — اگر خودتان می‌خواهید countdown سفارشی بسازید:

import { formatTimeRemaining } from '@partodata/ui'

formatTimeRemaining(new Date('2026-04-25T10:00:00Z'), 'fa')
// → "2 ساعت و 30 دقیقه دیگر"
formatTimeRemaining(Date.now() + 45_000, 'en')
// → "in 45s"

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

بکنید

  • وقتی پلتفرم/backend صراحتاً rate-limit برگرداند و resumeAt مشخص است این banner را نمایش دهید — کاربر منتظر چه زمانی می‌ماند را دقیق می‌فهمد - برای rate-limit جدی (block پلتفرم) از variant="destructive" و برای gateway موقت از variant="warning" استفاده کنید - وقتی retry override واقعاً شدنی است (مثل proxy جدید) onRetryNow بدهید؛ در غیر این صورت دکمه را پاس ندهید تا کاربر صبر کند - با actionType context را غنی کنید — کاربر می‌فهمد محدودیت روی like است نه comment

نکنید

  • برای خطاهای transient (network blip, 500) از این banner استفاده نکنید — از Sonner toast استفاده کنید - برای هشدار «نزدیک به سقف» (نه برخورد به سقف) از این استفاده نکنید — از QuotaProgressBar مکمل استفاده کنید - چندین RateLimitBanner را در یک صفحه انباشت نکنید — اولویت‌بندی کنید و فقط جدی‌ترین rate-limit را نمایش دهید - live={true} را در صفحاتی که چند banner هم‌زمان دارند روی همه فعال نکنید — هر کدام setInterval جداگانه می‌سازد

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

  • کامپوننت روی <Banner> زیرین سوار است و role="alert" می‌گیرد (هر دو tone یعنی destructive و warning جدی‌اند) — یعنی یک live region است و هنگام ظاهر شدن یک‌بار خوانده می‌شود، نه landmark؛ نقش banner مخصوص سربرگ صفحه است و اینجا استفاده نمی‌شود.
  • عنوان + دلیل + countdown همگی متن هستند؛ screen reader ترتیب خوانایی درستی دارد.
  • دکمه retry یک <button type="button"> استاندارد با focus ring است.
  • در حالت dismissible, دکمه × یک button با aria-label="بستن" است (این مقدار در Banner زیرین hardcode شده و locale-aware نیست).
  • countdown با data-slot="rate-limit-countdown" markup شده و aria-live="off" دارد — وقتی live={true} هر ثانیه به‌روز می‌شود ولی چون نزدیک‌ترین تنظیم live برای این زیردرخت off است، screen reader آن را دوباره نمی‌خواند و اعلان یک‌بارهٔ خود banner دست‌نخورده می‌ماند. اگر واقعاً به اعلان دوره‌ای نیاز دارید، aria-live="polite" را روی همان گره override کنید و نرخ تیک را هم پایین بیاورید.
  • motion-safe — countdown با react state هست، نه CSS animation؛ و در prefers-reduced-motion: reduce نرخ به‌روزرسانی از هر 1 ثانیه به هر 30 ثانیه کند می‌شود.

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

  • QuotaProgressBar — هشدار «نزدیک به سقف»؛ این banner مکمل است برای «بعد از سقف»
  • Banner — banner عمومی؛ این کامپوننت روی آن بنا شده
  • ActionTypeChip — نمایش نوع عملیات به‌صورت chip جداگانه