@scalio-cloud/ui 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +2 -1
- package/nav.css +22 -9
- package/package.json +1 -1
- package/react/blocks.js +12 -5
- package/react/profile.js +14 -1
- package/react/selftest.js +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.0
|
|
4
|
+
Login, Navigation und Banner vereinheitlichen (scalio-docs UISPEC section 3, ADR 0042 Nachtrag 2026-11-01).
|
|
5
|
+
- **Notice in the look of the test mode banner:** thin frame in the tone, 3px bar on the left, tinted background,
|
|
6
|
+
icon in the tone, text in the normal text colour (also for errors). Colours: red = `error` (Fehler), orange = `warn`
|
|
7
|
+
(wichtig), green = `ok` (gute Meldung), blue = `info` (Aktion). New aliases `important`, `success`, `action`
|
|
8
|
+
(`noticeKind`). Banners keep the full frame; stacked banners share one line. Dark mode has its own tones.
|
|
9
|
+
- `SelfTestBanner` is now a `Notice` banner (kind warn) and takes an optional `action` (e.g. link to «Tests»).
|
|
10
|
+
- **Behaviour change:** `normalizeTheme()` returns `system` when nothing (or something unknown) is stored, so every
|
|
11
|
+
app starts like the device on the first visit and on the login page (before: `light`).
|
|
12
|
+
- New `initialLang(stored, navLang)` / `browserLang()`: stored choice wins, otherwise German for a German browser and
|
|
13
|
+
English for everything else, the same in every app.
|
|
14
|
+
- **Visible change:** every `Notice` gets the new frame and colours (info notices turn from Klein blue to a lighter
|
|
15
|
+
blue, error text is no longer red, only bar and icon). No token changes.
|
|
16
|
+
|
|
3
17
|
## 0.21.0
|
|
4
18
|
Testmodus (scalio-docs ADR 0053, package scalio-selftest).
|
|
5
19
|
- `SelfTestPanel` for the page «Tests»: last runs with verdict, scope, source, version and counts; details per
|
package/README.md
CHANGED
|
@@ -127,7 +127,8 @@ scalio-connect 0.8.0 `peers.web.admin_router`.
|
|
|
127
127
|
`link` is the router's `NavLink` (it gets `to`, `end`, a `className` function and `onClick`); without it the
|
|
128
128
|
sidebar renders plain links and marks the item for `activePath`. Item test ids default to `nav-<path>`
|
|
129
129
|
(`nav-overview` for `/`), or set `testId`. Page blocks: `KpiTile` (tone `error` = red number),
|
|
130
|
-
`EmptyState` (children = next step), `Loading`, `Notice` (`
|
|
130
|
+
`EmptyState` (children = next step), `Loading`, `Notice` (`error` red = Fehler, `warn`/`important` orange =
|
|
131
|
+
wichtig, `ok`/`success` green = gute Meldung, `info`/`action` blue = Aktion; `banner` = full width), `StatusBadge`
|
|
131
132
|
(tones `neutral`, `running`, `ok`, `warn`, `error`) and `PageHeader` with `back` for detail pages.
|
|
132
133
|
|
|
133
134
|
Pages that belong together under one menu item keep their own addresses: the item lists them in `match`
|
package/nav.css
CHANGED
|
@@ -260,8 +260,14 @@
|
|
|
260
260
|
.scalio-spinner { width: 0.875rem; height: 0.875rem; border: 2px solid hsl(var(--border)); border-top-color: hsl(var(--primary)); border-radius: 999px; animation: scalio-spin 0.8s linear infinite; }
|
|
261
261
|
@keyframes scalio-spin { to { transform: rotate(360deg); } }
|
|
262
262
|
@media (prefers-reduced-motion: reduce) { .scalio-spinner { animation: none; } }
|
|
263
|
-
|
|
264
|
-
.
|
|
263
|
+
/* Notice: thin frame in the tone, 3px bar on the left, tinted background, icon in the tone (2026-11-01, look of the
|
|
264
|
+
test mode banner). Red = Fehler, orange = wichtig, green = gute Meldung, blue = Aktion. */
|
|
265
|
+
.scalio-notice {
|
|
266
|
+
--notice-tone: 37 99 235; display: flex; align-items: flex-start; gap: 0.625rem; padding: 0.75rem 1rem;
|
|
267
|
+
border: 1px solid rgb(var(--notice-tone) / 0.45); border-left: 3px solid rgb(var(--notice-tone)); border-radius: 2px;
|
|
268
|
+
background: rgb(var(--notice-tone) / 0.08); color: hsl(var(--foreground)); font-size: 0.875rem;
|
|
269
|
+
}
|
|
270
|
+
.scalio-notice-icon, .scalio-notice > .scalio-icon:first-child { flex-shrink: 0; margin-top: 0.125rem; color: rgb(var(--notice-tone)); }
|
|
265
271
|
.scalio-notice-body { flex: 1; min-width: 0; }
|
|
266
272
|
.scalio-notice-title { font-weight: 600; margin-bottom: 0.125rem; }
|
|
267
273
|
.scalio-notice-actions { display: flex; flex-shrink: 0; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
|
|
@@ -277,7 +283,11 @@
|
|
|
277
283
|
}
|
|
278
284
|
.scalio-notice-dismiss:hover { opacity: 1; }
|
|
279
285
|
.scalio-notice-action:focus-visible, .scalio-notice-dismiss:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 1px; }
|
|
280
|
-
.scalio-notice.is-banner { align-items: center; border-
|
|
286
|
+
.scalio-notice.is-banner { align-items: center; border-radius: 0; padding: 0.625rem 1rem; }
|
|
287
|
+
.scalio-notice.is-banner .scalio-notice-icon { margin-top: 0; }
|
|
288
|
+
.scalio-notice.is-banner .scalio-notice-title { margin-bottom: 0; }
|
|
289
|
+
.scalio-notice.is-banner .scalio-notice-body { display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 0.75rem; }
|
|
290
|
+
.scalio-notice.is-banner + .scalio-notice.is-banner { border-top-width: 0; }
|
|
281
291
|
@media (max-width: 639px) { .scalio-notice.is-banner { flex-wrap: wrap; } }
|
|
282
292
|
/* Password sign-in choice (Konten und Rollen -> Anmeldung). */
|
|
283
293
|
.scalio-policy { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
@@ -286,10 +296,15 @@
|
|
|
286
296
|
.scalio-policy-help { font-size: 0.75rem; color: hsl(var(--muted-foreground)); }
|
|
287
297
|
.scalio-policy-help.is-warn { color: #b45309; }
|
|
288
298
|
.dark .scalio-policy-help.is-warn { color: #fbbf24; }
|
|
289
|
-
.scalio-notice.is-info {
|
|
290
|
-
.scalio-notice.is-ok {
|
|
291
|
-
.scalio-notice.is-warn {
|
|
292
|
-
.scalio-notice.is-error {
|
|
299
|
+
.scalio-notice.is-info { --notice-tone: 37 99 235; }
|
|
300
|
+
.scalio-notice.is-ok { --notice-tone: 22 163 74; }
|
|
301
|
+
.scalio-notice.is-warn { --notice-tone: 217 119 6; }
|
|
302
|
+
.scalio-notice.is-error { --notice-tone: 220 38 38; }
|
|
303
|
+
.dark .scalio-notice.is-info { --notice-tone: 96 165 250; }
|
|
304
|
+
.dark .scalio-notice.is-ok { --notice-tone: 74 222 128; }
|
|
305
|
+
.dark .scalio-notice.is-warn { --notice-tone: 245 158 11; }
|
|
306
|
+
.dark .scalio-notice.is-error { --notice-tone: 248 113 113; }
|
|
307
|
+
.dark .scalio-notice { background: rgb(var(--notice-tone) / 0.12); }
|
|
293
308
|
.scalio-badge {
|
|
294
309
|
display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.0625rem 0.375rem; border: 1px solid; border-radius: 2px;
|
|
295
310
|
font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; white-space: nowrap;
|
|
@@ -516,6 +531,4 @@
|
|
|
516
531
|
.scalio-selftest .is-neutral { color: hsl(var(--muted-foreground)); }
|
|
517
532
|
.dark .scalio-selftest .is-ok, .dark .scalio-selftest li.is-ok .scalio-selftest-status { color: #4ade80; }
|
|
518
533
|
.dark .scalio-selftest .is-warn, .dark .scalio-selftest li.is-warn .scalio-selftest-status { color: #fbbf24; }
|
|
519
|
-
.scalio-selftest .scalio-notice.is-ok, .scalio-selftest .scalio-notice.is-error { color: inherit; }
|
|
520
|
-
.scalio-selftest-banner { display: flex; align-items: center; gap: 0.5rem; }
|
|
521
534
|
@media (max-width: 640px) { .scalio-selftest-run-head .scalio-btn { margin-left: 0; } }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalio-cloud/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Scalio Control Room design tokens, Tailwind preset, self-hosted fonts, the shared app header, sign-in layout, sidebar and page blocks",
|
|
5
5
|
"license": "SEE LICENSE IN README.md",
|
|
6
6
|
"private": false,
|
package/react/blocks.js
CHANGED
|
@@ -50,13 +50,20 @@ function Loading({ label, lang, labels, testId }) {
|
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
//
|
|
54
|
-
// title, one sentence, at most one main action plus "Später".
|
|
55
|
-
//
|
|
53
|
+
// One notice style for every hint in every app (ADR 0042 A6): thin frame, coloured bar on the left, icon, bold
|
|
54
|
+
// title, one sentence, at most one main action plus "Später". Colours (2026-11-01): red = error (Fehler),
|
|
55
|
+
// orange = warn (wichtig, e.g. test mode, licence), green = ok (gute Meldung), blue = info (Aktion, e.g. grant
|
|
56
|
+
// consent). The aliases important, success and action name the same kinds. banner: full width above the page
|
|
57
|
+
// (AppShell prop banner), several stack, the most important first. action: { label, onClick | href, testId }.
|
|
56
58
|
const NOTICE_ICONS = { info: "Info", ok: "Check", warn: "Alert", error: "Alert" };
|
|
59
|
+
const NOTICE_KINDS = { info: "info", action: "info", ok: "ok", success: "ok", warn: "warn", important: "warn", error: "error" };
|
|
60
|
+
|
|
61
|
+
function noticeKind(kind) {
|
|
62
|
+
return Object.prototype.hasOwnProperty.call(NOTICE_KINDS, kind) ? NOTICE_KINDS[kind] : "info";
|
|
63
|
+
}
|
|
57
64
|
|
|
58
65
|
function Notice({ kind, title, children, action, onDismiss, dismissLabel, banner, icon, lang, labels, testId }) {
|
|
59
|
-
const k =
|
|
66
|
+
const k = noticeKind(kind);
|
|
60
67
|
const L = labelsFor(lang, labels);
|
|
61
68
|
const Icons = require("./icons");
|
|
62
69
|
const Icon = icon === false ? null : icon || Icons[NOTICE_ICONS[k]];
|
|
@@ -93,4 +100,4 @@ function StatusBadge({ tone: t, children, testId }) {
|
|
|
93
100
|
return h("span", { className: cx("scalio-badge", `is-${tone(t, "neutral")}`), "data-testid": testId }, children);
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
module.exports = { KpiTile, EmptyState, Loading, Notice, StatusBadge, TONES };
|
|
103
|
+
module.exports = { KpiTile, EmptyState, Loading, Notice, noticeKind, StatusBadge, TONES };
|
package/react/profile.js
CHANGED
|
@@ -86,8 +86,19 @@ function applyTheme(mode) {
|
|
|
86
86
|
else if (mq.removeListener) mq.removeListener(set);
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
+
// Nothing stored yet (first visit, login page) means "system": every app starts like the device.
|
|
89
90
|
function normalizeTheme(value) {
|
|
90
|
-
return value === "dark" || value === "
|
|
91
|
+
return value === "dark" || value === "light" ? value : "system";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- language ----------------------------------------------------------------------------------------
|
|
95
|
+
// The same rule in every app: a stored choice wins; otherwise German for a German browser, English for the rest.
|
|
96
|
+
function browserLang(navLang) {
|
|
97
|
+
const v = navLang !== undefined ? navLang : (typeof navigator !== "undefined" && navigator.language) || "";
|
|
98
|
+
return String(v || "").toLowerCase().startsWith("de") ? "de" : "en";
|
|
99
|
+
}
|
|
100
|
+
function initialLang(stored, navLang) {
|
|
101
|
+
return stored === "de" || stored === "en" ? stored : browserLang(navLang);
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
// --- blocks ------------------------------------------------------------------------------------------
|
|
@@ -324,4 +335,6 @@ module.exports = {
|
|
|
324
335
|
resolveTheme,
|
|
325
336
|
applyTheme,
|
|
326
337
|
normalizeTheme,
|
|
338
|
+
browserLang,
|
|
339
|
+
initialLang,
|
|
327
340
|
};
|
package/react/selftest.js
CHANGED
|
@@ -265,13 +265,16 @@ function SelfTestPanel(props) {
|
|
|
265
265
|
: h("p", { className: "scalio-selftest-hint", "data-testid": "selftest-empty" }, L.never)));
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
// Hint above the page content while the test mode is on
|
|
268
|
+
// Hint above the page content while the test mode is on: a Notice banner, kind warn (orange = wichtig), the same
|
|
269
|
+
// in every app. Props: health ({enabled, suite}) or suite, lang, labels, optional action ({ label, href | onClick })
|
|
270
|
+
// e.g. to the page «Tests».
|
|
269
271
|
function SelfTestBanner(props) {
|
|
270
272
|
const suite = props.suite || (props.health && props.health.enabled && props.health.suite);
|
|
271
273
|
if (!suite) return null;
|
|
272
274
|
const L = selftestLabels(props.lang, props.labels);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
const { Notice } = require("./blocks");
|
|
276
|
+
return h(Notice, { kind: "warn", banner: true, icon: Icons.Activity, title: fill(L.banner, { suite }), action: props.action,
|
|
277
|
+
lang: props.lang, testId: "selftest-banner" });
|
|
275
278
|
}
|
|
276
279
|
|
|
277
280
|
module.exports = { SelfTestPanel, SelfTestBanner, selftestApi, selftestLabels, SELFTEST_LABELS: LABELS };
|