Files
reui-blocks/blocks/product-card9.tsx
T

331 lines
11 KiB
TypeScript

"use client";
import { ShoppingBag } from "lucide-react";
import { startTransition, useEffect, useMemo, useState } from "react";
import { Price, PriceValue } from "@/components/shadcnblocks/price";
import { AspectRatio } from "@/components/ui/aspect-ratio";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
interface ProductPrice {
regular: number;
sale?: number;
currency: string;
}
type Badge = {
text: string;
color: string;
};
type Image = {
srcset?: string;
src: string;
sizes?: string;
alt: string;
};
type Variant = {
color: string;
value: string;
id: string;
label: string;
link: string;
images: Array<Image>;
};
interface ProductColorsProps {
variants: Omit<Variant, "images">[];
value: string;
onOptionHover: (value: string) => void;
}
interface BadgesProps {
badges?: Array<Badge>;
}
interface Product {
name: string;
price: ProductPrice;
category: {
label: string;
link: string;
};
link: string;
badges?: Array<Badge>;
variants: Array<Variant>;
images: Array<Image>;
}
const PRODUCT_CARD: Product = {
name: "لیوان سرامیکی",
price: {
regular: 190000.0,
sale: 150000.0,
currency: "ریال",
},
category: {
label: "لیوان‌ها",
link: "#",
},
link: "#",
badges: [
{
text: "بهترین انتخاب!",
color: "oklch(54.6% 0.245 262.881)",
},
{
text: "حراج",
color: "oklch(57.7% 0.245 27.325)",
},
],
variants: [
{
color: "#A8A081",
link: "#",
value: "light_brown",
id: "light_brown",
label: "قهوه‌ای روشن",
images: [
{
src: "/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-1.jpg",
srcset:
"/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-1.jpg 640w",
alt: "لیوان سرامیکی قهوه‌ای روشن",
sizes:
"(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
},
{
src: "/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-1.jpg",
srcset:
"/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-1.jpg 640w",
alt: "لیوان سرامیکی نمای دوم",
sizes:
"(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
},
],
},
{
color: "#C2CCCD",
link: "#",
value: "light_blue",
id: "light_blue",
label: "آبی روشن",
images: [
{
src: "/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-1.jpg",
srcset:
"/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-1.jpg 640w",
alt: "لیوان سرامیکی آبی روشن",
sizes:
"(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
},
{
src: "/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-1.jpg",
srcset:
"/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-1.jpg 640w",
alt: "لیوان سرامیکی نمای دوم",
sizes:
"(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
},
],
},
],
images: [
{
src: "/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-1.jpg",
srcset:
"/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-bWlbcvFMUh4-unsplash-1.jpg 640w",
alt: "لیوان سرامیکی",
sizes: "(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
},
{
src: "/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-1.jpg",
srcset:
"/block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/cafeconcetto-L2r9L6Llyp4-unsplash-1.jpg 640w",
alt: "لیوان سرامیکی نمای دوم",
sizes: "(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
},
],
};
interface ProductCard9Props {
className?: string;
}
const ProductCard9 = ({ className }: ProductCard9Props) => {
const { regular, sale, currency } = PRODUCT_CARD.price;
const [selectedColor, setSelectedColor] = useState<string>("");
const [productImages, setProductImages] = useState(PRODUCT_CARD.images);
const onOptionHover = (value: string) => {
setSelectedColor(value);
};
const selectedVariantImages = useMemo(() => {
return (
PRODUCT_CARD.variants.find((v) => v.value === selectedColor)?.images ??
PRODUCT_CARD.images
);
}, [selectedColor]);
useEffect(() => {
startTransition(() => {
setProductImages(selectedVariantImages);
});
}, [selectedColor, selectedVariantImages]);
return (
<div className={cn("flex w-full justify-center", className)}>
<Card className="relative block w-full max-w-64 rounded-none border-none bg-background p-0 shadow-none">
<CardContent className="p-0">
<div className="group relative overflow-hidden">
<a href={PRODUCT_CARD.link}>
<AspectRatio
ratio={1}
className="flex items-center justify-center overflow-hidden rounded-xl bg-muted/20"
>
{productImages.map((img, index) => (
<div
className="absolute inset-0 size-full transition-opacity duration-700 first:z-20 group-hover:first:opacity-0 last:z-10 first:[&>img]:scale-105 first:[&>img]:delay-50 group-hover:first:[&>img]:scale-100 last:[&>img]:scale-105 group-hover:last:[&>img]:scale-100"
key={`product-list-9-card-img-wrapper-${index}`}
>
<img
srcSet={img.srcset}
src={img.src}
alt={img.alt}
sizes={img.sizes}
className="size-full object-contain object-center transition-transform duration-700"
/>
</div>
))}
</AspectRatio>
</a>
<div className="absolute start-2.5 top-2.5 z-60">
<Badges badges={PRODUCT_CARD.badges} />
</div>
<div className="absolute end-2.5 bottom-2.5 z-60 md:hidden">
<Button variant="secondary" size="icon" className="rounded-full">
<ShoppingBag />
</Button>
</div>
<div className="absolute inset-x-4 bottom-4 z-60 hidden md:block">
<Button className="w-full rounded-full opacity-0 duration-700 lg:translate-y-4 lg:group-hover:translate-y-0 lg:group-hover:opacity-100">
افزودن به سبد
</Button>
</div>
</div>
<div className="flex min-h-44 flex-col gap-2 px-5 pb-8 pt-6 lg:px-7 lg:pb-8">
<a
href={PRODUCT_CARD.category.link}
className="text-xs leading-loose font-bold text-muted-foreground uppercase underline-offset-4 hover:underline"
>
{PRODUCT_CARD.category.label}
</a>
<a
href={PRODUCT_CARD.link}
className="underline-offset-4 hover:underline"
>
<CardTitle className="text-xl leading-none font-medium">
{PRODUCT_CARD.name}
</CardTitle>
</a>
<Price
onSale={sale != null}
className="text-sm leading-normal font-bold"
>
<PriceValue
price={sale}
currency={currency}
variant="sale"
className="text-red-700"
/>
<PriceValue price={regular} currency={currency} variant="regular" />
</Price>
<ProductColors
value={selectedColor}
onOptionHover={onOptionHover}
variants={PRODUCT_CARD.variants}
/>
</div>
</CardContent>
</Card>
</div>
);
};
const ProductColors = ({
value,
onOptionHover,
variants,
}: ProductColorsProps) => {
if (!variants) return;
return (
<TooltipProvider>
<RadioGroup value={value} className="mt-2 flex items-center gap-2">
{variants.map((item, index) => (
<Label
className="relative block pb-1.5 after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:w-full after:bg-primary after:opacity-0 after:transition-opacity hover:after:opacity-100 has-data-[state=checked]:after:opacity-100"
htmlFor={item.id}
key={`product-list-9-color-${index}`}
>
<Tooltip>
<TooltipTrigger asChild>
<a href={item.link}>
<div
style={{
backgroundColor: item.color,
}}
className="size-5.5 border"
onMouseEnter={() => onOptionHover(item.value)}
></div>
</a>
</TooltipTrigger>
<TooltipContent>
<p>{item.label}</p>
</TooltipContent>
</Tooltip>
<RadioGroupItem
value={item.value}
id={item.id}
className="sr-only"
/>
</Label>
))}
</RadioGroup>
</TooltipProvider>
);
};
const Badges = ({ badges }: BadgesProps) => {
if (!badges) return;
return (
<div className="flex flex-col gap-1.5">
{badges.map((item, index) => (
<Badge
style={{ backgroundColor: item.color }}
key={`product-list-9-badge-${index}`}
className="rounded-full dark:text-white"
>
{item.text}
</Badge>
))}
</div>
);
};
export { ProductCard9 };