Rework the dashboard into two looks over one behaviour
A dashboard is a wall panel somebody hangs in their own hallway, so it now wears what they choose: a look, and a palette of their own colours. Two complete component sets live under `Dashboard/ui/` — `glass` (translucent panes over a slowly moving ground) and `material` (Material 3 tonal cards) — behind one prop contract. Every control's state, keyboard and `aria-` live in `ui/core` and are shared, so the two sets are the same dashboard drawn twice rather than two products: a set only decides what a control looks like while doing it. Four settings join the channel, each drivable by a flow like any other: `look`, `palette`, `background` and `touch`. A palette is an ordered list of hex colours — background, surface, primary, accent, text, then more chart colours — pasted from a coolors.co link or typed, written onto the canvas as the token variables everything already reads. Trailing roles are derived, so three colours are a whole dashboard, and derived text is held to AA rather than trusted (`theme.check.ts` measures it). A palette also decides light or dark, since its first colour is the ground. Widgets are measured against their own tile with container queries rather than against the viewport, animate through `motion`, and can be drawn without their title. The three reworks: - a bar draws a row per reading, up to eight, each in the dashboard's own data colours and each able to carry its own scale — replacing readings nested in one fill, which could only ever share one colour and stop at three. Documents written the old way are read as rows. - a chart's range picker moved to a column down its right-hand edge, which gives the plot back a whole row of a short tile. - the colour wheel became a disc: hue is the angle and saturation the distance from the middle, so a colour is one gesture rather than three, with brightness on a slider beside it. `index.css` and `lib/motion.ts` are untouched — the dashboard overrides token *values* on its canvas, never the blocks the two repos share.
This commit is contained in:
@@ -33,28 +33,6 @@ export const CHART_SLOTS = Array.from({ length: MAX_SERIES }, (_, index) =>
|
||||
/** No palette named; a chart left with this spreads itself. */
|
||||
export const NO_PALETTE: string[] = []
|
||||
|
||||
/**
|
||||
* A stored palette, made safe to draw with: a distinct subset, in draw order.
|
||||
*
|
||||
* Distinct on purpose, and worth keeping that way. Within the ramp only slot 1
|
||||
* against slot 5 clears the 3:1 guideline for non-text — 3.28 light, 3.37 dark,
|
||||
* measured in `BarWidget.tsx` — so two traces on neighbouring slots are already
|
||||
* close, and two on the *same* slot could not be told apart at all. Free
|
||||
* assignment with repeats is not an improvement on this; it is the bug.
|
||||
*
|
||||
* Empty is the answer for a dashboard that names none, and for anything
|
||||
* unrecognised: no palette, so the chart spreads itself — see `slotsFor`.
|
||||
*/
|
||||
export function paletteOf(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) return NO_PALETTE
|
||||
const slots = [...new Set(value.map(String))].filter((slot) =>
|
||||
CHART_SLOTS.includes(slot),
|
||||
)
|
||||
// One shared array for the common answer, so a dashboard that names no
|
||||
// palette does not hand its readers a new one on every render.
|
||||
return slots.length > 0 ? slots : NO_PALETTE
|
||||
}
|
||||
|
||||
/**
|
||||
* What a chart of *n* lines draws with when no palette was named: the ramp
|
||||
* spread, rather than its first *n* steps.
|
||||
@@ -183,15 +161,29 @@ const PADDING: uPlot.Padding = [10, 12, 0, 0]
|
||||
const canvasHeight = (element: HTMLElement) =>
|
||||
Math.max(60, element.clientHeight || 180)
|
||||
|
||||
/** A token, resolved for the canvas — which cannot read CSS variables. */
|
||||
function token(name: string): string {
|
||||
return getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(name)
|
||||
.trim()
|
||||
/**
|
||||
* A token, resolved for the canvas — which cannot read CSS variables.
|
||||
*
|
||||
* Read off the chart's own element rather than the document: a dashboard
|
||||
* states its colours on the canvas it is drawn on, and custom properties
|
||||
* inherit, so a tile inside one has to be asked where it stands. Asking the
|
||||
* root would draw the shell's axes on the dashboard's chart.
|
||||
*/
|
||||
function token(name: string, from: Element): string {
|
||||
return getComputedStyle(from).getPropertyValue(name).trim()
|
||||
}
|
||||
|
||||
const seriesColor = (index: number, slots: string[]) =>
|
||||
token(`--chart-${slots[index % slots.length]}`)
|
||||
/**
|
||||
* What one line is drawn in.
|
||||
*
|
||||
* A palette entry is either a slot of the app's own ramp — the shape a
|
||||
* dashboard stored before it could name colours — or a colour, which is what a
|
||||
* palette writes today. A colour is used as it stands.
|
||||
*/
|
||||
const seriesColor = (index: number, slots: string[], from: Element) => {
|
||||
const slot = slots[index % slots.length]
|
||||
return CHART_SLOTS.includes(slot) ? token(`--chart-${slot}`, from) : slot
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick labels that stay distinct.
|
||||
@@ -262,8 +254,8 @@ export function UplotChart({
|
||||
/** A named y axis; the unit stays on the ticks. */
|
||||
yLabel?: string
|
||||
/** The ramp slots this chart's lines take, in order — the dashboard's own
|
||||
* palette. Unset, the chart spreads itself across the ramp by how many
|
||||
* lines it draws, which is what one outside a dashboard (Health, Home)
|
||||
* palette — colours, or slots of the app's own ramp. Unset, the chart
|
||||
* spreads itself across the ramp by how many lines it draws, which is what one outside a dashboard (Health, Home)
|
||||
* always does. */
|
||||
palette?: string[]
|
||||
/** Draw the lines as a monotone cubic spline rather than straight segments.
|
||||
@@ -304,9 +296,9 @@ export function UplotChart({
|
||||
if (!element || labels.length === 0 || !ready) return
|
||||
|
||||
const axis = {
|
||||
stroke: () => token("--muted-foreground"),
|
||||
grid: { stroke: () => token("--border"), width: 1 },
|
||||
ticks: { stroke: () => token("--border"), width: 1 },
|
||||
stroke: () => token("--muted-foreground", element),
|
||||
grid: { stroke: () => token("--border", element), width: 1 },
|
||||
ticks: { stroke: () => token("--border", element), width: 1 },
|
||||
font: `11px ${getComputedStyle(element).fontFamily}`,
|
||||
}
|
||||
/** The x value the page was last told about, so a move within one bucket
|
||||
@@ -381,7 +373,7 @@ export function UplotChart({
|
||||
width: 2,
|
||||
// Read at draw time, so a theme toggle is a redraw rather than a
|
||||
// rebuilt chart.
|
||||
stroke: () => seriesColor(index, slots),
|
||||
stroke: () => seriesColor(index, slots, element),
|
||||
// The cursor readout is what decides how wide the legend gets, so
|
||||
// it is shortened here; a named unit is short enough to keep. Four
|
||||
// figures rather than three: this is the number someone is pointing
|
||||
|
||||
Reference in New Issue
Block a user