AeThex-Connect/astro-site/src/react-app/components/Chat/InfiniteScrollMessages.jsx

76 lines
2.1 KiB
JavaScript

/**
* InfiniteScrollMessages Component
* Wrapper around MessageList that implements infinite scroll
* Detects when user scrolls to top and loads more messages
*/
import React, { useEffect, useRef, useCallback } from 'react';
import MessageList from './MessageList';
import './InfiniteScrollMessages.css';
export default function InfiniteScrollMessages({
messages,
typingUsers,
onLoadMore,
hasMore = true,
isLoading = false,
threshold = 300, // Pixels from top to trigger load
}) {
const scrollContainerRef = useRef(null);
const sentinelRef = useRef(null);
const intersectionObserverRef = useRef(null);
// Intersection Observer for infinite scroll detection
useEffect(() => {
if (!hasMore || isLoading) return;
const options = {
root: scrollContainerRef.current,
rootMargin: `${threshold}px 0px 0px 0px`,
threshold: 0.01,
};
intersectionObserverRef.current = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasMore && !isLoading) {
onLoadMore?.();
}
});
}, options);
if (sentinelRef.current) {
intersectionObserverRef.current.observe(sentinelRef.current);
}
return () => {
if (intersectionObserverRef.current) {
intersectionObserverRef.current.disconnect();
}
};
}, [hasMore, isLoading, onLoadMore, threshold]);
return (
<div className="infinite-scroll-messages-container" ref={scrollContainerRef}>
{/* Loading indicator at top */}
{isLoading && (
<div className="scroll-loading-indicator">
<div className="spinner"></div>
<span>Loading messages...</span>
</div>
)}
{/* Sentinel element - triggers load when visible */}
<div ref={sentinelRef} className="scroll-sentinel" />
{/* Messages */}
<MessageList messages={messages} typingUsers={typingUsers} />
{/* End of messages indicator */}
{!hasMore && messages.length > 0 && (
<div className="scroll-end-indicator">
<span>Beginning of conversation</span>
</div>
)}
</div>
);
}