بنر محدودیت نرخ (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استفاده کنید
زمین بازی
با تغییر تنظیمات زیر، پیشنمایش زنده را مشاهده کنید.
استفاده
'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
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بدهید؛ در غیر این صورت دکمه را پاس ندهید تا کاربر صبر کند - باactionTypecontext را غنی کنید — کاربر میفهمد محدودیت روی like است نه comment
نکنید
- برای خطاهای transient (network blip, 500) از این banner استفاده نکنید — از
Sonnertoast استفاده کنید - برای هشدار «نزدیک به سقف» (نه برخورد به سقف) از این استفاده نکنید — از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 جداگانه