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;
|
||||
} | null>(null);
|
||||
|
||||
const [visibleGroupsCount, setVisibleGroupsCount] = useState(15);
|
||||
|
||||
// Edit modal state
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
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 () => {
|
||||
setIsLoading(true);
|
||||
setShowSkeleton(true);
|
||||
@@ -557,10 +586,9 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
);
|
||||
}
|
||||
|
||||
// For regular rendering in grid layout
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{dateGroups.map((group) => (
|
||||
{visibleDateGroups.map((group) => (
|
||||
<div key={group.label} className="space-y-4">
|
||||
<div className="sticky top-0 z-10 bg-background-default/95 backdrop-blur-sm">
|
||||
<h2 className="text-text-muted">{group.label}</h2>
|
||||
@@ -577,6 +605,15 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
</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>
|
||||
);
|
||||
};
|
||||
@@ -597,7 +634,7 @@ const SessionListView: React.FC<SessionListViewProps> = React.memo(
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<SearchView
|
||||
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 */
|
||||
paddingX?: number;
|
||||
paddingY?: number;
|
||||
handleScroll?: (viewport: HTMLDivElement) => void;
|
||||
}
|
||||
|
||||
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 viewportRef = React.useRef<HTMLDivElement>(null);
|
||||
const viewportEndRef = React.useRef<HTMLDivElement>(null);
|
||||
@@ -71,7 +83,11 @@ const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
|
||||
|
||||
setIsFollowing(isAtBottom);
|
||||
setIsScrolled(scrollTop > 0);
|
||||
}, []);
|
||||
|
||||
if (handleScrollProp) {
|
||||
handleScrollProp(viewport);
|
||||
}
|
||||
}, [handleScrollProp]);
|
||||
|
||||
// Track previous scroll height to detect content changes
|
||||
const prevScrollHeightRef = React.useRef<number>(0);
|
||||
|
||||
Reference in New Issue
Block a user