diff --git a/apps/university-web/src/components/university/UniversityCards/index.tsx b/apps/university-web/src/components/university/UniversityCards/index.tsx index 19d1fe05c..525b4e941 100644 --- a/apps/university-web/src/components/university/UniversityCards/index.tsx +++ b/apps/university-web/src/components/university/UniversityCards/index.tsx @@ -1,10 +1,18 @@ "use client"; +import { useWindowVirtualizer } from "@tanstack/react-virtual"; import clsx from "clsx"; +import { useCallback, useEffect, useRef, useState } from "react"; import type { ListUniversity } from "@/types/university"; import UniversityCard from "../../ui/UniverSityCard"; +const UNIVERSITY_CARD_HEIGHT = 91; +const UNIVERSITY_CARD_BOTTOM_PADDING = 10; +const UNIVERSITY_CARD_GAP = 10; +const ESTIMATED_UNIVERSITY_CARD_ROW_HEIGHT = UNIVERSITY_CARD_HEIGHT + UNIVERSITY_CARD_BOTTOM_PADDING; +const INITIAL_VIEWPORT_HEIGHT = 900; + type UniversityCardsProps = { colleges: ListUniversity[]; style?: React.CSSProperties; @@ -14,13 +22,81 @@ type UniversityCardsProps = { }; const UniversityCards = ({ colleges, style, className, showCapacity = true, linkPrefix }: UniversityCardsProps) => { + const listRef = useRef(null); + const [scrollMargin, setScrollMargin] = useState(0); + + const measureScrollMargin = useCallback(() => { + if (!listRef.current) { + return; + } + + setScrollMargin(listRef.current.getBoundingClientRect().top + window.scrollY); + }, []); + + useEffect(() => { + measureScrollMargin(); + + const animationFrameId = window.requestAnimationFrame(measureScrollMargin); + window.addEventListener("resize", measureScrollMargin); + + return () => { + window.cancelAnimationFrame(animationFrameId); + window.removeEventListener("resize", measureScrollMargin); + }; + }, [measureScrollMargin]); + + const getItemKey = useCallback((index: number) => colleges[index]?.id ?? index, [colleges]); + + const virtualizer = useWindowVirtualizer({ + count: colleges.length, + estimateSize: () => ESTIMATED_UNIVERSITY_CARD_ROW_HEIGHT, + gap: UNIVERSITY_CARD_GAP, + getItemKey, + overscan: 6, + scrollMargin, + initialRect: { + width: 0, + height: INITIAL_VIEWPORT_HEIGHT, + }, + useFlushSync: false, + }); + + const virtualItems = virtualizer.getVirtualItems(); + return ( -
- {colleges.map((college) => ( -
- -
- ))} +
+ {virtualItems.map((virtualItem) => { + const college = colleges[virtualItem.index]; + + if (!college) { + return null; + } + + return ( +
+ +
+ ); + })}
); };