From e6851cef6a63f5629aa624bd94ceb32521dd0a73 Mon Sep 17 00:00:00 2001 From: manNomi Date: Fri, 26 Jun 2026 13:41:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8C=80=ED=95=99=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EA=B0=80=EC=83=81=ED=99=94=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../university/UniversityCards/index.tsx | 88 +++++++++++++++++-- 1 file changed, 82 insertions(+), 6 deletions(-) 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 ( +
+ +
+ ); + })}
); };