'use client' import { useState, useRef, useEffect } from 'react' import { getImageProps } from 'next/image' import type { ProductImage } from '@homaroma/types' interface Props { productName: string categoryName?: string images: ProductImage[] stretchDesktop?: boolean } interface ResponsiveProductImageProps { image: ProductImage alt: string className: string fetchPriority?: 'high' | 'low' | 'auto' sizes: string } function ResponsiveProductImage({ image, alt, className, fetchPriority, sizes }: ResponsiveProductImageProps) { const desktop = getImageProps({ src: image.url, alt, width: 1200, height: 1600, sizes, fetchPriority, }).props const mobile = image.mobileUrl ? getImageProps({ src: image.mobileUrl, alt, width: 1200, height: 1200, sizes, fetchPriority, }).props : null return ( {mobile && ( )} ) } export default function ProductGallery({ productName, categoryName, images, stretchDesktop = false }: Props) { const [activeIndex, setActiveIndex] = useState(0) const scrollRef = useRef(null) const sortedImages = [...images].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) const imageCount = sortedImages.length const visibleIndex = Math.min(activeIndex, Math.max(imageCount - 1, 0)) useEffect(() => { const el = scrollRef.current if (!el) return const onScrollEnd = () => { const width = el.clientWidth if (width > 0) { const newIndex = Math.round(el.scrollLeft / width) if (newIndex >= 0 && newIndex < imageCount) setActiveIndex(newIndex) } } el.addEventListener('scrollend', onScrollEnd) return () => el.removeEventListener('scrollend', onScrollEnd) }, [imageCount]) const handleThumbnailClick = (idx: number) => { setActiveIndex(idx) const scroller = scrollRef.current const child = scroller?.children[idx] as HTMLElement | undefined if (scroller && child) scroller.scrollTo({ left: child.offsetLeft, behavior: 'smooth' }) } if (imageCount === 0) return null const frameClass = stretchDesktop ? 'relative aspect-square w-full overflow-hidden rounded-3xl bg-surface-card shadow-card md:aspect-auto md:h-full md:min-h-0' : 'relative aspect-square w-full overflow-hidden rounded-3xl bg-surface-card shadow-card' const mainImageClass = stretchDesktop ? 'h-full w-full object-cover md:object-contain' : 'h-full w-full object-cover' if (imageCount === 1) { return ( {categoryName && ( {categoryName} )} ) } return ( {sortedImages.map((image, idx) => ( handleThumbnailClick(idx)} aria-label={`View ${productName} image ${idx + 1}`} aria-current={visibleIndex === idx ? 'true' : undefined} className={`relative aspect-square w-14 flex-none snap-center overflow-hidden rounded-xl border-2 transition-all md:w-full md:rounded-2xl ${visibleIndex === idx ? 'border-brand-primary opacity-100' : 'border-transparent opacity-60 hover:opacity-100'}`} > ))} {sortedImages.map((image, idx) => ( ))} {categoryName && ( {categoryName} )} {sortedImages.map((image, idx) => ( ))} ) }