157 lines
5.1 KiB
TypeScript
157 lines
5.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import { Heart, ShoppingCart } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
export interface ProductCard {
|
|
id: string;
|
|
name: string;
|
|
price: string;
|
|
imageSrc: string;
|
|
imageAlt?: string;
|
|
rating: number;
|
|
reviewCount: string;
|
|
isFavorited?: boolean;
|
|
onFavorite?: () => void;
|
|
onProductClick?: () => void;
|
|
}
|
|
|
|
interface ProductCardThreeProps {
|
|
products?: ProductCard[];
|
|
carouselMode?: 'auto' | 'buttons';
|
|
gridVariant: 'uniform-all-items-equal' | 'bento-grid' | 'bento-grid-inverted' | 'two-columns-alternating-heights' | 'asymmetric-60-wide-40-narrow' | 'three-columns-all-equal-width' | 'four-items-2x2-equal-grid';
|
|
animationType: 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal';
|
|
uniformGridCustomHeightClasses?: string;
|
|
title: string;
|
|
titleSegments?: Array<{ type: 'text'; content: string } | { type: 'image'; src: string; alt?: string }>;
|
|
description: string;
|
|
tag?: string;
|
|
tagIcon?: React.ComponentType<any>;
|
|
tagAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
|
|
buttons?: Array<{ text: string; onClick?: () => void; href?: string }>;
|
|
buttonAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal';
|
|
textboxLayout: 'default' | 'split' | 'split-actions' | 'split-description' | 'inline-image';
|
|
useInvertedBackground: boolean;
|
|
ariaLabel?: string;
|
|
className?: string;
|
|
containerClassName?: string;
|
|
cardClassName?: string;
|
|
imageClassName?: string;
|
|
textBoxTitleClassName?: string;
|
|
textBoxTitleImageWrapperClassName?: string;
|
|
textBoxTitleImageClassName?: string;
|
|
textBoxDescriptionClassName?: string;
|
|
cardBrandClassName?: string;
|
|
cardNameClassName?: string;
|
|
cardPriceClassName?: string;
|
|
cardRatingClassName?: string;
|
|
actionButtonClassName?: string;
|
|
gridClassName?: string;
|
|
carouselClassName?: string;
|
|
controlsClassName?: string;
|
|
textBoxClassName?: string;
|
|
textBoxTagClassName?: string;
|
|
textBoxButtonContainerClassName?: string;
|
|
textBoxButtonClassName?: string;
|
|
textBoxButtonTextClassName?: string;
|
|
}
|
|
|
|
export const ProductCardThree: React.FC<ProductCardThreeProps> = ({
|
|
products = [],
|
|
gridVariant,
|
|
animationType,
|
|
title,
|
|
description,
|
|
textboxLayout,
|
|
useInvertedBackground,
|
|
ariaLabel = 'Product section',
|
|
className,
|
|
containerClassName,
|
|
cardClassName,
|
|
imageClassName,
|
|
}) => {
|
|
const [favorites, setFavorites] = useState<Set<string>>(new Set());
|
|
|
|
const toggleFavorite = (productId: string) => {
|
|
const newFavorites = new Set(favorites);
|
|
if (newFavorites.has(productId)) {
|
|
newFavorites.delete(productId);
|
|
} else {
|
|
newFavorites.add(productId);
|
|
}
|
|
setFavorites(newFavorites);
|
|
};
|
|
|
|
const handleProductClick = (product: ProductCard) => {
|
|
product.onProductClick?.();
|
|
};
|
|
|
|
const renderStars = (rating: number) => {
|
|
return (
|
|
<div className="flex gap-1">
|
|
{[1, 2, 3, 4, 5].map((star) => (
|
|
<span key={star} className={star <= rating ? 'text-yellow-400' : 'text-gray-300'}>
|
|
★
|
|
</span>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className={cn('product-card-three', containerClassName)} aria-label={ariaLabel}>
|
|
<div className="header">
|
|
<h2>{title}</h2>
|
|
<p>{description}</p>
|
|
</div>
|
|
|
|
<div className={cn('products-grid', gridClassName)}>
|
|
{products.map((product) => (
|
|
<div key={product.id} className={cn('product-card', cardClassName)}>
|
|
<div className="image-wrapper relative">
|
|
<img
|
|
src={product.imageSrc}
|
|
alt={product.imageAlt || product.name}
|
|
className={cn('w-full h-auto', imageClassName)}
|
|
/>
|
|
<button
|
|
onClick={() => toggleFavorite(product.id)}
|
|
className="absolute top-2 right-2 p-2 rounded-full bg-white/80 hover:bg-white transition-colors"
|
|
aria-label={`Toggle favorite for ${product.name}`}
|
|
>
|
|
<Heart
|
|
size={20}
|
|
className={cn(
|
|
'transition-colors',
|
|
favorites.has(product.id)
|
|
? 'fill-red-500 text-red-500'
|
|
: 'text-gray-400'
|
|
)}
|
|
/>
|
|
</button>
|
|
</div>
|
|
<div className="content">
|
|
<h3 className="text-lg font-semibold">{product.name}</h3>
|
|
<div className="flex items-center gap-2 mt-2">
|
|
{renderStars(product.rating)}
|
|
<span className="text-sm text-gray-600">({product.reviewCount})</span>
|
|
</div>
|
|
<p className="text-2xl font-bold mt-2">{product.price}</p>
|
|
<button
|
|
onClick={() => handleProductClick(product)}
|
|
className="mt-4 w-full px-4 py-2 bg-primary rounded hover:opacity-90 transition-opacity flex items-center justify-center gap-2"
|
|
>
|
|
<ShoppingCart size={18} />
|
|
Add to Cart
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ProductCardThree;
|