feat: lazy infinite scroller for session history view (#4922)
This commit is contained in:
@@ -185,6 +185,8 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
|||||||
currentIndex: number;
|
currentIndex: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
|
const [visibleGroupsCount, setVisibleGroupsCount] = useState(15);
|
||||||
|
|
||||||
// Edit modal state
|
// Edit modal state
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
const [editingSession, setEditingSession] = useState<Session | null>(null);
|
const [editingSession, setEditingSession] = useState<Session | null>(null);
|
||||||
@@ -210,6 +212,33 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const visibleDateGroups = useMemo(() => {
|
||||||
|
return dateGroups.slice(0, visibleGroupsCount);
|
||||||
|
}, [dateGroups, visibleGroupsCount]);
|
||||||
|
|
||||||
|
const handleScroll = useCallback(
|
||||||
|
(target: HTMLDivElement) => {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = target;
|
||||||
|
const threshold = 200;
|
||||||
|
|
||||||
|
if (
|
||||||
|
scrollHeight - scrollTop - clientHeight < threshold &&
|
||||||
|
visibleGroupsCount < dateGroups.length
|
||||||
|
) {
|
||||||
|
setVisibleGroupsCount((prev) => Math.min(prev + 5, dateGroups.length));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[visibleGroupsCount, dateGroups.length]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedSearchTerm) {
|
||||||
|
setVisibleGroupsCount(dateGroups.length);
|
||||||
|
} else {
|
||||||
|
setVisibleGroupsCount(15);
|
||||||
|
}
|
||||||
|
}, [debouncedSearchTerm, dateGroups.length]);
|
||||||
|
|
||||||
const loadSessions = useCallback(async () => {
|
const loadSessions = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setShowSkeleton(true);
|
setShowSkeleton(true);
|
||||||
@@ -557,10 +586,9 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For regular rendering in grid layout
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{dateGroups.map((group) => (
|
{visibleDateGroups.map((group) => (
|
||||||
<div key={group.label} className="space-y-4">
|
<div key={group.label} className="space-y-4">
|
||||||
<div className="sticky top-0 z-10 bg-background-default/95 backdrop-blur-sm">
|
<div className="sticky top-0 z-10 bg-background-default/95 backdrop-blur-sm">
|
||||||
<h2 className="text-text-muted">{group.label}</h2>
|
<h2 className="text-text-muted">{group.label}</h2>
|
||||||
@@ -577,6 +605,15 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{visibleGroupsCount < dateGroups.length && (
|
||||||
|
<div className="flex justify-center py-8">
|
||||||
|
<div className="flex items-center space-x-2 text-text-muted">
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-text-muted"></div>
|
||||||
|
<span>Loading more sessions...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -597,7 +634,7 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 relative px-8">
|
<div className="flex-1 min-h-0 relative px-8">
|
||||||
<ScrollArea className="h-full" data-search-scroll-area>
|
<ScrollArea handleScroll={handleScroll} className="h-full" data-search-scroll-area>
|
||||||
<div ref={containerRef} className="h-full relative">
|
<div ref={containerRef} className="h-full relative">
|
||||||
<SearchView
|
<SearchView
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
|
|||||||
@@ -15,10 +15,22 @@ interface ScrollAreaProps extends React.ComponentPropsWithoutRef<typeof ScrollAr
|
|||||||
/* padding needs to be passed into the container inside ScrollArea to avoid pushing the scrollbar out */
|
/* padding needs to be passed into the container inside ScrollArea to avoid pushing the scrollbar out */
|
||||||
paddingX?: number;
|
paddingX?: number;
|
||||||
paddingY?: number;
|
paddingY?: number;
|
||||||
|
handleScroll?: (viewport: HTMLDivElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
|
const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
|
||||||
({ className, children, autoScroll = false, paddingX, paddingY, ...props }, ref) => {
|
(
|
||||||
|
{
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
autoScroll = false,
|
||||||
|
paddingX,
|
||||||
|
paddingY,
|
||||||
|
handleScroll: handleScrollProp,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const rootRef = React.useRef<React.ElementRef<typeof ScrollAreaPrimitive.Root>>(null);
|
const rootRef = React.useRef<React.ElementRef<typeof ScrollAreaPrimitive.Root>>(null);
|
||||||
const viewportRef = React.useRef<HTMLDivElement>(null);
|
const viewportRef = React.useRef<HTMLDivElement>(null);
|
||||||
const viewportEndRef = React.useRef<HTMLDivElement>(null);
|
const viewportEndRef = React.useRef<HTMLDivElement>(null);
|
||||||
@@ -71,7 +83,11 @@ const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
|
|||||||
|
|
||||||
setIsFollowing(isAtBottom);
|
setIsFollowing(isAtBottom);
|
||||||
setIsScrolled(scrollTop > 0);
|
setIsScrolled(scrollTop > 0);
|
||||||
}, []);
|
|
||||||
|
if (handleScrollProp) {
|
||||||
|
handleScrollProp(viewport);
|
||||||
|
}
|
||||||
|
}, [handleScrollProp]);
|
||||||
|
|
||||||
// Track previous scroll height to detect content changes
|
// Track previous scroll height to detect content changes
|
||||||
const prevScrollHeightRef = React.useRef<number>(0);
|
const prevScrollHeightRef = React.useRef<number>(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user