'use client'
import { memo, useRef, useEffect, useCallback, Suspense, lazy, useState, useMemo } from 'react'
import { cn } from '@djangocfg/ui-core/lib'
import type { GalleryThumbnailsProps } from '../../types'
import { normalizeImageUrl } from '../../utils'
import { ImageSpinner } from '../shared'
// Lazy load virtualized thumbnails - only for 50+ images
const GalleryThumbnailsVirtual = lazy(() =>
import('./GalleryThumbnailsVirtual').then((mod) => ({ default: mod.GalleryThumbnailsVirtual }))
)
const SIZES = {
sm: 'w-14 h-10',
md: 'w-20 h-14',
lg: 'w-24 h-18',
} as const
/** Threshold for switching to virtualized rendering */
const VIRTUALIZATION_THRESHOLD = 50
/**
* GalleryThumbnails - Horizontal scrollable thumbnail strip
* Automatically switches to virtualized rendering for 50+ images
*/
export const GalleryThumbnails = memo(function GalleryThumbnails(props: GalleryThumbnailsProps) {
const { images } = props
// Use virtualized version for large galleries (lazy loaded)
if (images.length >= VIRTUALIZATION_THRESHOLD) {
return (
}>
)
}
return
})
// Simple skeleton for loading state
const ThumbnailsSkeleton = memo(function ThumbnailsSkeleton({ className }: { className?: string }) {
return (
{Array.from({ length: 5 }).map((_, i) => (
))}
)
})
/**
* Regular (non-virtualized) thumbnails for smaller galleries
*/
const GalleryThumbnailsRegular = memo(function GalleryThumbnailsRegular({
images,
currentIndex,
onSelect,
size = 'md',
className,
}: GalleryThumbnailsProps) {
const containerRef = useRef(null)
const itemRefs = useRef<(HTMLButtonElement | null)[]>([])
// Scroll active thumbnail into view
useEffect(() => {
const activeItem = itemRefs.current[currentIndex]
if (activeItem && containerRef.current) {
const container = containerRef.current
const itemLeft = activeItem.offsetLeft
const itemWidth = activeItem.offsetWidth
const containerWidth = container.offsetWidth
const scrollLeft = container.scrollLeft
// Check if item is not fully visible
if (itemLeft < scrollLeft || itemLeft + itemWidth > scrollLeft + containerWidth) {
activeItem.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
})
}
}
}, [currentIndex])
// Handle thumbnail click
const handleClick = useCallback(
(index: number) => {
onSelect(index)
},
[onSelect]
)
if (images.length <= 1) return null
return (
{images.map((image, index) => (
{
itemRefs.current[index] = el
}}
image={image}
index={index}
isActive={index === currentIndex}
size={size}
onClick={handleClick}
/>
))}
)
})
// Extracted thumbnail button component
interface ThumbnailButtonProps {
image: { id: string; src: string; thumbnail?: string; alt?: string }
index: number
isActive: boolean
size: 'sm' | 'md' | 'lg'
onClick: (index: number) => void
}
const ThumbnailButton = memo(
function ThumbnailButton({
image,
index,
isActive,
size,
onClick,
ref,
}: ThumbnailButtonProps & { ref?: React.Ref }) {
const [isLoaded, setIsLoaded] = useState(false)
// Reset loading state when image source changes
const imageSrc = useMemo(() => image?.thumbnail || image?.src, [image?.thumbnail, image?.src])
useEffect(() => {
setIsLoaded(false)
}, [imageSrc])
const handleClick = useCallback(() => {
onClick(index)
}, [onClick, index])
const handleLoad = useCallback(() => {
setIsLoaded(true)
}, [])
return (
)
}
)