بلاک‌ها · پشتیبانی و محتوا

بلاگِ مجله‌ای

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

بلاگ با چیدمانِ مجله: گزارشِ اصلی روی عکسِ تمام‌قد با محوشدنِ تیره، ستونِ پربازدیدها با شمارهٔ درشت، و فهرستِ افقیِ مقاله‌ها با عکس، نویسندهٔ عکس‌دار و فیلترِ دسته.

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

این بلاک از ۹ آیتم دیگ استفاده می‌کند: 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.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/link.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/reveal.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/link.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/reveal.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/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>
  )
}