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

اعلان‌های زنگوله

پیش‌نمایش تمام‌صفحه

پنلِ کشوییِ زنگوله: شمارِ تازه روی زنگوله، سگمنتِ همه/خوانده‌نشده، ردیف‌ها با عکس یا آیکون و اقدامِ مستقیم، «همه خوانده شد» و راهِ تنظیمات.

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

این بلاک از ۸ آیتم دیگ استفاده می‌کند: 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.json
pnpm 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.json
yarn 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.json
bunx --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>
  )
}