A widget that fell off the panel, after the canvas was made smaller, was drawn under the clip and could only be re-packed with Reflow. Editing, the canvas now extends to the lowest widget and scrolls, with the panel's own edge dashed and the area past it hatched, so the widget can be dragged back. The scale stays the panel's, and view mode is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
137 lines
4.8 KiB
CSS
137 lines
4.8 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;
|
|
}
|
|
|
|
/*
|
|
* Below the panel's bottom edge, while the editor has grown the canvas to
|
|
* reach widgets that fell off it: hatched, under a dashed line where the panel
|
|
* ends, so what it will not show reads as outside it.
|
|
*/
|
|
.canvas-beyond {
|
|
border-top: 3px dashed
|
|
color-mix(in srgb, var(--muted-foreground) 70%, transparent);
|
|
background: repeating-linear-gradient(
|
|
-45deg,
|
|
color-mix(in srgb, var(--muted-foreground) 14%, transparent) 0 3px,
|
|
transparent 3px 16px
|
|
);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|