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

فهرستِ ردیفیِ محصول

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

فهرستِ ردیفی برای مقایسهٔ سریع: عکس، مشخصاتِ کوتاه، امتیاز با تعدادِ نظر، ارسالِ سریع، قیمت و سبد؛ فیلترهای فعال به‌صورتِ دکمهٔ حذف‌شدنی.

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

این بلاک از ۶ آیتم دیگ استفاده می‌کند: add-to-cartbuttoncontrol-sizeiconspersiantoggle-group

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

۲. کد صفحه

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

product-list-rows.tsx
"use client"

import * as React from "react"

import { StarIcon, TruckIcon, XIcon } from "@/components/icons"
import { AddToCart } from "@/components/ui/add-to-cart"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"

const PRODUCTS: { id: string; title: string; image: StockImageName; price: number; rating: number; reviews: number; specs: string; fast?: boolean; brand: string }[] = [
  { id: "1", title: "گلدانِ سرامیکی دوقلو", image: "productVase", price: 1_290_000, rating: 4.8, reviews: 212, specs: "سرامیک · ۳۲ سانتی‌متر · دست‌ساز", fast: true, brand: "خانهٔ سفال" },
  { id: "2", title: "ظرفِ سفالی لعاب‌مات", image: "coverMinimalRoom", price: 890_000, rating: 4.5, reviews: 96, specs: "سفال · ۲۴ سانتی‌متر", brand: "خانهٔ سفال" },
  { id: "3", title: "ست فنجانِ دست‌ساز", image: "studioSunlit", price: 1_640_000, rating: 4.7, reviews: 141, specs: "شش عدد · لعابِ براق", fast: true, brand: "کارگاهِ میبد" },
  { id: "4", title: "جاشمعی سنگی", image: "coverNotebook", price: 420_000, rating: 4.2, reviews: 38, specs: "سنگِ مصنوعی · ۱۲ سانتی‌متر", brand: "کارگاهِ میبد" },
]
const SORTS = [
  { value: "popular", label: "محبوب" },
  { value: "cheap", label: "ارزان" },
  { value: "rating", label: "امتیاز" },
]

/**
 * فهرستِ ردیفی محصول (برای مقایسهٔ سریع): هر ردیف عکس، مشخصاتِ کوتاه، امتیاز با تعدادِ نظر،
 * نشانِ ارسالِ سریع و قیمت با دکمهٔ سبد؛ فیلترهای فعال به‌صورتِ چیپِ حذف‌شدنی بالای فهرست.
 */
export default function ProductListRowsBlock() {
  const [sort, setSort] = React.useState("popular")
  const [filters, setFilters] = React.useState(["ارسالِ سریع", "خانهٔ سفال"])
  const visible = PRODUCTS.filter((product) => (!filters.includes("ارسالِ سریع") || product.fast) && (!filters.includes("خانهٔ سفال") || product.brand === "خانهٔ سفال")).sort((a, b) =>
    sort === "cheap" ? a.price - b.price : sort === "rating" ? b.rating - a.rating : b.reviews - a.reviews
  )

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-4xl flex-col gap-6 p-4 sm:p-6">
        <header className="flex flex-wrap items-end justify-between gap-x-4 gap-y-6">
          <div className="flex flex-col gap-1">
            <h1 className="text-2xl font-bold">سفال و سرامیک</h1>
            <p className="text-muted-foreground">{toFaDigits(visible.length)} کالا</p>
          </div>
          <ToggleGroup type="single" variant="segmented" value={sort} onValueChange={(value) => value && setSort(value as string)} aria-label="مرتب‌سازی">
            {SORTS.map((item) => (
              <ToggleGroupItem key={item.value} value={item.value}>
                {item.label}
              </ToggleGroupItem>
            ))}
          </ToggleGroup>
        </header>
        {filters.length > 0 && (
          <div className="flex flex-wrap items-center gap-2" aria-label="فیلترهای فعال">
            {filters.map((filter) => (
              <Button key={filter} variant="outline" onClick={() => setFilters((list) => list.filter((item) => item !== filter))} aria-label={`برداشتنِ فیلترِ ${filter}`}>
                {filter}
                <XIcon />
              </Button>
            ))}
            <Button variant="ghost" onClick={() => setFilters([])}>
              پاک کردنِ همه
            </Button>
          </div>
        )}
        <ul className="flex flex-col divide-y divide-border">
          {visible.map((product) => (
            <li key={product.id} className="grid grid-cols-[6rem_minmax(0,1fr)] gap-4 py-5 sm:grid-cols-[8rem_minmax(0,1fr)_auto] sm:items-center">
              {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
              <img src={stockImages[product.image].src} alt={stockImages[product.image].alt} loading="lazy" className="aspect-square w-full rounded-lg object-cover" />
              <div className="flex flex-col gap-1.5">
                <span className="text-xs text-muted-foreground">{product.brand}</span>
                <a href="#" className="font-bold hover:underline">
                  {product.title}
                </a>
                <span className="text-sm text-muted-foreground">{product.specs}</span>
                <span className="flex flex-wrap items-center gap-3 text-sm">
                  <span className="flex items-center gap-1">
                    <StarIcon aria-hidden className="size-4 fill-[var(--dig-orange-400)] text-[var(--dig-orange-400)]" />
                    {toFaDigits(String(product.rating).replace(".", "٫"))}
                    <span className="text-muted-foreground">({toFaDigits(product.reviews)})</span>
                  </span>
                  {product.fast && (
                    <span className="flex items-center gap-1 text-success">
                      <TruckIcon aria-hidden className="size-4" />
                      ارسالِ فردا
                    </span>
                  )}
                </span>
              </div>
              <div className="col-span-2 flex items-center justify-between gap-3 sm:col-span-1 sm:flex-col sm:items-end">
                <span className="text-lg font-black tabular-nums">{formatCurrency(product.price)}</span>
                <AddToCart variant="default" label="افزودن" itemName={product.title} />
              </div>
            </li>
          ))}
          {visible.length === 0 && <li className="py-10 text-center text-muted-foreground">با این فیلترها کالایی نیست؛ یکی را بردار.</li>}
        </ul>
      </main>
    </ControlSizeProvider>
  )
}