بلاک‌ها · حساب کاربری

اعلان‌های خوانده‌نشده و همه، با «همه خوانده شد».

۱. نصب کامپوننت‌ها

این بلاک از ۶ آیتم دیگ استفاده می‌کند: buttoncontrol-sizeempty-stateiconspersiantoggle-group

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toggle-group.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toggle-group.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toggle-group.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

notifications.tsx
"use client"

import * as React from "react"

import { ShieldCheckIcon, TruckIcon, WalletIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { EmptyState, EmptyStateDescription, EmptyStateTitle } from "@/components/ui/empty-state"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatRelativeTime, toFaDigits } from "@/lib/persian"

type Kind = "order" | "money" | "security"
type Notification = { id: number; kind: Kind; title: string; body: string; date: string; read: boolean }

const ICON: Record<Kind, React.ReactNode> = { order: <TruckIcon />, money: <WalletIcon />, security: <ShieldCheckIcon /> }
const INITIAL: Notification[] = [
  { id: 1, kind: "order", title: "سفارشِ ۱۰۴۲ ارسال شد", body: "بسته امروز به پست تحویل داده شد.", date: "2026-09-30T08:10:00", read: false },
  { id: 2, kind: "money", title: "واریزِ حقوق", body: "۳۸٬۰۰۰٬۰۰۰ تومان به حسابِ جاری واریز شد.", date: "2026-09-29T08:00:00", read: false },
  { id: 3, kind: "security", title: "ورود از دستگاهِ تازه", body: "ورود از مرورگرِ کروم در تهران؛ اگر خودت نبودی رمز را عوض کن.", date: "2026-09-27T21:40:00", read: true },
]

/**
 * صفحهٔ اعلان‌ها: سگمنتِ خوانده‌نشده/همه با شمار و «همه خوانده شد» در همان ردیف (کارِ فهرست
 * است، نه عنوان)، ردیف‌های لمسی با آیکونِ نوع، زمینهٔ برند و نقطه برای خوانده‌نشده‌ها، و
 * حالتِ خالی مثبت. لمسِ هر ردیف آن را خوانده می‌کند.
 */
export default function NotificationsBlock() {
  const [items, setItems] = React.useState(INITIAL)
  const [filter, setFilter] = React.useState("unread")
  const unread = items.filter((item) => !item.read)
  const rows = filter === "unread" ? unread : items

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-2xl flex-col gap-8 p-4 sm:p-6">
        <header className="flex flex-col gap-1">
          <h1 className="text-2xl font-bold">اعلان‌ها</h1>
          <p className="text-muted-foreground">سفارش‌ها، پرداخت‌ها و امنیتِ حساب.</p>
        </header>

        <div className="flex flex-col gap-4">
          <div className="flex flex-wrap items-center justify-between gap-3">
            <ToggleGroup type="single" variant="segmented" value={filter} onValueChange={(value) => value && setFilter(value as string)} aria-label="فیلترِ اعلان‌ها">
              <ToggleGroupItem value="unread" className="gap-2">
                خوانده‌نشده
                <span className="text-xs text-muted-foreground tabular-nums">{toFaDigits(unread.length)}</span>
              </ToggleGroupItem>
              <ToggleGroupItem value="all" className="gap-2">
                همه
                <span className="text-xs text-muted-foreground tabular-nums">{toFaDigits(items.length)}</span>
              </ToggleGroupItem>
            </ToggleGroup>
            <Button variant="ghost" disabled={unread.length === 0} onClick={() => setItems((current) => current.map((item) => ({ ...item, read: true })))}>
              همه خوانده شد
            </Button>
          </div>

          {rows.length === 0 ? (
            <EmptyState variant="bordered" className="rounded-xl py-12">
              <EmptyStateTitle>همه را خوانده‌ای</EmptyStateTitle>
              <EmptyStateDescription>اعلانِ تازه که بیاید همین‌جا می‌بینی.</EmptyStateDescription>
            </EmptyState>
          ) : (
            <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border bg-card">
              {rows.map((item) => (
                <li key={item.id}>
                  <a
                    href="#"
                    onClick={() => setItems((current) => current.map((entry) => (entry.id === item.id ? { ...entry, read: true } : entry)))}
                    className={`flex gap-4 p-4 ${item.read ? "hover:bg-subtle" : "bg-primary-subtle"}`}
                  >
                    <span aria-hidden className={`flex size-10 shrink-0 items-center justify-center rounded-full [&>svg]:size-5 ${item.kind === "security" ? "bg-[color-mix(in_oklab,var(--dig-orange-500)_15%,var(--card))] text-[var(--dig-orange-600)]" : "bg-card text-brand-secondary"}`}>
                      {ICON[item.kind]}
                    </span>
                    <span className="flex min-w-0 flex-1 flex-col gap-1">
                      <span className="flex items-start justify-between gap-3">
                        <span className={item.read ? "font-medium" : "font-bold"}>
                          {item.title}
                          {!item.read && <span className="sr-only"> (خوانده‌نشده)</span>}
                        </span>
                        <span className="shrink-0 text-xs text-muted-foreground">{formatRelativeTime(item.date)}</span>
                      </span>
                      <span className="text-sm leading-6 text-muted-foreground">{item.body}</span>
                    </span>
                    {!item.read && <span aria-hidden className="mt-2 size-2.5 shrink-0 rounded-full bg-[var(--dig-blue-500)]" />}
                  </a>
                </li>
              ))}
            </ul>
          )}
        </div>
      </main>
    </ControlSizeProvider>
  )
}