This commit is contained in:
Your Name committed 2026-07-12 03:11:37 -04:00
1 parent 5d111b2866
commit 62dec90f82
2 files changed
+97 -11

No files matched your search

@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, useRef, useEffect } from 'react'
import type { ProductImage } from '../types'
interface ProductImageGalleryProps {
@@ -10,6 +10,29 @@ interface ProductImageGalleryProps {
export default function ProductImageGallery({ images, fallback, alt = 'Product Image', badgeText }: ProductImageGalleryProps) {
const [currentIndex, setCurrentIndex] = useState(0)
const scrollRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (scrollRef.current) {
const container = scrollRef.current
const child = container.children[currentIndex] as HTMLElement
if (child) {
container.scrollTo({ left: child.offsetLeft, behavior: 'smooth' })
}
}
}, [currentIndex])
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const container = e.currentTarget
const scrollLeft = container.scrollLeft
const width = container.clientWidth
if (width > 0) {
const newIndex = Math.round(scrollLeft / width)
if (newIndex !== currentIndex && newIndex >= 0 && newIndex < (images?.length || 0)) {
setCurrentIndex(newIndex)
}
}
}
// Sort images by order if they exist, else just use the array
const sortedImages = images?.length ? [...images].sort((a, b) => (a.order || 0) - (b.order || 0)) : []
@@ -60,18 +83,38 @@ export default function ProductImageGallery({ images, fallback, alt = 'Product I
{/* Main Image */}
<div className="relative aspect-square w-full flex-1 overflow-hidden rounded-3xl bg-pop-bg shadow-sm border border-pop-black/10 flex items-center justify-center">
<div
ref={scrollRef}
onScroll={handleScroll}
className="flex w-full h-full overflow-x-auto snap-x snap-mandatory hide-scrollbar"
>
{sortedImages.map((img, idx) => (
<div key={idx} className="relative w-full h-full flex-none snap-center snap-always block">
<img
key={currentIndex}
src={sortedImages[currentIndex].url}
className="w-full h-full object-cover animate-fade-in"
alt={alt}
src={img.url}
alt={`${alt} ${idx + 1}`}
className="absolute inset-0 w-full h-full object-cover"
onError={(e) => { (e.target as HTMLImageElement).src = fallback }}
/>
</div>
))}
</div>
{badgeText && (
<div className="absolute top-6 left-6 bg-pop-bg backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-pop-pink shadow-sm pointer-events-none z-10">
{badgeText}
</div>
)}
{/* Mobile Dots */}
<div className="md:hidden absolute bottom-4 left-0 right-0 flex justify-center gap-2 z-10 pointer-events-none">
{sortedImages.map((_, idx) => (
<div
key={idx}
className={`w-1.5 h-1.5 rounded-full shadow-[0_1px_2px_rgba(0,0,0,0.3)] transition-all ${currentIndex === idx ? 'bg-white scale-125' : 'bg-white/50'}`}
/>
))}
</div>
</div>
</div>
)
+49 -6
View File
@@ -1,6 +1,6 @@
'use client'
import { useState } from 'react'
import { useState, useRef, useEffect } from 'react'
import Image from 'next/image'
interface Props {
@@ -11,6 +11,29 @@ interface Props {
export default function ProductGallery({ productName, categoryName, images }: Props) {
const [activeIndex, setActiveIndex] = useState(0)
const scrollRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (scrollRef.current) {
const container = scrollRef.current
const child = container.children[activeIndex] as HTMLElement
if (child) {
container.scrollTo({ left: child.offsetLeft, behavior: 'smooth' })
}
}
}, [activeIndex])
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const container = e.currentTarget
const scrollLeft = container.scrollLeft
const width = container.clientWidth
if (width > 0) {
const newIndex = Math.round(scrollLeft / width)
if (newIndex !== activeIndex && newIndex >= 0 && newIndex < images.length) {
setActiveIndex(newIndex)
}
}
}
if (images.length === 0) return null
@@ -60,19 +83,39 @@ export default function ProductGallery({ productName, categoryName, images }: Pr
{/* Main Image */}
<div className="relative aspect-square w-full flex-1 overflow-hidden rounded-3xl bg-surface-card shadow-card">
<div
ref={scrollRef}
onScroll={handleScroll}
className="flex w-full h-full overflow-x-auto snap-x snap-mandatory hide-scrollbar"
>
{images.map((img, idx) => (
<div key={idx} className="relative w-full h-full flex-none snap-center snap-always block">
<Image
key={activeIndex}
src={images[activeIndex]}
alt={productName}
src={img}
alt={`${productName} ${idx + 1}`}
fill
priority={true}
className="object-cover animate-fade-in"
priority={idx === 0}
className="object-cover"
/>
</div>
))}
</div>
{categoryName && (
<div className="absolute left-5 top-5 rounded-full bg-surface-card/90 px-4 py-2 text-xs font-bold uppercase tracking-widest text-text-main shadow-sm pointer-events-none z-10">
{categoryName}
</div>
)}
{/* Mobile Dots */}
<div className="md:hidden absolute bottom-4 left-0 right-0 flex justify-center gap-2 z-10 pointer-events-none">
{images.map((_, idx) => (
<div
key={idx}
className={`w-1.5 h-1.5 rounded-full shadow-[0_1px_2px_rgba(0,0,0,0.3)] transition-all ${activeIndex === idx ? 'bg-white scale-125' : 'bg-white/50'}`}
/>
))}
</div>
</div>
</div>
)