پرتوپرتو

مرکز اعلان (NotificationCenter)

مرکز اعلان مبتنی بر Popover با نشان شمارنده، severity رنگ‌بندی، فیلترهای تب، و عملیات خوانده‌شده/بستن

معرفی

NotificationCenter یک دکمه‌ی زنگ با نشان شمارنده‌ی اعلان‌های خوانده‌نشده است. کلیک روی آن یک Popover باز می‌کند که لیست اعلان‌ها را با ترتیب زمانی و دسته‌بندی severity نشان می‌دهد. کاربر می‌تواند هر اعلان را خوانده‌شده علامت بزند، ببندد، یا با کلیک روی متن اعلان به مقصد هدایت شود.

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

  • در App Shell (نزدیک منوی کاربر) برای آگاه‌سازی از هشدارها، بولتن‌های جدید، تکمیل job، و خطاهای سیستمی
  • برای صف هشدارهای افکارسنجی (مثل «گسترش ناگهانی پست بحرانی» یا «کاهش دقت کراولر»)
  • همراه با Toast (Sonner): توست برای اعلام لحظه‌ای، NotificationCenter برای تاریخچه

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

  • برای نمایش یک پیام مهم دائمی در بالای صفحه — از Banner استفاده کنید
  • برای پیام درون‌فرمی (خطای فیلد، توضیح context) — از Alert یا Callout استفاده کنید
  • برای تأیید destructive — از AlertDialog / ConfirmDialog استفاده کنید
← روی زنگ کلیک کنید

زمین بازی

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

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

استفاده

'use client'
import * as React from 'react'
import { NotificationCenter, type NotificationItem } from '@parto-system-design/ui'

function Header() {
  const [items, setItems] = React.useState<NotificationItem[]>([
    {
      id: 'n1',
      title: 'هشدار گسترش پست بحرانی',
      body: 'پست خوشه «رونمایی محصول جدید» در ۲۰ دقیقه اخیر ۳۴۰۰ بازنشر گرفت.',
      timestamp: new Date(Date.now() - 3 * 60 * 1000),
      severity: 'critical',
      read: false,
      action: { label: 'باز کردن خوشه', onClick: () => router.push('/clusters/42') },
    },
  ])

  return (
    <NotificationCenter
      items={items}
      onMarkRead={(id) => setItems((p) => p.map((i) => (i.id === id ? { ...i, read: true } : i)))}
      onMarkAllRead={() => setItems((p) => p.map((i) => ({ ...i, read: true })))}
      onDismiss={(id) => setItems((p) => p.filter((i) => i.id !== id))}
    />
  )
}

severity و رنگ‌بندی

پنج نوع severity پشتیبانی می‌شود. هر severity یک رنگ خط‌حاشیه‌ی شروع + رنگ نقطه/آیکون اختصاصی دارد:

severityتوکن منبعمورد استفاده
info--brand-defaultپیش‌فرض — اطلاع‌رسانی عادی (گزارش آماده، کاربر جدید)
success--sentiment-positiveتکمیل موفقیت‌آمیز یک job (تحلیل کامنت تکمیل شد)
warning--warning-defaultهشدارهای غیر-بحرانی (کاهش دقت، نزدیک شدن به quota)
destructive--destructive-defaultخطای سیستم، از دست رفتن اتصال
critical--status-criticalبحران دامنه‌ای افکارسنجی (گسترش ناگهانی پست، شکست تهدیدات)

فیلترهای تب

پنل می‌تواند چند تب فیلتر داشته باشد که کاربر با کلیک بینشان جابه‌جا می‌شود:

← پالت با تب‌های فیلتر
<NotificationCenter
  items={items}
  filters={[
    { key: 'all', label: 'همه', predicate: () => true },
    { key: 'unread', label: 'خوانده‌نشده', predicate: (i) => !i.read },
    { key: 'critical', label: 'بحرانی', predicate: (i) => i.severity === 'critical' },
  ]}
  defaultFilter="unread"
/>

overflow نشان شمارنده

اگر تعداد خوانده‌نشده‌ها از ۹۹ عبور کند، نشان به +۹۹ (یا 99+ در انگلیسی) تبدیل می‌شود تا width ثابت بماند:

← ۱۳۷ اعلان — نشان «+۹۹»

ادغام با Toast

الگوی رایج: اعلان‌های لحظه‌ای با toast() نمایش داده می‌شوند، و هم‌زمان به state اپ اضافه می‌شوند تا در NotificationCenter بمانند.

import { toast } from 'sonner'

function emit(item: NotificationItem) {
  setNotifications((p) => [item, ...p])
  toast(item.title, { description: item.body })
}

Props

Prop

Type

NotificationItem

Prop

Type

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

بکنید

  • state آیتم‌ها را در store مرکزی اپ نگه دارید (zustand/redux/context). NotificationCenter presentational است و داده را نمی‌سازد - برای اعلان‌های real-time، toast() را برای لحظه‌ای + افزودن به state برای تاریخچه هم‌زمان اجرا کنید - در اپ‌های چندبخشی، از filters برای تفکیک منبع اعلان (سیستم / افکارسنجی / عملیات) استفاده کنید - برای اعلان‌های پایدار که کاربر نباید حذفشان کند (مثل گزارش امضاء‌شده)، onDismiss را نپاس کنید

نکنید

  • بیش از ۵۰ اعلان را یک‌جا render نکنید — اعلان‌های قدیمی را به صفحه‌ی «تاریخچه» ببرید - severity را به دلخواه استفاده نکنید — هر severity معنای مشخص دارد - NotificationCenter را درون مودال یا Drawer قرار ندهید — جایگاه قراردادی آن در Header است - برای نمایش وضعیت online/offline از severity استفاده نکنید — این یک پیام کوتاه، نه تاریخچه‌ای است

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

  • دکمه‌ی زنگ aria-label دارد که در حالت وجود اعلان خوانده‌نشده شامل تعداد آن‌هاست
  • نشان شمارنده aria-hidden="true" است چون اطلاعات آن در aria-label دکمه قرار دارد
  • تب‌های فیلتر role="tablist" + role="tab" + aria-selected دارند
  • زمان هر اعلان با <time datetime="..."> markup شده تا assistive tech آن را درست بخواند
  • تمرکز صفحه‌کلید با Tab روی دکمه‌ی زنگ → Enter برای باز کردن → Tab درون پنل
  • Escape پنل را می‌بندد (از Popover primitive)

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

  • Banner — برای پیام مهم دائمی در بالای صفحه
  • Alert — برای پیام درون‌صفحه‌ای (context، خطای فرم)
  • Sonner — برای اعلان لحظه‌ای گذرا
  • AlertDialog — برای تأیید destructive