perf(web): smooth item sidebar collapse (#504)

This commit is contained in:
Suspense
2026-07-28 09:50:30 -04:00
committed by GitHub
parent 315f843fa5
commit 5d6f6323d5
28 changed files with 2389 additions and 676 deletions
@@ -0,0 +1,48 @@
import { useEffect, useRef, useState } from "react";
export function prefersReducedMotion(): boolean {
return (
typeof window !== "undefined" &&
typeof window.matchMedia === "function" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches
);
}
/**
* Separates the layout snap from the visible compositor transition by one
* animation frame. This is the minimum handoff needed for the inverse main
* transform to take effect before the sidebar starts moving; it never waits on
* item metadata, poster decoding, backdrop loading, or frame-rate heuristics.
*/
export function useImmediateSidebarCollapse(collapsed: boolean): boolean {
const [visualCollapsed, setVisualCollapsed] = useState(collapsed);
const [reduceMotion, setReduceMotion] = useState(prefersReducedMotion);
const frameRef = useRef(0);
useEffect(() => {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
const handleChange = (event: MediaQueryListEvent) => {
// Synchronize the internal visual state on both edges so disabling the
// preference cannot play a stale catch-up transition.
setVisualCollapsed(collapsed);
setReduceMotion(event.matches);
};
query.addEventListener("change", handleChange);
return () => query.removeEventListener("change", handleChange);
}, [collapsed]);
useEffect(() => {
if (visualCollapsed === collapsed || reduceMotion) return;
if (typeof requestAnimationFrame !== "function") {
const timer = window.setTimeout(() => setVisualCollapsed(collapsed), 0);
return () => window.clearTimeout(timer);
}
frameRef.current = requestAnimationFrame(() => setVisualCollapsed(collapsed));
return () => cancelAnimationFrame(frameRef.current);
}, [collapsed, reduceMotion, visualCollapsed]);
return reduceMotion ? collapsed : visualCollapsed;
}