'use client'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { RiCloseLine } from '@remixicon/react'; import { Button } from '@/components/ui/button'; interface ProductImage { url: string; altText?: string; } interface ProductDetailGalleryProps { images: ProductImage[]; } const ProductDetailGallery: React.FC = ({ images, }) => { const [zoomedIndex, setZoomedIndex] = useState(null); const [activeIndex, setActiveIndex] = useState(0); const scrollerRef = useRef(null); const isZoomed = zoomedIndex !== null; const close = useCallback(() => setZoomedIndex(null), []); // The slide whose left edge sits closest to the scroller's left edge is the // one in view. Measuring rects keeps this correct whatever the gap or width. const handleScroll = useCallback(() => { const scroller = scrollerRef.current; if (!scroller) return; const scrollerLeft = scroller.getBoundingClientRect().left; let nearest = 0; let smallestOffset = Infinity; Array.from(scroller.children).forEach((child, index) => { const offset = Math.abs(child.getBoundingClientRect().left - scrollerLeft); if (offset < smallestOffset) { smallestOffset = offset; nearest = index; } }); setActiveIndex(nearest); }, []); const scrollToIndex = (index: number) => { const scroller = scrollerRef.current; const slide = scroller?.children[index]; if (!scroller || !slide) return; const offset = slide.getBoundingClientRect().left - scroller.getBoundingClientRect().left; scroller.scrollTo({ left: scroller.scrollLeft + offset, behavior: 'smooth' }); }; // Close on Escape, and keep the page behind the overlay from scrolling. useEffect(() => { if (!isZoomed) return; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') close(); }; document.addEventListener('keydown', onKeyDown); const previousOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.removeEventListener('keydown', onKeyDown); document.body.style.overflow = previousOverflow; }; }, [isZoomed, close]); if (images.length === 0) { return (
); } const isSingle = images.length === 1; const zoomedImage = zoomedIndex !== null ? images[zoomedIndex] : null; return ( <> {/* Swipeable carousel on mobile, grid from sm up */}
{images.map((image, index) => ( ))}
{/* Carousel pagination — the grid needs no dots, so mobile only */} {!isSingle && (
{images.map((_, index) => (
)} {zoomedImage && (
{zoomedImage.altText event.stopPropagation()} className="max-h-full max-w-full object-contain bg-white" />
)} ); }; export default ProductDetailGallery;