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:
@@ -2,12 +2,15 @@ import { expect, type Page, test } from "@playwright/test"
|
||||
import { api, apiPage, deleteAll } from "./utils/api"
|
||||
|
||||
/**
|
||||
* The dashboard editor's two quiet failures.
|
||||
* The dashboard editor's quiet failures.
|
||||
*
|
||||
* Both are about a placement that nothing complains about: the grid library
|
||||
* Two are about a placement that nothing complains about: the grid library
|
||||
* corrects a stored layout against the columns alone, so shrinking the canvas
|
||||
* hides whatever now falls past its bottom edge; and the header that drags a
|
||||
* widget used to also select it, so every drop opened the settings panel.
|
||||
*
|
||||
* The third is what a widget drawn without its title is dragged by, since the
|
||||
* header it used to be dragged by is the thing that is gone.
|
||||
*/
|
||||
|
||||
const dashboardName = `test_editor_${Date.now().toString(36)}`
|
||||
@@ -131,3 +134,55 @@ test("the drag handle moves a widget without selecting it", async ({
|
||||
await frame.click()
|
||||
await expect(settings).toBeVisible()
|
||||
})
|
||||
|
||||
test("a widget without its title can still be dragged and picked", async ({
|
||||
page,
|
||||
}) => {
|
||||
await openEditor(page)
|
||||
|
||||
const frame = page.getByTestId("widget-frame").filter({ hasText: "Below" })
|
||||
const settings = page.getByTestId("widget-settings")
|
||||
await frame.click()
|
||||
await expect(settings).toBeVisible()
|
||||
|
||||
await page.getByTestId("widget-show-title").click()
|
||||
|
||||
// The title is gone from the tile — and so is the header that used to be the
|
||||
// handle, so the editor grows one of its own rather than losing the widget.
|
||||
const untitled = page
|
||||
.getByTestId("widget-frame")
|
||||
.filter({ hasNotText: "Top" })
|
||||
await expect(untitled).not.toContainText("Below")
|
||||
await expect(untitled.locator(".widget-grip")).toBeVisible()
|
||||
|
||||
await untitled.locator(".widget-grip").click()
|
||||
// A press on the grip is a move, not a pick — as it is with a header.
|
||||
await expect(page.getByTestId("widget-settings")).toBeVisible()
|
||||
})
|
||||
|
||||
test("a palette is pasted as a link and read as roles", async ({ page }) => {
|
||||
await openEditor(page)
|
||||
await page.getByTestId("edit-dashboard").click()
|
||||
|
||||
await page
|
||||
.getByTestId("dashboard-palette-input")
|
||||
.fill("https://coolors.co/palette/264653-2a9d8f-e9c46a-f4a261-e76f51")
|
||||
|
||||
const swatches = page.getByTestId("dashboard-palette").getByRole("img")
|
||||
await expect(swatches).toHaveCount(5)
|
||||
await expect(swatches.first()).toHaveAttribute("aria-label", /Background/)
|
||||
|
||||
// The roles are positional, so turning the list is how a palette that
|
||||
// arrived in the wrong order is put right.
|
||||
await page.getByTestId("dashboard-palette-rotate").click()
|
||||
await expect(swatches.first()).toHaveAttribute(
|
||||
"aria-label",
|
||||
/Background #2a9d8f/,
|
||||
)
|
||||
|
||||
// And the canvas beside the panel wears it.
|
||||
await expect(page.getByTestId("canvas-surface")).toHaveAttribute(
|
||||
"data-palette",
|
||||
"",
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user