اعلانها
پیشنمایش تمامصفحهاعلانهای خواندهنشده و همه، با «همه خوانده شد».
۱. نصب کامپوننتها
این بلاک از ۶ آیتم دیگ استفاده میکند: 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.jsonpnpm 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.jsonyarn 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.jsonbunx --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>
)
}