مقاله
پیشنمایش تمامصفحهسرِ وسطچین با نویسنده و اشتراک، کاورِ پهن، متنِ خوانا با فهرستِ مطالبِ چسبان، نکته، نقلقول، گالری، برچسبها، کارتِ نویسنده و مطالبِ مرتبط.
۱. نصب کامپوننتها
این بلاک از ۱۱ آیتم دیگ استفاده میکند: article-cardavatarbadgebreadcrumbbuttonchipcontrol-sizeiconspersiantoasttypography
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/article-card.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.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 https://docs.digdesign.ir/r/typography.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/article-card.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.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 https://docs.digdesign.ir/r/typography.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/article-card.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.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 https://docs.digdesign.ir/r/typography.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/article-card.json https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/breadcrumb.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.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 https://docs.digdesign.ir/r/typography.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
article.tsx
"use client"
import * as React from "react"
import { AvatarGenderIcon, CopyIcon, HeartIcon, InfoIcon, ShareIcon } from "@/components/icons"
import {
ArticleCard,
ArticleCardBody,
ArticleCardCategory,
ArticleCardGrid,
ArticleCardMedia,
ArticleCardMeta,
ArticleCardTitle,
} from "@/components/ui/article-card"
import { Avatar } from "@/components/ui/avatar"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Badge } from "@/components/ui/badge"
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Button } from "@/components/ui/button"
import { Chip, ChipLabel } from "@/components/ui/chip"
import { toast } from "@/components/ui/toast"
import { TypographyBlockquote, TypographyH2, TypographyList, TypographyP } from "@/components/ui/typography"
import { formatJalali, toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"
const TOC = [
{ id: "habit-1", title: "درگاه را خودتان باز کنید" },
{ id: "habit-2", title: "کارتِ جدا برای خرید آنلاین" },
{ id: "habit-3", title: "پیامکِ تراکنش روشن بماند" },
{ id: "when-hacked", title: "اگر کارت لو رفت" },
]
const RELATED: { category: string; title: string; minutes: number; cover: keyof typeof stockImages }[] = [
{ category: "مالی شخصی", title: "پساندازِ اضطراری: چقدر کافی است؟", minutes: 5, cover: "coverMinimalRoom" },
{ category: "امنیت", title: "راهنمای کاملِ رمزِ پویا", minutes: 6, cover: "coverWorkspace" },
{ category: "اخبار", title: "کارمزدِ جدیدِ کارتبهکارت از مهر", minutes: 3, cover: "coverCalculator" },
]
function ShareActions({ className }: { className?: string }) {
const [saved, setSaved] = React.useState(false)
return (
<div className={className}>
<Button
variant="outline"
size="icon"
aria-label="کپی پیوند"
onClick={() => {
void navigator.clipboard?.writeText(window.location.href)
toast.success("پیوندِ مقاله کپی شد.")
}}
>
<CopyIcon />
</Button>
<Button variant="outline" size="icon" aria-label="اشتراکگذاری">
<ShareIcon />
</Button>
<Button
variant="outline"
size="icon"
className={saved ? "text-destructive hover:text-destructive" : undefined}
aria-label={saved ? "حذف از علاقهمندیها" : "افزودن به علاقهمندیها"}
aria-pressed={saved}
onClick={() => setSaved((value) => !value)}
>
<HeartIcon filled={saved} />
</Button>
</div>
)
}
/**
* صفحهٔ مقاله: سرِ وسطچین با نویسنده، کاورِ پهن، ستونِ متنِ خوانا (حدود ۷۰ نویسه)
* با فهرستِ مطالبِ چسبان در دسکتاپ، کارتِ نویسنده، برچسبها و مطالبِ مرتبط.
*/
export default function ArticleBlock() {
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-6xl flex-col gap-12 px-4 py-10 sm:px-6">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">خانه</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="#">مجله</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>امنیت کارت بانکی</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<article className="flex flex-col gap-12">
{/* سر: دسته، تیتر و خلاصه یک گروهِ نزدیک. */}
<header className="flex max-w-3xl flex-col gap-4">
<Badge variant="soft" className="self-start">
امنیت · بانکداری
</Badge>
<h1 className="text-3xl leading-snug font-bold text-balance sm:text-4xl sm:leading-snug">پنج عادتِ ساده برای امنیتِ کارتِ بانکی</h1>
<p className="text-lg leading-8 text-muted-foreground">بیشترِ کلاهبرداریها با چند عادتِ ساده جلوگیری میشود؛ اینجا مهمترینشان را مرور کردهایم.</p>
</header>
{/* کاور و زیرش نویسنده و اشتراک در یک ردیف. */}
<div className="flex flex-col gap-6">
<div className="overflow-hidden rounded-xl bg-muted">
{/* eslint-disable-next-line @next/next/no-img-element -- کاورِ تزئینی ثابت */}
<img src={stockImages.coverNotebook.src} alt="" className="aspect-[2/1] w-full object-cover sm:aspect-[21/9]" />
</div>
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-3">
<Avatar alt="آرمیتا رستگار" src={stockImages.personWoman3.src} icon={<AvatarGenderIcon gender="female" />} />
<div className="flex flex-col text-sm">
<span className="font-semibold">آرمیتا رستگار</span>
<span className="text-muted-foreground">
{formatJalali(new Date("2026-09-25"), "long")} · {toFaDigits(5)} دقیقه مطالعه
</span>
</div>
</div>
<ShareActions className="flex gap-2" />
</div>
</div>
{/* متن در ستونِ پهن؛ فهرستِ مطالب در دسکتاپ کنارش میچسبد. */}
<div className="grid grid-cols-1 gap-12 lg:grid-cols-[13rem_minmax(0,52rem)] lg:justify-center">
<nav aria-label="فهرست مطالب" className="hidden lg:block">
<div className="sticky top-24 flex flex-col gap-3 border-s border-border ps-4 text-sm">
<span className="font-semibold">در این مقاله</span>
{TOC.map((item) => (
<a key={item.id} href={`#${item.id}`} className="leading-6 text-muted-foreground transition-colors hover:text-foreground">
{item.title}
</a>
))}
</div>
</nav>
<div className="flex min-w-0 flex-col gap-6 text-base leading-8">
<TypographyP className="text-lg leading-9">
هر روز دهها پیامکِ جعلی با نامِ بانکها فرستاده میشود. خبرِ خوب این است که با چند عادتِ ساده، تقریباً همهٔ این
دامها بیاثر میشوند؛ بدونِ اینکه خریدِ آنلاین سخت شود.
</TypographyP>
<TypographyH2 id="habit-1" className="mt-6 scroll-mt-24 border-b-0 pb-0 text-2xl">
۱. درگاه را خودتان باز کنید
</TypographyH2>
<TypographyP>
رمزِ پویا را فقط در درگاهی وارد کنید که خودتان از سایتِ فروشگاه به آن رسیدهاید. هیچ بانکی رمزِ پویا را تلفنی یا
پیامکی از شما نمیخواهد.
</TypographyP>
{/* نکته: کادرِ تهرنگ با آیکون؛ از متنِ اصلی جدا ولی همسطحِ آن. */}
<aside className="flex gap-3 rounded-xl bg-[color-mix(in_oklab,var(--brand-secondary)_8%,var(--background))] p-5 text-sm leading-7">
<InfoIcon aria-hidden className="mt-1 size-5 shrink-0 text-brand-secondary" />
<p>
<strong className="font-semibold">نشانی درگاه را نگاه کنید:</strong> درگاههای رسمی همیشه با{" "}
<bdi dir="ltr">shaparak.ir</bdi> تمام میشوند.
</p>
</aside>
<TypographyH2 id="habit-2" className="mt-6 scroll-mt-24 border-b-0 pb-0 text-2xl">
۲. کارتِ جدا برای خرید آنلاین
</TypographyH2>
<TypographyList>
<li>یک کارت با موجودی کم فقط برای خریدهای اینترنتی نگه دارید.</li>
<li>قبل از هر خریدِ بزرگ، فقط همان مبلغ را به آن منتقل کنید.</li>
<li>سقفِ خریدِ روزانهٔ کارت را از اپِ بانک پایین بیاورید.</li>
</TypographyList>
<TypographyBlockquote className="my-4 border-s-4 border-brand-secondary text-xl leading-9 font-semibold not-italic">
«رمزِ دوم مثلِ کلیدِ خانه است؛ آن را به هیچکس نمیدهید، حتی اگر خودش را مأمورِ بانک معرفی کند.»
</TypographyBlockquote>
<figure className="flex flex-col gap-3">
<div className="grid grid-cols-3 gap-3">
{(["coverCalculator", "coverFinanceDesk", "coverOfficeSupplies"] as const).map((cover) => (
// eslint-disable-next-line @next/next/no-img-element -- تصویرِ تزئینی ثابت
<img key={cover} src={stockImages[cover].src} alt="" className="aspect-square w-full rounded-xl object-cover" />
))}
</div>
<figcaption className="text-center text-sm text-muted-foreground">سه نشانهٔ پیامکِ جعلی: پیوندِ کوتاه، فوریت و درخواستِ رمز.</figcaption>
</figure>
<TypographyH2 id="habit-3" className="mt-6 scroll-mt-24 border-b-0 pb-0 text-2xl">
۳. پیامکِ تراکنش روشن بماند
</TypographyH2>
<TypographyP>پیامکِ هر برداشت، زودترین هشدار است. اگر قطعش کردهاید، همین امروز از اپِ بانک دوباره فعالش کنید.</TypographyP>
<TypographyH2 id="when-hacked" className="mt-6 scroll-mt-24 border-b-0 pb-0 text-2xl">
اگر کارت لو رفت
</TypographyH2>
<TypographyP>همان لحظه کارت را از اپلیکیشنِ بانک مسدود کنید، رمزها را عوض کنید و با شمارهٔ رسمی بانک تماس بگیرید.</TypographyP>
{/* پایانِ متن: برچسبها و اشتراک در یک ردیف. */}
<div className="mt-6 flex flex-wrap items-center justify-between gap-4 border-y border-border py-6">
<div className="flex flex-wrap gap-2">
{["امنیت", "کارت بانکی", "رمز پویا", "کلاهبرداری"].map((tag) => (
<Chip key={tag}>
<ChipLabel>{tag}</ChipLabel>
</Chip>
))}
</div>
<ShareActions className="flex gap-2" />
</div>
{/* کارتِ نویسنده */}
<section aria-label="دربارهٔ نویسنده" className="flex flex-col gap-4 rounded-xl bg-muted p-6 sm:flex-row sm:items-center">
<Avatar size="lg" alt="آرمیتا رستگار" src={stockImages.personWoman3.src} icon={<AvatarGenderIcon gender="female" />} />
<div className="flex flex-1 flex-col gap-1">
<span className="font-semibold">آرمیتا رستگار</span>
<span className="text-sm text-muted-foreground">نویسندهٔ حوزهٔ امنیتِ بانکی؛ ده سال کار در تیمِ پیشگیری از تقلب.</span>
</div>
<Button variant="outline" className="bg-background">
دنبال کردن
</Button>
</section>
</div>
</div>
</article>
<section aria-labelledby="related-title" className="flex flex-col gap-8 border-t border-border pt-16">
<h2 id="related-title" className="text-xl font-bold">
مطالبِ مرتبط
</h2>
<ArticleCardGrid>
{RELATED.map((post) => (
<ArticleCard key={post.title}>
<ArticleCardMedia>
{/* eslint-disable-next-line @next/next/no-img-element -- کاورِ تزئینی ثابت */}
<img src={stockImages[post.cover].src} alt="" loading="lazy" />
</ArticleCardMedia>
<ArticleCardBody>
<ArticleCardCategory>{post.category}</ArticleCardCategory>
<ArticleCardTitle href="#">{post.title}</ArticleCardTitle>
<ArticleCardMeta className="justify-start">{toFaDigits(post.minutes)} دقیقه مطالعه</ArticleCardMeta>
</ArticleCardBody>
</ArticleCard>
))}
</ArticleCardGrid>
</section>
</main>
</ControlSizeProvider>
)
}