بلاک‌ها · موبایل

جزئیاتِ کالا (اپ)

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

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

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

این بلاک از ۷ آیتم دیگ استفاده می‌کند: 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.json
pnpm 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.json
yarn 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.json
bunx --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>
  )
}