'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 ( ) } )