جزئیاتِ کالا (اپ)
پیشنمایش تمامصفحهجزئیاتِ کالا در اپ: عکسهای تمامعرض با اسکرولِ افقی و نشانگر، بازگشت/پسند/اشتراک روی عکس، رنگ با سگمنت و نوارِ پایینیِ چسبان با تعداد و افزودن به سبد.
۱. نصب کامپوننتها
این بلاک از ۷ آیتم دیگ استفاده میکند: add-to-cartapp-barbuttoncolor-swatch-pickercontrol-sizeiconspersian
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/button.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/persian.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/button.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/persian.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/button.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/persian.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/app-bar.json https://docs.digdesign.ir/r/button.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/persian.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
mobile-product.tsx
"use client"
import * as React from "react"
import { ChevronRightIcon, HeartIcon, ShareIcon, StarIcon, TruckIcon } from "@/components/icons"
import { AppBar, AppBarActions, AppBarLeading } from "@/components/ui/app-bar"
import { AddToCart } from "@/components/ui/add-to-cart"
import { Button } from "@/components/ui/button"
import { ColorSwatchPicker, ColorSwatchPickerItem } from "@/components/ui/color-swatch-picker"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"
const PHOTOS = [stockImages.productVase, stockImages.coverMinimalRoom, stockImages.studioSunlit]
const COLORS = [
{ value: "cream", label: "کرم", swatch: "var(--dig-orange-100)" },
{ value: "olive", label: "سبزِ زیتونی", swatch: "var(--dig-green-700)" },
{ value: "navy", label: "سرمهای", swatch: "var(--dig-blue-900)" },
]
/**
* جزئیاتِ کالا در اپ: عکسهای تمامعرض با اسکرولِ افقی قفلشونده و نشانگر، دکمههای
* نوارِ بالا با بازگشت، پسند و اشتراک (جدا از عکس تا با فلشِ گالری اشتباه نشود)، رنگ با دایرههای رنگی، و نوارِ پایینی چسبان با قیمت و «افزودن به سبد».
*/
export default function MobileProductBlock() {
const [photo, setPhoto] = React.useState(0)
const [liked, setLiked] = React.useState(false)
const [color, setColor] = React.useState(COLORS[0].value)
const [quantity, setQuantity] = React.useState(0)
return (
<ControlSizeProvider size="xl" asChild>
<div className="mx-auto flex min-h-svh w-full max-w-md flex-col bg-background">
<AppBar sticky>
<AppBarLeading>
<Button variant="ghost" size="icon" aria-label="بازگشت">
<ChevronRightIcon className="ltr:rotate-180" />
</Button>
</AppBarLeading>
<AppBarActions>
<Button variant="ghost" size="icon" aria-label="اشتراکگذاری">
<ShareIcon />
</Button>
<Button variant="ghost" size="icon" aria-label={liked ? "حذف از علاقهمندیها" : "افزودن به علاقهمندیها"} aria-pressed={liked} onClick={() => setLiked((value) => !value)} className={liked ? "text-destructive" : ""}>
<HeartIcon filled={liked} />
</Button>
</AppBarActions>
</AppBar>
<div className="relative">
<div
className="flex snap-x snap-mandatory overflow-x-auto [scrollbar-width:none]"
onScroll={(event) => setPhoto(Math.round(Math.abs(event.currentTarget.scrollLeft) / event.currentTarget.clientWidth))}
>
{PHOTOS.map((image, index) => (
// eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ
<img key={image.src} src={image.src} alt={index === 0 ? image.alt : ""} className="aspect-square w-full shrink-0 snap-center object-cover" />
))}
</div>
<div aria-hidden className="absolute inset-x-0 bottom-3 flex justify-center gap-1.5">
{PHOTOS.map((image, index) => (
<span key={image.src} className={`h-1.5 rounded-full transition-all ${index === photo ? "w-5 bg-[var(--dig-white)]" : "w-1.5 bg-[var(--dig-neutral-400)]"}`} />
))}
</div>
</div>
<main className="flex flex-1 flex-col gap-6 p-4">
<div className="flex flex-col gap-2">
<span className="text-sm text-muted-foreground">خانهٔ سفال</span>
<h1 className="text-2xl font-bold">گلدانِ سرامیکی دوقلو</h1>
<span className="flex items-center gap-1.5 text-sm">
<StarIcon aria-hidden className="size-4 fill-[var(--dig-orange-400)] text-[var(--dig-orange-400)]" />
۴٫۸ <span className="text-muted-foreground">(۲۱۲ نظر)</span>
</span>
</div>
<div className="flex flex-col gap-3">
<span id="mp-color" className="text-sm font-medium">
رنگ: {COLORS.find((item) => item.value === color)?.label}
</span>
<ColorSwatchPicker value={color} onValueChange={setColor} aria-labelledby="mp-color" size="lg" radius="full" indicator="ring" className="gap-4 p-1">
{COLORS.map((item) => (
<ColorSwatchPickerItem key={item.value} value={item.value} color={item.swatch} label={item.label} />
))}
</ColorSwatchPicker>
</div>
<div className="flex items-center gap-3 rounded-xl bg-subtle p-4 text-sm">
<TruckIcon aria-hidden className="size-5 text-brand-secondary" />
<span className="flex-1">ارسالِ فردا، رایگان برای خریدِ بالای ۳ میلیون</span>
</div>
<p className="leading-8 text-muted-foreground">دستسازِ کارگاهِ لالجین با لعابِ مات؛ مناسبِ گلِ طبیعی و خشک. ارتفاعِ ۳۲ سانتیمتر.</p>
</main>
<footer className="sticky bottom-0 flex items-center gap-3 border-t border-border bg-background p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
<span className="flex shrink-0 flex-col">
<span className="text-xs text-muted-foreground">{quantity > 1 ? `${toFaDigits(quantity)} عدد` : "قیمت"}</span>
<span className="font-black tabular-nums">{formatCurrency(1_290_000 * Math.max(1, quantity))}</span>
</span>
<AddToCart variant="default" fullWidth itemName="گلدان" max={10} quantity={quantity} onQuantityChange={setQuantity} className="flex-1" />
</footer>
</div>
</ControlSizeProvider>
)
}