جزئیات محصول
پیشنمایش تمامصفحهگالری تصاویر، امتیاز، قیمت، انتخاب رنگ و تعداد، افزودن به سبد، مشخصات و معرفی.
۱. نصب کامپوننتها
این بلاک از ۱۲ آیتم دیگ استفاده میکند: add-to-cartbadgebuttonchipcolor-swatch-pickercontrol-sizeiconslabelpersianproduct-cardtabstoggle-group
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.json https://docs.digdesign.ir/r/color-swatch-picker.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/tabs.json https://docs.digdesign.ir/r/toggle-group.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.json https://docs.digdesign.ir/r/color-swatch-picker.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/tabs.json https://docs.digdesign.ir/r/toggle-group.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.json https://docs.digdesign.ir/r/color-swatch-picker.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/tabs.json https://docs.digdesign.ir/r/toggle-group.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chip.json https://docs.digdesign.ir/r/color-swatch-picker.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/label.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/tabs.json https://docs.digdesign.ir/r/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
product-detail.tsx
"use client"
import * as React from "react"
import { HeartIcon, ImageIcon, RefreshIcon, ShieldCheckIcon, StarIcon, TruckIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { AddToCart } from "@/components/ui/add-to-cart"
import { Button } from "@/components/ui/button"
import { Chip } from "@/components/ui/chip"
import { ColorSwatchPicker, ColorSwatchPickerItem } from "@/components/ui/color-swatch-picker"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Label } from "@/components/ui/label"
import { ProductCard, ProductCardBody, ProductCardMedia, ProductCardPrice, ProductCardTitle } from "@/components/ui/product-card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency } from "@/lib/persian"
const VIEWS = ["نمای روبهرو", "نمای کنار", "جزئیات بند", "در جعبه"]
// رنگِ واقعی کالا داده است، نه توکنِ رابط؛ از پالتِ پایهٔ دیگ میآید تا hex خام نباشد.
const COLORS = [
{ value: "black", label: "مشکی", swatch: "var(--dig-graphite-900)" },
{ value: "white", label: "سفید", swatch: "var(--dig-neutral-50)" },
{ value: "blue", label: "سرمهای", swatch: "var(--dig-blue-900)" },
{ value: "sage", label: "سبزِ مریمی", swatch: "var(--dig-green-300)" },
]
const FEATURES = ["حذفِ نویزِ فعال", "۳۰ ساعت شارژ", "بلوتوث ۵٫۳", "ضدِ تعریق"]
const SPECS = [
["اتصال", "بلوتوث ۵٫۳"],
["شارژدهی", "تا ۳۰ ساعت"],
["وزن", "۲۵۰ گرم"],
["گارانتی", "۱۸ ماه"],
]
const PROMISES = [
{ icon: <TruckIcon />, title: "ارسالِ رایگان", detail: "تحویلِ ۲ روزه" },
{ icon: <ShieldCheckIcon />, title: "۱۸ ماه گارانتی", detail: "اصالت و سلامت" },
{ icon: <RefreshIcon />, title: "۷ روز بازگشت", detail: "بدونِ پرسش" },
]
const RELATED = [
{ id: "r1", title: "کیف محافظ هدفون", price: 420_000 },
{ id: "r2", title: "کابل شارژ تایپسی بافتهشده", price: 180_000 },
{ id: "r3", title: "پایهٔ رومیزی هدفون", price: 690_000 },
{ id: "r4", title: "ایرپد یدکی نرم", price: 240_000 },
]
export default function ProductDetailBlock() {
const [view, setView] = React.useState(VIEWS[0])
const [color, setColor] = React.useState("black")
const [liked, setLiked] = React.useState(false)
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-6xl flex-col gap-12 p-4 sm:p-6">
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:gap-12">
{/* گالری: تصویرِ اصلی با برچسبهای روی تصویر، زیرش بندانگشتیها. */}
<section aria-label="تصاویر محصول" className="flex flex-col gap-3 self-start">
<div
role="img"
aria-label={view}
className="relative flex aspect-square items-center justify-center rounded-xl bg-[linear-gradient(145deg,var(--muted),color-mix(in_oklab,var(--brand-secondary)_10%,var(--muted)))] text-placeholder"
>
<ImageIcon className="size-16" />
<span className="absolute start-4 top-4 rounded-full bg-background px-3 py-1 text-xs font-semibold">تازه رسید</span>
<span className="absolute end-4 top-4 flex items-center gap-1 rounded-full bg-background px-2.5 py-1 text-xs font-semibold">
<StarIcon className="size-3.5 text-warning" />
۴٫۴
</span>
</div>
<ToggleGroup
type="single"
value={view}
onValueChange={(next) => next && setView(next as string)}
aria-label="انتخاب تصویر"
className="grid w-full grid-cols-4 gap-3"
>
{VIEWS.map((item) => (
<ToggleGroupItem
key={item}
value={item}
aria-label={item}
className="aspect-square h-auto w-full rounded-lg! bg-muted text-placeholder data-[state=on]:bg-muted data-[state=on]:ring-2 data-[state=on]:ring-foreground [&_svg]:size-6"
>
<ImageIcon />
</ToggleGroupItem>
))}
</ToggleGroup>
</section>
{/* سلسلهمراتب: معرفی، قیمت، ویژگیها، انتخابها، خرید، تعهدها؛ هر گروه با فاصلهٔ بیشتر از قبلی. */}
<section aria-label="جزئیات محصول" className="flex flex-col">
<div className="flex flex-col gap-3">
<span className="text-sm font-medium text-brand-secondary">صوتی / هدفون بیسیم</span>
<h1 className="text-3xl leading-snug font-bold">هدفون بیسیم دیگ پرو</h1>
<p className="leading-7 text-muted-foreground">
حذفِ نویزِ فعال و باتری سیساعته برای رفتوآمدِ روزانه و کار در فضای شلوغ؛ سبک، تاشو و همراهِ هر روز.
</p>
</div>
<div className="mt-6 flex flex-col gap-1.5">
<div className="flex flex-wrap items-center gap-3">
<span className="text-3xl font-bold">{formatCurrency(3_490_000)}</span>
<Badge variant="soft" color="success">
ارسالِ رایگان
</Badge>
</div>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<s>{formatCurrency(4_100_000)}</s>
<Badge color="destructive" size="sm">
٪۱۵
</Badge>
<span>· قیمت با مالیات</span>
</div>
</div>
<ul aria-label="ویژگیها" className="mt-6 flex flex-wrap gap-2">
{FEATURES.map((feature) => (
<li key={feature}>
<Chip variant="soft">{feature}</Chip>
</li>
))}
</ul>
<ControlSizeProvider size="xl">
<div className="mt-8 flex flex-col gap-3">
<Label id="color-label" className="gap-1">
رنگ:
<span className="font-normal text-muted-foreground">{COLORS.find((item) => item.value === color)?.label}</span>
</Label>
<ColorSwatchPicker
value={color}
onValueChange={setColor}
aria-labelledby="color-label"
size="md"
radius="full"
indicator="ring"
className="gap-3 p-1"
>
{COLORS.map((item) => (
<ColorSwatchPickerItem key={item.value} value={item.value} color={item.swatch} label={item.label} />
))}
</ColorSwatchPicker>
</div>
{/* تعداد بعد از افزودن روی همین دکمه انتخاب میشود؛ فیلدِ جدا لازم نیست. */}
<div className="mt-8 flex gap-3">
<AddToCart variant="default" fullWidth label="افزودن به سبد خرید" itemName="هدفون" max={7} className="flex-1" />
<Button
variant="outline"
size="icon"
aria-pressed={liked}
aria-label={liked ? "حذف از علاقهمندیها" : "افزودن به علاقهمندیها"}
onClick={() => setLiked((value) => !value)}
className={liked ? "text-destructive hover:text-destructive" : undefined}
>
<HeartIcon filled={liked} />
</Button>
</div>
</ControlSizeProvider>
<ul aria-label="تعهدهای فروشگاه" className="mt-8 grid grid-cols-3 gap-3">
{PROMISES.map((item) => (
<li key={item.title} className="flex flex-col items-center gap-2 rounded-xl border border-border p-3 text-center">
<span aria-hidden className="text-muted-foreground [&_svg]:size-5">
{item.icon}
</span>
<span className="flex flex-col gap-0.5">
<span className="text-sm font-semibold">{item.title}</span>
<span className="text-xs text-muted-foreground">{item.detail}</span>
</span>
</li>
))}
</ul>
</section>
</div>
<Tabs defaultValue="specs" className="gap-4">
<TabsList>
<TabsTrigger value="specs">مشخصات</TabsTrigger>
<TabsTrigger value="about">معرفی</TabsTrigger>
</TabsList>
<TabsContent value="specs">
<dl className="grid grid-cols-1 gap-x-8 text-sm sm:grid-cols-2">
{SPECS.map(([key, value]) => (
<div key={key} className="flex justify-between gap-4 border-b border-border py-3">
<dt className="text-muted-foreground">{key}</dt>
<dd className="font-medium">{value}</dd>
</div>
))}
</dl>
</TabsContent>
<TabsContent value="about">
<p className="max-w-3xl text-sm leading-7 text-muted-foreground">
هدفون دیگ پرو با حذف نویز فعال و باتری سیساعته برای رفتوآمد روزانه و کار در فضای شلوغ ساخته شده است.
</p>
</TabsContent>
</Tabs>
<section aria-labelledby="related-title" className="flex flex-col gap-4">
<h2 id="related-title" className="text-xl font-bold">
معمولاً با هم خریده میشوند
</h2>
<div className="flex gap-3 overflow-x-auto pb-2">
{RELATED.map((item) => (
<ProductCard key={item.id} size="sm">
<ProductCardMedia />
<ProductCardBody>
<ProductCardTitle>{item.title}</ProductCardTitle>
<ProductCardPrice amount={formatCurrency(item.price)} />
</ProductCardBody>
</ProductCard>
))}
</div>
</section>
</main>
</ControlSizeProvider>
)
}