UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

514 lines (511 loc) 21.2 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { useState, useEffect, useCallback } from 'react'; import { GlassCore } from '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import '../../primitives/motion/MotionFramer.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { useEcommerce } from './GlassEcommerceProvider.js'; const ProductCard = ({ recommendation, variant, showPrice = true, showRating = true, showQuickActions = true, onProductClick, onAddToCart, onAddToWishlist }) => { const { product, score, reason, explanation, confidence } = recommendation; const [imageError, setImageError] = useState(false); const [isHovered, setIsHovered] = useState(false); const getReasonIcon = reason => { switch (reason) { case "similar": return "🔄"; case "trending": return "📈"; case "personalized": return "🎯"; case "bought-together": return "🛍️"; case "viewed-together": return "👀"; default: return "✨"; } }; const getReasonColor = reason => { switch (reason) { case "similar": return "bg-blue-100 text-blue-800"; case "trending": return "bg-red-100 text-red-800"; case "personalized": return "bg-purple-100 text-purple-800"; case "bought-together": return "bg-green-100 text-green-800"; case "viewed-together": return "bg-orange-100 text-orange-800"; default: return "bg-gray-100 text-gray-800"; } }; const renderCompactCard = () => jsxs("div", { className: 'glass-flex glass-items-center glass-gap-3 glass-p-3 hover:glass-surface-subtle glass-radius-lg transition-colors', children: [jsx("img", { src: imageError ? "https://via.placeholder.com/60x60?text=No+Image" : product.thumbnail || product.images[0], alt: product.name, className: 'w-12 h-12 object-cover glass-radius', onError: () => setImageError(true) }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsx("h3", { className: 'font-medium glass-text-secondary glass-text-sm truncate', children: product.name }), showPrice && jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 mt-1', children: [jsxs("span", { className: 'glass-text-sm font-medium glass-text-secondary', children: ["$", product.price.toFixed(2)] }), product.originalPrice && product.originalPrice > product.price && jsxs("span", { className: 'glass-text-xs glass-text-secondary line-through', children: ["$", product.originalPrice.toFixed(2)] })] })] }), showQuickActions && jsxs("div", { className: "glass-flex glass-items-center glass-gap-1", children: [jsx("button", { onClick: e => { e.stopPropagation(); onAddToWishlist(product.id); }, className: 'glass-p-1 glass-text-secondary hover:text-primary transition-colors glass-focus glass-touch-target glass-contrast-guard', title: "Add to wishlist", children: "\u2661" }), jsx("button", { onClick: e => { e.stopPropagation(); onAddToCart(product.id); }, className: 'glass-p-1 glass-text-secondary hover:text-primary transition-colors glass-focus glass-touch-target glass-contrast-guard', title: "Add to cart", children: "\uD83D\uDED2" })] })] }); const renderListCard = () => jsxs("div", { className: 'glass-flex glass-gap-4 glass-p-4 glass-border glass-border-subtle glass-radius-lg hover:shadow-md transition-all', children: [jsx("img", { src: imageError ? "https://via.placeholder.com/120x120?text=No+Image" : product.thumbnail || product.images[0], alt: product.name, className: 'w-20 h-20 object-cover glass-radius', onError: () => setImageError(true) }), jsxs("div", { className: "glass-flex-1", children: [jsxs("div", { className: 'glass-flex glass-items-start glass-justify-between mb-2', children: [jsx("h3", { className: 'font-medium glass-text-secondary line-clamp-2', children: product.name }), jsxs("div", { className: cn("text-xs px-2 py-1 rounded-full", getReasonColor(reason)), children: [getReasonIcon(reason), " ", reason.replace("-", " ")] })] }), jsx("p", { className: 'glass-text-sm glass-text-secondary mb-3 line-clamp-2', children: explanation }), jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-3", children: [showPrice && jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsxs("span", { className: 'glass-text-lg font-semibold glass-text-secondary', children: ["$", product.price.toFixed(2)] }), product.originalPrice && product.originalPrice > product.price && jsxs("span", { className: 'glass-text-sm glass-text-secondary line-through', children: ["$", product.originalPrice.toFixed(2)] })] }), showRating && jsxs("div", { className: "glass-flex glass-items-center glass-gap-1", children: [jsx("span", { className: 'text-primary', children: "\u2605" }), jsxs("span", { className: "glass-text-sm glass-text-secondary", children: [product.rating.toFixed(1), " (", product.reviewCount, ")"] })] })] }), showQuickActions && jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("button", { onClick: e => { e.stopPropagation(); onAddToWishlist(product.id); }, className: 'glass-px-3 glass-py-1 glass-text-sm glass-text-secondary hover:text-primary transition-colors glass-focus glass-touch-target glass-contrast-guard', children: "\u2661 Wishlist" }), jsx("button", { onClick: e => { e.stopPropagation(); onAddToCart(product.id); }, className: 'glass-px-3 glass-py-1 glass-surface-blue text-primary glass-text-sm glass-radius hover:glass-surface-blue transition-colors glass-focus glass-touch-target glass-contrast-guard', children: "Add to Cart" })] })] }), jsxs("div", { className: 'mt-2 glass-flex glass-items-center glass-gap-2', children: [jsx("span", { className: "glass-text-xs glass-text-secondary", children: "Match confidence:" }), jsx("div", { className: 'glass-flex-1 glass-surface-subtle glass-radius-full h-1', children: jsx("div", { className: 'glass-surface-blue h-1 glass-radius-full transition-all', style: { width: `${confidence * 100}%` } }) }), jsxs("span", { className: "glass-text-xs glass-text-secondary", children: [Math.round(confidence * 100), "%"] })] })] })] }); const renderGridCard = () => jsxs("div", { className: 'group relative glass-surface-subtle glass-border glass-border-subtle glass-radius-lg overflow-hidden hover:shadow-lg transition-all duration-300', onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [jsxs("div", { className: 'relative aspect-square overflow-hidden', children: [jsx("img", { src: imageError ? "https://via.placeholder.com/300x300?text=No+Image" : product.thumbnail || product.images[0], alt: product.name, className: 'glass-w-full glass-h-full object-cover group-hover:scale-105 transition-transform duration-300', onError: () => setImageError(true) }), jsxs("div", { className: 'absolute glass-top-2 left-2 glass-flex glass-flex-col glass-gap-1', children: [product.isOnSale && jsx("span", { className: 'glass-surface-red text-primary glass-text-xs glass-px-2 glass-py-1 glass-radius', children: "SALE" }), product.isNew && jsx("span", { className: 'glass-surface-green text-primary glass-text-xs glass-px-2 glass-py-1 glass-radius', children: "NEW" }), product.isBestseller && jsx("span", { className: 'glass-surface-yellow text-primary glass-text-xs glass-px-2 glass-py-1 glass-radius', children: "BESTSELLER" })] }), jsx("div", { className: 'absolute glass-top-2 right-2', children: jsx("div", { className: cn("text-xs px-2 py-1 rounded-full", getReasonColor(reason)), children: getReasonIcon(reason) }) }), showQuickActions && jsxs("div", { className: cn("absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center gap-3 transition-opacity duration-300", isHovered ? "opacity-100" : "opacity-0"), children: [jsx("button", { onClick: e => { e.stopPropagation(); onAddToWishlist(product.id); }, className: 'glass-p-2 glass-surface-subtle glass-radius-full glass-text-secondary hover:text-primary transition-colors glass-focus glass-touch-target glass-contrast-guard', title: "Add to wishlist", children: "\u2661" }), jsx("button", { onClick: e => { e.stopPropagation(); onAddToCart(product.id); }, className: 'glass-p-2 glass-surface-blue text-primary glass-radius-full hover:glass-surface-blue transition-colors glass-focus glass-touch-target glass-contrast-guard', title: "Add to cart", children: "\uD83D\uDED2" })] })] }), jsxs("div", { className: "glass-p-4", children: [jsx("h3", { className: 'font-medium glass-text-secondary mb-2 line-clamp-2', children: product.name }), product.brand && jsx("p", { className: 'glass-text-sm glass-text-secondary mb-2', children: product.brand }), showRating && jsxs("div", { className: 'glass-flex glass-items-center glass-gap-1 mb-3', children: [jsx("div", { className: 'glass-flex text-primary', children: Array.from({ length: 5 }, (_, i) => jsx("span", { className: i < Math.floor(product.rating) ? "text-yellow-400" : "text-gray-300", children: "\u2605" }, i)) }), jsxs("span", { className: "glass-text-sm glass-text-secondary", children: ["(", product.reviewCount, ")"] })] }), showPrice && jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 mb-3', children: [jsxs("span", { className: 'glass-text-lg font-semibold glass-text-secondary', children: ["$", product.price.toFixed(2)] }), product.originalPrice && product.originalPrice > product.price && jsxs("span", { className: 'glass-text-sm glass-text-secondary line-through', children: ["$", product.originalPrice.toFixed(2)] })] }), jsx("p", { className: 'glass-text-xs glass-text-secondary mb-3', children: explanation }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("span", { className: "glass-text-xs glass-text-secondary", children: "Match:" }), jsx("div", { className: 'glass-flex-1 glass-surface-subtle glass-radius-full h-1', children: jsx("div", { className: 'glass-surface-blue h-1 glass-radius-full', style: { width: `${confidence * 100}%` } }) }), jsxs("span", { className: "glass-text-xs glass-text-secondary", children: [Math.round(confidence * 100), "%"] })] })] })] }); return jsxs("div", { "data-glass-component": true, onClick: () => onProductClick?.(product), className: 'cursor-pointer', children: [variant === "compact" && renderCompactCard(), variant === "list" && renderListCard(), (variant === "grid" || variant === "carousel") && renderGridCard()] }); }; const GlassProductRecommendations = ({ productId, title = "Recommended for You", subtitle, maxItems = 8, variant = "grid", showPrices = true, showRatings = true, showQuickActions = true, recommendationType = "all", className, onProductClick }) => { const { recommendations, getRecommendations, generateRecommendations, addToCart, addToWishlist } = useEcommerce(); const [loading, setLoading] = useState(false); const [currentRecommendations, setCurrentRecommendations] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); // Load recommendations useEffect(() => { if (!productId) return; const loadRecommendations = async () => { setLoading(true); let recs = getRecommendations(productId); if (recs.length === 0) { recs = await generateRecommendations(productId); } // Filter by recommendation type if (recommendationType !== "all") { recs = recs.filter(rec => rec.reason === recommendationType); } setCurrentRecommendations(recs.slice(0, maxItems)); setLoading(false); }; loadRecommendations(); }, [productId, recommendationType, maxItems, getRecommendations, generateRecommendations]); const handleAddToCart = useCallback(productId => { addToCart(productId, 1); // You could show a toast notification here }, [addToCart]); const handleAddToWishlist = useCallback(productId => { addToWishlist(productId); // You could show a toast notification here }, [addToWishlist]); const nextSlide = useCallback(() => { setCurrentIndex(prev => prev + 1 >= Math.ceil(currentRecommendations.length / getItemsPerView()) ? 0 : prev + 1); }, [currentRecommendations.length]); const prevSlide = useCallback(() => { setCurrentIndex(prev => prev - 1 < 0 ? Math.ceil(currentRecommendations.length / getItemsPerView()) - 1 : prev - 1); }, [currentRecommendations.length]); const getItemsPerView = () => { switch (variant) { case "carousel": return 4; case "grid": return 8; default: return currentRecommendations.length; } }; if (loading) { return jsx(GlassCore, { className: cn("p-6", className), children: jsx("div", { className: "glass-flex glass-items-center glass-justify-center glass-py-12", children: jsx("div", { className: 'animate-spin glass-radius-full h-12 w-12 glass-border-4 glass-border-blue glass-border-t-transparent' }) }) }); } if (currentRecommendations.length === 0) { return jsx(GlassCore, { className: cn("p-6", className), children: jsxs("div", { className: 'text-center glass-py-12', children: [jsx("div", { className: 'glass-text-4xl mb-4', children: "\uD83E\uDD16" }), jsx("h3", { className: 'glass-text-lg font-medium glass-text-secondary mb-2', children: "No recommendations available" }), jsx("p", { className: "glass-text-secondary", children: "We're still learning about your preferences. Check back later!" })] }) }); } return jsxs(GlassCore, { className: cn("p-6", className), children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-6', children: [jsxs("div", { children: [jsx("h2", { className: 'glass-text-xl font-semibold glass-text-secondary', children: title }), subtitle && jsx("p", { className: 'glass-text-secondary mt-1', children: subtitle })] }), variant === "carousel" && jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("button", { onClick: prevSlide, className: 'glass-p-2 glass-text-secondary hover:glass-text-secondary transition-colors glass-focus glass-touch-target glass-contrast-guard glass-focus glass-touch-target glass-contrast-guard', children: "\u2190" }), jsxs("span", { className: "glass-text-sm glass-text-secondary", children: [currentIndex + 1, " /", " ", Math.ceil(currentRecommendations.length / getItemsPerView())] }), jsx("button", { onClick: nextSlide, className: 'glass-p-2 glass-text-secondary hover:glass-text-secondary transition-colors glass-focus glass-touch-target glass-contrast-guard glass-focus glass-touch-target glass-contrast-guard', children: "\u2192" })] })] }), jsx("div", { className: cn(variant === "grid" && "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6", variant === "carousel" && "overflow-hidden", variant === "list" && "space-y-4", variant === "compact" && "space-y-2"), children: variant === "carousel" ? jsx("div", { className: 'glass-flex transition-transform duration-300', style: { transform: `translateX(-${currentIndex * 100}%)`, width: `${Math.ceil(currentRecommendations.length / getItemsPerView()) * 100}%` }, children: Array.from({ length: Math.ceil(currentRecommendations.length / getItemsPerView()) }, (_, slideIndex) => jsx("div", { className: "glass-grid glass-grid-cols-4 glass-gap-6 glass-w-full glass-flex-shrink-0", children: currentRecommendations.slice(slideIndex * getItemsPerView(), (slideIndex + 1) * getItemsPerView()).map(recommendation => jsx(ProductCard, { recommendation: recommendation, variant: variant, showPrice: showPrices, showRating: showRatings, showQuickActions: showQuickActions, onProductClick: onProductClick, onAddToCart: handleAddToCart, onAddToWishlist: handleAddToWishlist }, recommendation.productId)) }, slideIndex)) }) : currentRecommendations.map(recommendation => jsx(ProductCard, { recommendation: recommendation, variant: variant, showPrice: showPrices, showRating: showRatings, showQuickActions: showQuickActions, onProductClick: onProductClick, onAddToCart: handleAddToCart, onAddToWishlist: handleAddToWishlist }, recommendation.productId)) }), jsx("div", { className: 'mt-6 glass-p-4 glass-surface-subtle glass-radius-lg', children: jsxs("div", { className: "glass-flex glass-items-start glass-gap-3", children: [jsx("div", { className: "glass-text-2xl", children: "\uD83E\uDD16" }), jsxs("div", { children: [jsx("h3", { className: 'font-medium text-primary mb-1', children: "AI Insights" }), jsxs("p", { className: 'glass-text-sm text-primary', children: ["These recommendations are personalized based on your browsing history, purchase patterns, and preferences similar to users like you. Our AI analyzes ", currentRecommendations.length, " factors to suggest the best products for you."] }), jsx("div", { className: 'glass-flex glass-flex-wrap glass-gap-2 mt-3', children: Array.from(new Set(currentRecommendations.map(r => r.reason))).map(reason => jsxs("span", { className: cn("text-xs px-2 py-1 rounded-full", getReasonColor(reason)), children: [getReasonIcon(reason), " ", reason.replace("-", " ")] }, reason)) })] })] }) })] }); function getReasonColor(reason) { switch (reason) { case "similar": return "bg-blue-100 text-blue-800"; case "trending": return "bg-red-100 text-red-800"; case "personalized": return "bg-purple-100 text-purple-800"; case "bought-together": return "bg-green-100 text-green-800"; case "viewed-together": return "bg-orange-100 text-orange-800"; default: return "bg-gray-100 text-gray-800"; } } function getReasonIcon(reason) { switch (reason) { case "similar": return "🔄"; case "trending": return "📈"; case "personalized": return "🎯"; case "bought-together": return "🛍️"; case "viewed-together": return "👀"; default: return "✨"; } } }; export { GlassProductRecommendations }; //# sourceMappingURL=GlassProductRecommendations.js.map