378 lines
13 KiB
TypeScript
378 lines
13 KiB
TypeScript
"use client";
|
|
import { Minus, Plus } from "lucide-react";
|
|
import { startTransition, useEffect, useState } from "react";
|
|
|
|
import { Price, PriceValue } from "@/components/shadcnblocks/price";
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardTitle } from "@/components/ui/card";
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "@/components/ui/collapsible";
|
|
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 Image = {
|
|
src: string;
|
|
srcset?: string;
|
|
alt: string;
|
|
sizes?: string;
|
|
};
|
|
|
|
type ProductColorOptions = {
|
|
images: Image[];
|
|
id: string;
|
|
value: string;
|
|
label: string;
|
|
colors: string[];
|
|
};
|
|
|
|
interface Product {
|
|
colorOptions: Array<ProductColorOptions>;
|
|
link: string;
|
|
name: string;
|
|
price: ProductPrice;
|
|
variant: string;
|
|
}
|
|
|
|
interface ProductCardColorOptionsProps {
|
|
options: ProductColorOptions[];
|
|
onValueChange: (value: string) => void;
|
|
value: string;
|
|
}
|
|
|
|
const PRODUCT_CARD: Product = {
|
|
name: "هودی اسلیت اسنشال",
|
|
variant: "product-1-1",
|
|
price: {
|
|
regular: 490000.0,
|
|
sale: 380000.0,
|
|
currency: "ریال",
|
|
},
|
|
colorOptions: [
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Man-in-White-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Man-in-White-Hoodie-2.png 1200w, /block-media/shadcnblocks/block/ecommerce/clothes/Man-in-White-Hoodie-1.png 640w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Man-in-Hoodie-&-Jacket-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Man-in-Hoodie-&-Jacket-2.png 904w, /block-media/shadcnblocks/block/ecommerce/clothes/Man-in-Hoodie-&-Jacket-1.png 482w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-1-1",
|
|
value: "product-1-1",
|
|
label: "سفید",
|
|
colors: ["#F4F2F0"],
|
|
},
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Casual-Outfit-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Casual-Outfit-2.png 675w, /block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Casual-Outfit-1.png 360w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Person-in-Green-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Person-in-Green-Hoodie-2.png 655w, /block-media/shadcnblocks/block/ecommerce/clothes/Person-in-Green-Hoodie-1.png 349w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-1-2",
|
|
value: "product-1-2",
|
|
label: "زیتونی",
|
|
colors: ["#52462C"],
|
|
},
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Navy-and-Orange-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Navy-and-Orange-Hoodie-2.png 675w, /block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Navy-and-Orange-Hoodie-1.png 360w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-1-3",
|
|
value: "product-1-3",
|
|
label: "سرمهای و نارنجی",
|
|
colors: ["#2B233A", "#F6862E"],
|
|
},
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Blue-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Blue-Hoodie-2.png 675w, /block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Blue-Hoodie-1.png 360w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-2-1",
|
|
value: "product-2-1",
|
|
label: "آبی",
|
|
colors: ["#233A4E", "#879698"],
|
|
},
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Beige-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Beige-Hoodie-2.png 675w, /block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Beige-Hoodie-1.png 360w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Close-up-of-Beige-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Close-up-of-Beige-Hoodie-2.png 655w, /block-media/shadcnblocks/block/ecommerce/clothes/Close-up-of-Beige-Hoodie-1.png 349w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-2-2",
|
|
value: "product-2-2",
|
|
label: "بژ",
|
|
colors: ["#BDA994"],
|
|
},
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Maroon-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Maroon-Hoodie-2.png 675w, /block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Maroon-Hoodie-1.png 360w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-3-1",
|
|
value: "product-3-1",
|
|
label: "شرابی",
|
|
colors: ["#54272E"],
|
|
},
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Young-Person-in-Bright-Fashion-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Person-in-Bright-Fashion-2.png 675w, /block-media/shadcnblocks/block/ecommerce/clothes/Young-Person-in-Bright-Fashion-1.png 360w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-3-2",
|
|
value: "product-3-2",
|
|
label: "نارنجی سوخته",
|
|
colors: ["#C75922"],
|
|
},
|
|
{
|
|
images: [
|
|
{
|
|
src: "/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Green-Hoodie-2.png",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Green-Hoodie-2.png 675w, /block-media/shadcnblocks/block/ecommerce/clothes/Young-Man-in-Green-Hoodie-1.png 360w",
|
|
alt: "هودی اسلیت اسنشال",
|
|
sizes: "100w",
|
|
},
|
|
],
|
|
id: "product-3-3",
|
|
value: "product-3-3",
|
|
label: "سبز لیمویی",
|
|
colors: ["#91A12E"],
|
|
},
|
|
],
|
|
link: "#",
|
|
};
|
|
|
|
interface ProductCard21Props {
|
|
className?: string;
|
|
}
|
|
|
|
const ProductCard21 = ({ className }: ProductCard21Props) => {
|
|
const [selectedVariant, setVariant] = useState(PRODUCT_CARD.variant);
|
|
const [variantImages, setSelectedVariantImages] = useState<
|
|
Image[] | undefined
|
|
>();
|
|
const { regular, sale, currency } = PRODUCT_CARD.price;
|
|
|
|
const onValueChange = (value: string) => {
|
|
setVariant(value);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const checkedColorImages = PRODUCT_CARD.colorOptions.find(
|
|
(option) => option.value === selectedVariant,
|
|
)?.images;
|
|
startTransition(() => {
|
|
setSelectedVariantImages(checkedColorImages);
|
|
});
|
|
}, [selectedVariant]);
|
|
|
|
return (
|
|
<div className={cn("flex w-full justify-center", className)}>
|
|
<Card className="w-full max-w-64 rounded-none border-none bg-background p-0 shadow-none">
|
|
<CardContent className="flex flex-col px-0">
|
|
<a href={PRODUCT_CARD.link} className="group">
|
|
<AspectRatio
|
|
ratio={1}
|
|
className="flex items-center justify-center overflow-hidden bg-muted/20"
|
|
>
|
|
{variantImages &&
|
|
variantImages.map((img, index) => (
|
|
<img
|
|
key={`product-card-21-img-${index}`}
|
|
src={img.src}
|
|
srcSet={img.srcset}
|
|
alt={img.alt}
|
|
sizes={img.sizes}
|
|
className="absolute inset-0 size-full object-contain object-center transition-opacity duration-200 first:opacity-100 group-hover:first:not-only:opacity-0 nth-2:opacity-0 group-hover:nth-2:opacity-100"
|
|
/>
|
|
))}
|
|
</AspectRatio>
|
|
</a>
|
|
<div className="min-h-44 space-y-2 px-5 pb-8 pt-6 lg:px-7 lg:pb-8">
|
|
<ProductCardColorOptions
|
|
onValueChange={onValueChange}
|
|
value={selectedVariant}
|
|
options={PRODUCT_CARD.colorOptions}
|
|
/>
|
|
<a href={PRODUCT_CARD.link}>
|
|
<CardTitle className="text-xl leading-none font-medium">
|
|
{PRODUCT_CARD.name}
|
|
</CardTitle>
|
|
</a>
|
|
<Price className="mt-3 text-sm font-medium" onSale={sale != null}>
|
|
<PriceValue price={regular} currency={currency} variant="regular" />
|
|
<PriceValue
|
|
price={sale}
|
|
currency={currency}
|
|
variant="sale"
|
|
className="text-red-800"
|
|
/>
|
|
</Price>
|
|
<Button variant="outline" size="sm" className="mt-3 rounded-full">
|
|
<Plus />
|
|
افزودن
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ProductCardColorOptions = ({
|
|
options,
|
|
value,
|
|
onValueChange,
|
|
}: ProductCardColorOptionsProps) => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
if (!options) return;
|
|
|
|
return (
|
|
<TooltipProvider>
|
|
<Collapsible
|
|
open={isOpen}
|
|
onOpenChange={setIsOpen}
|
|
className="flex items-start justify-between gap-4"
|
|
>
|
|
<RadioGroup
|
|
value={value}
|
|
onValueChange={onValueChange}
|
|
className="flex flex-wrap items-center gap-2"
|
|
>
|
|
{options.slice(0, 5).map((option, index) => (
|
|
<Tooltip key={`product-card-21-${index}`}>
|
|
<TooltipTrigger asChild>
|
|
<Label
|
|
style={
|
|
option.colors?.length === 1
|
|
? {
|
|
backgroundColor: option.colors[0],
|
|
}
|
|
: {
|
|
backgroundImage: `linear-gradient(45deg, ${option.colors?.[0]} 0%, ${option.colors?.[0]} 50%, ${option.colors?.[1]} 50%, ${option.colors?.[1]} 100%)`,
|
|
}
|
|
}
|
|
htmlFor={option.id}
|
|
className="size-8 cursor-pointer rounded-full border-4 border-background has-data-[state=checked]:ring"
|
|
>
|
|
<RadioGroupItem
|
|
value={option.value}
|
|
id={option.id}
|
|
className="sr-only"
|
|
/>
|
|
</Label>
|
|
</TooltipTrigger>
|
|
<TooltipContent>{option.label}</TooltipContent>
|
|
</Tooltip>
|
|
))}
|
|
|
|
<CollapsibleContent className="contents">
|
|
{options.slice(5).map((option, index) => (
|
|
<Tooltip key={`product-card-21-${index}`}>
|
|
<TooltipTrigger asChild>
|
|
<Label
|
|
style={
|
|
option.colors?.length === 1
|
|
? {
|
|
backgroundColor: option.colors[0],
|
|
}
|
|
: {
|
|
backgroundImage: `linear-gradient(45deg, ${option.colors?.[0]} 0%, ${option.colors?.[0]} 50%, ${option.colors?.[1]} 50%, ${option.colors?.[1]} 100%)`,
|
|
}
|
|
}
|
|
htmlFor={option.id}
|
|
className="size-8 cursor-pointer rounded-full border-4 border-background has-data-[state=checked]:ring"
|
|
>
|
|
<RadioGroupItem
|
|
value={option.value}
|
|
id={option.id}
|
|
className="sr-only"
|
|
/>
|
|
</Label>
|
|
</TooltipTrigger>
|
|
<TooltipContent>{option.label}</TooltipContent>
|
|
</Tooltip>
|
|
))}
|
|
</CollapsibleContent>
|
|
</RadioGroup>
|
|
<CollapsibleTrigger asChild>
|
|
<Button variant="outline" size="icon" className="size-8 rounded-full">
|
|
{isOpen ? <Minus /> : <Plus />}
|
|
</Button>
|
|
</CollapsibleTrigger>
|
|
</Collapsible>
|
|
</TooltipProvider>
|
|
);
|
|
};
|
|
|
|
export { ProductCard21 };
|