علاقهمندیها
پیشنمایش تمامصفحهگریدِ کارتِ محصول با عکس، تبِ همه/تخفیفدار/موجود، برداشتن با امکانِ برگرداندن، افزودن به سبد و «موجود شد خبرم کن» برای ناموجودها، و حالتِ خالی.
۱. نصب کامپوننتها
این بلاک از ۱۰ آیتم دیگ استفاده میکند: add-to-cartbadgebuttoncontrol-sizeempty-stateiconspersianproduct-cardtoasttoggle-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/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.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/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.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/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.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/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
wishlist.tsx
"use client"
import * as React from "react"
import { BellIcon, HeartIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { AddToCart } from "@/components/ui/add-to-cart"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateTitle } from "@/components/ui/empty-state"
import { ProductCard, ProductCardAction, ProductCardBadge, ProductCardBody, ProductCardCategory, ProductCardFooter, ProductCardGrid, ProductCardMedia, ProductCardPrice, ProductCardTitle } from "@/components/ui/product-card"
import { toast } from "@/components/ui/toast"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
type Item = {
id: string
title: string
category: string
image: StockImageName
price: number
oldPrice?: number
inStock: boolean
}
const INITIAL: Item[] = [
{ id: "w1", title: "گلدانِ سرامیکی دوقلو", category: "دکوراسیون", image: "productVase", price: 1_290_000, oldPrice: 1_520_000, inStock: true },
{ id: "w2", title: "ست لیوانِ کاغذی برنددار", category: "کافه", image: "projectCafeBrand", price: 420_000, inStock: true },
{ id: "w3", title: "میزِ کارِ چوبی مینیمال", category: "مبلمان", image: "coverMinimalRoom", price: 8_900_000, inStock: false },
{ id: "w4", title: "دفترچه و خودکارِ فلزی", category: "نوشتافزار", image: "coverNotebook", price: 360_000, oldPrice: 450_000, inStock: true },
]
/**
* علاقهمندیها: شمارش و تبِ «همه / تخفیفدار / موجود»، گریدِ کارتِ محصول با عکس،
* برداشتن از علاقهمندی، افزودن به سبد، و برای ناموجودها «خبرم کن» بهجای خرید.
* فهرستِ خالی یک راهِ برگشت به فروشگاه دارد.
*/
export default function WishlistBlock() {
const [items, setItems] = React.useState(INITIAL)
const [filter, setFilter] = React.useState("all")
const visible = items.filter((item) => filter === "all" || (filter === "sale" ? item.oldPrice : item.inStock))
const remove = (item: Item) => {
setItems((list) => list.filter((entry) => entry.id !== item.id))
toast(`«${item.title}» از علاقهمندیها برداشته شد.`, {
action: { label: "برگردان", onClick: () => setItems((list) => [...list, item]) },
})
}
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-6xl flex-col gap-8 p-4 sm:p-6">
<header className="flex flex-wrap items-end justify-between gap-x-4 gap-y-8">
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-bold">علاقهمندیها</h1>
<p className="text-muted-foreground">{toFaDigits(items.length)} کالا · قیمتها لحظهای بهروز میشوند</p>
</div>
<ToggleGroup type="single" variant="segmented" value={filter} onValueChange={(value) => value && setFilter(value as string)} aria-label="فیلترِ علاقهمندیها">
<ToggleGroupItem value="all">همه</ToggleGroupItem>
<ToggleGroupItem value="sale">تخفیفدار</ToggleGroupItem>
<ToggleGroupItem value="stock">موجود</ToggleGroupItem>
</ToggleGroup>
</header>
{visible.length === 0 ? (
<EmptyState variant="bordered">
<EmptyStateTitle>{items.length === 0 ? "هنوز چیزی دوست نداشتی" : "کالایی با این فیلتر نیست"}</EmptyStateTitle>
<EmptyStateDescription>روی قلبِ هر کالا بزن تا اینجا بماند و از تخفیفش باخبر شوی.</EmptyStateDescription>
<EmptyStateActions>
<Button>رفتن به فروشگاه</Button>
</EmptyStateActions>
</EmptyState>
) : (
<ProductCardGrid size="lg">
{visible.map((item) => (
<ProductCard key={item.id} size="lg">
<ProductCardMedia>
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages[item.image].src} alt={stockImages[item.image].alt} />
{/* تخفیف کنارِ قیمت آمده؛ روی عکس فقط وضعیتِ ناموجود. */}
{!item.inStock && (
<ProductCardBadge>
<Badge variant="soft">ناموجود</Badge>
</ProductCardBadge>
)}
<ProductCardAction>
<Button variant="outline" size="icon" aria-label={`برداشتنِ ${item.title} از علاقهمندیها`} aria-pressed onClick={() => remove(item)} className="bg-background text-destructive hover:text-destructive">
<HeartIcon filled />
</Button>
</ProductCardAction>
</ProductCardMedia>
<ProductCardBody>
<ProductCardCategory>{item.category}</ProductCardCategory>
<ProductCardTitle>{item.title}</ProductCardTitle>
<ProductCardPrice
amount={formatCurrency(item.price)}
originalAmount={item.oldPrice ? formatCurrency(item.oldPrice) : undefined}
discount={item.oldPrice ? `٪${toFaDigits(Math.round((1 - item.price / item.oldPrice) * 100))}` : undefined}
/>
</ProductCardBody>
<ProductCardFooter>
{item.inStock ? (
<AddToCart fullWidth variant="default" itemName={item.title} />
) : (
<Button variant="outline" fullWidth onClick={() => toast.success("موجود شد، خبرت میکنیم.")}>
<BellIcon />
خبرم کن
</Button>
)}
</ProductCardFooter>
</ProductCard>
))}
</ProductCardGrid>
)}
</main>
</ControlSizeProvider>
)
}