Size the rail to what it carries

Stretched end to end, a panel with two dashboards showed a pill nine
tenths empty — which was tolerable while the rail was chrome outside the
canvas and obvious once it moved onto the panel itself. It now hugs its
entries and sits centred in the column it reserves; that column is the
same width either way, so no arrangement moves.

A rail longer than the panel is tall still scrolls, and still draws no
bar: a wall panel is swiped, and there is no room for one.
This commit is contained in:
2026-08-24 11:20:32 +02:00
parent 6238728dce
commit 579eb8e61a
4 changed files with 24 additions and 8 deletions
-4
View File
@@ -35,10 +35,6 @@ Deferring because out of scope is fine, but don't mention deferring than.
(`ui/segmented.tsx`, `Common/RangePicker.tsx`, and the flow screens). The (`ui/segmented.tsx`, `Common/RangePicker.tsx`, and the flow screens). The
dashboard no longer shares them — it has its own, one per look — so dashboard no longer shares them — it has its own, one per look — so
unifying the remaining three is now purely an app-side job. unifying the remaining three is now purely an app-side job.
- CHORE/UI: a panel's rail runs the full height of the canvas whatever it
carries, so two dashboards leave most of a tall pill empty. Hugging its
contents and centring would read better; the column it reserves stays the
same either way.
- CHORE/UI: the house panels are laid out for 1280x800 — twelve columns, twelve - CHORE/UI: the house panels are laid out for 1280x800 — twelve columns, twelve
rows. A chart's fixed chrome is now its legend alone: the range picker moved rows. A chart's fixed chrome is now its legend alone: the range picker moved
to a column down the right-hand edge and gave its row back, so a third chart to a column down the right-hand edge and gave its row back, so a third chart
@@ -158,7 +158,11 @@ export function Rail({ entries }: RailProps) {
aria-label="Dashboards on this panel" aria-label="Dashboards on this panel"
data-testid={TESTID.rail} data-testid={TESTID.rail}
className={cn( className={cn(
"gl-surface gl-rail pointer-events-auto absolute inset-y-3 left-3 z-10 flex w-12 flex-col items-center gap-1 p-1", "gl-surface gl-rail pointer-events-auto absolute left-3 top-1/2 z-10 flex max-h-[calc(100%-1.5rem)] w-12 -translate-y-1/2 flex-col items-center gap-1 p-1",
// As tall as what it carries, centred in the column it reserves: a
// rail of two stretched to the height of the panel is mostly empty
// pill. More dashboards than the panel is tall still scroll, but no
// bar is ever drawn — a wall panel is swiped, and there is no room.
"overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", "overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
)} )}
> >
@@ -124,9 +124,11 @@ export function Rail({ entries }: RailProps) {
aria-label="Dashboards on this panel" aria-label="Dashboards on this panel"
data-testid={TESTID.rail} data-testid={TESTID.rail}
className={cn( className={cn(
"m3-rail pointer-events-auto absolute inset-y-3 left-3 z-10 flex w-12 flex-col items-center gap-1 p-1", "m3-rail pointer-events-auto absolute left-3 top-1/2 z-10 flex max-h-[calc(100%-1.5rem)] w-12 -translate-y-1/2 flex-col items-center gap-1 p-1",
// More dashboards than the column is tall still scroll, but no bar is // As tall as what it carries, centred in the column it reserves: a
// ever drawn: a wall panel is swiped, and there is no room for one. // rail of two stretched to the height of the panel is mostly empty
// pill. More dashboards than the panel is tall still scroll, but no
// bar is ever drawn — a wall panel is swiped, and there is no room.
"overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", "overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
)} )}
> >
+14
View File
@@ -8,6 +8,9 @@ import { api, apiPage, deleteAll } from "./utils/api"
* is inside the canvas, scaled with it, and wearing the dashboard's own look. * is inside the canvas, scaled with it, and wearing the dashboard's own look.
* Drawn outside, it was a strip of the app's design bolted to the edge of * Drawn outside, it was a strip of the app's design bolted to the edge of
* somebody's wall panel — and on a scaled canvas it did not even line up. * somebody's wall panel — and on a scaled canvas it did not even line up.
*
* It is also as tall as what it carries. Stretched end to end, a panel with
* two dashboards showed a pill nine tenths empty.
*/ */
const flowName = `test_panel_${Date.now().toString(36)}` const flowName = `test_panel_${Date.now().toString(36)}`
@@ -114,6 +117,17 @@ test("the rail is drawn on the panel, in the panel's own look", async ({
) )
expect(inside, "the rail is drawn outside the panel it belongs to").toBe(true) expect(inside, "the rail is drawn outside the panel it belongs to").toBe(true)
// As tall as what it carries rather than as tall as the panel: a rail of
// two stretched end to end is mostly empty pill.
expect(
box.height,
"the rail is stretched to the height of the panel",
).toBeLessThan(canvas.height / 2)
const centres = Math.abs(
box.y + box.height / 2 - (canvas.y + canvas.height / 2),
)
expect(centres, "the rail is not centred in its column").toBeLessThan(2)
// And the arrangement keeps clear of it rather than sitting under it. // And the arrangement keeps clear of it rather than sitting under it.
const tile = (await page.getByTestId("widget-frame").first().boundingBox())! const tile = (await page.getByTestId("widget-frame").first().boundingBox())!
expect(tile.x, "a widget is drawn under the rail").toBeGreaterThanOrEqual( expect(tile.x, "a widget is drawn under the rail").toBeGreaterThanOrEqual(