بلاک‌ها · معرفی محصول

تغییرات نسخه‌ها

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

خطِ زمانیِ نسخه‌ها با نسخه و تاریخِ چسبان، عنوان و خلاصه، عکسِ قابلیتِ اصلی، تغییرات با برچسبِ تازه/بهبود/رفعِ خطا، فیلترِ نوع با تب و عضویت در خبرنامهٔ نسخه‌ها.

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

این بلاک از ۹ آیتم دیگ استفاده می‌کند: avatarbadgebuttoncontrol-sizeiconsinputpersiantoasttoggle-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/input.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.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/input.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.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/input.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.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/input.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

changelog.tsx
"use client"

import * as React from "react"

import { EnvelopeIcon } 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 { Input } from "@/components/ui/input"
import { toast } from "@/components/ui/toast"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatJalali, isValidEmail, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"

type Kind = "new" | "improved" | "fixed"
const KIND: Record<Kind, { label: string; color: "success" | "info" | "warning" }> = {
  new: { label: "تازه", color: "success" },
  improved: { label: "بهبود", color: "info" },
  fixed: { label: "رفعِ خطا", color: "warning" },
}

const RELEASES: {
  version: string
  date: string
  title: string
  summary: string
  image?: StockImageName
  author: StockImageName
  changes: { kind: Kind; text: string }[]
}[] = [
  {
    version: "2.4",
    date: "2026-10-01",
    title: "گزارشِ خودکارِ ماهانه",
    summary: "هر ماه یک گزارشِ آماده با سود و زیان، هزینه‌های بزرگ و پیشنهادِ صرفه‌جویی، بی‌آن‌که چیزی بسازید.",
    image: "coverFinanceDesk",
    author: "personWoman1",
    changes: [
      { kind: "new", text: "گزارشِ ماهانه با خروجی PDF و ارسالِ خودکار به ایمیل" },
      { kind: "improved", text: "دسته‌بندی هزینه‌ها ۳۰٪ دقیق‌تر شد" },
      { kind: "fixed", text: "نمایشِ نادرستِ تاریخ در فاکتورهای اسفند" },
    ],
  },
  {
    version: "2.3",
    date: "2026-09-12",
    title: "حالتِ تیره و یادآورِ قسط",
    summary: "رابط حالا تیره هم می‌شود و یک روز قبل از هر قسط پیامک می‌گیرید.",
    author: "personMan2",
    changes: [
      { kind: "new", text: "حالتِ تیره با دو تمِ آبی و خاکستری" },
      { kind: "new", text: "یادآورِ پیامکی قسط و چک" },
      { kind: "improved", text: "بارگذاری داشبورد دو برابر سریع‌تر" },
    ],
  },
  {
    version: "2.2",
    date: "2026-08-20",
    title: "دسترسی تیمی",
    summary: "هر عضو نقشِ خودش را دارد و مدیر می‌بیند چه کسی چه چیزی را عوض کرده.",
    author: "personMan1",
    changes: [
      { kind: "new", text: "نقش‌های مدیر، حسابدار و بیننده" },
      { kind: "new", text: "گزارشِ فعالیتِ اعضا" },
      { kind: "fixed", text: "خروجِ ناخواسته پس از تعویضِ رمز" },
    ],
  },
]

/**
 * تغییراتِ نسخه‌ها: خطِ زمانی عمودی با نسخه و تاریخ در ستونِ کنار، عنوان و خلاصه،
 * عکسِ قابلیتِ اصلی، فهرستِ تغییرات با برچسبِ نوع، فیلترِ نوع با تب و عضویت در
 * خبرنامهٔ نسخه‌ها.
 */
export default function ChangelogBlock() {
  const [kind, setKind] = React.useState<"all" | Kind>("all")
  const [email, setEmail] = React.useState("")

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-4xl flex-col gap-12 px-4 py-16 sm:px-6">
        <header className="flex flex-col gap-6">
          <div className="flex flex-col gap-3">
            <Badge variant="soft" className="self-start">
              تغییرات
            </Badge>
            <h1 className="text-4xl leading-snug font-black sm:text-5xl">تازه‌های حساب‌یار</h1>
            <p className="text-lg leading-8 text-muted-foreground">هر چه ساختیم، بهتر کردیم یا درست کردیم؛ نسخه به نسخه.</p>
          </div>
          <form
            onSubmit={(event) => {
              event.preventDefault()
              if (!isValidEmail(email)) return
              setEmail("")
              toast.success("از این به بعد تازه‌ها را ایمیل می‌کنیم.")
            }}
            className="flex max-w-md gap-2"
          >
            <Input type="email" dir="ltr" aria-label="ایمیل" placeholder="name@example.com" startContent={<EnvelopeIcon />} value={email} onChange={(event) => setEmail(event.target.value)} className="flex-1" />
            <Button type="submit" variant="outline" disabled={!isValidEmail(email)}>
              خبرم کن
            </Button>
          </form>
        </header>

        <ToggleGroup type="single" variant="segmented" value={kind} onValueChange={(value) => value && setKind(value as "all" | Kind)} aria-label="نوعِ تغییر" className="self-start">
          <ToggleGroupItem value="all">همه</ToggleGroupItem>
          <ToggleGroupItem value="new">تازه</ToggleGroupItem>
          <ToggleGroupItem value="improved">بهبود</ToggleGroupItem>
          <ToggleGroupItem value="fixed">رفعِ خطا</ToggleGroupItem>
        </ToggleGroup>

        <ol className="flex flex-col">
          {RELEASES.map((release, index) => {
            const changes = release.changes.filter((change) => kind === "all" || change.kind === kind)
            if (changes.length === 0) return null
            return (
              <li key={release.version} className="grid grid-cols-1 gap-4 md:grid-cols-[10rem_minmax(0,1fr)] md:gap-10">
                {/* ستونِ زمان: نسخه و تاریخ، چسبان در دسکتاپ. */}
                <div className="flex items-center gap-3 md:sticky md:top-6 md:flex-col md:items-start md:self-start md:gap-1">
                  <span className="text-lg font-black tabular-nums">نسخهٔ {toFaDigits(release.version).replace(".", "٫")}</span>
                  <time className="text-sm text-muted-foreground" dateTime={release.date}>
                    {formatJalali(new Date(release.date), "long")}
                  </time>
                  {index === 0 && <Badge color="violet">آخرین</Badge>}
                </div>
                <article className={`flex flex-col gap-5 border-s border-border ps-6 md:ps-10 ${index < RELEASES.length - 1 ? "pb-16" : ""}`}>
                  <span aria-hidden className="-ms-[calc(1.5rem+5px)] -mb-5 size-2.5 rounded-full bg-brand-secondary md:-ms-[calc(2.5rem+5px)]" />
                  <h2 className="text-2xl font-bold">{release.title}</h2>
                  <p className="leading-8 text-muted-foreground">{release.summary}</p>
                  {release.image && (
                    // eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ
                    <img src={stockImages[release.image].src} alt="" className="aspect-[2/1] w-full rounded-xl object-cover" />
                  )}
                  <ul className="flex flex-col gap-3">
                    {changes.map((change) => (
                      <li key={change.text} className="flex items-start gap-3">
                        <Badge variant="soft" color={KIND[change.kind].color} className="mt-0.5 shrink-0">
                          {KIND[change.kind].label}
                        </Badge>
                        <span className="leading-7">{change.text}</span>
                      </li>
                    ))}
                  </ul>
                  <span className="flex items-center gap-2 text-sm text-muted-foreground">
                    <Avatar size="sm" alt={stockImages[release.author].alt} src={stockImages[release.author].src} />
                    منتشر شده توسطِ تیمِ محصول
                  </span>
                </article>
              </li>
            )
          })}
        </ol>
      </main>
    </ControlSizeProvider>
  )
}