Scroll the current dashboard to the middle of the phone rail

On mount and on every switch the horizontal rail scrolls itself (not the
page) so the active entry is centred, instantly under reduced motion.
This commit is contained in:
2026-10-06 07:24:53 +02:00
parent eca2ccde2f
commit 70421c7454
@@ -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<HTMLDivElement>(null)
const still = useReducedMotion()
const entries = useQueries({
queries: dashboards.map((name) => dashboardQueryOptions(name)),
combine: (results) =>
@@ -44,5 +48,28 @@ export function PanelRail({
})),
})
return <Rail entries={entries} horizontal={horizontal} />
// 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 (
<div ref={box} className="contents">
<Rail entries={entries} horizontal={horizontal} />
</div>
)
}