import clsx from "clsx"; import { ElementType, UIEventHandler, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import styles from "./scroll.module.scss"; interface VirtualScrollProps { component?: ElementType; className?: string | string[]; itemHeight: number; total: number; tolerance?: number; startIndex?: number; getItems: (offset: number, limit: number) => JSX.Element | JSX.Element[]; } export default function VirtualScroll(props: VirtualScrollProps) { const { component: Component = "ul", className, itemHeight, total, startIndex = 0, getItems, } = props; const [wrapHeight, setWrapHeight] = useState(0); const [scrollY, setScrollY] = useState(startIndex * itemHeight); const offset = useMemo( () => Math.max(0, Math.floor(scrollY / itemHeight)), [itemHeight, scrollY] ); const limit = useMemo(() => { return Math.floor(wrapHeight / itemHeight) + 1; }, [itemHeight, wrapHeight]); const [wrapElement, setWrapElement] = useState(null); const handleScroll: UIEventHandler = (e) => { setScrollY((e.target as HTMLDivElement).scrollTop); }; const resize = useCallback(() => { if (wrapElement) setWrapHeight(wrapElement.clientHeight); }, [wrapElement]); const setWrapRef = useCallback((ele: HTMLDivElement) => { if (ele) { setWrapElement(ele); ele.scrollTo(0, scrollY); if (ele) setWrapHeight(ele.clientHeight); } else { setWrapElement(null); } }, []); useEffect(() => { if (window) { window.addEventListener("resize", resize); } return () => { window.removeEventListener("resize", resize); }; }, [resize]); return (
{getItems(offset, limit)}
); }