diff --git a/workloads/ecommerce/components/product/product-card-gallery.tsx b/workloads/ecommerce/components/product/product-card-gallery.tsx new file mode 100644 index 0000000..608b398 --- /dev/null +++ b/workloads/ecommerce/components/product/product-card-gallery.tsx @@ -0,0 +1,112 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { useRef, useState } from "react"; + +// Galería deslizable de la tarjeta de producto: permite ver todas las +// imágenes desde el listado sin entrar al producto. En móvil se desliza +// con el dedo (scroll-snap nativo); en escritorio aparecen flechas al +// pasar el mouse. Cada imagen sigue siendo un link a la ficha. +export function ProductCardGallery({ + images, + name, + href, + priority = false, +}: { + images: string[]; + name: string; + href: string; + priority?: boolean; +}) { + const scroller = useRef(null); + const [index, setIndex] = useState(0); + const total = images.length; + + const goTo = (target: number) => { + const node = scroller.current; + if (!node) return; + const next = Math.max(0, Math.min(total - 1, target)); + node.scrollTo({ left: next * node.clientWidth, behavior: "smooth" }); + }; + + const onScroll = () => { + const node = scroller.current; + if (!node || node.clientWidth === 0) return; + setIndex(Math.round(node.scrollLeft / node.clientWidth)); + }; + + return ( + <> +
1 ? onScroll : undefined} + className="flex h-full snap-x snap-mandatory overflow-x-auto [overscroll-behavior-x:contain] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" + > + {images.map((src, slide) => ( + 1 ? `${name}, imagen ${slide + 1} de ${total}` : name} + tabIndex={slide === 0 ? 0 : -1} + > + {slide + + ))} +
+ + {total > 1 && ( + <> + + +
+
+ {images.map((src, dot) => ( + + ))} +
+
+ + )} + + ); +} diff --git a/workloads/ecommerce/components/product/product-card.tsx b/workloads/ecommerce/components/product/product-card.tsx index 8fd4dd2..3000218 100644 --- a/workloads/ecommerce/components/product/product-card.tsx +++ b/workloads/ecommerce/components/product/product-card.tsx @@ -1,9 +1,9 @@ "use client"; -import Image from "next/image"; import Link from "next/link"; import { Heart, ShoppingBag, Sparkles, TriangleAlert } from "lucide-react"; import { useState } from "react"; +import { ProductCardGallery } from "@/components/product/product-card-gallery"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import type { Product } from "@/lib/headless/types"; @@ -22,6 +22,9 @@ export function ProductCard({ product, priority = false }: { product: Product; p ? Math.round((1 - product.price / product.compareAtPrice) * 100) : null; + const galleryImages = Array.from(new Set(product.images.filter(Boolean))); + if (galleryImages.length === 0) galleryImages.push(product.thumbnail); + const [selectedVariantId, setSelectedVariantId] = useState(product.variants[0]?.id); const selectedVariant = product.variants.find((variant) => variant.id === selectedVariantId) ?? product.variants[0]; const outOfStock = selectedVariant?.available === false; @@ -37,7 +40,7 @@ export function ProductCard({ product, priority = false }: { product: Product; p return (
- {product.name} +
{outOfStock && Agotado}{discountPercent && discountPercent > 0 && -{discountPercent}%}{product.isNew && Nuevo}{product.featured && Top}
{outOfStock &&
}