From cd83733465869e62289b50d357e3ac10f0ce1bcb Mon Sep 17 00:00:00 2001 From: Yao Xiao <108576690+Charlie-XIAO@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:59:26 -0500 Subject: [PATCH] app(ui/ux): add scroll-to-top and scroll-to-botton buttons (#13) --- app/frontend/src/components/ChatPanel.tsx | 2 + .../src/components/FCScrollButtons.tsx | 77 +++++++++++++++++++ app/frontend/src/components/RetrievePanel.tsx | 2 + app/frontend/src/utils.ts | 12 +++ 4 files changed, 93 insertions(+) create mode 100644 app/frontend/src/components/FCScrollButtons.tsx diff --git a/app/frontend/src/components/ChatPanel.tsx b/app/frontend/src/components/ChatPanel.tsx index 7b16696..165ba0e 100644 --- a/app/frontend/src/components/ChatPanel.tsx +++ b/app/frontend/src/components/ChatPanel.tsx @@ -29,6 +29,7 @@ import { addMessageUtilities, scrollToBottom } from "../utils"; import { MessageDocs } from "./MessageDocs"; import { RetrievalPanelCommandPalette } from "./RetrievePanelCommandPalette"; import { FCDeleteChatButton } from "./FCDeleteChatButton"; +import { FCScrollButtons } from "./FCScrollButtons"; interface ChatPanelProps { model: ModelType; @@ -107,6 +108,7 @@ export const ChatPanel = ({ onClick={() => setMessages(() => [])} />, , + , ]; // Persistent right functional components diff --git a/app/frontend/src/components/FCScrollButtons.tsx b/app/frontend/src/components/FCScrollButtons.tsx new file mode 100644 index 0000000..d5f6d6a --- /dev/null +++ b/app/frontend/src/components/FCScrollButtons.tsx @@ -0,0 +1,77 @@ +/** + * @file FCScrollButtons.tsx + * + * The functional component for the scroll to top and scroll to bottom buttons, + * used at the bottom of the chat input. + */ + +import { IconButton, Tooltip } from "@radix-ui/themes"; +import { RefObject, useEffect, useRef, useState } from "react"; +import { GoMoveToBottom, GoMoveToTop } from "react-icons/go"; +import { scrollToBottom, scrollToTop } from "../utils"; + +interface FCScrollButtonsProps { + containerRef: RefObject; +} + +export const FCScrollButtons = ({ containerRef }: FCScrollButtonsProps) => { + const [canScrollUp, setCanScrollUp] = useState(false); + const [canScrollDown, setCanScrollDown] = useState(false); + const debounceTimer = useRef(null); + + // Handler for the container scroll event; it is debounced for better + // performance + const handleScroll = () => { + if (debounceTimer.current !== null) { + clearTimeout(debounceTimer.current); + } + debounceTimer.current = setTimeout(() => { + if (containerRef.current !== null) { + const { scrollTop, scrollHeight, clientHeight } = containerRef.current; + setCanScrollUp(scrollTop > 0); + setCanScrollDown(scrollTop + clientHeight < scrollHeight); + } + }, 300); + }; + + useEffect(() => { + const container = containerRef.current; + if (container !== null) { + container.addEventListener("scroll", handleScroll, { passive: true }); + } + + return () => { + if (container !== null) { + container.removeEventListener("scroll", handleScroll); + } + if (debounceTimer.current !== null) { + clearTimeout(debounceTimer.current); + } + }; + }, [containerRef]); + + return ( + <> + + scrollToTop(containerRef)} + > + + + + + scrollToBottom(containerRef)} + > + + + + + ); +}; diff --git a/app/frontend/src/components/RetrievePanel.tsx b/app/frontend/src/components/RetrievePanel.tsx index 90201c9..871b1a8 100644 --- a/app/frontend/src/components/RetrievePanel.tsx +++ b/app/frontend/src/components/RetrievePanel.tsx @@ -20,6 +20,7 @@ import { ChatErrorMessage } from "./ChatErrorMessage"; import { addMessageUtilities, scrollToBottom } from "../utils"; import { MessageRetrieved } from "./MessageRetrieved"; import { FCTopKSelector } from "./FCTopKSelector"; +import { FCScrollButtons } from "./FCScrollButtons"; interface RetrievalPanelProps { messages: ChatDisplay[]; @@ -70,6 +71,7 @@ export const RetrievePanel = ({ disabled={messages.length === 0 || loading} onClick={() => setMessages(() => [])} />, + , ]; // Persistent right functional components diff --git a/app/frontend/src/utils.ts b/app/frontend/src/utils.ts index 6dcf5f8..f9aac90 100644 --- a/app/frontend/src/utils.ts +++ b/app/frontend/src/utils.ts @@ -41,3 +41,15 @@ export const scrollToBottom = (ref: RefObject) => { }); } }; + +/** + * Scroll to the top of a React ref object. + */ +export const scrollToTop = (ref: RefObject) => { + if (ref.current !== null) { + ref.current.scrollTo({ + top: 0, + behavior: "smooth", + }); + } +};