import { useVirtualizer } from '@tanstack/react-virtual'; import { DetailedHTMLProps, Fragment, HTMLAttributes, ReactElement, forwardRef, useEffect, useRef, useState } from 'react'; import { classNames } from './classNames'; import { NitroLimitedEditionStyledNumberView } from './limited-edition'; import { styleNames } from './styleNames'; type Props = { items: T[]; columnCount: number; overscan?: number; estimateSize?: number; squareItems?: boolean; itemRender?: (item: T, index?: number) => ReactElement; } const InfiniteGridRoot = (props: Props) => { const { items = [], columnCount = 4, overscan = 5, estimateSize = 45, squareItems = false, itemRender = null } = props; const parentRef = useRef(null); if(squareItems) { return (
{ items.map((item, index) => { if(!item) return ; return { itemRender(item, index) }; }) }
); } const virtualizer = useVirtualizer({ count: Math.ceil(items.length / columnCount), overscan, getScrollElement: () => parentRef.current, estimateSize: () => estimateSize }); useEffect(() => { const element = parentRef.current; if(!element || !items) return; const checkAndApplyPadding = () => { if(!element) return; element.style.paddingRight = (element.scrollHeight > element.clientHeight) ? '0.25rem' : '0'; }; checkAndApplyPadding(); window.addEventListener('resize', checkAndApplyPadding); return () => { window.removeEventListener('resize', checkAndApplyPadding); }; }, [ items ]); useEffect(() => { if(!items || !items.length) return; virtualizer.scrollToIndex(0); }, [ items, virtualizer ]); const virtualItems = virtualizer.getVirtualItems(); return (
{ virtualItems.map(virtualRow => (
{ Array.from(Array(columnCount)).map((e, i) => { const index = (i + (virtualRow.index * columnCount)); const item = items[index]; if(!item) return ; return ( { itemRender(item, index) } ); }) }
)) }
); }; const InfiniteGridItem = forwardRef, HTMLDivElement>>((props, ref) => { const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, itemHighlight = false, disabled = false, className = null, style = {}, children = null, ...rest } = props; const [ backgroundImageUrl, setBackgroundImageUrl ] = useState(null); const disposed = useRef(false); useEffect(() => { if(!itemImage || !itemImage.length) { setBackgroundImageUrl(null); return; } const image = new Image(); image.onload = () => { if(disposed.current) return; setBackgroundImageUrl(image.src); }; image.src = itemImage; }, [ itemImage ]); useEffect(() => { disposed.current = false; return () => { disposed.current = true; }; }, []); return (
0)) && 'unique-item', itemUniqueSoldout && 'sold-out', itemUnseen && ' animate-pulse-glow-gold border-yellow-400/60', className ) } style={ styleNames( backgroundImageUrl && backgroundImageUrl.length && !(itemUniqueSoldout || (itemUniqueNumber > 0)) && { backgroundImage: `url(${ backgroundImageUrl })` }, itemColor && { backgroundColor: itemColor }, style ) } { ...rest }> { (itemCount > itemCountMinimum) &&
{ itemCount }
} { (itemUniqueNumber > 0) && <>
} { children }
); }); InfiniteGridItem.displayName = 'InfiniteGridItem'; export const InfiniteGrid = Object.assign(InfiniteGridRoot, { Item: InfiniteGridItem });