From 64c644f42c658ac173b688c5c165b121024bbd4a Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Thu, 17 Sep 2026 14:04:43 +0300 Subject: [PATCH] =?UTF-8?q?Header=20round=206c=20(owner):=20the=20sidebar?= =?UTF-8?q?=20toggle=20owns=20an=20animated=20slot=20=E2=80=94=20no=20more?= =?UTF-8?q?=20one-frame=20jumps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the sidebar toggled, the header's toggle mounted/unmounted in a single frame, so its 48px footprint (button + gap) popped and dragged the title and chips sideways while the sidebar was still mid-slide — 'kind of masked by the motion, but very much noticeable.' The toggle is now ALWAYS MOUNTED, owning a slot whose width+margin animate in lockstep with the sidebar's own width transition (same duration-200, same easing, both gated on mount so no load flash): - when open the slot is exactly ZERO (w-0 with -mr-3 cancelling the header's gap-3) — resting layouts are pixel-identical to the old conditional render; - the icon fades with the slot (opacity in the same 200ms) and is clipped by it as the slot collapses — no popping in either direction; hidden state is pointer-events-none, aria-hidden, tabIndex -1; - the mount-gating rule grows to transition-[width,margin,opacity]. rAF-verified both directions: the title's x-path is monotone from frame one (old bug teleported 48px in one frame; the margin blip caught on the way is also gone), icon opacity eases 0.12→1 over the slide. Gates: typecheck, lint, 469/469. --- components/sidebar.tsx | 43 +++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 2425cd3..e1b387c 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -52,22 +52,47 @@ export function SidebarProvider({ children }: { children: React.ReactNode }) { return ( {/* transitions gated on mount: no slide-in flash on page load */} -
{children}
+
{children}
); } -/** Hamburger for page headers — only visible when the sidebar is closed. */ +/** Hamburger for page headers — only visible when the sidebar is closed. + * + * Owner, round 6c: the toggle is ALWAYS MOUNTED, owning a slot whose + * width+margin animate in LOCKSTEP with the sidebar's own width + * transition (same duration-200, same easing, both gated on mount) — + * before, it mounted/unmounted in one frame, so its 48px footprint + * (button + gap) popped and dragged the title and chips sideways while + * the sidebar was still mid-slide. When open the slot is exactly zero + * (w-0 -mr-3: width 0, the negative margin cancels the header's gap-3), + * so resting layouts are pixel-identical to the old conditional render. + * The icon fades with the slot and is clipped by it as it collapses — + * no popping in either direction. */ export function SidebarToggle() { const { open, toggle } = useSidebar(); - if (open) return null; return ( - + + + ); }