feat: 가상 스크롤 동적 높이 지원

- measureElement와 data-index 사용으로 각 아이템 실제 높이 측정
- 고정 높이(h-[120px]) 제거하여 내용에 맞게 자동 조절
This commit is contained in:
caadiq 2026-01-10 09:46:38 +09:00
parent 0cab67dfbe
commit d22466ec23

View file

@ -43,7 +43,7 @@ function Schedule() {
const [searchInput, setSearchInput] = useState(''); const [searchInput, setSearchInput] = useState('');
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const SEARCH_LIMIT = 20; // 20 const SEARCH_LIMIT = 20; // 20
const ITEM_HEIGHT = 136; // (120px) + (16px) const ESTIMATED_ITEM_HEIGHT = 120; // ( )
// Intersection Observer for infinite scroll // Intersection Observer for infinite scroll
const { ref: loadMoreRef, inView } = useInView({ const { ref: loadMoreRef, inView } = useInView({
@ -289,11 +289,11 @@ function Schedule() {
}); });
}, [schedules, selectedDate, currentYearMonth, selectedCategories, isSearchMode, searchTerm, searchResults]); }, [schedules, selectedDate, currentYearMonth, selectedCategories, isSearchMode, searchTerm, searchResults]);
// ( ) // ( , )
const virtualizer = useVirtualizer({ const virtualizer = useVirtualizer({
count: isSearchMode && searchTerm ? filteredSchedules.length : 0, count: isSearchMode && searchTerm ? filteredSchedules.length : 0,
getScrollElement: () => scrollContainerRef.current, getScrollElement: () => scrollContainerRef.current,
estimateSize: () => ITEM_HEIGHT, estimateSize: () => ESTIMATED_ITEM_HEIGHT,
overscan: 5, // overscan: 5, //
}); });
@ -834,7 +834,7 @@ function Schedule() {
<> <>
<div <div
style={{ style={{
height: `${virtualizer.getTotalSize() - 16}px`, height: `${virtualizer.getTotalSize()}px`,
width: '100%', width: '100%',
position: 'relative', position: 'relative',
}} }}
@ -850,19 +850,20 @@ function Schedule() {
return ( return (
<div <div
key={virtualItem.key} key={virtualItem.key}
ref={virtualizer.measureElement}
data-index={virtualItem.index}
style={{ style={{
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
width: '100%', width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`, transform: `translateY(${virtualItem.start}px)`,
}} }}
> >
<div className={virtualItem.index < filteredSchedules.length - 1 ? "pb-4" : ""}> <div className={virtualItem.index < filteredSchedules.length - 1 ? "pb-4" : ""}>
<div <div
onClick={() => handleScheduleClick(schedule)} onClick={() => handleScheduleClick(schedule)}
className="flex items-stretch bg-white rounded-2xl shadow-sm hover:shadow-md transition-shadow overflow-hidden cursor-pointer h-[120px]" className="flex items-stretch bg-white rounded-2xl shadow-sm hover:shadow-md transition-shadow overflow-hidden cursor-pointer"
> >
{/* 날짜 영역 */} {/* 날짜 영역 */}
<div <div