import { useVirtualizer } from '@tanstack/react-virtual'; import type { ReactElement, ReactNode, UIEvent } from 'react'; import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { IconButton } from './IconButton'; interface Props { data: T[]; render: (item: T, index: number) => ReactElement; header?: ReactNode; } export function AutoScroller({ data, render, header }: Props) { const containerRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); // The virtualizer const rowVirtualizer = useVirtualizer({ count: data.length, getScrollElement: () => containerRef.current, estimateSize: () => 27, // react-virtual requires a height, so we'll give it one }); // Scroll to new items const handleScroll = useCallback( (e: UIEvent) => { const el = e.currentTarget; // Set auto-scroll when container is scrolled const pixelsFromBottom = el.scrollHeight - (el.scrollTop + el.clientHeight); const newAutoScroll = pixelsFromBottom <= 0; if (newAutoScroll !== autoScroll) { setAutoScroll(newAutoScroll); } }, [autoScroll], ); // Scroll to bottom on count change useLayoutEffect(() => { if (!autoScroll) return; const el = containerRef.current; if (el == null) return; el.scrollTop = el.scrollHeight; }, [autoScroll, data.length]); return (
{!autoScroll && (
setAutoScroll((v) => !v)} />
)} {header ?? }
{rowVirtualizer.getVirtualItems().map((virtualItem) => (
{render(data[virtualItem.index]!, virtualItem.index)}
))}
); }