کتابخانهٔ رسانه
پیشنمایش تمامصفحهگریدِ عکسها با انتخابِ چندتایی، حاشیهٔ برند برای انتخابشدهها و نوارِ اقدامِ گروهی (دانلود، حذف).
۱. نصب کامپوننتها
این بلاک از ۶ آیتم دیگ استفاده میکند: buttoncheckboxcontrol-sizeiconspersiantoast
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.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/toast.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.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/toast.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.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/toast.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/checkbox.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/toast.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
files-gallery.tsx
"use client"
import * as React from "react"
import { ArrowUpIcon, CheckIcon, DownloadIcon, TrashIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { toast } from "@/components/ui/toast"
import { toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
const MEDIA: { id: string; image: StockImageName; name: string }[] = [
{ id: "1", image: "productVase", name: "گلدان-سفید.webp" },
{ id: "2", image: "studioSunlit", name: "استودیو.webp" },
{ id: "3", image: "coverMinimalRoom", name: "اتاق.webp" },
{ id: "4", image: "coverNotebook", name: "دفتر.webp" },
{ id: "5", image: "coverCalculator", name: "ماشینحساب.webp" },
{ id: "6", image: "coverOfficeSupplies", name: "لوازم.webp" },
{ id: "7", image: "projectCafeBrand", name: "کافه.webp" },
{ id: "8", image: "coverWorkspace", name: "میز-کار.webp" },
]
/**
* کتابخانهٔ رسانه: گریدِ عکسها با انتخابِ چندتایی (چکباکس روی هر عکس و حاشیهٔ برند برای
* انتخابشدهها)، و نوارِ اقدام که با انتخاب ظاهر میشود: دانلود و حذفِ گروهی.
*/
export default function FilesGalleryBlock() {
const [media, setMedia] = React.useState(MEDIA)
const [selected, setSelected] = React.useState<string[]>(["2"])
// حذفِ گروهی برگشتپذیر است: اسنکبارِ «برگردان» بهجای دیالوگِ تأیید.
function removeSelected() {
const before = media
setMedia((list) => list.filter((item) => !selected.includes(item.id)))
toast(`${toFaDigits(selected.length)} عکس حذف شد.`, { action: { label: "برگردان", onClick: () => setMedia(before) } })
setSelected([])
}
const toggle = (id: string) => setSelected((list) => (list.includes(id) ? list.filter((item) => item !== id) : [...list, id]))
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-5xl flex-col gap-6 p-4 sm:p-6">
<header className="flex flex-wrap items-end justify-between gap-x-4 gap-y-6">
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-bold">رسانهها</h1>
<p className="text-muted-foreground">{toFaDigits(media.length)} عکس · ۱۸ مگابایت</p>
</div>
<Button>
<ArrowUpIcon />
بارگذاری
</Button>
</header>
<div className={`flex min-h-14 flex-wrap items-center justify-between gap-3 rounded-xl px-4 py-2 ${selected.length ? "bg-primary-subtle" : "bg-subtle"}`} aria-live="polite">
<span className="text-sm">{selected.length ? `${toFaDigits(selected.length)} مورد انتخاب شده` : "برای کارِ گروهی، عکسها را انتخاب کن."}</span>
{selected.length > 0 && (
<div className="flex gap-2">
<Button variant="outline" className="bg-card" onClick={() => toast.success("دانلود شروع شد.")}>
<DownloadIcon />
دانلود
</Button>
<Button variant="outline" color="destructive" className="bg-card" onClick={removeSelected}>
<TrashIcon />
حذف
</Button>
</div>
)}
</div>
<ul className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
{media.map((item) => {
const checked = selected.includes(item.id)
return (
<li key={item.id} className={`group relative overflow-hidden rounded-xl border-2 ${checked ? "border-primary" : "border-card"}`}>
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages[item.image].src} alt={stockImages[item.image].alt} loading="lazy" className="aspect-square w-full object-cover" />
<div className="absolute start-2 top-2 flex size-9 items-center justify-center rounded-lg bg-card shadow-control">
<Checkbox id={`fg-${item.id}`} checked={checked} onCheckedChange={() => toggle(item.id)} aria-label={`انتخابِ ${item.name}`} />
</div>
{checked && (
<span aria-hidden className="absolute end-2 top-2 flex size-6 items-center justify-center rounded-full bg-primary text-primary-foreground [&>svg]:size-3.5">
<CheckIcon />
</span>
)}
<span className="absolute inset-x-0 bottom-0 truncate bg-linear-to-t from-[var(--dig-graphite-950)] to-transparent px-3 pt-6 pb-2 text-xs text-[var(--dig-white)]">{item.name}</span>
</li>
)
})}
</ul>
</main>
</ControlSizeProvider>
)
}