x
This commit is contained in:
1 parent
5d111b2866
commit
62dec90f82
2 files changed
+103
-17
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import type { ProductImage } from '../types'
|
import type { ProductImage } from '../types'
|
||||||
|
|
||||||
interface ProductImageGalleryProps {
|
interface ProductImageGalleryProps {
|
||||||
@@ -10,6 +10,29 @@ interface ProductImageGalleryProps {
|
|||||||
|
|
||||||
export default function ProductImageGallery({ images, fallback, alt = 'Product Image', badgeText }: ProductImageGalleryProps) {
|
export default function ProductImageGallery({ images, fallback, alt = 'Product Image', badgeText }: ProductImageGalleryProps) {
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
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
|
// 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)) : []
|
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 */}
|
{/* 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 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">
|
||||||
<img
|
<div
|
||||||
key={currentIndex}
|
ref={scrollRef}
|
||||||
src={sortedImages[currentIndex].url}
|
onScroll={handleScroll}
|
||||||
className="w-full h-full object-cover animate-fade-in"
|
className="flex w-full h-full overflow-x-auto snap-x snap-mandatory hide-scrollbar"
|
||||||
alt={alt}
|
>
|
||||||
onError={(e) => { (e.target as HTMLImageElement).src = fallback }}
|
{sortedImages.map((img, idx) => (
|
||||||
/>
|
<div key={idx} className="relative w-full h-full flex-none snap-center snap-always block">
|
||||||
|
<img
|
||||||
|
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 && (
|
{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">
|
<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}
|
{badgeText}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -11,6 +11,29 @@ interface Props {
|
|||||||
|
|
||||||
export default function ProductGallery({ productName, categoryName, images }: Props) {
|
export default function ProductGallery({ productName, categoryName, images }: Props) {
|
||||||
const [activeIndex, setActiveIndex] = useState(0)
|
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
|
if (images.length === 0) return null
|
||||||
|
|
||||||
@@ -60,19 +83,39 @@ export default function ProductGallery({ productName, categoryName, images }: Pr
|
|||||||
|
|
||||||
{/* Main Image */}
|
{/* Main Image */}
|
||||||
<div className="relative aspect-square w-full flex-1 overflow-hidden rounded-3xl bg-surface-card shadow-card">
|
<div className="relative aspect-square w-full flex-1 overflow-hidden rounded-3xl bg-surface-card shadow-card">
|
||||||
<Image
|
<div
|
||||||
key={activeIndex}
|
ref={scrollRef}
|
||||||
src={images[activeIndex]}
|
onScroll={handleScroll}
|
||||||
alt={productName}
|
className="flex w-full h-full overflow-x-auto snap-x snap-mandatory hide-scrollbar"
|
||||||
fill
|
>
|
||||||
priority={true}
|
{images.map((img, idx) => (
|
||||||
className="object-cover animate-fade-in"
|
<div key={idx} className="relative w-full h-full flex-none snap-center snap-always block">
|
||||||
/>
|
<Image
|
||||||
|
src={img}
|
||||||
|
alt={`${productName} ${idx + 1}`}
|
||||||
|
fill
|
||||||
|
priority={idx === 0}
|
||||||
|
className="object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
{categoryName && (
|
{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">
|
<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}
|
{categoryName}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user