اعلانهای اپ
پیشنمایش تمامصفحهاعلانها در اپ: سگمنتِ همه/خواندهنشده، گروهبندیِ زمانی، ردیفهای لمسی با عکس یا آیکون، نقطه و زمینهٔ خواندهنشده و «همه خوانده شد».
۱. نصب کامپوننتها
این بلاک از ۷ آیتم دیگ استفاده میکند: app-baravatarbuttoncontrol-sizeempty-stateiconstoggle-group
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/avatar.json 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/toggle-group.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/avatar.json 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/toggle-group.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/avatar.json 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/toggle-group.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/avatar.json 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/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
mobile-notifications.tsx
"use client"
import * as React from "react"
import { ChevronRightIcon, ReceiptIcon, TicketIcon, TruckIcon } from "@/components/icons"
import { AppBar, AppBarActions, AppBarLeading, AppBarTitle } from "@/components/ui/app-bar"
import { Avatar } from "@/components/ui/avatar"
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 { stockImages } from "@/lib/stock-images"
type Item = { id: string; group: "امروز" | "این هفته"; title: string; text: string; time: string; read: boolean; icon?: React.ReactNode; photo?: string }
const ITEMS: Item[] = [
{ id: "1", group: "امروز", title: "سفارشت در راه است", text: "پیک تا ساعتِ ۱۸ میرسد.", time: "۱۰ دقیقه پیش", read: false, icon: <TruckIcon /> },
{ id: "2", group: "امروز", title: "سارا محمدی", text: "برایت ۲۰۰ هزار تومان فرستاد.", time: "۲ ساعت پیش", read: false, photo: stockImages.personWoman2.src },
{ id: "3", group: "این هفته", title: "پاسخِ پشتیبانی", text: "درخواستِ ۵۰۲۱ پاسخ داده شد.", time: "دوشنبه", read: true, icon: <TicketIcon /> },
{ id: "4", group: "این هفته", title: "قبضِ برق", text: "تا پنجشنبه مهلتِ پرداخت داری.", time: "یکشنبه", read: true, icon: <ReceiptIcon /> },
]
/**
* اعلانهای اپ: سگمنتِ «همه/خواندهنشده»، گروهبندی زمانی، ردیفهای کاملاً لمسی با عکس یا
* آیکون، نقطهٔ خواندهنشده و زمینهٔ برندِ کمرنگ؛ «همه خوانده شد» در نوارِ بالا.
*/
export default function MobileNotificationsBlock() {
const [items, setItems] = React.useState(ITEMS)
const [filter, setFilter] = React.useState("all")
const visible = items.filter((item) => filter === "all" || !item.read)
const groups = ["امروز", "این هفته"] as const
return (
<ControlSizeProvider size="xl" asChild>
<div className="mx-auto flex min-h-svh w-full max-w-md flex-col bg-background">
<AppBar sticky>
<AppBarLeading>
<Button variant="ghost" size="icon" aria-label="بازگشت">
<ChevronRightIcon className="ltr:rotate-180" />
</Button>
</AppBarLeading>
<AppBarTitle>اعلانها</AppBarTitle>
<AppBarActions>
<Button variant="ghost" disabled={items.every((item) => item.read)} onClick={() => setItems((list) => list.map((item) => ({ ...item, read: true })))}>
همه خوانده شد
</Button>
</AppBarActions>
</AppBar>
<main className="flex flex-1 flex-col gap-4 p-4">
<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">
خواندهنشده
</ToggleGroupItem>
</ToggleGroup>
{visible.length === 0 ? (
<EmptyState className="flex-1">
<EmptyStateTitle>همه را خواندهای</EmptyStateTitle>
<EmptyStateDescription>اعلانِ تازه که بیاید همینجا میبینی.</EmptyStateDescription>
</EmptyState>
) : (
groups.map((group) => {
const rows = visible.filter((item) => item.group === group)
if (!rows.length) return null
return (
<section key={group} aria-labelledby={`mn-${group}`} className="flex flex-col gap-1">
<h2 id={`mn-${group}`} className="px-1 py-2 text-sm font-medium text-muted-foreground">
{group}
</h2>
<ul className="flex flex-col gap-1">
{rows.map((item) => (
<li key={item.id}>
<a
href="#"
onClick={() => setItems((list) => list.map((entry) => (entry.id === item.id ? { ...entry, read: true } : entry)))}
className={`flex min-h-16 items-start gap-3 rounded-xl p-3 ${item.read ? "hover:bg-subtle" : "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-subtle text-brand-secondary [&>svg]:size-5">
{item.icon}
</span>
)}
<span className="flex flex-1 flex-col gap-0.5">
<span className={item.read ? "font-medium" : "font-bold"}>{item.title}</span>
<span className="text-sm leading-6 text-muted-foreground">{item.text}</span>
<span className="text-xs text-muted-foreground">{item.time}</span>
</span>
{!item.read && <span aria-label="خواندهنشده" className="mt-2 size-2.5 shrink-0 rounded-full bg-[var(--dig-blue-500)]" />}
</a>
</li>
))}
</ul>
</section>
)
})
)}
</main>
</div>
</ControlSizeProvider>
)
}