Several stateless instructions where a single button is one — six presets are otherwise six tiles to place, six titles to read, and the message they share repeated six times. Nothing is read back, as for a single button: what these send is an instruction, and the last one sent is not a state to draw. Auto-fit columns rather than a configured count: a tile is resized in the editor and scaled again to whatever panel it hangs on, so how many fit is not something the document can know. The editor reads `cfg.buttons` raw rather than through `buttonsOf`, which drops the blanks — a row being typed into is blank until the first keystroke. Also carried along by the hooks: the generated SDK was stale (it had no `RunsReadMetricNames`) and one pre-existing block in `test_panels.py` was unformatted.
122 lines
4.3 KiB
CSS
122 lines
4.3 KiB
CSS
/*
|
|
* Dashboard surface, routed through the design tokens.
|
|
*
|
|
* Scoped like `Flow/flow.css`: the grid maths and the react-grid-layout
|
|
* overrides live beside the components that use them rather than in index.css,
|
|
* so nothing here touches the byte-identical token blocks.
|
|
*/
|
|
|
|
/* The placement grid, as plain CSS. One dot per cell corner: the surface hands
|
|
in the pitch its widgets actually snap to (`--dot-x` / `--dot-y`), and the
|
|
negative offset puts a dot centre on the grid's own origin. */
|
|
.dot-canvas {
|
|
background-image: radial-gradient(
|
|
circle at 1px 1px,
|
|
color-mix(in srgb, var(--muted-foreground) 30%, transparent) 1.5px,
|
|
transparent 0
|
|
);
|
|
background-size: var(--dot-x, 24px) var(--dot-y, 24px);
|
|
background-position: -1px -1px;
|
|
}
|
|
|
|
/*
|
|
* View mode's grid. Column count is per dashboard (`--widget-cols`), so a wall
|
|
* panel can be matched to its own width. It does not reflow: the grid lives on
|
|
* a canvas of the panel's own pixel size, which is scaled to the viewport.
|
|
* A phone gets `.widget-stacked` below instead, which is a different surface
|
|
* rather than a narrower version of this one.
|
|
*/
|
|
.widget-grid {
|
|
display: grid;
|
|
gap: 0.75rem;
|
|
/* The row height follows the canvas, handed in beside the column count;
|
|
5rem is what a 1920x1080 panel at 12 columns derives. */
|
|
grid-auto-rows: var(--row-height, 5rem);
|
|
grid-template-columns: repeat(var(--widget-cols), minmax(0, 1fr));
|
|
/* Stacked, this is a flex item in an `auto` track, and its automatic minimum
|
|
size is the widest thing any widget holds — one long message name would
|
|
size the column and take the page sideways with it. The fixed-pixel canvas
|
|
the non-stacked path sits on already uses `minmax(0, 1fr)`, so this only
|
|
ever matters on a phone. See DESIGN-GUIDELINES.md -> Responsive. */
|
|
min-width: 0;
|
|
}
|
|
|
|
.widget-cell {
|
|
grid-column: span var(--w);
|
|
grid-row: span var(--h);
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Only once something has actually been placed; an untouched dashboard has
|
|
every widget at 0,0 and would pile up on one cell. */
|
|
.widget-grid[data-placed] .widget-cell {
|
|
grid-column: var(--x) / span var(--w);
|
|
grid-row: var(--y) / span var(--h);
|
|
}
|
|
|
|
/*
|
|
* One column, in reading order, at the viewport's own width — what a phone
|
|
* gets instead of a wall panel shrunk to 18%. Arranging is not a phone
|
|
* feature, so the stored x/w are dropped and only the height a widget asked
|
|
* for survives: a chart still needs its room, a stat still does not.
|
|
* See DESIGN-GUIDELINES.md → Responsive.
|
|
*/
|
|
.widget-grid.widget-stacked,
|
|
.widget-grid.widget-stacked[data-placed] {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.widget-grid.widget-stacked .widget-cell {
|
|
grid-column: auto;
|
|
grid-row: auto;
|
|
height: calc(var(--h) * var(--row-height, 5rem) + (var(--h) - 1) * 0.75rem);
|
|
}
|
|
|
|
/*
|
|
* Motion. A selection indicator moving is emphasized (Material).
|
|
* `<MotionConfig reducedMotion="user">` only covers `motion/react`, so CSS
|
|
* asks for itself. The dashboard's own widgets are drawn by the component
|
|
* sets and animate through `motion`; what is left here is the app-side
|
|
* segmented shape, which the flow screens and the dashboard editor share.
|
|
*/
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
/* One indicator that slides between segments, rather than a fill that jumps
|
|
from cell to cell. */
|
|
.widget-segment-thumb {
|
|
transition:
|
|
left var(--duration-base) var(--ease-emphasized),
|
|
width var(--duration-base) var(--ease-emphasized);
|
|
}
|
|
}
|
|
|
|
/* react-grid-layout ships a red placeholder and a black handle glyph. */
|
|
.react-grid-item.react-grid-placeholder {
|
|
background: var(--primary);
|
|
border-radius: var(--radius-lg);
|
|
opacity: 0.12;
|
|
}
|
|
|
|
.react-grid-item > .react-resizable-handle::after {
|
|
border-right-color: var(--muted-foreground);
|
|
border-bottom-color: var(--muted-foreground);
|
|
}
|
|
|
|
/*
|
|
* A grid of presses, inside one tile.
|
|
*
|
|
* Auto-fit rather than a configured column count: the tile is resized in the
|
|
* editor and scaled again to whatever panel it hangs on, so the number of
|
|
* columns that fits is not something the document can know. `1fr` rows so the
|
|
* presses share the tile's height however many rows they wrap into — a target
|
|
* on a wall panel is pressed without looking at it.
|
|
*/
|
|
.widget-buttons {
|
|
display: grid;
|
|
gap: 0.5rem;
|
|
grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr));
|
|
grid-auto-rows: 1fr;
|
|
height: 100%;
|
|
min-height: 0;
|
|
}
|