UNPKG

wgp-next-shop-ui-lib

Version:

Библиотека UI-компонентов для Next-Shop с поддержкой e-commerce функций

209 lines (190 loc) 6.51 kB
'use client'; import React, { useState } from 'react'; import { Heart, ShoppingCart, Eye, Star } from 'lucide-react'; const ProductCard = ({ product, onAddToCart = null, onToggleFavorite = null, onViewDetails = null, showRating = true, showFavorite = true, showQuickView = true, customStyles = {}, renderPrice = null, renderImage = null, className = '' }) => { const [isHovered, setIsHovered] = useState(false); const [isImageLoading, setIsImageLoading] = useState(true); if (!product) return null; const { id, name, price, originalPrice, discount, image, images = [], rating = 0, reviewCount = 0, inStock = true, isFavorite = false, badge = null } = product; const defaultStyles = { card: "bg-white rounded-lg shadow-sm hover:shadow-md transition-all duration-300 border border-gray-100 overflow-hidden group", imageContainer: "relative overflow-hidden aspect-square", image: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105", badge: "absolute top-2 left-2 px-2 py-1 rounded text-xs font-medium z-10", actions: "absolute top-2 right-2 flex flex-col gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-300", actionButton: "p-2 bg-white rounded-full shadow-md hover:shadow-lg transition-all duration-200 hover:scale-110", content: "p-4", title: "font-medium text-gray-900 line-clamp-2 mb-2", rating: "flex items-center gap-1 text-sm text-gray-600 mb-2", priceContainer: "flex items-center gap-2 mb-3", price: "font-bold text-lg text-gray-900", originalPrice: "text-sm text-gray-500 line-through", discount: "text-sm text-red-600 font-medium", addToCartButton: "w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition-colors duration-200 font-medium", outOfStockButton: "w-full bg-gray-300 text-gray-500 py-2 px-4 rounded-md cursor-not-allowed", loadingImage: "w-full h-full bg-gray-200 animate-pulse flex items-center justify-center" }; const styles = { ...defaultStyles, ...customStyles }; const getBadgeColor = (badgeType) => { switch (badgeType) { case 'sale': return 'bg-red-500 text-white'; case 'new': return 'bg-green-500 text-white'; case 'hot': return 'bg-orange-500 text-white'; default: return 'bg-gray-500 text-white'; } }; const renderStars = (rating) => { return Array.from({ length: 5 }, (_, i) => ( <Star key={i} size={14} className={i < rating ? 'fill-yellow-400 text-yellow-400' : 'text-gray-300'} /> )); }; const handleAddToCart = (e) => { e.preventDefault(); e.stopPropagation(); onAddToCart?.(product); }; const handleToggleFavorite = (e) => { e.preventDefault(); e.stopPropagation(); onToggleFavorite?.(product); }; const handleQuickView = (e) => { e.preventDefault(); e.stopPropagation(); onViewDetails?.(product); }; return ( <div className={`${styles.card} ${className}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* Изображение */} <div className={styles.imageContainer}> {renderImage ? renderImage(product, isHovered) : ( <> {isImageLoading && ( <div className={styles.loadingImage}> <div className="w-12 h-12 border-2 border-gray-300 border-t-blue-600 rounded-full animate-spin"></div> </div> )} <img src={isHovered && images[1] ? images[1] : image} alt={name} className={styles.image} onLoad={() => setIsImageLoading(false)} onError={() => setIsImageLoading(false)} style={{ display: isImageLoading ? 'none' : 'block' }} /> </> )} {/* Бейдж */} {badge && ( <div className={`${styles.badge} ${getBadgeColor(badge.type)}`}> {badge.text} </div> )} {/* Экшены */} <div className={styles.actions}> {showFavorite && ( <button onClick={handleToggleFavorite} className={`${styles.actionButton} ${isFavorite ? 'text-red-500' : 'text-gray-600'}`} > <Heart size={16} fill={isFavorite ? 'currentColor' : 'none'} /> </button> )} {showQuickView && ( <button onClick={handleQuickView} className={`${styles.actionButton} text-gray-600`} > <Eye size={16} /> </button> )} </div> </div> {/* Контент */} <div className={styles.content}> {/* Название */} <h3 className={styles.title}>{name}</h3> {/* Рейтинг */} {showRating && rating > 0 && ( <div className={styles.rating}> <div className="flex"> {renderStars(rating)} </div> <span>({reviewCount})</span> </div> )} {/* Цена */} <div className={styles.priceContainer}> {renderPrice ? renderPrice(product) : ( <> <span className={styles.price}> {typeof price === 'number' ? `${price.toLocaleString()} ₽` : price} </span> {originalPrice && originalPrice > price && ( <span className={styles.originalPrice}> {typeof originalPrice === 'number' ? `${originalPrice.toLocaleString()} ₽` : originalPrice} </span> )} {discount && ( <span className={styles.discount}> -{discount}% </span> )} </> )} </div> {/* Кнопка добавления в корзину */} {inStock ? ( <button onClick={handleAddToCart} className={styles.addToCartButton} > <ShoppingCart size={16} className="inline mr-2" /> В корзину </button> ) : ( <button disabled className={styles.outOfStockButton} > Нет в наличии </button> )} </div> </div> ); }; export default ProductCard;