From 61daffe66547db1e9802babc37debbc5829b5138 Mon Sep 17 00:00:00 2001 From: Ulrik Andersen Date: Mon, 8 Dec 2025 15:23:35 +0100 Subject: [PATCH] Fix diff sidebar flash on page load --- .../sidebar/view/SecondarySplitHeader.tsx | 4 +++ .../view/internal/secondary/Container.tsx | 29 ++++++++++++++----- .../view/internal/tertiary/RightContainer.tsx | 7 +++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/features/sidebar/view/SecondarySplitHeader.tsx b/src/features/sidebar/view/SecondarySplitHeader.tsx index 870bae8b..78ff1057 100644 --- a/src/features/sidebar/view/SecondarySplitHeader.tsx +++ b/src/features/sidebar/view/SecondarySplitHeader.tsx @@ -123,9 +123,12 @@ const ToggleDiffButton = ({ isDiffAvailable: boolean }) => { const [isMac, setIsMac] = useState(false) + // Prevent flash of button during SSR/hydration. Only render after client mount. + const [mounted, setMounted] = useState(false) useEffect(() => { // checkIsMac uses window so we delay the check. const timeout = window.setTimeout(() => { + setMounted(true) setIsMac(checkIsMac()) }, 0) return () => window.clearTimeout(timeout) @@ -138,6 +141,7 @@ const ToggleDiffButton = ({ : isDiffbarOpen ? "Hide changes" : "Show changes" + if (!mounted) return null return ( diff --git a/src/features/sidebar/view/internal/secondary/Container.tsx b/src/features/sidebar/view/internal/secondary/Container.tsx index 6a3a9ead..d5b084aa 100644 --- a/src/features/sidebar/view/internal/secondary/Container.tsx +++ b/src/features/sidebar/view/internal/secondary/Container.tsx @@ -1,3 +1,6 @@ +"use client" + +import { useState, useEffect } from "react" import { SxProps } from "@mui/system" import { Box, Stack } from "@mui/material" import { styled } from "@mui/material/styles" @@ -18,15 +21,23 @@ const SecondaryContainer = ({ children?: React.ReactNode, isSM: boolean, }) => { + // Disable transitions on initial render to prevent layout shift + const [enableTransitions, setEnableTransitions] = useState(false) + useEffect(() => { + // Enable transitions after first paint + const rafId = requestAnimationFrame(() => setEnableTransitions(true)) + return () => cancelAnimationFrame(rafId) + }, []) + const sx = { overflow: "hidden" } return ( <> {children} @@ -43,19 +54,20 @@ interface WrapperStackProps { readonly isSidebarOpen: boolean readonly diffWidth: number readonly isDiffOpen: boolean + readonly enableTransitions: boolean } const WrapperStack = styled(Stack, { - shouldForwardProp: (prop) => prop !== "isSidebarOpen" && prop !== "sidebarWidth" && prop !== "diffWidth" && prop !== "isDiffOpen" -})(({ theme, sidebarWidth, isSidebarOpen, diffWidth, isDiffOpen }) => { + shouldForwardProp: (prop) => prop !== "isSidebarOpen" && prop !== "sidebarWidth" && prop !== "diffWidth" && prop !== "isDiffOpen" && prop !== "enableTransitions" +})(({ theme, sidebarWidth, isSidebarOpen, diffWidth, isDiffOpen, enableTransitions }) => { return { - transition: theme.transitions.create(["margin", "width"], { + transition: enableTransitions ? theme.transitions.create(["margin", "width"], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen - }), + }) : "none", marginLeft: isSidebarOpen ? 0 : `-${sidebarWidth}px`, marginRight: isDiffOpen ? 0 : `-${diffWidth}px`, - ...((isSidebarOpen || isDiffOpen) && { + ...((isSidebarOpen || isDiffOpen) && enableTransitions && { transition: theme.transitions.create(["margin", "width"], { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen, @@ -69,6 +81,7 @@ const InnerSecondaryContainer = ({ isSidebarOpen, diffWidth, isDiffOpen, + enableTransitions, children, sx }: { @@ -76,6 +89,7 @@ const InnerSecondaryContainer = ({ isSidebarOpen: boolean diffWidth: number isDiffOpen: boolean + enableTransitions: boolean children: React.ReactNode sx?: SxProps }) => { @@ -87,8 +101,9 @@ const InnerSecondaryContainer = ({ isSidebarOpen={isSidebarOpen} diffWidth={diffWidth} isDiffOpen={isDiffOpen} + enableTransitions={enableTransitions} sx={{ ...sx, width: "100%", overflowY: "auto" }} - + > {children} diff --git a/src/features/sidebar/view/internal/tertiary/RightContainer.tsx b/src/features/sidebar/view/internal/tertiary/RightContainer.tsx index f48f7adb..52b8721c 100644 --- a/src/features/sidebar/view/internal/tertiary/RightContainer.tsx +++ b/src/features/sidebar/view/internal/tertiary/RightContainer.tsx @@ -1,5 +1,6 @@ 'use client' +import { useState, useEffect } from "react" import { SxProps } from "@mui/system" import { Drawer as MuiDrawer } from "@mui/material" import { useTheme } from "@mui/material/styles" @@ -15,6 +16,12 @@ const RightContainer = ({ onClose?: () => void children?: React.ReactNode }) => { + // Don't render drawer until client-side to prevent flash + const [mounted, setMounted] = useState(false) + useEffect(() => { + requestAnimationFrame(() => setMounted(true)) + }, []) + if (!mounted) return null return ( <>