diff --git a/backend/fluksio/flow/dashboards.py b/backend/fluksio/flow/dashboards.py index 5017708..be5a25e 100644 --- a/backend/fluksio/flow/dashboards.py +++ b/backend/fluksio/flow/dashboards.py @@ -112,9 +112,9 @@ SETTING_DTYPES: dict[str, str] = { "theme": "str", # Read-only: the input widgets stop publishing. "locked": "bool", - # "glass" | "material": which of the two component sets draws this - # dashboard. Anything else is material. The two share every feature; only - # the drawing differs. + # "fluksio" | "material" | "glass": which component set draws this + # dashboard. Anything else is "fluksio", the app's own design. The three + # share every feature; only the drawing differs. "look": "str", # The dashboard's own colours, as an ordered list of hex strings. Position # is the role: background, surface, primary, accent, text, and anything diff --git a/docs/interface/dashboards.md b/docs/interface/dashboards.md index 8a824d4..9e849a1 100644 --- a/docs/interface/dashboards.md +++ b/docs/interface/dashboards.md @@ -100,7 +100,7 @@ set them. | Setting | Is | Driven by | |---|---|---| -| **Look** | `Material` or `Glass` | a `str` message | +| **Look** | `Fluksio`, `Material` or `Glass` | a `str` message | | **Theme** | `System`, `Light` or `Dark` | a `str` message | | **Palette** | the dashboard's colours, in order | a `list` message | | **Background** | the URL of an image | a `str` message | @@ -130,11 +130,14 @@ locking a panel down remotely. ### Look -**Material** lays flat, tonal cards on a plain ground. **Glass** floats -translucent tiles over a soft, slowly moving one. They are two complete sets of -components, not two stylesheets — but they are the same dashboard: every widget, -every control and every keystroke behaves identically, so switching look never -changes what a panel can do. +**Fluksio** is the app's own design — `--card` surfaces separated by a hairline +and a low shadow, pill controls, one slate-blue accent — and is what a dashboard +wears until you choose otherwise. **Material** lays flat, tonal cards on a plain +ground. **Glass** floats translucent tiles over a soft, slowly moving one. + +They are three complete sets of components, not three stylesheets — but they are +the same dashboard: every widget, every control and every keystroke behaves +identically, so switching look never changes what a panel can do. ### Palette diff --git a/frontend/src/components/Dashboard/panels.tsx b/frontend/src/components/Dashboard/panels.tsx index 329c504..d122aa8 100644 --- a/frontend/src/components/Dashboard/panels.tsx +++ b/frontend/src/components/Dashboard/panels.tsx @@ -1421,10 +1421,12 @@ export function DashboardPanel({ onChange={(setting) => setSetting("look", setting)} />

- How this dashboard is drawn. Glass floats translucent tiles over a - soft moving ground; Material lays flat tonal cards on a plain one. - The widgets are the same either way — a look changes what they - look like and nothing about what they do. + How this dashboard is drawn. Fluksio is the app's own design, and + what a dashboard wears until you choose otherwise; Material lays + flat tonal cards on a plain ground; Glass floats translucent tiles + over a soft moving one. The widgets are the same whichever you + pick — a look changes what they look like and nothing about what + they do.

diff --git a/frontend/src/components/Dashboard/settings.tsx b/frontend/src/components/Dashboard/settings.tsx index de496fe..d9e813f 100644 --- a/frontend/src/components/Dashboard/settings.tsx +++ b/frontend/src/components/Dashboard/settings.tsx @@ -34,7 +34,7 @@ import { isLight, parsePalette, rolesOf } from "./ui/core/theme" export const SETTING_DTYPES = { theme: "str", locked: "bool", - /** `glass` or `material`; anything else is material. */ + /** `fluksio`, `material` or `glass`; anything else is `fluksio`. */ look: "str", /** Hex colours, roles by position — see `ui/core/theme.ts`. */ palette: "list", @@ -88,17 +88,18 @@ export function useSetting( return live?.value ?? setting.value } -/** What a dashboard is drawn as. Anything unrecorded is material. */ -export type Look = "glass" | "material" +/** What a dashboard is drawn as. Anything unrecorded wears the app's own. */ +export type Look = "fluksio" | "glass" | "material" -/** The looks, as the editor offers them. */ +/** The looks, as the editor offers them. The dashboard's own comes first. */ export const LOOK_CHOICES = [ + ["fluksio", "Fluksio"], ["material", "Material"], ["glass", "Glass"], ] as const export const lookOf = (value: unknown): Look => - value === "glass" ? "glass" : "material" + value === "glass" ? "glass" : value === "material" ? "material" : "fluksio" /** Which of the two component sets draws this dashboard. */ export const useDashboardLook = ( diff --git a/frontend/src/components/Dashboard/ui/core/motion.ts b/frontend/src/components/Dashboard/ui/core/motion.ts index 3a050ea..cf70814 100644 --- a/frontend/src/components/Dashboard/ui/core/motion.ts +++ b/frontend/src/components/Dashboard/ui/core/motion.ts @@ -28,6 +28,17 @@ type LookMotion = { } export const LOOK: Record = { + fluksio: { + // The app's own timing, as `lib/motion.ts` states it: quick, and settling + // without overshoot. A dashboard wearing the product's design should move + // the way the rest of the product does. + spring: { type: "spring", stiffness: 420, damping: 34, mass: 1 }, + enter: { + hidden: { opacity: 0, y: 8 }, + visible: { opacity: 1, y: 0 }, + }, + drift: 0, + }, material: { spring: { type: "spring", stiffness: 550, damping: 38, mass: 1 }, enter: { diff --git a/frontend/src/components/Dashboard/ui/fluksio/Controls.tsx b/frontend/src/components/Dashboard/ui/fluksio/Controls.tsx new file mode 100644 index 0000000..bd3e62a --- /dev/null +++ b/frontend/src/components/Dashboard/ui/fluksio/Controls.tsx @@ -0,0 +1,223 @@ +/** + * Fluksio: the controls. + * + * Every control is a pill, and a press is answered by the colour changing and + * nothing else — no ripple, no glow, no lift. That restraint is the house + * style rather than an omission: the one accent is spent on saying what is + * selected, not on saying that something was touched. + * + * Every one of these is a `ui/core` hook in a different coat — the state, the + * keyboard and the `aria-` come from there. + */ + +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown } from "lucide-react" +import { motion } from "motion/react" + +import { cn } from "@/lib/utils" +import { asOriginal, text } from "../core/config" +import type { + ButtonProps, + InputProps, + SegmentedProps, + SelectProps, + SliderProps, + SwitchProps, +} from "../core/contract" +import { useSegmented, useSliderDrag, useSwitch } from "../core/controls" +import { useLook } from "../core/look" +import { LOOK } from "../core/motion" + +export function Button({ + variant = "tonal", + pressed, + disabled, + label, + onClick, + children, +}: ButtonProps) { + return ( + + ) +} + +export function Switch(props: SwitchProps) { + const { buttonProps } = useSwitch(props) + return ( + + ) +} + +export function Slider(props: SliderProps) { + const { fraction, inputProps, marks } = useSliderDrag(props) + const orientation = props.orientation ?? "horizontal" + return ( +
+
+ + + + {/* The control itself, laid transparent over what is drawn: it keeps + the keyboard, the pointer and every `aria-`, and owes the look + nothing. */} + + +
+ {marks.length > 0 ? ( + // Decoration: the input itself announces min, max and where it stands. +
+ {marks.map((mark) => ( + + {mark.label} + + ))} +
+ ) : null} +
+ ) +} + +export function Segmented(props: SegmentedProps) { + const { chosen, thumbId, groupProps, itemProps } = useSegmented(props) + const vertical = props.orientation === "vertical" + return ( +
+ {props.options.map(([value, label], index) => ( + + ))} +
+ ) +} + +export function Select({ + value, + options, + label, + disabled, + onChange, +}: SelectProps) { + // The menu is portalled to `body`, which is outside the canvas — so it + // re-states the dashboard's own colours rather than borrowing the app's. + const { style } = useLook() + return ( + onChange(asOriginal(selected, options))} + > + + + + + + + + + + + + {options.map((option) => ( + + + {option.label ?? text(option.value)} + + + + + + ))} + + + + + ) +} + +export function Input({ + value, + type, + label, + disabled, + onChange, + onCommit, +}: InputProps) { + return ( + onChange(event.target.value)} + onBlur={onCommit} + onKeyDown={(event) => { + if (event.key === "Enter") onCommit() + }} + /> + ) +} diff --git a/frontend/src/components/Dashboard/ui/fluksio/Data.tsx b/frontend/src/components/Dashboard/ui/fluksio/Data.tsx new file mode 100644 index 0000000..15f0ba7 --- /dev/null +++ b/frontend/src/components/Dashboard/ui/fluksio/Data.tsx @@ -0,0 +1,213 @@ +/** + * Fluksio: the readings. + * + * Plain fills on recessed tracks, and every number written out beside the + * picture it is drawn as — an angle or a length nobody can measure is not a + * reading. Data colours come from the dashboard; the surfaces around them do + * not compete for attention with the readings on them. + */ +import { motion, useTransform } from "motion/react" + +import { cn } from "@/lib/utils" +import { arcPath, GAUGE_START, GAUGE_SWEEP, GAUGE_TRACK } from "../core/arc" +import { cssOf } from "../core/color" +import { format } from "../core/config" +import type { + BarProps, + ColorDiskProps, + GaugeProps, + ReadoutProps, +} from "../core/contract" +import { TESTID } from "../core/contract" +import { useColorDisk } from "../core/disc" +import { useAnimatedFraction, useAnimatedNumber } from "../core/values" +import { Slider } from "./Controls" + +export function Readout({ + value, + precision, + unit, + size = "hero", +}: ReadoutProps) { + const { label, numeric } = useAnimatedNumber(value, precision) + return ( + + + {numeric ? ( + {label} + ) : ( + format(value, precision) + )} + + {unit ? ( + + {unit} + + ) : null} + + ) +} + +export function Gauge({ value, min, max, precision, unit, label }: GaugeProps) { + const fraction = useAnimatedFraction( + value === null + ? 0 + : Math.min(1, Math.max(0, (value - min) / (max - min || 1))), + ) + const { label: reading, numeric } = useAnimatedNumber(value, precision) + return ( +
+ + + {/* The same full arc as the track, revealed rather than re-pathed: `d` + is not animatable, so a reading that redrew the arc could only + jump. `pathLength` normalises it, which makes the reveal the + fraction itself. */} + + + {numeric ? ( + {reading} + ) : ( + format(value, precision) + )} + {unit ?? ""} + + +
+ ) +} + +function Row({ row }: { row: BarProps["rows"][number] }) { + const fraction = useAnimatedFraction(row.fraction) + const width = useTransform(fraction, (at) => `${at * 100}%`) + return ( +
+ {row.label} + + + + {/* Beside the track rather than on it: a number written on a fill has to + clear the fill it sits on and the track it slides onto, and one that + reads across a room cannot do both. */} + + {format(row.value, row.precision)} + {row.unit ?? ""} + +
+ ) +} + +export function Bar({ rows }: BarProps) { + // No group role of its own: each row already announces what it reads and + // what it is worth, and the tile's title is on the frame around them. + return ( +
+ {rows.map((row, index) => ( + // Two rows can read the same message under different labels; position + // is the identity, as it is for chart series. + + ))} +
+ ) +} + +export function ColorDisk({ + name, + hsv, + disabled, + onChange, + onCommit, +}: ColorDiskProps) { + const { discProps, thumb, shade } = useColorDisk({ + name, + hsv, + disabled, + onChange, + onCommit, + }) + const [hue, saturation, brightness] = hsv + return ( +
+
+ + +
+ { + onChange([hue, saturation, next]) + onCommit() + }} + /> +
+ ) +} diff --git a/frontend/src/components/Dashboard/ui/fluksio/Surfaces.tsx b/frontend/src/components/Dashboard/ui/fluksio/Surfaces.tsx new file mode 100644 index 0000000..c970e36 --- /dev/null +++ b/frontend/src/components/Dashboard/ui/fluksio/Surfaces.tsx @@ -0,0 +1,182 @@ +/** + * Fluksio: the surfaces. + * + * The house style. A widget is a `--card` told from the page by a hairline and + * a low shadow rather than by colour, floating chrome is frosted, and the one + * accent is spent on what a person can act on. Nothing here holds state: the + * behaviour is in `ui/core`. + */ +import { Link } from "@tanstack/react-router" +import { Lock } from "lucide-react" +import { motion } from "motion/react" + +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip" +import { cn } from "@/lib/utils" +import { ICONS } from "../../icons" +import type { + BackdropProps, + FrameProps, + NoticeProps, + RailProps, +} from "../core/contract" +import { TESTID } from "../core/contract" +import { LOOK } from "../core/motion" + +/** The page colour is the ground; only an image is drawn over it. */ +export function Backdrop({ image }: BackdropProps) { + if (!image) return null + return ( +
+ ) +} + +function Issue({ issue }: { issue: string }) { + return ( + + + + + {issue} + + ) +} + +export function Frame({ + title, + issue, + grip, + scroll, + selected, + onClick, + children, +}: FrameProps) { + return ( + // A card is not a control: the click only picks it in edit mode, and every + // interactive element inside keeps its own role and keyboard handling. + // biome-ignore lint/a11y/useKeyWithClickEvents: see above. + // biome-ignore lint/a11y/noStaticElementInteractions: see above. +
+ {title ? ( +
+ {title} + {issue ? : null} +
+ ) : null} +
+ {children} +
+ {/* No header to sit in, so the fault takes the corner instead — a widget + drawn without its title still says when it is mis-wired. */} + {!title && issue ? ( +
+ +
+ ) : null} + {/* And the editor still needs something to drag it by. */} + {!title && grip ? ( + + ) : null} +
+ ) +} + +/** Two letters off the title, so a rail of four reads as four different things. */ +function initials(label: string): string { + const words = label.split(/[\s_-]+/).filter(Boolean) + if (words.length === 0) return "?" + if (words.length === 1) return words[0].slice(0, 2).toUpperCase() + return (words[0][0] + words[1][0]).toUpperCase() +} + +export function Rail({ entries }: RailProps) { + return ( + + ) +} + +export function Notice({ children }: NoticeProps) { + return ( +
+ + {children} +
+ ) +} diff --git a/frontend/src/components/Dashboard/ui/fluksio/fluksio.css b/frontend/src/components/Dashboard/ui/fluksio/fluksio.css new file mode 100644 index 0000000..9941311 --- /dev/null +++ b/frontend/src/components/Dashboard/ui/fluksio/fluksio.css @@ -0,0 +1,317 @@ +/* + * Fluksio — the dashboard wearing the product's own design. + * + * The other two looks are somebody else's language spoken well. This one is + * the house style, and it follows the root `DESIGN-GUIDELINES.md` to the + * letter: `--card` surfaces told apart from the page by a hairline and a low + * shadow rather than by colour, every control a pill, 16px panels, one + * slate-blue accent and nothing else competing with it. + * + * Which makes it the sober one, and the right default: a dashboard nobody has + * dressed yet should look like the rest of the app. + * + * The one deliberate departure is the selector, whose choice is held in + * `--primary` rather than `--accent`. That is a decision about the widget + * rather than about this look — a control must not change what it signals + * when the look changes — so all three sets hold it the same way. + * + * Paint only; the measurements are in `../core/core.css`. + */ + +[data-look="fluksio"] { + --fx-radius: var(--radius-lg); + --fx-field: var(--radius-md); +} + +/* --- surfaces ------------------------------------------------------------ */ + +.fx-frame { + border: 1px solid var(--border); + border-radius: var(--fx-radius); + background: var(--card); + color: var(--card-foreground); + box-shadow: var(--shadow-e1); +} + +.fx-frame[data-selected] { + border-color: var(--primary); + outline: 2px solid var(--primary); + outline-offset: -2px; +} + +.fx-frame-title { + color: var(--muted-foreground); +} + +/* Floating chrome over content is frosted (guidelines → Overlay surfaces). */ +.fx-rail, +.fx-notice { + border: 1px solid var(--border); + background: color-mix(in srgb, var(--card) 80%, transparent); + box-shadow: var(--shadow-e2); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); +} + +.fx-rail { + border-radius: var(--fx-radius); + color: var(--card-foreground); +} + +.fx-notice { + border-radius: 9999px; + color: var(--muted-foreground); +} + +/* A content chip is transparent at rest and fills on hover or when active. */ +.fx-rail-item { + border-radius: 9999px; + color: var(--muted-foreground); + transition: + background-color var(--duration-fast) var(--ease-standard), + color var(--duration-fast) var(--ease-standard); +} + +.fx-rail-item[aria-current="page"] { + color: var(--foreground); +} + +[data-look="fluksio"]:not([data-touch]) .fx-rail-item:hover { + background: color-mix(in srgb, var(--accent) 50%, transparent); +} + +/* --- controls ------------------------------------------------------------ */ + +.fx-button { + display: inline-flex; + min-width: 0; + min-height: var(--dui-control); + align-items: center; + justify-content: center; + gap: 0.5rem; + border-radius: 9999px; + padding-inline: 1.25rem; + font-size: var(--dui-text); + font-weight: 500; + background: var(--secondary); + color: var(--secondary-foreground); + transition: background-color var(--duration-fast) var(--ease-standard); +} + +.fx-button[data-variant="filled"], +.fx-button[aria-pressed="true"] { + background: var(--primary); + color: var(--primary-foreground); +} + +.fx-button[data-variant="text"] { + background: transparent; +} + +[data-look="fluksio"]:not([data-touch]) .fx-button:hover { + background: color-mix(in srgb, var(--secondary) 80%, var(--foreground)); +} + +[data-look="fluksio"]:not([data-touch]) .fx-button[data-variant="filled"]:hover, +[data-look="fluksio"]:not([data-touch]) .fx-button[aria-pressed="true"]:hover { + background: color-mix(in srgb, var(--primary) 90%, var(--foreground)); +} + +.fx-button:disabled { + opacity: 0.5; + pointer-events: none; +} + +.fx-switch { + position: relative; + display: inline-flex; + width: calc(var(--dui-control) * 1.6); + height: calc(var(--dui-control) * 0.75); + flex: none; + align-items: center; + justify-content: flex-start; + border-radius: 9999px; + padding: 0.125rem; + background: var(--input); + transition: background-color var(--duration-fast) var(--ease-standard); +} + +.fx-switch[data-state="on"] { + justify-content: flex-end; + background: var(--primary); +} + +.fx-switch-thumb { + display: block; + height: calc(var(--dui-control) * 0.75 - 0.25rem); + aspect-ratio: 1; + border-radius: 50%; + background: var(--background); + box-shadow: var(--shadow-e1); +} + +.fx-switch:disabled { + opacity: 0.5; + pointer-events: none; +} + +.fx-slider .dui-slider-rail { + border-radius: 9999px; + background: var(--muted); +} + +.fx-slider .dui-slider-fill { + border-radius: 9999px; + background: var(--primary); +} + +.fx-slider .dui-slider-thumb { + border: 2px solid var(--primary); + background: var(--background); + box-shadow: var(--shadow-e1); +} + +/* The one segmented shape: a single border pill, no dividers, transparent + segments — with the choice held in the primary the widget signals with. */ +.fx-segmented { + position: relative; + display: grid; + min-width: 0; + align-items: center; + border: 1px solid var(--border); + border-radius: 9999px; + padding: 0.25rem; +} + +.fx-segmented[data-orientation="vertical"] { + height: 100%; + grid-auto-flow: row; + grid-auto-rows: minmax(0, 1fr); + border-radius: var(--fx-radius); +} + +.fx-segment { + position: relative; + display: flex; + min-width: 0; + min-height: calc(var(--dui-control) - 0.5rem); + align-items: center; + justify-content: center; + border-radius: 9999px; + padding-inline: 0.625rem; + font-size: var(--dui-text); + color: var(--muted-foreground); + transition: color var(--duration-fast) var(--ease-standard); +} + +.fx-segment[data-active] { + color: var(--primary-foreground); +} + +[data-look="fluksio"]:not([data-touch]) .fx-segment:not([data-active]):hover { + background: color-mix(in srgb, var(--accent) 50%, transparent); +} + +.fx-segment-thumb { + position: absolute; + inset: 0; + z-index: 0; + border-radius: 9999px; + background: var(--primary); +} + +.fx-segment-label, +.fx-switch-thumb, +.fx-lift { + position: relative; + z-index: 1; +} + +.fx-segment-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.fx-field { + display: flex; + width: 100%; + min-width: 0; + min-height: var(--dui-control); + align-items: center; + gap: 0.5rem; + border: 1px solid var(--input); + border-radius: 9999px; + background: var(--background); + padding-inline: 1rem; + font-size: var(--dui-text); + color: var(--foreground); + text-align: left; + transition: + border-color var(--duration-fast) var(--ease-standard), + box-shadow var(--duration-fast) var(--ease-standard); +} + +.fx-field::placeholder { + color: var(--muted-foreground); +} + +.fx-field:disabled { + opacity: 0.5; + pointer-events: none; +} + +/* Menus and popovers are 12px with the floating elevation. */ +.fx-menu { + border: 1px solid var(--border); + border-radius: var(--fx-field); + background: var(--popover); + color: var(--popover-foreground); + box-shadow: var(--shadow-e2); +} + +.fx-menu-item { + border-radius: var(--radius-sm); +} + +.fx-menu-item[data-highlighted] { + background: var(--accent); + color: var(--accent-foreground); +} + +/* --- readings ------------------------------------------------------------ */ + +.fx-track { + border-radius: 9999px; + background: var(--muted); +} + +.fx-fill { + border-radius: 9999px; + background: var(--dui-fill, var(--primary)); +} + +.fx-disc-thumb { + border: 3px solid var(--card); + box-shadow: var(--shadow-e1); +} + +/* --- focus --------------------------------------------------------------- */ + +[data-look="fluksio"] + :is( + .fx-button, + .fx-switch, + .fx-segment, + .fx-field, + .fx-rail-item, + .dui-disc + ):focus-visible { + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent); +} + +[data-look="fluksio"] .dui-slider-input:focus-visible ~ .dui-slider-thumb { + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 50%, transparent); +} diff --git a/frontend/src/components/Dashboard/ui/fluksio/index.ts b/frontend/src/components/Dashboard/ui/fluksio/index.ts new file mode 100644 index 0000000..9aa733c --- /dev/null +++ b/frontend/src/components/Dashboard/ui/fluksio/index.ts @@ -0,0 +1,30 @@ +/** + * Fluksio — the dashboard in the product's own design, and the one a + * dashboard wears until somebody chooses otherwise. + * + * Every entry of `ComponentSet` is implemented here; the behaviour comes from + * `ui/core`, so this set cannot do anything the others cannot. + */ +import "./fluksio.css" + +import type { ComponentSet } from "../core/contract" +import { Button, Input, Segmented, Select, Slider, Switch } from "./Controls" +import { Bar, ColorDisk, Gauge, Readout } from "./Data" +import { Backdrop, Frame, Notice, Rail } from "./Surfaces" + +export const fluksio: ComponentSet = { + Backdrop, + Frame, + Button, + Switch, + Slider, + Segmented, + Select, + Input, + Readout, + Gauge, + Bar, + ColorDisk, + Rail, + Notice, +} diff --git a/frontend/src/components/Dashboard/ui/index.ts b/frontend/src/components/Dashboard/ui/index.ts index 99e7885..4236dca 100644 --- a/frontend/src/components/Dashboard/ui/index.ts +++ b/frontend/src/components/Dashboard/ui/index.ts @@ -2,7 +2,7 @@ * The dashboard's components, in whichever look this dashboard wears. * * A widget asks for the set and draws with it; it never learns which one it - * got, which is what keeps the two looks the same dashboard. Behaviour lives + * got, which is what keeps the three looks the same dashboard. Behaviour lives * under `core/` and is shared, so there is nothing for a set to disagree * about beyond markup and motion. */ @@ -11,10 +11,11 @@ import "./core/core.css" import type { Look } from "../settings" import type { ComponentSet } from "./core/contract" import { useLook } from "./core/look" +import { fluksio } from "./fluksio" import { glass } from "./glass" import { material } from "./material" -const SETS: Record = { glass, material } +const SETS: Record = { fluksio, glass, material } /** The components this dashboard is drawn with. */ export const useUi = (): ComponentSet => SETS[useLook().look] diff --git a/frontend/tests/widgets.spec.ts b/frontend/tests/widgets.spec.ts index abc8327..5711d60 100644 --- a/frontend/tests/widgets.spec.ts +++ b/frontend/tests/widgets.spec.ts @@ -626,14 +626,14 @@ async function setLook( }) } -// Both looks, in both themes. A look is mostly colour and depth, so the four -// screenshots are the assertion that neither collapses — and the checks around -// them are that a look changes how the panel is drawn and nothing else. +// Every look, in both themes. A look is mostly colour and depth, so the +// screenshots are the assertion that none of them collapses — and the checks +// around them are that a look changes how the panel is drawn and nothing else. for (const scheme of ["light", "dark"] as const) { test.describe(`${scheme} theme`, () => { test.use({ colorScheme: scheme }) - for (const look of ["material", "glass"] as const) { + for (const look of ["fluksio", "material", "glass"] as const) { test(`the panel reads in ${scheme} ${look}`, async ({ page }) => { await openPanel(page) await setLook(page, dashboardName, { look: { value: look } }) @@ -691,7 +691,7 @@ for (const scheme of ["light", "dark"] as const) { path: `screenshots/widgets/${scheme}-palette.png`, }) await setLook(page, dashboardName, { - look: { value: "material" }, + look: { value: "fluksio" }, palette: { value: [] }, }) })