import { ForwardedRef, forwardRef, useCallback, useEffect, useState, } from "react"; import styles from "./ScrollContainer.module.scss"; import { subMonths } from "date-fns"; interface ScrollContainerProps { direction: "horizontal" | "vertical"; /** A list of things managed */ list: T[]; generatePreviousObject: () => void; generateNextObject: () => void; } /** * Generic infinite scrolling container */ function ScrollContainer( { direction, list }: ScrollContainerProps, ref: ForwardedRef ) { const [resetScrollToEl, setResetScrollToEl] = useState< [HTMLElement, number] | null >(null); // Viewport state const [viewportSize, setViewportSize] = useState<[number, number]>([0, 0]); const [viewportWidth, viewportHeight] = viewportSize; const viewportMetric = direction === "horizontal" ? viewportWidth : viewportHeight; // Scroll element state const [scrollyEl, setScrollyEl] = useState(null); const scrollyRef = useCallback((node: HTMLDivElement) => { if (node) { setScrollyEl(node); setViewportSize([node.clientWidth, node.clientHeight]); } }, []); // Add resize observer on the viewport useEffect(() => { const observer = new ResizeObserver((entries) => { if (entries && scrollyEl) { setViewportSize([scrollyEl.clientWidth, scrollyEl.clientHeight]); } }); if (scrollyEl) observer.observe(scrollyEl); return () => { if (scrollyEl) observer.unobserve(scrollyEl); }; }, [scrollyEl]); // Add intersection observer to tell when we scrolled too far useEffect(() => { if (!scrollyEl) return; const children: HTMLElement[] = [...scrollyEl.children]; const firstChild = children[0]; const lastChild = children[children.length - 1]; const observer = new IntersectionObserver((entries) => { if (viewportMetric === 0) return; let newList = [...list]; for (const entry of entries) { if (!entry.isIntersecting) continue; if (entry.target === firstChild) { console.log("intersected"); const firstChildDate = new Date(firstChild.dataset.isodate!); const prevMonth = subMonths(firstChildDate, 1); newList = [prevMonth, ...monthsShown]; newList = newMonthsShown.slice(0, NUM_MONTHS_SHOWN); setResetScrollToEl([ firstChild, scrollyEl.scrollTop - firstChild.offsetTop, ]); } else if (entry.target === lastChild) { const lastChildDate = new Date(lastChild.dataset.isodate!); const nextMonth = addMonths(lastChildDate, 1); newMonthsShown = [...monthsShown, nextMonth]; newMonthsShown = newMonthsShown.slice(-NUM_MONTHS_SHOWN); } } setMonthsShown(newMonthsShown); }); }); return
; } export default forwardRef(ScrollContainer);