بلاگِ مجلهای
پیشنمایش تمامصفحهبلاگ با چیدمانِ مجله: گزارشِ اصلی روی عکسِ تمامقد با محوشدنِ تیره، ستونِ پربازدیدها با شمارهٔ درشت، و فهرستِ افقیِ مقالهها با عکس، نویسندهٔ عکسدار و فیلترِ دسته.
۱. نصب کامپوننتها
این بلاک از ۹ آیتم دیگ استفاده میکند: avatarbadgebuttoncontrol-sizeiconslinkpersianrevealtoggle-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/link.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/reveal.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/link.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/reveal.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/link.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/reveal.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/link.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/reveal.json https://docs.digdesign.ir/r/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
blog-magazine.tsx
"use client"
import * as React from "react"
import { ArrowUpIcon } 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 { Link } from "@/components/ui/link"
import { Reveal } from "@/components/ui/reveal"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatJalali, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
type Post = { id: string; category: string; title: string; excerpt: string; author: string; photo: StockImageName; date: string; minutes: number; cover: StockImageName }
const LEAD: Post = {
id: "lead",
category: "گزارش",
title: "کافههایی که با داده زنده ماندند",
excerpt: "سه کافهٔ کوچک در تهران و شیراز، یک سال با گزارشِ روزانهٔ فروش؛ چه چیزی را عوض کردند و چه چیزی را نه.",
author: "لیلا احمدی",
photo: "personWoman3",
date: "2026-10-02",
minutes: 12,
cover: "studioSunlit",
}
const TRENDING = [
"قانونِ ۵۰/۳۰/۲۰ برای بودجهبندی ماهانه",
"کارمزدِ جدیدِ کارتبهکارت از مهر",
"پساندازِ اضطراری: چقدر کافی است؟",
"مالیاتِ کسبوکارهای آنلاین در سالِ جدید",
]
const POSTS: Post[] = [
{ id: "1", category: "مالی شخصی", title: "قسط یا نقد؟ حسابِ سادهای که جواب میدهد", excerpt: "با تورمِ فعلی، خریدِ قسطی گاهی ارزانتر تمام میشود؛ اینجا حسابش را کردهایم.", author: "آرش نوری", photo: "personMan1", date: "2026-09-30", minutes: 6, cover: "coverCalculator" },
{ id: "2", category: "محصول", title: "پشتِ صحنهٔ گزارشِ ماهانهٔ خودکار", excerpt: "از ایده تا انتشار در شش هفته؛ تیم چه چیزهایی را کنار گذاشت.", author: "مهسا کریمی", photo: "personWoman1", date: "2026-09-27", minutes: 8, cover: "coverWorkspace" },
{ id: "3", category: "کسبوکار", title: "فاکتورِ رسمی بدونِ دردسر", excerpt: "چه اطلاعاتی لازم است و کجا بیشتر اشتباه میکنیم.", author: "نیما رضایی", photo: "personMan2", date: "2026-09-25", minutes: 5, cover: "coverOfficeSupplies" },
{ id: "4", category: "مالی شخصی", title: "دفترچهٔ هزینهای که واقعاً پر میشود", excerpt: "عادتِ یادداشتِ خرج را در دو هفته بسازید.", author: "سارا محمدی", photo: "personWoman2", date: "2026-09-21", minutes: 4, cover: "coverNotebook" },
]
const CATEGORIES = ["همه", "مالی شخصی", "محصول", "کسبوکار"]
function Byline({ post, light }: { post: Post; light?: boolean }) {
return (
<span className={`flex items-center gap-2 text-sm ${light ? "text-[var(--dig-neutral-300)]" : "text-muted-foreground"}`}>
<Avatar size="sm" alt={post.author} src={stockImages[post.photo].src} />
<span className={light ? "font-medium text-[var(--dig-white)]" : "font-medium text-foreground"}>{post.author}</span>
<span aria-hidden>·</span>
{formatJalali(new Date(post.date), "short")}
<span aria-hidden>·</span>
{toFaDigits(post.minutes)} دقیقه
</span>
)
}
/**
* بلاگِ مجلهای: گزارشِ اصلی روی عکسِ تمامقد با محوشدنِ تیره، ستونِ «پربازدیدها» با
* شمارهٔ درشت، و فهرستِ افقی مقالهها (عکسِ کنار متن) با فیلترِ دسته. ردیفها نرم وارد میشوند.
*/
export default function BlogMagazineBlock() {
const [category, setCategory] = React.useState("همه")
const visible = POSTS.filter((post) => category === "همه" || post.category === category)
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-6xl flex-col gap-16 px-4 py-12 sm:px-6">
<header className="flex flex-wrap items-end justify-between gap-4 border-b border-border pb-6">
<div className="flex flex-col gap-1">
<h1 className="text-4xl font-black">مجلهٔ حسابیار</h1>
<p className="text-muted-foreground">پول، کار و زندگی؛ هر هفته چند روایتِ خوب.</p>
</div>
<span className="text-sm text-muted-foreground">{formatJalali(new Date("2026-10-06"), "long")}</span>
</header>
<div className="grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
<article className="relative flex min-h-[28rem] overflow-hidden rounded-xl">
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages[LEAD.cover].src} alt={stockImages[LEAD.cover].alt} className="absolute inset-0 size-full object-cover" />
<div aria-hidden className="absolute inset-0 bg-linear-to-t from-[var(--dig-graphite-950)] via-[color-mix(in_oklab,var(--dig-graphite-950)_45%,transparent)] to-transparent" />
<div className="relative mt-auto flex flex-col gap-4 p-6 text-[var(--dig-white)] sm:p-10">
<Badge className="self-start bg-[var(--dig-orange-500)] text-[var(--dig-white)]">{LEAD.category}</Badge>
<h2 className="text-3xl leading-snug font-black text-balance sm:text-4xl">
<a href="#" className="hover:underline">
{LEAD.title}
</a>
</h2>
<p className="max-w-xl leading-7 text-[var(--dig-neutral-300)]">{LEAD.excerpt}</p>
<Byline post={LEAD} light />
</div>
</article>
<aside aria-labelledby="mag-trending" className="flex flex-col gap-2 rounded-xl bg-subtle p-6">
<h2 id="mag-trending" className="flex items-center gap-2 font-bold">
<ArrowUpIcon aria-hidden className="size-4 rotate-45 text-brand-secondary" />
پربازدیدهای هفته
</h2>
<ol className="flex flex-col divide-y divide-border">
{TRENDING.map((title, index) => (
<li key={title} className="flex gap-4 py-4 last:pb-0">
<span aria-hidden className="text-3xl leading-none font-black text-[var(--dig-graphite-400)] tabular-nums dark:text-[var(--dig-graphite-500)]">
{toFaDigits(index + 1)}
</span>
<a href="#" className="leading-7 font-medium hover:underline">
{title}
</a>
</li>
))}
</ol>
</aside>
</div>
<section aria-labelledby="mag-latest" className="flex flex-col gap-8">
<div className="flex flex-wrap items-center justify-between gap-4">
<h2 id="mag-latest" className="text-2xl font-black">
تازهترینها
</h2>
<div className="max-w-full overflow-x-auto">
<ToggleGroup type="single" variant="segmented" value={category} onValueChange={(value) => value && setCategory(value as string)} aria-label="دستهبندی">
{CATEGORIES.map((item) => (
<ToggleGroupItem key={item} value={item} className="whitespace-nowrap">
{item}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
</div>
<ul className="flex flex-col divide-y divide-border">
{visible.map((post, index) => (
<Reveal asChild key={post.id} delay={index * 100}>
<li className="grid grid-cols-[6.5rem_minmax(0,1fr)] gap-4 py-5 first:pt-0 sm:grid-cols-[14rem_minmax(0,1fr)] sm:gap-8 sm:py-6">
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages[post.cover].src} alt="" loading="lazy" className="aspect-square w-full rounded-lg object-cover sm:aspect-[4/3]" />
<div className="flex min-w-0 flex-col gap-1.5 sm:gap-3">
<span className="text-sm font-medium text-brand-secondary">{post.category}</span>
<h3 className="text-base leading-snug font-bold sm:text-xl">
<a href="#" className="hover:underline">
{post.title}
</a>
</h3>
<p className="hidden leading-7 text-muted-foreground sm:block">{post.excerpt}</p>
<Byline post={post} />
</div>
</li>
</Reveal>
))}
</ul>
<div className="flex items-center justify-center gap-6 pt-4">
<Button variant="outline">مقالههای بیشتر</Button>
<Link href="#">آرشیوِ همهٔ شمارهها</Link>
</div>
</section>
</main>
</ControlSizeProvider>
)
}