perf: throttle scroll handlers with requestAnimationFrame

Wrap scroll handlers in thread.tsx, InboxSidebar.tsx, and
DocumentsTableShell.tsx with requestAnimationFrame batching so scroll
position state updates fire at most once per animation frame instead of
on every scroll event (up to 60/sec at 60fps). Add cleanup useEffect to
cancel pending frames on unmount.

Fixes #1103
This commit is contained in:
Matt Van Horn 2026-04-02 23:21:57 -07:00
parent c1c4c534c0
commit 3621951f2a
3 changed files with 27 additions and 9 deletions

View file

@ -816,12 +816,18 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
const isDesktop = useMediaQuery("(min-width: 640px)");
const { openDialog: openUploadDialog } = useDocumentUploadDialog();
const [toolsScrollPos, setToolsScrollPos] = useState<"top" | "middle" | "bottom">("top");
const toolsRafRef = useRef<number>();
const handleToolsScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
const el = e.currentTarget;
const atTop = el.scrollTop <= 2;
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 2;
setToolsScrollPos(atTop ? "top" : atBottom ? "bottom" : "middle");
if (toolsRafRef.current) return;
toolsRafRef.current = requestAnimationFrame(() => {
const atTop = el.scrollTop <= 2;
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 2;
setToolsScrollPos(atTop ? "top" : atBottom ? "bottom" : "middle");
toolsRafRef.current = undefined;
});
}, []);
useEffect(() => () => { if (toolsRafRef.current) cancelAnimationFrame(toolsRafRef.current); }, []);
const isComposerTextEmpty = useAuiState(({ composer }) => {
const text = composer.text?.trim() || "";
return text.length === 0;