react-virtual in messages list
This commit is contained in:
@@ -1,18 +1,10 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { ArrowDown } from 'lucide-react';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import type { ChatMessage } from './types';
|
||||
import { MessageBubble, StreamingBubble } from './MessageBubble';
|
||||
|
||||
type MessageListProps = {
|
||||
messages: ChatMessage[];
|
||||
streamingText: string;
|
||||
isGenerating: boolean;
|
||||
showJumpToBottom: boolean;
|
||||
onJumpToBottom: () => void;
|
||||
onQuestionAnswer?: (text: string) => void;
|
||||
scrollViewportRef: RefObject<HTMLDivElement | null>;
|
||||
bottomRef: RefObject<HTMLDivElement | null>;
|
||||
};
|
||||
const MESSAGE_HEIGHT = 100; // Estimated height per message bubble
|
||||
const OVERSCAN = 5;
|
||||
|
||||
export const MessageList = ({
|
||||
messages,
|
||||
@@ -23,30 +15,63 @@ export const MessageList = ({
|
||||
onQuestionAnswer,
|
||||
scrollViewportRef,
|
||||
bottomRef,
|
||||
}: MessageListProps) => (
|
||||
<div className="flex-1 min-h-0 relative">
|
||||
<div ref={scrollViewportRef} className="h-full overflow-y-auto">
|
||||
<div className="p-4 space-y-3">
|
||||
}: {
|
||||
messages: ChatMessage[];
|
||||
streamingText: string;
|
||||
isGenerating: boolean;
|
||||
showJumpToBottom: boolean;
|
||||
onJumpToBottom: () => void;
|
||||
onQuestionAnswer?: (text: string) => void;
|
||||
scrollViewportRef: React.RefObject<HTMLDivElement | null>;
|
||||
bottomRef: React.RefObject<HTMLDivElement | null>;
|
||||
}) => {
|
||||
const virtualizer = useVirtualizer({
|
||||
count: messages.length,
|
||||
getScrollElement: () => scrollViewportRef.current,
|
||||
estimateSize: () => MESSAGE_HEIGHT,
|
||||
overscan: OVERSCAN,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex-1 min-h-0 relative">
|
||||
<div ref={scrollViewportRef} className="h-full overflow-y-auto">
|
||||
{messages.length === 0 && !isGenerating && (
|
||||
<div className="flex items-center justify-center h-full min-h-[200px] text-duck-dark/30 text-sm">
|
||||
Send a message to start
|
||||
</div>
|
||||
)}
|
||||
{messages.map((msg, i) => (
|
||||
<MessageBubble key={i} message={msg} onAnswer={onQuestionAnswer} />
|
||||
))}
|
||||
<div
|
||||
className="p-4 space-y-3"
|
||||
style={{ height: virtualizer.getTotalSize() }}
|
||||
>
|
||||
{virtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const msg = messages[virtualRow.index]!;
|
||||
return (
|
||||
<div
|
||||
key={virtualRow.key}
|
||||
className="absolute left-0 w-full"
|
||||
style={{
|
||||
top: virtualRow.start,
|
||||
height: virtualRow.size,
|
||||
}}
|
||||
>
|
||||
<MessageBubble message={msg} onAnswer={onQuestionAnswer} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{isGenerating && <StreamingBubble text={streamingText} />}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showJumpToBottom && (
|
||||
<button
|
||||
onClick={onJumpToBottom}
|
||||
className="absolute bottom-2 left-1/2 -translate-x-1/2 bg-duck-teal text-white rounded-full p-1.5 shadow-lg hover:bg-duck-teal/90 transition-colors cursor-pointer"
|
||||
>
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{showJumpToBottom && (
|
||||
<button
|
||||
onClick={onJumpToBottom}
|
||||
className="absolute bottom-2 left-1/2 -translate-x-1/2 bg-duck-teal text-white rounded-full p-1.5 shadow-lg hover:bg-duck-teal/90 transition-colors cursor-pointer"
|
||||
>
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user