اعلانهای زنگوله
پیشنمایش تمامصفحهپنلِ کشوییِ زنگوله: شمارِ تازه روی زنگوله، سگمنتِ همه/خواندهنشده، ردیفها با عکس یا آیکون و اقدامِ مستقیم، «همه خوانده شد» و راهِ تنظیمات.
۱. نصب کامپوننتها
این بلاک از ۸ آیتم دیگ استفاده میکند: avatarbadgebuttoncontrol-sizeiconspersianpopovertoggle-group
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/popover.json https://docs.digdesign.ir/r/toggle-group.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/popover.json https://docs.digdesign.ir/r/toggle-group.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/popover.json https://docs.digdesign.ir/r/toggle-group.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/popover.json https://docs.digdesign.ir/r/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
notifications-popover.tsx
"use client"
import * as React from "react"
import { BellIcon, ReceiptIcon, SettingsIcon, TruckIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"
type Item = { id: string; title: string; text: string; time: string; read: boolean; icon?: React.ReactNode; photo?: string; action?: string }
const ITEMS: Item[] = [
{ id: "1", title: "سارا محمدی", text: "از تو در «داشبوردِ فروش» نام برد.", time: "۵ دقیقه پیش", read: false, photo: stockImages.personWoman2.src, action: "پاسخ" },
{ id: "2", title: "سفارشِ ۸۸۲۱۰۴۲ ارسال شد", text: "پیک تا ساعتِ ۱۸ میرسد.", time: "۱ ساعت پیش", read: false, icon: <TruckIcon /> },
{ id: "3", title: "فاکتورِ مهر آماده است", text: "مبلغ ۷٬۳۱۵٬۰۰۰ تومان.", time: "دیروز", read: true, icon: <ReceiptIcon />, action: "دیدنِ فاکتور" },
]
/**
* اعلانها در پنلِ کشویی زنگوله: نشانِ شمارِ تازه روی زنگوله، سگمنتِ همه/خواندهنشده، ردیفها
* با عکس یا آیکون و نقطهٔ تازه، اقدامِ مستقیم روی ردیف (پاسخ، دیدنِ فاکتور)، «همه خوانده شد»
* و راهِ تنظیمات؛ بدونِ ترکِ صفحه.
*/
export default function NotificationsPopoverBlock() {
const [items, setItems] = React.useState(ITEMS)
const [filter, setFilter] = React.useState("all")
const unread = items.filter((item) => !item.read).length
const visible = items.filter((item) => filter === "all" || !item.read)
return (
<ControlSizeProvider size="xl" asChild>
<div className="flex min-h-[36rem] w-full justify-end bg-subtle p-4 sm:p-6">
<h1 className="sr-only">اعلانها</h1>
<Popover defaultOpen>
<PopoverTrigger asChild>
<Button variant="outline" size="icon" aria-label={unread ? `اعلانها، ${toFaDigits(unread)} تازه` : "اعلانها"} className="relative bg-card">
<BellIcon />
{unread > 0 && <Badge color="destructive" className="absolute -end-1.5 -top-1.5 min-w-5 justify-center px-1">{toFaDigits(unread)}</Badge>}
</Button>
</PopoverTrigger>
<ControlSizeProvider size="xl" asChild>
<PopoverContent align="end" className="flex w-[min(24rem,calc(100vw-2rem))] flex-col gap-0 p-0">
<div className="flex items-center justify-between gap-3 border-b border-border p-4">
<span className="font-bold">اعلانها</span>
<Button variant="ghost" disabled={unread === 0} onClick={() => setItems((list) => list.map((item) => ({ ...item, read: true })))}>
همه خوانده شد
</Button>
</div>
<div className="p-3">
<ToggleGroup type="single" variant="segmented" value={filter} onValueChange={(value) => value && setFilter(value as string)} aria-label="فیلتر" className="w-full">
<ToggleGroupItem value="all" className="flex-1">
همه
</ToggleGroupItem>
<ToggleGroupItem value="unread" className="flex-1">
خواندهنشده {unread > 0 && `(${toFaDigits(unread)})`}
</ToggleGroupItem>
</ToggleGroup>
</div>
{visible.length === 0 ? (
<p className="px-4 pt-4 pb-8 text-center text-sm text-muted-foreground">همه را خواندهای. اعلانِ تازه که بیاید اینجاست.</p>
) : (
<ul className="flex max-h-80 flex-col overflow-y-auto">
{visible.map((item) => (
<li key={item.id} className={`flex gap-3 border-t border-border p-4 ${item.read ? "" : "bg-primary-subtle"}`}>
{item.photo ? (
<Avatar alt="" src={item.photo} />
) : (
<span aria-hidden className="flex size-10 shrink-0 items-center justify-center rounded-full bg-card text-brand-secondary [&>svg]:size-5">
{item.icon}
</span>
)}
<div className="flex flex-1 flex-col gap-1">
<span className={item.read ? "text-sm font-medium" : "text-sm font-bold"}>{item.title}</span>
<span className="text-sm leading-6 text-muted-foreground">{item.text}</span>
<div className="mt-1 flex items-center justify-between gap-2">
<span className="text-xs text-muted-foreground">{item.time}</span>
{item.action && (
<Button variant="outline" className="bg-card" onClick={() => setItems((list) => list.map((entry) => (entry.id === item.id ? { ...entry, read: true } : entry)))}>
{item.action}
</Button>
)}
</div>
</div>
{!item.read && <span aria-label="تازه" className="mt-2 size-2.5 shrink-0 rounded-full bg-[var(--dig-blue-500)]" />}
</li>
))}
</ul>
)}
<Button variant="ghost" className="rounded-none border-t border-border">
<SettingsIcon />
تنظیمِ اعلانها
</Button>
</PopoverContent>
</ControlSizeProvider>
</Popover>
</div>
</ControlSizeProvider>
)
}