this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Scroll only if the user is at the bottom of the container and remove visible bottom gap from chat messages stack (#2)

authored by

James Blair and committed by
GitHub
bcb084d8 b2f4d3e2

+60 -6
+60 -6
apps/web/src/components/chat-messages.tsx
··· 1 - import { useEffect, useRef } from "react"; 1 + import { useEffect, useLayoutEffect, useRef } from "react"; 2 2 import type { ChatMessage as ChatMessageType } from "#/lib/types"; 3 3 import { ChatMessage } from "./chat-message"; 4 4 import { Stack } from "./layout"; ··· 7 7 messages: ChatMessageType[]; 8 8 } 9 9 10 + const SCROLL_THRESHOLD_PX = 2; 11 + 12 + function getDistanceFromBottom(container: HTMLDivElement) { 13 + return container.scrollHeight - container.scrollTop - container.clientHeight; 14 + } 15 + 16 + function scrollToBottom(container: HTMLDivElement) { 17 + container.scrollTo({ 18 + top: container.scrollHeight, 19 + behavior: "instant", 20 + }); 21 + } 22 + 10 23 export function ChatMessages({ messages }: ChatMessageProps) { 11 - const endRef = useRef<HTMLDivElement>(null); 24 + const containerRef = useRef<HTMLDivElement>(null); 25 + const hasInitializedScrollRef = useRef(false); 26 + const shouldStickToBottomRef = useRef(true); 27 + const previousMessagesRef = useRef(messages); 12 28 13 29 useEffect(() => { 14 - if (messages.length === 0) return; 15 - endRef.current?.scrollIntoView({ block: "end", behavior: "instant" }); 30 + const container = containerRef.current; 31 + if (!container) return; 32 + 33 + function updateShouldStickToBottom() { 34 + shouldStickToBottomRef.current = 35 + getDistanceFromBottom(container) <= SCROLL_THRESHOLD_PX; 36 + } 37 + 38 + updateShouldStickToBottom(); 39 + container.addEventListener("scroll", updateShouldStickToBottom); 40 + 41 + return () => { 42 + container.removeEventListener("scroll", updateShouldStickToBottom); 43 + }; 44 + }, []); 45 + 46 + useLayoutEffect(() => { 47 + const container = containerRef.current; 48 + if (!container || messages.length === 0) return; 49 + 50 + const previousMessages = previousMessagesRef.current; 51 + const previousLastMessage = previousMessages.at(-1); 52 + const lastMessage = messages.at(-1); 53 + const messageWasAppended = messages.length > previousMessages.length; 54 + const shouldForceScroll = 55 + !hasInitializedScrollRef.current || 56 + (messageWasAppended && 57 + (lastMessage?.role === "user" || 58 + (previousLastMessage?.role === "user" && 59 + lastMessage?.role === "assistant"))); 60 + 61 + if (!hasInitializedScrollRef.current) { 62 + hasInitializedScrollRef.current = true; 63 + } 64 + 65 + if (shouldForceScroll || shouldStickToBottomRef.current) { 66 + scrollToBottom(container); 67 + shouldStickToBottomRef.current = true; 68 + } 69 + 70 + previousMessagesRef.current = messages; 16 71 }, [messages]); 17 72 18 73 return ( 19 - <Stack className="no-scrollbar flex-1 overflow-y-auto"> 74 + <Stack ref={containerRef} className="no-scrollbar flex-1 overflow-y-auto"> 20 75 {messages.map((message) => ( 21 76 <ChatMessage 22 77 key={message.id} ··· 24 79 text={message.text} 25 80 /> 26 81 ))} 27 - <div ref={endRef} /> 28 82 </Stack> 29 83 ); 30 84 }