"use client"; import { useVirtualizer } from "@tanstack/react-virtual"; import { AlertCircle, Inbox } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import { useEffect, useRef, useState, type MutableRefObject, type ReactNode, } from "react"; import { EASE_OUT, SPRING_PANEL } from "@/lib/ease"; import { cn } from "@/lib/utils"; export type InfiniteMasonryKey = string | number | bigint; export interface InfiniteMasonryProps { items: readonly T[]; getItemKey: (item: T, index: number) => InfiniteMasonryKey; renderItem: (item: T, index: number) => ReactNode; onLoadMore: () => void | Promise; hasMore: boolean; loading?: boolean; error?: ReactNode; onRetry?: () => void; estimateSize?: (item: T, index: number) => number; renderLoadingItem?: (index: number) => ReactNode; emptyState?: ReactNode; endState?: ReactNode; minColumnWidth?: number; maxColumns?: number; gap?: number; overscan?: number; prefetch?: number; animateItems?: boolean; ariaLabel?: string; className?: string; contentClassName?: string; itemClassName?: string; } type MasonryMetrics = { columns: number; width: number; }; function useMasonryMetrics({ elementRef, gap, maxColumns, minColumnWidth, }: { elementRef: React.RefObject; gap: number; maxColumns: number; minColumnWidth: number; }) { const [metrics, setMetrics] = useState({ columns: 1, width: 0, }); useEffect(() => { const element = elementRef.current; if (!element) return; const update = (width: number) => { const columns = Math.min( maxColumns, Math.max(1, Math.floor((width + gap) / (minColumnWidth + gap))), ); setMetrics((current) => current.columns === columns && current.width === width ? current : { columns, width }, ); }; update(element.getBoundingClientRect().width); const observer = new ResizeObserver(([entry]) => { update(entry.contentRect.width); }); observer.observe(element); return () => observer.disconnect(); }, [elementRef, gap, maxColumns, minColumnWidth]); return metrics; } function DefaultLoadingItem({ index }: { index: number }) { return (