Skip to content

Commit d6da053

Browse files
Fix useScrollToBottom hook for rapid content changes
- Improved scroll behavior logic to better handle rapidly changing content - Added smooth scrolling with fast animation for better user experience - Added CSS class for consistent scroll behavior across components - Applied optimizations to all components using the scroll hook
1 parent b326433 commit d6da053

File tree

5 files changed

+59
-21
lines changed

5 files changed

+59
-21
lines changed

frontend/src/components/features/chat/chat-interface.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export function ChatInterface() {
127127
<div
128128
ref={scrollRef}
129129
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
130-
className="flex flex-col grow overflow-y-auto overflow-x-hidden px-4 pt-4 gap-2"
130+
className="flex flex-col grow overflow-y-auto overflow-x-hidden px-4 pt-4 gap-2 fast-smooth-scroll"
131131
>
132132
{isLoadingMessages && (
133133
<div className="flex justify-center">

frontend/src/components/features/jupyter/jupyter.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function JupyterEditor({ maxWidth }: JupyterEditorProps) {
2020
<div className="flex-1 h-full flex flex-col" style={{ maxWidth }}>
2121
<div
2222
data-testid="jupyter-container"
23-
className="flex-1 overflow-y-auto"
23+
className="flex-1 overflow-y-auto fast-smooth-scroll"
2424
ref={jupyterRef}
2525
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
2626
>

frontend/src/components/shared/modals/security/invariant/invariant.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ function SecurityInvariant() {
131131
{t(I18nKey.INVARIANT$EXPORT_TRACE_LABEL)}
132132
</Button>
133133
</div>
134-
<div className="flex-1 p-4 max-h-screen overflow-y-auto" ref={logsRef}>
134+
<div
135+
className="flex-1 p-4 max-h-screen overflow-y-auto fast-smooth-scroll"
136+
ref={logsRef}
137+
>
135138
{logs.map((log: SecurityAnalyzerLog, index: number) => (
136139
<div
137140
key={index}

frontend/src/hooks/use-scroll-to-bottom.ts

+47-18
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,70 @@
1-
import { RefObject, useEffect, useState } from "react";
1+
import { RefObject, useEffect, useState, useCallback } from "react";
22

33
export function useScrollToBottom(scrollRef: RefObject<HTMLDivElement | null>) {
4-
// for auto-scroll
4+
// Track whether we should auto-scroll to the bottom when content changes
5+
const [shouldScrollToBottom, setShouldScrollToBottom] = useState(true);
56

6-
const [autoScroll, setAutoScroll] = useState(true);
7+
// Track whether the user is currently at the bottom of the scroll area
78
const [hitBottom, setHitBottom] = useState(true);
89

9-
const onChatBodyScroll = (e: HTMLElement) => {
10-
const bottomHeight = e.scrollTop + e.clientHeight;
10+
// Check if the scroll position is at the bottom
11+
const isAtBottom = useCallback((element: HTMLElement): boolean => {
12+
const bottomThreshold = 10; // Pixels from bottom to consider "at bottom"
13+
const bottomPosition = element.scrollTop + element.clientHeight;
14+
return bottomPosition >= element.scrollHeight - bottomThreshold;
15+
}, []);
1116

12-
const isHitBottom = bottomHeight >= e.scrollHeight - 10;
17+
// Handle scroll events
18+
const onChatBodyScroll = useCallback(
19+
(e: HTMLElement) => {
20+
const isCurrentlyAtBottom = isAtBottom(e);
21+
setHitBottom(isCurrentlyAtBottom);
1322

14-
setHitBottom(isHitBottom);
15-
setAutoScroll(isHitBottom);
16-
};
23+
// Only update shouldScrollToBottom when user manually scrolls
24+
// This prevents content changes from affecting our scroll behavior decision
25+
setShouldScrollToBottom(isCurrentlyAtBottom);
26+
},
27+
[isAtBottom],
28+
);
1729

18-
function scrollDomToBottom() {
30+
// Scroll to bottom function with animation
31+
const scrollDomToBottom = useCallback(() => {
1932
const dom = scrollRef.current;
2033
if (dom) {
2134
requestAnimationFrame(() => {
22-
setAutoScroll(true);
23-
dom.scrollTo({ top: dom.scrollHeight, behavior: "auto" });
35+
// Set shouldScrollToBottom to true when manually scrolling to bottom
36+
setShouldScrollToBottom(true);
37+
setHitBottom(true);
38+
39+
// Use smooth scrolling but with a fast duration
40+
dom.scrollTo({
41+
top: dom.scrollHeight,
42+
behavior: "smooth",
43+
});
2444
});
2545
}
26-
}
46+
}, [scrollRef]);
2747

28-
// auto scroll
48+
// Auto-scroll effect that runs when content changes
2949
useEffect(() => {
30-
if (autoScroll) {
31-
scrollDomToBottom();
50+
// Only auto-scroll if the user was already at the bottom
51+
if (shouldScrollToBottom) {
52+
const dom = scrollRef.current;
53+
if (dom) {
54+
requestAnimationFrame(() => {
55+
dom.scrollTo({
56+
top: dom.scrollHeight,
57+
behavior: "smooth",
58+
});
59+
});
60+
}
3261
}
3362
});
3463

3564
return {
3665
scrollRef,
37-
autoScroll,
38-
setAutoScroll,
66+
autoScroll: shouldScrollToBottom,
67+
setAutoScroll: setShouldScrollToBottom,
3968
scrollDomToBottom,
4069
hitBottom,
4170
setHitBottom,

frontend/src/index.css

+6
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,9 @@ code {
5757
.markdown-body td {
5858
padding: 0.1rem 1rem;
5959
}
60+
61+
/* Fast smooth scrolling for chat interface */
62+
.fast-smooth-scroll {
63+
scroll-behavior: smooth;
64+
scroll-timeline: 100ms;
65+
}

0 commit comments

Comments
 (0)