267 lines
8.4 KiB
TypeScript
267 lines
8.4 KiB
TypeScript
"use client";
|
|
|
|
import { Eye, ShoppingCart } from "lucide-react";
|
|
import { 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";
|
|
|
|
const STOCK_STATUS = {
|
|
IN_STOCK: "IN_STOCK",
|
|
OUT_OF_STOCK: "OUT_OF_STOCK",
|
|
} as const;
|
|
|
|
type StockStatusCode = keyof typeof STOCK_STATUS;
|
|
|
|
interface ProductPrice {
|
|
regular: number;
|
|
sale?: number;
|
|
currency: string;
|
|
}
|
|
|
|
type Image = {
|
|
srcset: string;
|
|
src: string;
|
|
sizes: string;
|
|
alt: string;
|
|
};
|
|
|
|
type Variant = {
|
|
value: string;
|
|
id: string;
|
|
label: string;
|
|
image: Image;
|
|
};
|
|
interface Product {
|
|
name: string;
|
|
price: ProductPrice;
|
|
link: string;
|
|
variantId: string;
|
|
stockStatusCode: StockStatusCode;
|
|
badges: Array<{
|
|
text: string;
|
|
color: string;
|
|
}>;
|
|
image: Image;
|
|
variants: Array<Variant>;
|
|
}
|
|
|
|
const PRODUCT_CARD: Product = {
|
|
name: "بشقاب سرامیکی",
|
|
price: {
|
|
regular: 990000.0,
|
|
sale: 890000.0,
|
|
currency: "ریال",
|
|
},
|
|
link: "#",
|
|
variantId: "green",
|
|
stockStatusCode: "IN_STOCK",
|
|
badges: [
|
|
{
|
|
text: "حراج",
|
|
color: "oklch(57.7% 0.245 27.325)",
|
|
},
|
|
],
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-1.jpg",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-1.jpg 640w",
|
|
alt: "بشقاب سرامیکی سبز",
|
|
sizes: "(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
|
|
},
|
|
variants: [
|
|
{
|
|
label: "سبز",
|
|
id: "green",
|
|
value: "green",
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-1.jpg",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410297-1.jpg 640w",
|
|
alt: "بشقاب سرامیکی سبز",
|
|
sizes: "(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
|
|
},
|
|
},
|
|
{
|
|
label: "صورتی",
|
|
id: "pink",
|
|
value: "pink",
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410289-3.jpg",
|
|
srcset:
|
|
"/block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410289-3.jpg 1920w, /block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410289-2.jpg 1280w, /block-media/shadcnblocks/block/ecommerce/other/pexels-micheile-10410289-1.jpg 640w",
|
|
alt: "بشقاب سرامیکی صورتی",
|
|
sizes: "(min-width: 1920px) 1920px, (min-width: 1280px) 1280px, 100vw",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
interface ProductCard10Props {
|
|
className?: string;
|
|
}
|
|
|
|
const ProductCard10 = ({ className }: ProductCard10Props) => {
|
|
const [productImage, setProductImage] = useState(PRODUCT_CARD.image);
|
|
|
|
const [selectedVariant, setSelectedVariant] = useState(
|
|
PRODUCT_CARD.variantId,
|
|
);
|
|
const { regular, sale, currency } = PRODUCT_CARD.price;
|
|
|
|
const onValueChange = (value: string) => {
|
|
setSelectedVariant(value);
|
|
};
|
|
|
|
const selectedVariantImages = useMemo(() => {
|
|
return (
|
|
PRODUCT_CARD.variants.find((v) => v.value === selectedVariant)?.image ??
|
|
PRODUCT_CARD.image
|
|
);
|
|
}, [selectedVariant]);
|
|
|
|
useEffect(() => {
|
|
setProductImage(selectedVariantImages);
|
|
}, [selectedVariantImages]);
|
|
|
|
return (
|
|
<div className={cn("flex w-full justify-center", className)}>
|
|
<Card className="group relative block w-full max-w-64 rounded-none border-none bg-background p-0 shadow-none">
|
|
<a
|
|
href={PRODUCT_CARD.link}
|
|
className="absolute inset-0 z-10 size-full"
|
|
></a>
|
|
<CardContent className="p-0">
|
|
<div className="relative overflow-hidden">
|
|
<AspectRatio
|
|
ratio={1}
|
|
className="flex items-center justify-center overflow-hidden rounded-xl bg-muted/20"
|
|
>
|
|
{productImage && (
|
|
<img
|
|
srcSet={productImage.srcset}
|
|
src={productImage.src}
|
|
alt={productImage.alt}
|
|
sizes={productImage.sizes}
|
|
className="size-full origin-center object-contain object-center transition-transform duration-700 group-hover:scale-105"
|
|
/>
|
|
)}
|
|
</AspectRatio>
|
|
{PRODUCT_CARD.stockStatusCode === STOCK_STATUS.OUT_OF_STOCK && (
|
|
<div className="absolute start-2.5 top-2.5 z-60">
|
|
<Badge>فروخته شد</Badge>
|
|
</div>
|
|
)}
|
|
{PRODUCT_CARD.stockStatusCode === STOCK_STATUS.IN_STOCK &&
|
|
PRODUCT_CARD.price.sale && (
|
|
<div className="absolute start-2.5 top-2.5 z-60">
|
|
<Badge variant="destructive">حراج</Badge>
|
|
</div>
|
|
)}
|
|
<div className="absolute inset-x-5 top-1/2 z-60 hidden -translate-y-1/2 md:block">
|
|
<div className="flex flex-col gap-2 opacity-0 duration-700 lg:translate-y-4 lg:group-hover:translate-y-0 lg:group-hover:opacity-100">
|
|
<Button
|
|
disabled={
|
|
PRODUCT_CARD.stockStatusCode === STOCK_STATUS.OUT_OF_STOCK
|
|
}
|
|
className="w-full"
|
|
>
|
|
<ShoppingCart />
|
|
افزودن به سبد
|
|
</Button>
|
|
<Button variant="secondary" className="w-full">
|
|
<Eye />
|
|
مشاهده سریع
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex min-h-44 flex-col gap-2 px-5 pb-8 pt-6 lg:px-7 lg:pb-8">
|
|
<CardTitle className="text-xl leading-none font-medium underline-offset-3 group-hover:underline">
|
|
{PRODUCT_CARD.name}
|
|
</CardTitle>
|
|
<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>
|
|
<div className="relative z-10 mt-4">
|
|
<ProductVariants
|
|
value={selectedVariant}
|
|
onValueChange={onValueChange}
|
|
variants={PRODUCT_CARD.variants}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface ProductVariantsProps {
|
|
value: string;
|
|
onValueChange: (value: string) => void;
|
|
variants: Array<Variant>;
|
|
}
|
|
|
|
const ProductVariants = ({
|
|
value,
|
|
onValueChange,
|
|
variants,
|
|
}: ProductVariantsProps) => {
|
|
return (
|
|
<TooltipProvider>
|
|
<RadioGroup value={value} onValueChange={onValueChange} className="flex">
|
|
{variants.map((item, index) => (
|
|
<Tooltip key={`product-card-10-variant-option-${index}`}>
|
|
<TooltipTrigger asChild>
|
|
<Label
|
|
className="h-11.5 w-8.5 cursor-pointer overflow-hidden rounded-sm has-data-[state=checked]:ring-2"
|
|
htmlFor={item.id}
|
|
>
|
|
<img
|
|
src={item.image.src}
|
|
srcSet={item.image.srcset}
|
|
alt={item.image.alt}
|
|
sizes={item.image.sizes}
|
|
className="size-full object-contain object-center"
|
|
/>
|
|
<RadioGroupItem
|
|
value={item.value}
|
|
id={item.id}
|
|
className="sr-only"
|
|
/>
|
|
</Label>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>{item.label}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
))}
|
|
</RadioGroup>
|
|
</TooltipProvider>
|
|
);
|
|
};
|
|
|
|
export { ProductCard10 };
|