@aiaiai-pt/design-system 0.47.0 → 0.49.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/components/EChart.svelte +5 -1
- package/components/map-utils.js +1 -1
- package/components/renderer/chart-option.d.ts +3 -0
- package/components/renderer/chart-option.ts +19 -1
- package/components/renderer/dispatch.d.ts +65 -28
- package/components/renderer/dispatch.ts +83 -44
- package/components/renderer/registry.d.ts +38 -9
- package/components/renderer/registry.ts +38 -9
- package/package.json +6 -2
- package/tokens/semantic.css +5 -5
- package/tokens/themes/ubp.css +224 -0
- package/tokens/utilities.css +9 -0
package/components/EChart.svelte
CHANGED
|
@@ -142,9 +142,13 @@
|
|
|
142
142
|
return buildChartOption(chartData, {
|
|
143
143
|
tokens: {
|
|
144
144
|
accent: token("--color-accent", "#2563eb"),
|
|
145
|
-
|
|
145
|
+
// Semantic tokens name the primary text colour `--color-text` (see
|
|
146
|
+
// tokens/semantic.css); `--color-text-primary` never existed, so this
|
|
147
|
+
// read silently fell back to #1f2937 — invisible on dark schemes (#89).
|
|
148
|
+
textPrimary: token("--color-text", token("--color-text-primary", "#1f2937")),
|
|
146
149
|
textSecondary: token("--color-text-secondary", "#6b7280"),
|
|
147
150
|
border: token("--color-border", "#e5e7eb"),
|
|
151
|
+
surface: token("--color-surface", "#ffffff"),
|
|
148
152
|
},
|
|
149
153
|
locale,
|
|
150
154
|
legend,
|
package/components/map-utils.js
CHANGED
|
@@ -186,7 +186,7 @@ export function watchTheme(onchange) {
|
|
|
186
186
|
const mo = new MutationObserver(() => onchange());
|
|
187
187
|
mo.observe(document.documentElement, {
|
|
188
188
|
attributes: true,
|
|
189
|
-
attributeFilter: ["data-theme", "class"],
|
|
189
|
+
attributeFilter: ["data-theme", "data-scheme", "data-contrast", "class"],
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
@@ -26,6 +26,9 @@ export interface ChartTokens {
|
|
|
26
26
|
textPrimary: string;
|
|
27
27
|
textSecondary: string;
|
|
28
28
|
border: string;
|
|
29
|
+
/** Panel surface behind floating chrome (tooltip); themes the default-white
|
|
30
|
+
ECharts tooltip for dark/high-contrast schemes (#89). */
|
|
31
|
+
surface: string;
|
|
29
32
|
}
|
|
30
33
|
export interface BuildChartOptionOpts {
|
|
31
34
|
tokens: ChartTokens;
|
|
@@ -28,6 +28,9 @@ export interface ChartTokens {
|
|
|
28
28
|
textPrimary: string;
|
|
29
29
|
textSecondary: string;
|
|
30
30
|
border: string;
|
|
31
|
+
/** Panel surface behind floating chrome (tooltip); themes the default-white
|
|
32
|
+
ECharts tooltip for dark/high-contrast schemes (#89). */
|
|
33
|
+
surface: string;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export interface BuildChartOptionOpts {
|
|
@@ -44,6 +47,16 @@ export interface BuildChartOptionOpts {
|
|
|
44
47
|
innerRadius?: number;
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
/** Token-driven tooltip chrome — ECharts defaults to a white panel that
|
|
51
|
+
* ignores the scheme entirely (#89). */
|
|
52
|
+
function tooltipChrome(tokens: ChartTokens): Record<string, unknown> {
|
|
53
|
+
return {
|
|
54
|
+
backgroundColor: tokens.surface,
|
|
55
|
+
borderColor: tokens.border,
|
|
56
|
+
textStyle: { color: tokens.textPrimary },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
/** A localised number formatter (DS-supplied — never operator code). */
|
|
48
61
|
function makeFmt(locale: string): (value: number) => string {
|
|
49
62
|
const nf = new Intl.NumberFormat(locale);
|
|
@@ -78,12 +91,13 @@ function buildPieOption(
|
|
|
78
91
|
return {
|
|
79
92
|
animation: false,
|
|
80
93
|
tooltip: {
|
|
94
|
+
...tooltipChrome(tokens),
|
|
81
95
|
trigger: "item",
|
|
82
96
|
formatter: (p: { name: string; value: number; percent: number }) =>
|
|
83
97
|
`${p.name}: ${fmt(p.value)} (${p.percent}%)`,
|
|
84
98
|
},
|
|
85
99
|
...(opts.legend
|
|
86
|
-
? { legend: { show: true, textStyle: { color: tokens.textPrimary } } }
|
|
100
|
+
? { legend: { show: true, top: 0, textStyle: { color: tokens.textPrimary } } }
|
|
87
101
|
: {}),
|
|
88
102
|
color: palette,
|
|
89
103
|
series: [
|
|
@@ -208,6 +222,7 @@ function buildCartesianOption(
|
|
|
208
222
|
containLabel: true,
|
|
209
223
|
},
|
|
210
224
|
tooltip: {
|
|
225
|
+
...tooltipChrome(tokens),
|
|
211
226
|
trigger: "axis",
|
|
212
227
|
axisPointer: { type: "shadow" },
|
|
213
228
|
formatter: (
|
|
@@ -227,6 +242,9 @@ function buildCartesianOption(
|
|
|
227
242
|
? {
|
|
228
243
|
legend: {
|
|
229
244
|
show: true,
|
|
245
|
+
// echarts 6 lays the unpositioned legend over the bottom category
|
|
246
|
+
// labels; pin it to the top strip grid.top already reserves (#89).
|
|
247
|
+
top: 0,
|
|
230
248
|
data: series.map((s) => s.name),
|
|
231
249
|
textStyle: { color: tokens.textPrimary },
|
|
232
250
|
},
|
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Widget dispatcher —
|
|
2
|
+
* Widget dispatcher — COMPATIBILITY ADAPTER over @aiaiai-pt/widget-system/core.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* @deprecated Import the generic dispatch from `@aiaiai-pt/widget-system/core`
|
|
5
|
+
* instead. This module is the S2 (#60) compatibility shim: it preserves the
|
|
6
|
+
* Atelier-shaped `@aiaiai-pt/design-system/renderer/dispatch` import surface for
|
|
7
|
+
* one migration window while the ranking ALGORITHM lives in exactly one place —
|
|
8
|
+
* `@aiaiai-pt/widget-system/core`. Scheduled for removal in the next MAJOR of
|
|
9
|
+
* `@aiaiai-pt/design-system` (S3). See `docs/migration/widget-system.md`.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Classification (#60 AC5): the tester/priority ranking loop is a GENERIC
|
|
12
|
+
* widget-system API — it is delegated below and NOT reimplemented here. The
|
|
13
|
+
* `(binding, schema, type)` tester SIGNATURE is ATELIER-COUPLED (it names
|
|
14
|
+
* `Binding`/`OntologySchema`/`Block` from ./types) and stays here as a thin
|
|
15
|
+
* adapter until consumers migrate to the ctx-shaped `WidgetMatchContext`.
|
|
14
16
|
*
|
|
15
|
-
* TH-08 (R-SEC-07) preserved:
|
|
16
|
-
*
|
|
17
|
-
* is
|
|
17
|
+
* TH-08 (R-SEC-07) preserved end-to-end: `core.selectEntry` returns the matched
|
|
18
|
+
* entry's known-good `key` literal — the operator's raw `binding.kind`/`type`
|
|
19
|
+
* is never returned as the key, so it never reaches `class`/`data-*`/`style`.
|
|
18
20
|
*/
|
|
21
|
+
import { type Match as CoreMatch, type RenderDecision as CoreRenderDecision } from "@aiaiai-pt/widget-system/core";
|
|
19
22
|
import type { Binding, Block, OntologySchema } from "./types";
|
|
20
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Tester sentinel — "this widget does not apply to this binding".
|
|
25
|
+
*
|
|
26
|
+
* @deprecated Re-exported from `@aiaiai-pt/widget-system/core`. Import it from
|
|
27
|
+
* there directly.
|
|
28
|
+
*/
|
|
21
29
|
export declare const NOT_APPLICABLE = -1;
|
|
22
30
|
/**
|
|
23
31
|
* A tester ranks a registry entry against a binding (+ optional schema and the
|
|
@@ -26,38 +34,67 @@ export declare const NOT_APPLICABLE = -1;
|
|
|
26
34
|
* generic kind widget (`entity-list`) for the SAME `binding.kind` without
|
|
27
35
|
* editing the kind entry. `type` is UNTRUSTED — it only influences ranking; the
|
|
28
36
|
* resolved `key` still comes from the matched entry (TH-08), never from `type`.
|
|
37
|
+
*
|
|
38
|
+
* @deprecated ATELIER-COUPLED signature. The generic tester in
|
|
39
|
+
* `@aiaiai-pt/widget-system/core` is `(ctx: WidgetMatchContext) => number`.
|
|
40
|
+
* Migrate custom testers to read `ctx.kind`/`ctx.type` from a match context.
|
|
29
41
|
*/
|
|
30
42
|
export type WidgetTester = (binding: Binding, schema: OntologySchema | null, type?: string) => number;
|
|
43
|
+
/**
|
|
44
|
+
* @deprecated ATELIER-COUPLED entry shape (its `tester` names `Binding`). The
|
|
45
|
+
* generic entry lives in `@aiaiai-pt/widget-system/core` as
|
|
46
|
+
* `RegistryEntry<P, Ctx>`.
|
|
47
|
+
*/
|
|
31
48
|
export interface RegistryEntry<P> {
|
|
32
49
|
/** Known-good literal. Safe to render as `data-widget={key}` (TH-08). */
|
|
33
50
|
key: string;
|
|
34
51
|
payload: P;
|
|
35
52
|
tester: WidgetTester;
|
|
36
53
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The matched entry. Structurally identical to
|
|
56
|
+
* `@aiaiai-pt/widget-system/core`'s `Match<P>` — re-exported so consumers can
|
|
57
|
+
* migrate the import without a type change.
|
|
58
|
+
*
|
|
59
|
+
* @deprecated Import `Match` from `@aiaiai-pt/widget-system/core`.
|
|
60
|
+
*/
|
|
61
|
+
export type Match<P> = CoreMatch<P>;
|
|
62
|
+
/**
|
|
63
|
+
* The render decision. Structurally identical to
|
|
64
|
+
* `@aiaiai-pt/widget-system/core`'s `RenderDecision`.
|
|
65
|
+
*
|
|
66
|
+
* @deprecated Import `RenderDecision` from `@aiaiai-pt/widget-system/core`.
|
|
67
|
+
*/
|
|
68
|
+
export type RenderDecision = CoreRenderDecision;
|
|
41
69
|
/**
|
|
42
70
|
* Run every tester against the binding; the highest applicable score wins.
|
|
43
71
|
* Ties resolve to the first-registered entry (stable, deterministic). Returns
|
|
44
|
-
* `null` when no tester is applicable
|
|
45
|
-
*
|
|
72
|
+
* `null` when no tester is applicable.
|
|
73
|
+
*
|
|
74
|
+
* DELEGATION (#60): the ranking loop is NOT implemented here. Each
|
|
75
|
+
* Atelier-shaped entry is adapted into a `core.RegistryEntry` whose tester
|
|
76
|
+
* closes over `(binding, schema, type)` — so the full `(binding, schema, type)`
|
|
77
|
+
* tester contract (including `schema`, which the ctx model omits) is preserved
|
|
78
|
+
* — and the loop itself runs in `@aiaiai-pt/widget-system/core.selectEntry`.
|
|
79
|
+
* The `ctx` handed to core is a formality: core invokes `tester(ctx)` once per
|
|
80
|
+
* entry, and our adapted tester ignores it in favour of the closed-over args.
|
|
81
|
+
*
|
|
82
|
+
* @deprecated Use `selectEntry(entries, ctx)` from
|
|
83
|
+
* `@aiaiai-pt/widget-system/core` with `ctx: WidgetMatchContext`.
|
|
46
84
|
*/
|
|
47
85
|
export declare function selectEntry<P>(entries: ReadonlyArray<RegistryEntry<P>>, binding: Binding, schema: OntologySchema | null, type?: string): Match<P> | null;
|
|
48
|
-
export type RenderDecision = {
|
|
49
|
-
render: "widget";
|
|
50
|
-
} | {
|
|
51
|
-
render: "empty";
|
|
52
|
-
} | {
|
|
53
|
-
render: "error";
|
|
54
|
-
};
|
|
55
86
|
/**
|
|
56
87
|
* Fail-closed per blast radius (§14.8). Given whether a widget matched AND
|
|
57
88
|
* whether its data resolved, decide how the slot renders:
|
|
58
89
|
* - matched + data ok → render the widget
|
|
59
90
|
* - failed, optional slot → soft-empty (render nothing)
|
|
60
91
|
* - failed, structural slot → visible error (MUST NOT silently vanish)
|
|
61
|
-
*
|
|
92
|
+
*
|
|
93
|
+
* DELEGATION (#60): the decision is `@aiaiai-pt/widget-system/core.decideRender`;
|
|
94
|
+
* this adapter only projects the Atelier `Block.importance` field onto the
|
|
95
|
+
* generic `importance` argument.
|
|
96
|
+
*
|
|
97
|
+
* @deprecated Use `decideRender(importance, matched, dataOk)` from
|
|
98
|
+
* `@aiaiai-pt/widget-system/core`, passing `block.importance` directly.
|
|
62
99
|
*/
|
|
63
100
|
export declare function decideRender(block: Pick<Block, "importance">, matched: boolean, dataOk: boolean): RenderDecision;
|
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Widget dispatcher —
|
|
2
|
+
* Widget dispatcher — COMPATIBILITY ADAPTER over @aiaiai-pt/widget-system/core.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* @deprecated Import the generic dispatch from `@aiaiai-pt/widget-system/core`
|
|
5
|
+
* instead. This module is the S2 (#60) compatibility shim: it preserves the
|
|
6
|
+
* Atelier-shaped `@aiaiai-pt/design-system/renderer/dispatch` import surface for
|
|
7
|
+
* one migration window while the ranking ALGORITHM lives in exactly one place —
|
|
8
|
+
* `@aiaiai-pt/widget-system/core`. Scheduled for removal in the next MAJOR of
|
|
9
|
+
* `@aiaiai-pt/design-system` (S3). See `docs/migration/widget-system.md`.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Classification (#60 AC5): the tester/priority ranking loop is a GENERIC
|
|
12
|
+
* widget-system API — it is delegated below and NOT reimplemented here. The
|
|
13
|
+
* `(binding, schema, type)` tester SIGNATURE is ATELIER-COUPLED (it names
|
|
14
|
+
* `Binding`/`OntologySchema`/`Block` from ./types) and stays here as a thin
|
|
15
|
+
* adapter until consumers migrate to the ctx-shaped `WidgetMatchContext`.
|
|
14
16
|
*
|
|
15
|
-
* TH-08 (R-SEC-07) preserved:
|
|
16
|
-
*
|
|
17
|
-
* is
|
|
17
|
+
* TH-08 (R-SEC-07) preserved end-to-end: `core.selectEntry` returns the matched
|
|
18
|
+
* entry's known-good `key` literal — the operator's raw `binding.kind`/`type`
|
|
19
|
+
* is never returned as the key, so it never reaches `class`/`data-*`/`style`.
|
|
18
20
|
*/
|
|
21
|
+
import {
|
|
22
|
+
decideRender as coreDecideRender,
|
|
23
|
+
NOT_APPLICABLE as CORE_NOT_APPLICABLE,
|
|
24
|
+
selectEntry as coreSelectEntry,
|
|
25
|
+
type Match as CoreMatch,
|
|
26
|
+
type RenderDecision as CoreRenderDecision,
|
|
27
|
+
} from "@aiaiai-pt/widget-system/core";
|
|
19
28
|
import type { Binding, Block, OntologySchema } from "./types";
|
|
20
29
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Tester sentinel — "this widget does not apply to this binding".
|
|
32
|
+
*
|
|
33
|
+
* @deprecated Re-exported from `@aiaiai-pt/widget-system/core`. Import it from
|
|
34
|
+
* there directly.
|
|
35
|
+
*/
|
|
36
|
+
export const NOT_APPLICABLE = CORE_NOT_APPLICABLE;
|
|
23
37
|
|
|
24
38
|
/**
|
|
25
39
|
* A tester ranks a registry entry against a binding (+ optional schema and the
|
|
@@ -28,6 +42,10 @@ export const NOT_APPLICABLE = -1;
|
|
|
28
42
|
* generic kind widget (`entity-list`) for the SAME `binding.kind` without
|
|
29
43
|
* editing the kind entry. `type` is UNTRUSTED — it only influences ranking; the
|
|
30
44
|
* resolved `key` still comes from the matched entry (TH-08), never from `type`.
|
|
45
|
+
*
|
|
46
|
+
* @deprecated ATELIER-COUPLED signature. The generic tester in
|
|
47
|
+
* `@aiaiai-pt/widget-system/core` is `(ctx: WidgetMatchContext) => number`.
|
|
48
|
+
* Migrate custom testers to read `ctx.kind`/`ctx.type` from a match context.
|
|
31
49
|
*/
|
|
32
50
|
export type WidgetTester = (
|
|
33
51
|
binding: Binding,
|
|
@@ -35,6 +53,11 @@ export type WidgetTester = (
|
|
|
35
53
|
type?: string,
|
|
36
54
|
) => number;
|
|
37
55
|
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated ATELIER-COUPLED entry shape (its `tester` names `Binding`). The
|
|
58
|
+
* generic entry lives in `@aiaiai-pt/widget-system/core` as
|
|
59
|
+
* `RegistryEntry<P, Ctx>`.
|
|
60
|
+
*/
|
|
38
61
|
export interface RegistryEntry<P> {
|
|
39
62
|
/** Known-good literal. Safe to render as `data-widget={key}` (TH-08). */
|
|
40
63
|
key: string;
|
|
@@ -42,16 +65,38 @@ export interface RegistryEntry<P> {
|
|
|
42
65
|
tester: WidgetTester;
|
|
43
66
|
}
|
|
44
67
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
68
|
+
/**
|
|
69
|
+
* The matched entry. Structurally identical to
|
|
70
|
+
* `@aiaiai-pt/widget-system/core`'s `Match<P>` — re-exported so consumers can
|
|
71
|
+
* migrate the import without a type change.
|
|
72
|
+
*
|
|
73
|
+
* @deprecated Import `Match` from `@aiaiai-pt/widget-system/core`.
|
|
74
|
+
*/
|
|
75
|
+
export type Match<P> = CoreMatch<P>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The render decision. Structurally identical to
|
|
79
|
+
* `@aiaiai-pt/widget-system/core`'s `RenderDecision`.
|
|
80
|
+
*
|
|
81
|
+
* @deprecated Import `RenderDecision` from `@aiaiai-pt/widget-system/core`.
|
|
82
|
+
*/
|
|
83
|
+
export type RenderDecision = CoreRenderDecision;
|
|
49
84
|
|
|
50
85
|
/**
|
|
51
86
|
* Run every tester against the binding; the highest applicable score wins.
|
|
52
87
|
* Ties resolve to the first-registered entry (stable, deterministic). Returns
|
|
53
|
-
* `null` when no tester is applicable
|
|
54
|
-
*
|
|
88
|
+
* `null` when no tester is applicable.
|
|
89
|
+
*
|
|
90
|
+
* DELEGATION (#60): the ranking loop is NOT implemented here. Each
|
|
91
|
+
* Atelier-shaped entry is adapted into a `core.RegistryEntry` whose tester
|
|
92
|
+
* closes over `(binding, schema, type)` — so the full `(binding, schema, type)`
|
|
93
|
+
* tester contract (including `schema`, which the ctx model omits) is preserved
|
|
94
|
+
* — and the loop itself runs in `@aiaiai-pt/widget-system/core.selectEntry`.
|
|
95
|
+
* The `ctx` handed to core is a formality: core invokes `tester(ctx)` once per
|
|
96
|
+
* entry, and our adapted tester ignores it in favour of the closed-over args.
|
|
97
|
+
*
|
|
98
|
+
* @deprecated Use `selectEntry(entries, ctx)` from
|
|
99
|
+
* `@aiaiai-pt/widget-system/core` with `ctx: WidgetMatchContext`.
|
|
55
100
|
*/
|
|
56
101
|
export function selectEntry<P>(
|
|
57
102
|
entries: ReadonlyArray<RegistryEntry<P>>,
|
|
@@ -59,41 +104,35 @@ export function selectEntry<P>(
|
|
|
59
104
|
schema: OntologySchema | null,
|
|
60
105
|
type?: string,
|
|
61
106
|
): Match<P> | null {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
if (best === null) return null;
|
|
73
|
-
// TH-08: key from the matched entry, never from the binding/block.
|
|
74
|
-
return { key: best.key, payload: best.payload };
|
|
107
|
+
const ctx = { kind: binding.kind, type };
|
|
108
|
+
const adapted = entries.map((entry) => ({
|
|
109
|
+
key: entry.key,
|
|
110
|
+
payload: entry.payload,
|
|
111
|
+
// Close over the Atelier-shaped selection inputs; core supplies `ctx` but
|
|
112
|
+
// this adapter reads the richer (binding, schema, type) triple instead.
|
|
113
|
+
tester: () => entry.tester(binding, schema, type),
|
|
114
|
+
}));
|
|
115
|
+
return coreSelectEntry(adapted, ctx);
|
|
75
116
|
}
|
|
76
117
|
|
|
77
|
-
export type RenderDecision =
|
|
78
|
-
| { render: "widget" }
|
|
79
|
-
| { render: "empty" }
|
|
80
|
-
| { render: "error" };
|
|
81
|
-
|
|
82
118
|
/**
|
|
83
119
|
* Fail-closed per blast radius (§14.8). Given whether a widget matched AND
|
|
84
120
|
* whether its data resolved, decide how the slot renders:
|
|
85
121
|
* - matched + data ok → render the widget
|
|
86
122
|
* - failed, optional slot → soft-empty (render nothing)
|
|
87
123
|
* - failed, structural slot → visible error (MUST NOT silently vanish)
|
|
88
|
-
*
|
|
124
|
+
*
|
|
125
|
+
* DELEGATION (#60): the decision is `@aiaiai-pt/widget-system/core.decideRender`;
|
|
126
|
+
* this adapter only projects the Atelier `Block.importance` field onto the
|
|
127
|
+
* generic `importance` argument.
|
|
128
|
+
*
|
|
129
|
+
* @deprecated Use `decideRender(importance, matched, dataOk)` from
|
|
130
|
+
* `@aiaiai-pt/widget-system/core`, passing `block.importance` directly.
|
|
89
131
|
*/
|
|
90
132
|
export function decideRender(
|
|
91
133
|
block: Pick<Block, "importance">,
|
|
92
134
|
matched: boolean,
|
|
93
135
|
dataOk: boolean,
|
|
94
136
|
): RenderDecision {
|
|
95
|
-
|
|
96
|
-
return block.importance === "structural"
|
|
97
|
-
? { render: "error" }
|
|
98
|
-
: { render: "empty" };
|
|
137
|
+
return coreDecideRender(block.importance, matched, dataOk);
|
|
99
138
|
}
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DS renderer widget registry — the
|
|
2
|
+
* DS renderer widget registry — the Atelier-coupled BASE registry (#492).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
4
|
+
* @deprecated This is an S2 (#60) COMPATIBILITY surface. It is NOT a generic
|
|
5
|
+
* widget-system API — do not reclassify it as one (#60 AC5). It is an
|
|
6
|
+
* Atelier-coupled base registry: a module-global entry list preloaded with the
|
|
7
|
+
* DS widgets (stat-grid, results-chart, chart, metabase-embed) whose testers
|
|
8
|
+
* name `Binding`/`Block`/`WidgetKind` from ./types.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Migration destination: the generic isolated-registry machinery lives in
|
|
11
|
+
* `@aiaiai-pt/widget-system/core` (`createRegistry()` — an isolated, override-
|
|
12
|
+
* aware factory, not a module-global singleton) and its base-widget preload in
|
|
13
|
+
* `@aiaiai-pt/widget-system/widgets` (`registerBaseWidgets`). The Atelier host
|
|
14
|
+
* owns which widgets it preloads. This module is scheduled for removal in the
|
|
15
|
+
* next MAJOR of `@aiaiai-pt/design-system` (S3). See
|
|
16
|
+
* `docs/migration/widget-system.md`.
|
|
17
|
+
*
|
|
18
|
+
* NO DUPLICATE DISPATCH (#60 AC3): this module holds ZERO ranking logic. It
|
|
19
|
+
* only stores entries and delegates resolution to `./dispatch.selectEntry`,
|
|
20
|
+
* which itself delegates the ranking loop to `@aiaiai-pt/widget-system/core`.
|
|
12
21
|
*
|
|
13
22
|
* TH-08 preserved: `resolveWidget` returns the matched entry's known-good `key`
|
|
14
23
|
* literal — the operator's raw block `type`/`binding.kind` is never the key.
|
|
@@ -22,9 +31,18 @@ export type WidgetComponent = Component<WidgetProps>;
|
|
|
22
31
|
* `type` hint ranks it ABOVE the kind-generic widget (score 20 > 10) for the
|
|
23
32
|
* same binding. `type` is UNTRUSTED — it only influences ranking; the resolved
|
|
24
33
|
* key is the entry literal (TH-08).
|
|
34
|
+
*
|
|
35
|
+
* @deprecated ATELIER-COUPLED builder (its `kind` is a `WidgetKind` and its
|
|
36
|
+
* tester names `Binding`). Migrate to `byTypeOnKind` from
|
|
37
|
+
* `@aiaiai-pt/widget-system/core`, whose tester reads a ctx `WidgetMatchContext`.
|
|
25
38
|
*/
|
|
26
39
|
export declare function byTypeOnKind(key: string, kind: WidgetKind, component: unknown): RegistryEntry<WidgetComponent>;
|
|
27
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* A widget whose tester is the generic "binding.kind === K" check (score 10).
|
|
42
|
+
*
|
|
43
|
+
* @deprecated ATELIER-COUPLED builder. Migrate to `byKind` from
|
|
44
|
+
* `@aiaiai-pt/widget-system/core` (ctx-shaped tester).
|
|
45
|
+
*/
|
|
28
46
|
export declare function byKind(key: string, kind: WidgetKind, component: unknown): RegistryEntry<WidgetComponent>;
|
|
29
47
|
/**
|
|
30
48
|
* Register a widget in the host-extensible registry. Call at app startup
|
|
@@ -37,6 +55,11 @@ export declare function byKind(key: string, kind: WidgetKind, component: unknown
|
|
|
37
55
|
*
|
|
38
56
|
* @param entry - A registry entry built with `byKind`, `byTypeOnKind`, or a
|
|
39
57
|
* hand-crafted `{ key, payload, tester }` for a custom tester.
|
|
58
|
+
*
|
|
59
|
+
* @deprecated ATELIER-COUPLED module-global registration. Migrate to an
|
|
60
|
+
* isolated `createRegistry()` from `@aiaiai-pt/widget-system/core` plus
|
|
61
|
+
* `registerBaseWidgets` from `@aiaiai-pt/widget-system/widgets`, and call
|
|
62
|
+
* `registry.register(...)` on the host-owned instance.
|
|
40
63
|
*/
|
|
41
64
|
export declare function registerWidget(entry: RegistryEntry<WidgetComponent>): void;
|
|
42
65
|
/**
|
|
@@ -44,5 +67,11 @@ export declare function registerWidget(entry: RegistryEntry<WidgetComponent>): v
|
|
|
44
67
|
* the full registry (base DS entries + host-registered). Returns `null` when
|
|
45
68
|
* no widget applies — the caller renders per blast radius (`decideRender`),
|
|
46
69
|
* never interpolating the block's raw `type`/`kind`.
|
|
70
|
+
*
|
|
71
|
+
* Resolution delegates to `./dispatch.selectEntry` (→ widget-system/core); this
|
|
72
|
+
* module contributes only the entry list, never the ranking loop.
|
|
73
|
+
*
|
|
74
|
+
* @deprecated Migrate to `registry.resolve(ctx)` on an isolated
|
|
75
|
+
* `createRegistry()` from `@aiaiai-pt/widget-system/core`.
|
|
47
76
|
*/
|
|
48
77
|
export declare function resolveWidget(block: Block, schema?: OntologySchema | null): Match<WidgetComponent> | null;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DS renderer widget registry — the
|
|
2
|
+
* DS renderer widget registry — the Atelier-coupled BASE registry (#492).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
4
|
+
* @deprecated This is an S2 (#60) COMPATIBILITY surface. It is NOT a generic
|
|
5
|
+
* widget-system API — do not reclassify it as one (#60 AC5). It is an
|
|
6
|
+
* Atelier-coupled base registry: a module-global entry list preloaded with the
|
|
7
|
+
* DS widgets (stat-grid, results-chart, chart, metabase-embed) whose testers
|
|
8
|
+
* name `Binding`/`Block`/`WidgetKind` from ./types.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Migration destination: the generic isolated-registry machinery lives in
|
|
11
|
+
* `@aiaiai-pt/widget-system/core` (`createRegistry()` — an isolated, override-
|
|
12
|
+
* aware factory, not a module-global singleton) and its base-widget preload in
|
|
13
|
+
* `@aiaiai-pt/widget-system/widgets` (`registerBaseWidgets`). The Atelier host
|
|
14
|
+
* owns which widgets it preloads. This module is scheduled for removal in the
|
|
15
|
+
* next MAJOR of `@aiaiai-pt/design-system` (S3). See
|
|
16
|
+
* `docs/migration/widget-system.md`.
|
|
17
|
+
*
|
|
18
|
+
* NO DUPLICATE DISPATCH (#60 AC3): this module holds ZERO ranking logic. It
|
|
19
|
+
* only stores entries and delegates resolution to `./dispatch.selectEntry`,
|
|
20
|
+
* which itself delegates the ranking loop to `@aiaiai-pt/widget-system/core`.
|
|
12
21
|
*
|
|
13
22
|
* TH-08 preserved: `resolveWidget` returns the matched entry's known-good `key`
|
|
14
23
|
* literal — the operator's raw block `type`/`binding.kind` is never the key.
|
|
@@ -34,6 +43,10 @@ export type WidgetComponent = Component<WidgetProps>;
|
|
|
34
43
|
* `type` hint ranks it ABOVE the kind-generic widget (score 20 > 10) for the
|
|
35
44
|
* same binding. `type` is UNTRUSTED — it only influences ranking; the resolved
|
|
36
45
|
* key is the entry literal (TH-08).
|
|
46
|
+
*
|
|
47
|
+
* @deprecated ATELIER-COUPLED builder (its `kind` is a `WidgetKind` and its
|
|
48
|
+
* tester names `Binding`). Migrate to `byTypeOnKind` from
|
|
49
|
+
* `@aiaiai-pt/widget-system/core`, whose tester reads a ctx `WidgetMatchContext`.
|
|
37
50
|
*/
|
|
38
51
|
export function byTypeOnKind(
|
|
39
52
|
key: string,
|
|
@@ -48,7 +61,12 @@ export function byTypeOnKind(
|
|
|
48
61
|
};
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* A widget whose tester is the generic "binding.kind === K" check (score 10).
|
|
66
|
+
*
|
|
67
|
+
* @deprecated ATELIER-COUPLED builder. Migrate to `byKind` from
|
|
68
|
+
* `@aiaiai-pt/widget-system/core` (ctx-shaped tester).
|
|
69
|
+
*/
|
|
52
70
|
export function byKind(
|
|
53
71
|
key: string,
|
|
54
72
|
kind: WidgetKind,
|
|
@@ -92,6 +110,11 @@ const _entries: RegistryEntry<WidgetComponent>[] = [
|
|
|
92
110
|
*
|
|
93
111
|
* @param entry - A registry entry built with `byKind`, `byTypeOnKind`, or a
|
|
94
112
|
* hand-crafted `{ key, payload, tester }` for a custom tester.
|
|
113
|
+
*
|
|
114
|
+
* @deprecated ATELIER-COUPLED module-global registration. Migrate to an
|
|
115
|
+
* isolated `createRegistry()` from `@aiaiai-pt/widget-system/core` plus
|
|
116
|
+
* `registerBaseWidgets` from `@aiaiai-pt/widget-system/widgets`, and call
|
|
117
|
+
* `registry.register(...)` on the host-owned instance.
|
|
95
118
|
*/
|
|
96
119
|
export function registerWidget(entry: RegistryEntry<WidgetComponent>): void {
|
|
97
120
|
_entries.push(entry);
|
|
@@ -102,6 +125,12 @@ export function registerWidget(entry: RegistryEntry<WidgetComponent>): void {
|
|
|
102
125
|
* the full registry (base DS entries + host-registered). Returns `null` when
|
|
103
126
|
* no widget applies — the caller renders per blast radius (`decideRender`),
|
|
104
127
|
* never interpolating the block's raw `type`/`kind`.
|
|
128
|
+
*
|
|
129
|
+
* Resolution delegates to `./dispatch.selectEntry` (→ widget-system/core); this
|
|
130
|
+
* module contributes only the entry list, never the ranking loop.
|
|
131
|
+
*
|
|
132
|
+
* @deprecated Migrate to `registry.resolve(ctx)` on an isolated
|
|
133
|
+
* `createRegistry()` from `@aiaiai-pt/widget-system/core`.
|
|
105
134
|
*/
|
|
106
135
|
export function resolveWidget(
|
|
107
136
|
block: Block,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiaiai-pt/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Design system tokens and Svelte components for aiaiai products",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
14
|
"workspaces": [
|
|
15
|
-
"site"
|
|
15
|
+
"site",
|
|
16
|
+
"packages/widget-system"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build:types": "rm -f components/*.d.ts components/renderer/*.d.ts && svelte-package --input . --output dist && cp dist/components/*.d.ts components/ && cp dist/components/renderer/*.d.ts components/renderer/ && rm -rf dist",
|
|
@@ -73,6 +74,9 @@
|
|
|
73
74
|
"components",
|
|
74
75
|
"tokens"
|
|
75
76
|
],
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"@aiaiai-pt/widget-system": ">=0.1.0 <1"
|
|
79
|
+
},
|
|
76
80
|
"peerDependencies": {
|
|
77
81
|
"@codemirror/commands": "^6.10.3",
|
|
78
82
|
"@codemirror/lang-json": "^6.0.2",
|
package/tokens/semantic.css
CHANGED
|
@@ -230,11 +230,11 @@
|
|
|
230
230
|
dark wash for free. Per-theme bespoke dark = a `[data-theme="x"]`
|
|
231
231
|
block in the theme file, not here.
|
|
232
232
|
|
|
233
|
-
Selector is `:root[data-scheme="dark"]` (0,
|
|
234
|
-
blocks (`:root[data-theme="x"]`,
|
|
235
|
-
on order — DS styles load after the injected theme
|
|
236
|
-
resolving an "auto" preference must set the RESOLVED value
|
|
237
|
-
(a pre-paint `prefers-color-scheme` read), never "auto" itself. */
|
|
233
|
+
Selector is `:root[data-scheme="dark"]` (0,2,0 — pseudo-class + attribute):
|
|
234
|
+
it TIES runtime-injected tenant theme blocks (`:root[data-theme="x"]`,
|
|
235
|
+
also 0,2,0) and wins on order — DS styles load after the injected theme
|
|
236
|
+
block. Consumers resolving an "auto" preference must set the RESOLVED value
|
|
237
|
+
on <html> (a pre-paint `prefers-color-scheme` read), never "auto" itself. */
|
|
238
238
|
|
|
239
239
|
:root[data-scheme="dark"] {
|
|
240
240
|
color-scheme: dark;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* aiaiai Design System — UBP Theme (ubp.css)
|
|
3
|
+
*
|
|
4
|
+
* Professional instrument theme for the urban intelligence workspace
|
|
5
|
+
* (ubp-ai-workspace). Tier 2 (Bespoke): distinct cool-steel identity
|
|
6
|
+
* optimised for data-dense, map-centric, dark-primary interfaces.
|
|
7
|
+
*
|
|
8
|
+
* Selector: [data-theme="ubp"]
|
|
9
|
+
* Paired scheme: [data-scheme="dark"] for the workspace default.
|
|
10
|
+
* The generic dark layer (semantic.css) handles neutral role inversion;
|
|
11
|
+
* the [data-theme="ubp"][data-scheme="dark"] block below adjusts only
|
|
12
|
+
* the brand values that need different treatment on a cool-dark surface.
|
|
13
|
+
*
|
|
14
|
+
* ─── Neutral palette — owned, not verbatim Tailwind slate ───────────────
|
|
15
|
+
* The UBP neutral ramp is derived from Tailwind slate shifted 10° toward
|
|
16
|
+
* teal/steel in OKLCH. This is a deliberate identity decision: the hue
|
|
17
|
+
* drift gives mid-tones (400–600) a visible cool-steel signature while
|
|
18
|
+
* keeping the lightest stops (50–200) effectively neutral, making the
|
|
19
|
+
* palette perceptually distinct from Tailwind defaults without tipping
|
|
20
|
+
* into obvious teal.
|
|
21
|
+
*
|
|
22
|
+
* Before → After (10° OKLCH hue shift, all other OKLCH params unchanged):
|
|
23
|
+
* #f8fafc → #f8fafc (slate-50, unchanged at 8-bit precision)
|
|
24
|
+
* #f1f5f9 → #f0f5f9 (slate-100, surface-secondary)
|
|
25
|
+
* #e2e8f0 → #e1e9f0 (slate-200, surface-tertiary)
|
|
26
|
+
* #cbd5e1 → #c9d6e0 (slate-300, border)
|
|
27
|
+
* #94a3b8 → #91a4b7 (slate-400, border-strong / dark text-secondary)
|
|
28
|
+
* #64748b → #60758a (slate-500, text-muted)
|
|
29
|
+
* #475569 → #435668 (slate-600, dark border-strong)
|
|
30
|
+
* #334155 → #2f4254 (slate-700, text-secondary light / dark surfaces)
|
|
31
|
+
* #1e293b → #1a2a3a (slate-800, dark surface-secondary)
|
|
32
|
+
* #0f172a → #0b182a (slate-900, text light / dark surface)
|
|
33
|
+
*
|
|
34
|
+
* ─── Font decision: inherit DS pairing ──────────────────────────────────
|
|
35
|
+
* UBP inherits Sistema's default type pairing unchanged:
|
|
36
|
+
* sans → Instrument Sans (--font-sans / --raw-font-sans)
|
|
37
|
+
* mono → JetBrains Mono, ui-monospace (--font-mono / --raw-font-mono)
|
|
38
|
+
* (Berkeley Mono leads the mono stack in the token but ships no @font-face;
|
|
39
|
+
* JetBrains Mono is the effective shipped face — see sistema #82.)
|
|
40
|
+
* The theme sets NO --font-sans or --font-mono override; font role-tokens
|
|
41
|
+
* resolve to the DS defaults via semantic.css inheritance.
|
|
42
|
+
* Full decision record: dev_docs/solutions/ubp-font-self-hosting.md
|
|
43
|
+
*
|
|
44
|
+
* ─── Historical 3.0:1 subtitle regression control ────────────────────────
|
|
45
|
+
* The 3.0:1 defect occurred when --color-text-muted was used in subtitle
|
|
46
|
+
* roles, giving only 3.0:1 contrast on the surface. This theme prevents it
|
|
47
|
+
* by ensuring --color-text-secondary (the subtitle token) clears WCAG AA
|
|
48
|
+
* (≥4.5:1) on every declared surface. See tests/ubp-theme-contrast.test.ts.
|
|
49
|
+
*
|
|
50
|
+
* ─── Owned by: DS-H0 #65, implemented under #66 ─────────────────────────
|
|
51
|
+
* Apply via: <html data-theme="ubp"> (light) or
|
|
52
|
+
* <html data-theme="ubp" data-scheme="dark"> (dark / workspace default)
|
|
53
|
+
* Layer independently with data-contrast, data-text-size, data-link-highlight.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
[data-theme="ubp"] {
|
|
57
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
58
|
+
TYPOGRAPHY — inherited from DS (no override)
|
|
59
|
+
Instrument Sans (sans) + JetBrains Mono (mono) via semantic.css.
|
|
60
|
+
═══════════════════════════════════════════════════════════════ */
|
|
61
|
+
|
|
62
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
63
|
+
COLOR — Cool steel palette (light-scheme base)
|
|
64
|
+
Neutrals: 10° OKLCH teal-shift from Tailwind slate — see file header.
|
|
65
|
+
═══════════════════════════════════════════════════════════════ */
|
|
66
|
+
|
|
67
|
+
/* Surfaces: cool off-white — sterile precision vs aiaiai warm paper */
|
|
68
|
+
--color-surface: #f8fafc; /* steel-50 (unchanged at 8-bit) */
|
|
69
|
+
--color-surface-secondary: #f0f5f9; /* steel-100 */
|
|
70
|
+
--color-surface-tertiary: #e1e9f0; /* steel-200 */
|
|
71
|
+
--color-surface-raised: #ffffff; /* pure white for popovers/menus */
|
|
72
|
+
--color-overlay: rgba(
|
|
73
|
+
11,
|
|
74
|
+
24,
|
|
75
|
+
42,
|
|
76
|
+
0.5
|
|
77
|
+
); /* cool dark overlay (steel-900 base) */
|
|
78
|
+
|
|
79
|
+
/* Borders: cool steel */
|
|
80
|
+
--color-border: #c9d6e0; /* steel-300 */
|
|
81
|
+
--color-border-strong: #91a4b7; /* steel-400 */
|
|
82
|
+
|
|
83
|
+
/* Text: cool dark steel — contrast validated in ubp-theme-contrast.test.ts
|
|
84
|
+
text (#0b182a) on surface (#f8fafc): 17.03:1 [AAA]
|
|
85
|
+
text-secondary (#2f4254) on surface (#f8fafc): 9.90:1 [AAA — subtitle safe]
|
|
86
|
+
text-muted (#60758a) on surface (#f8fafc): 4.55:1 [AA — above 3.0 defect floor] */
|
|
87
|
+
--color-text: #0b182a; /* steel-900 */
|
|
88
|
+
--color-text-secondary: #2f4254; /* steel-700 */
|
|
89
|
+
--color-text-muted: #60758a; /* steel-500 */
|
|
90
|
+
--color-text-on-accent: #ffffff; /* white on blue-600: 5.17:1 [AA] */
|
|
91
|
+
|
|
92
|
+
/* Accent: UBP blue — maps, focus rings, primary actions
|
|
93
|
+
accent (#2563eb) on surface (#f8fafc): 4.94:1 [AA for normal text, ≥3:1 for UI] */
|
|
94
|
+
--color-accent: #2563eb; /* blue-600 */
|
|
95
|
+
--color-accent-hover: #1d4ed8; /* blue-700 */
|
|
96
|
+
--color-accent-subtle: #eff6ff; /* blue-50 equivalent — selected states */
|
|
97
|
+
|
|
98
|
+
/* Status semantic colours: override for cool-palette compatibility.
|
|
99
|
+
WCAG AA on --color-surface (#f8fafc) and --color-surface-secondary (#f1f5f9).
|
|
100
|
+
Subtle washes re-tuned for the cool surfaces.
|
|
101
|
+
NOTE: --color-info is NOT overridden; inherits DS teal-blue
|
|
102
|
+
(--raw-color-blue-600, #2e63a3 — 5.85:1 on surface), distinct from
|
|
103
|
+
--color-accent (#2563eb). See contrast suite: tests/ubp-theme-contrast.test.ts */
|
|
104
|
+
--color-destructive: #dc2626; /* red-600, 4.62:1 on surface */
|
|
105
|
+
--color-destructive-hover: #b91c1c; /* red-700 */
|
|
106
|
+
--color-destructive-subtle: #fef2f2;
|
|
107
|
+
|
|
108
|
+
--color-success: #16a34a; /* green-600, 3.15:1 on surface — large UI only */
|
|
109
|
+
--color-success-subtle: #f0fdf4;
|
|
110
|
+
|
|
111
|
+
--color-warning: #ca8a04; /* amber-600, 3.1:1 on surface — large UI only */
|
|
112
|
+
--color-warning-subtle: #fffbeb;
|
|
113
|
+
|
|
114
|
+
/* Focus: accent blue, visible on all surfaces */
|
|
115
|
+
--focus-ring-color: var(--color-accent);
|
|
116
|
+
|
|
117
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
118
|
+
RADIUS — Keep sharp (precision instrument)
|
|
119
|
+
Already: sm=2px md=4px lg=8px from semantic.css defaults
|
|
120
|
+
═══════════════════════════════════════════════════════════════ */
|
|
121
|
+
/* No override — sharp radii match the professional platform aesthetic */
|
|
122
|
+
|
|
123
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
124
|
+
ELEVATION — Subtle shadow on raised surfaces (instrument feel)
|
|
125
|
+
Uses steel-900 (#0b182a) as the shadow base (10° teal-shifted).
|
|
126
|
+
═══════════════════════════════════════════════════════════════ */
|
|
127
|
+
--elevation-raised:
|
|
128
|
+
0 1px 3px rgba(11, 24, 42, 0.08), 0 1px 2px rgba(11, 24, 42, 0.04);
|
|
129
|
+
--elevation-overlay:
|
|
130
|
+
0 4px 16px rgba(11, 24, 42, 0.12), 0 2px 6px rgba(11, 24, 42, 0.06);
|
|
131
|
+
|
|
132
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
133
|
+
MOTION — unchanged from DS defaults.
|
|
134
|
+
Product choreography (map transitions, panel morphs) belongs in
|
|
135
|
+
the host's ws.css, not the theme.
|
|
136
|
+
═══════════════════════════════════════════════════════════════ */
|
|
137
|
+
|
|
138
|
+
/* Total color overrides: 22. Elevation: 2. Focus alias: 1.
|
|
139
|
+
Grand total: ~25 tokens. Typography/motion/spacing/grid unchanged.
|
|
140
|
+
--color-info and --color-info-subtle NOT overridden (inherit DS teal-blue). */
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Desktop density: compact button heights for toolbar/sidebar layouts.
|
|
144
|
+
Scoped to ≥1024px so mobile targets stay at the DS default (44px for
|
|
145
|
+
--button-lg-height), satisfying the Tier-2 a11y checklist minimum.
|
|
146
|
+
Analogous to how other bespoke themes adjust density only where a
|
|
147
|
+
pointer device makes smaller targets safe (WCAG 2.5.5 advisory). */
|
|
148
|
+
@media (min-width: 1024px) {
|
|
149
|
+
[data-theme="ubp"] {
|
|
150
|
+
--button-sm-height: 28px; /* DS Tier-2 floor: 28px min (reference/theming.md:221) */
|
|
151
|
+
--button-md-height: 32px;
|
|
152
|
+
--button-lg-height: 44px; /* DS Tier-2 floor: 44px min (reference/theming.md:221) */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* Dark-scheme brand override.
|
|
158
|
+
*
|
|
159
|
+
* The generic dark layer (`:root[data-scheme="dark"]` in semantic.css, specificity
|
|
160
|
+
* 0,2,0 — pseudo-class + attribute) inverts neutral-derived roles to the warm
|
|
161
|
+
* neutral-950 ramp. This block overrides only the brand values that require a
|
|
162
|
+
* cool-steel dark base:
|
|
163
|
+
* - Surfaces / borders / text / overlay → cool steel (not warm neutral)
|
|
164
|
+
* - Accent → lighter blue (link contrast: 7.03:1 on steel-900 [AAA])
|
|
165
|
+
* - text-on-accent → dark text on the lighter blue fill (7.03:1 [AAA])
|
|
166
|
+
* - Subtle washes re-derived against the cool base via color-mix
|
|
167
|
+
*
|
|
168
|
+
* Selector specificity: (0,2,0) — ties the generic dark layer (also 0,2,0) when
|
|
169
|
+
* both `data-theme="ubp"` and `data-scheme="dark"` are present; wins by source
|
|
170
|
+
* order because ubp.css loads after semantic.css. The `auto` scheme is resolved
|
|
171
|
+
* before paint; "dark" here always matches an explicit decision.
|
|
172
|
+
*
|
|
173
|
+
* Subtitle regression control:
|
|
174
|
+
* text (#f8fafc) on surface (#0b182a): 17.03:1 [AAA]
|
|
175
|
+
* text-secondary (#91a4b7) on surface (#0b182a): 6.96:1 [AA — subtitle safe]
|
|
176
|
+
* text-muted (#60758a) on surface (#0b182a): 3.74:1 — correctly limited to
|
|
177
|
+
* non-essential UI labels, NOT body text or subtitles.
|
|
178
|
+
*/
|
|
179
|
+
[data-theme="ubp"][data-scheme="dark"] {
|
|
180
|
+
/* Surfaces: cool steel (deeper than the warm neutral-950 generic layer)
|
|
181
|
+
10° OKLCH teal-shift from Tailwind slate — see file header for before/after. */
|
|
182
|
+
--color-surface: #0b182a; /* steel-900 */
|
|
183
|
+
--color-surface-secondary: #1a2a3a; /* steel-800 */
|
|
184
|
+
--color-surface-tertiary: #2f4254; /* steel-700 */
|
|
185
|
+
--color-surface-raised: #1a2a3a; /* steel-800 */
|
|
186
|
+
--color-overlay: rgba(0, 0, 0, 0.7);
|
|
187
|
+
|
|
188
|
+
/* Borders */
|
|
189
|
+
--color-border: #2f4254; /* steel-700 */
|
|
190
|
+
--color-border-strong: #435668; /* steel-600 */
|
|
191
|
+
|
|
192
|
+
/* Text */
|
|
193
|
+
--color-text: #f8fafc; /* steel-50: 17.03:1 on surface [AAA] */
|
|
194
|
+
--color-text-secondary: #91a4b7; /* steel-400: 6.96:1 on surface [AA] */
|
|
195
|
+
--color-text-muted: #60758a; /* steel-500: 3.74:1 — non-essential only */
|
|
196
|
+
|
|
197
|
+
/* Accent: blue-400 lifts to 7.03:1 on steel-900, clearing AAA for link text.
|
|
198
|
+
Dual-use: as a fill, dark text (#0b182a) on blue-400 gives 7.03:1 [AAA]. */
|
|
199
|
+
--color-accent: #60a5fa; /* blue-400 */
|
|
200
|
+
--color-accent-hover: #93c5fd; /* blue-300 */
|
|
201
|
+
--color-text-on-accent: #0b182a; /* steel-900 on blue-400: 7.03:1 [AAA] */
|
|
202
|
+
|
|
203
|
+
/* Accent subtle: re-derived for the cool-steel base */
|
|
204
|
+
--color-accent-subtle: color-mix(in srgb, #60a5fa 16%, #0b182a);
|
|
205
|
+
|
|
206
|
+
/* Status subtles: re-derived for cool-steel surface */
|
|
207
|
+
--color-destructive-subtle: color-mix(
|
|
208
|
+
in srgb,
|
|
209
|
+
var(--color-destructive) 14%,
|
|
210
|
+
#0b182a
|
|
211
|
+
);
|
|
212
|
+
--color-success-subtle: color-mix(in srgb, var(--color-success) 14%, #0b182a);
|
|
213
|
+
--color-warning-subtle: color-mix(in srgb, var(--color-warning) 14%, #0b182a);
|
|
214
|
+
--color-info-subtle: color-mix(in srgb, var(--color-info) 14%, #0b182a);
|
|
215
|
+
|
|
216
|
+
/* Elevation: more dramatic on dark */
|
|
217
|
+
--elevation-raised:
|
|
218
|
+
0 1px 4px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
219
|
+
--elevation-overlay:
|
|
220
|
+
0 8px 24px rgba(0, 0, 0, 0.5), 0 3px 8px rgba(0, 0, 0, 0.3);
|
|
221
|
+
|
|
222
|
+
/* Focus ring: accent blue, visible on all dark surfaces */
|
|
223
|
+
--focus-ring-color: var(--color-accent);
|
|
224
|
+
}
|
package/tokens/utilities.css
CHANGED
|
@@ -1056,6 +1056,15 @@
|
|
|
1056
1056
|
letter-spacing: var(--type-caption-tracking);
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
|
+
.type-overline {
|
|
1060
|
+
font-family: var(--type-overline-font);
|
|
1061
|
+
font-size: var(--type-overline-size);
|
|
1062
|
+
font-weight: var(--type-overline-weight);
|
|
1063
|
+
line-height: var(--type-overline-leading);
|
|
1064
|
+
letter-spacing: var(--type-overline-tracking);
|
|
1065
|
+
text-transform: uppercase;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1059
1068
|
/* ═══════════════════════════════════════════════
|
|
1060
1069
|
TYPOGRAPHY UTILITIES
|
|
1061
1070
|
═══════════════════════════════════════════════ */
|