diff --git a/frontend/src/components/Dashboard/PanelRail.tsx b/frontend/src/components/Dashboard/PanelRail.tsx index d179edf..a48126b 100644 --- a/frontend/src/components/Dashboard/PanelRail.tsx +++ b/frontend/src/components/Dashboard/PanelRail.tsx @@ -1,5 +1,7 @@ import { useQueries } from "@tanstack/react-query" import type { LinkProps } from "@tanstack/react-router" +import { useReducedMotion } from "motion/react" +import { useEffect, useRef } from "react" import { dashboardQueryOptions } from "@/components/Dashboard/queries" import { useUi } from "./ui" @@ -32,6 +34,8 @@ export function PanelRail({ horizontal?: boolean }) { const { Rail } = useUi() + const box = useRef(null) + const still = useReducedMotion() const entries = useQueries({ queries: dashboards.map((name) => dashboardQueryOptions(name)), combine: (results) => @@ -44,5 +48,28 @@ export function PanelRail({ })), }) - return + // A row of more dashboards than fit scrolls sideways, so the current one is + // brought to its middle. Only the row moves: `scrollIntoView` would scroll + // the page under it too. + // biome-ignore lint/correctness/useExhaustiveDependencies: a new current dashboard is what scrolls. + useEffect(() => { + if (!horizontal) return + const item = box.current?.querySelector('[aria-current="page"]') + const row = item?.closest(".dui-rail") + if (!item || !row) return + const at = item.getBoundingClientRect() + const frame = row.getBoundingClientRect() + row.scrollBy({ + left: at.left + at.width / 2 - (frame.left + frame.width / 2), + behavior: still ? "instant" : "smooth", + }) + }, [current, horizontal, still]) + + // `contents`, so the wrapper is only something to search from: the caller + // places the rail itself. + return ( +
+ +
+ ) }