From 611de0f27423ce83f109b3c4b027c20d1d06ef4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 27 Jun 2026 17:58:22 +0200 Subject: [PATCH] fix(settings): stop the mobile section selector animating a scroll from the top --- src/components/admin/admin-shell.tsx | 20 +++++++++----------- src/components/settings/settings-shell.tsx | 11 +++++------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/components/admin/admin-shell.tsx b/src/components/admin/admin-shell.tsx index 39f94bb37..1e4bd7ede 100644 --- a/src/components/admin/admin-shell.tsx +++ b/src/components/admin/admin-shell.tsx @@ -37,7 +37,6 @@ import { } from "lucide-react"; import { cn } from "@/lib/utils"; -import { scrollBehaviorForUser } from "@/lib/motion"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; import { isAdminSectionSlug, type AdminSectionSlug } from "./section-slugs"; @@ -188,16 +187,15 @@ export function AdminShell({ active, children }: AdminShellProps) { '[aria-current="page"]', ); if (!activeChip) return; - activeChip.scrollIntoView({ - block: "nearest", - // v1.4.36 W4b — `inline: "start"` pins the active chip to the - // left edge of the scroller. `inline: "center"` over-scrolled - // and the first one or two chips were unreachable on narrow - // viewports. - inline: "start", - // v1.4.43 W5-H5 — respect `prefers-reduced-motion`. - behavior: scrollBehaviorForUser(), - }); + // Adjust only the strip's own horizontal offset, instantly. The old + // `scrollIntoView` walked every scrollable ancestor and could nudge the + // document vertically, and a smooth behaviour animated the strip from a + // reset `scrollLeft: 0` to the target on every tap — an unwanted "scroll + // from the start" sweep. `scrollTo({ left, behavior: "auto" })` confines + // the motion to the strip's horizontal axis and jumps without animation. + const maxScroll = strip.scrollWidth - strip.clientWidth; + const target = Math.max(0, Math.min(activeChip.offsetLeft, maxScroll)); + strip.scrollTo({ left: target, behavior: "auto" }); }, [activeSlug]); // The section bodies are already role-gated, but the shell frame diff --git a/src/components/settings/settings-shell.tsx b/src/components/settings/settings-shell.tsx index e091cfc9c..865b52a57 100644 --- a/src/components/settings/settings-shell.tsx +++ b/src/components/settings/settings-shell.tsx @@ -43,7 +43,6 @@ import { } from "lucide-react"; import { cn } from "@/lib/utils"; -import { scrollBehaviorForUser } from "@/lib/motion"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; import type { ModuleKey } from "@/lib/modules/registry"; @@ -366,11 +365,11 @@ export function SettingsShell({ // box, which is the origin for `scrollLeft`. const maxScroll = strip.scrollWidth - strip.clientWidth; const target = Math.max(0, Math.min(active.offsetLeft, maxScroll)); - strip.scrollTo({ - left: target, - // v1.4.43 W5-H5 — respect `prefers-reduced-motion`. - behavior: scrollBehaviorForUser(), - }); + // Position the active chip instantly. A smooth behaviour here animates + // the strip from its reset `scrollLeft: 0` to the target on every tap, + // which reads as an unwanted "scroll from the start" sweep when swapping + // sections. An instant jump keeps the chip in view without the sweep. + strip.scrollTo({ left: target, behavior: "auto" }); }, [activeSlug]); // v1.18.6.1 — resolve the heading once. Pages pass an explicit `heading`