بلاک‌ها · فروشگاهی

سبد و «بعداً می‌خرم»

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

سبد با سگمنتِ سبد/ذخیره‌شده، جابه‌جاییِ کالا بینِ دو فهرست و یادآوریِ کاهشِ قیمت.

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

این بلاک از ۶ آیتم دیگ استفاده می‌کند: buttoncontrol-sizeiconspersiantoasttoggle-group

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add 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/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/button.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/toggle-group.json
yarn dlx digdesign@latest add 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/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/button.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/toggle-group.json

۲. کد صفحه

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

cart-saved.tsx
"use client"

import * as React from "react"

import { HeartIcon, ShoppingCartIcon, TrashIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { toast } from "@/components/ui/toast"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"

type Item = { id: string; title: string; image: StockImageName; price: number; note?: string }
const CART: Item[] = [
  { id: "1", title: "گلدانِ سرامیکی دوقلو", image: "productVase", price: 1_290_000 },
  { id: "2", title: "دفترِ یادداشتِ جلدسخت", image: "coverNotebook", price: 180_000 },
]
const SAVED: Item[] = [
  { id: "3", title: "ظرفِ سفالی لعاب‌مات", image: "coverMinimalRoom", price: 890_000, note: "قیمت ٪۱۰ کم شد" },
  { id: "4", title: "ست فنجانِ دست‌ساز", image: "studioSunlit", price: 1_640_000 },
]

/**
 * سبد با «بعداً می‌خرم»: سگمنتِ سبد/ذخیره‌شده با شمار، جابه‌جایی هر کالا بینِ دو فهرست
 * با یک دکمه، و یادآوری کاهشِ قیمتِ کالاهای ذخیره‌شده؛ جمع فقط برای کالاهای سبد.
 */
export default function CartSavedBlock() {
  const [cart, setCart] = React.useState(CART)
  const [saved, setSaved] = React.useState(SAVED)
  const [tab, setTab] = React.useState("cart")
  const list = tab === "cart" ? cart : saved
  const total = cart.reduce((sum, item) => sum + item.price, 0)

  function move(item: Item) {
    if (tab === "cart") {
      setCart((items) => items.filter((entry) => entry.id !== item.id))
      setSaved((items) => [item, ...items])
    } else {
      setSaved((items) => items.filter((entry) => entry.id !== item.id))
      setCart((items) => [...items, item])
    }
  }

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-3xl flex-col gap-8 p-4 sm:p-6">
        <header className="flex flex-wrap items-end justify-between gap-x-4 gap-y-6">
          <h1 className="text-2xl font-bold">سبد خرید</h1>
          <ToggleGroup type="single" variant="segmented" value={tab} onValueChange={(value) => value && setTab(value as string)} aria-label="فهرست">
            <ToggleGroupItem value="cart" className="gap-2">
              سبد
              <span className="text-xs text-muted-foreground tabular-nums">{toFaDigits(cart.length)}</span>
            </ToggleGroupItem>
            <ToggleGroupItem value="saved" className="gap-2">
              بعداً می‌خرم
              <span className="text-xs text-muted-foreground tabular-nums">{toFaDigits(saved.length)}</span>
            </ToggleGroupItem>
          </ToggleGroup>
        </header>

        {list.length === 0 ? (
          <p className="rounded-xl bg-subtle p-8 text-center text-muted-foreground">{tab === "cart" ? "سبد خالی است." : "چیزی برای بعد ذخیره نکرده‌ای."}</p>
        ) : (
          <ul className="flex flex-col gap-3">
            {list.map((item) => (
              <li key={item.id} className="flex items-center gap-4 rounded-xl border border-border bg-card p-4">
                {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                <img src={stockImages[item.image].src} alt="" className="size-20 shrink-0 rounded-lg object-cover" />
                <span className="flex flex-1 flex-col gap-1">
                  <span className="font-medium">{item.title}</span>
                  <span className="font-bold tabular-nums">{formatCurrency(item.price)}</span>
                  {item.note && tab === "saved" && <span className="text-xs font-medium text-success">{item.note}</span>}
                </span>
                <div className="flex flex-col gap-2 sm:flex-row">
                  <Button variant="outline" onClick={() => move(item)}>
                    {tab === "cart" ? <HeartIcon /> : <ShoppingCartIcon />}
                    {tab === "cart" ? "بعداً" : "به سبد"}
                  </Button>
                  <Button variant="ghost" size="icon" aria-label={`حذفِ ${item.title}`} onClick={() => {
                      const set = tab === "cart" ? setCart : setSaved
                      const index = list.findIndex((row) => row.id === item.id)
                      set((rows) => rows.filter((row) => row.id !== item.id))
                      toast(`«${item.title}» حذف شد.`, { action: { label: "برگردان", onClick: () => set((rows) => (rows.some((row) => row.id === item.id) ? rows : [...rows.slice(0, index), item, ...rows.slice(index)])) } })
                    }}>
                    <TrashIcon />
                  </Button>
                </div>
              </li>
            ))}
          </ul>
        )}

        {tab === "cart" && cart.length > 0 && (
          <div className="flex flex-wrap items-center justify-between gap-4 border-t border-border pt-8">
            <span className="flex flex-col">
              <span className="text-sm text-muted-foreground">جمعِ سبد</span>
              <span className="text-xl font-black tabular-nums">{formatCurrency(total)}</span>
            </span>
            <Button>ادامهٔ خرید</Button>
          </div>
        )}
      </main>
    </ControlSizeProvider>
  )
}