@tailor-platform/app-shell 1.15.0 → 1.17.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/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +811 -131
- package/dist/app-shell.js +7155 -5797
- package/dist/{appshell-DLVBFo5J.js → appshell-CahhZy7v.js} +656 -524
- package/dist/semantic-contrast.test.ts +154 -0
- package/dist/style-entry.d.ts +147 -92
- package/dist/testing.d.ts +152 -95
- package/dist/testing.js +1 -1
- package/dist/theme-bridge.test.ts +101 -1
- package/dist/theme.bridge.css +91 -0
- package/dist/theme.css +6 -1
- package/dist/themes/_template.css +7 -1
- package/dist/themes/bloom.css +0 -6
- package/dist/themes/cream.css +0 -6
- package/dist/themes/default.css +389 -69
- package/dist/vite-plugin.d.ts +147 -92
- package/package.json +8 -8
- package/skills/app-shell-patterns/SKILL.md +108 -19
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
- package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
- package/skills/app-shell-patterns/references/api/define-module.md +247 -0
- package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
- package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
- package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
- package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
- package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
- package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
- package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
- package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +132 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +150 -0
- package/skills/app-shell-patterns/references/api/use-app-shell.md +150 -0
- package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
- package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
- package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
- package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
- package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
- package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
- package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
- package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
- package/skills/app-shell-patterns/references/api/use-toast.md +104 -0
- package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
- package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
- package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
- package/skills/app-shell-patterns/references/components/alert.md +202 -0
- package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
- package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
- package/skills/app-shell-patterns/references/components/attachment.md +138 -0
- package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
- package/skills/app-shell-patterns/references/components/avatar.md +80 -0
- package/skills/app-shell-patterns/references/components/badge.md +309 -0
- package/skills/app-shell-patterns/references/components/button.md +164 -0
- package/skills/app-shell-patterns/references/components/card.md +181 -0
- package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
- package/skills/app-shell-patterns/references/components/combobox.md +318 -0
- package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
- package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
- package/skills/app-shell-patterns/references/components/data-table.md +1168 -0
- package/skills/app-shell-patterns/references/components/date-picker.md +259 -0
- package/skills/app-shell-patterns/references/components/default-header.md +75 -0
- package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
- package/skills/app-shell-patterns/references/components/description-card.md +569 -0
- package/skills/app-shell-patterns/references/components/dialog.md +164 -0
- package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
- package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
- package/skills/app-shell-patterns/references/components/form.md +378 -0
- package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
- package/skills/app-shell-patterns/references/components/grid.md +184 -0
- package/skills/app-shell-patterns/references/components/input.md +83 -0
- package/skills/app-shell-patterns/references/components/layout.md +539 -0
- package/skills/app-shell-patterns/references/components/menu.md +218 -0
- package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
- package/skills/app-shell-patterns/references/components/select.md +283 -0
- package/skills/app-shell-patterns/references/components/sheet.md +237 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +472 -0
- package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
- package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
- package/skills/app-shell-patterns/references/components/spinner.md +81 -0
- package/skills/app-shell-patterns/references/components/table.md +188 -0
- package/skills/app-shell-patterns/references/components/tabs.md +158 -0
- package/skills/app-shell-patterns/references/components/textarea.md +122 -0
- package/skills/app-shell-patterns/references/components/timeline.md +317 -0
- package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
- package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
- package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +356 -0
- package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +31 -18
- package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +425 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +420 -0
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +785 -0
- package/skills/app-shell-patterns/references/migrations.md +55 -0
- package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +10 -33
- package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
- package/skills/app-shell-patterns/references/patterns/form-modal.md +37 -92
- package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
- package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
- package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
- package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +136 -139
- package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +13 -88
- package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
- package/skills/app-shell-patterns/references/fundamental/design-system.md +0 -440
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WCAG 1.4.3 contrast checks for the semantic colour roles, computed from
|
|
8
|
+
* `themes/default.css` itself. Nothing is copied into the test, so a change to
|
|
9
|
+
* a hue step or an alias that breaks a pair fails here.
|
|
10
|
+
*
|
|
11
|
+
* Scope of the guarantee: the default state of each pair, on the default
|
|
12
|
+
* palette's `--card` and `--background`. Not covered on purpose:
|
|
13
|
+
* - hover states (`surface-hover`) against 4.5:1: subtle-badge hover text is
|
|
14
|
+
* below 4.5:1 for some roles (danger and success in light mode). They are held
|
|
15
|
+
* at a documented floor (MIN_HOVER_TEXT) so they cannot get worse unnoticed,
|
|
16
|
+
* see decisions/semantic-color-roles.md;
|
|
17
|
+
* - branded palettes (cream, bloom) and their tinted shell backgrounds;
|
|
18
|
+
* - the neutral intent, whose values are the pre-existing system tokens;
|
|
19
|
+
* - the warning `solid` fill against the card (a fill, not a text pair).
|
|
20
|
+
*/
|
|
21
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const css = readFileSync(join(here, "themes/default.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
23
|
+
|
|
24
|
+
type Rgba = { r: number; g: number; b: number; a: number };
|
|
25
|
+
type Mode = "light" | "dark";
|
|
26
|
+
|
|
27
|
+
/** Custom properties in effect on <html> for a mode, applying rules in source order. */
|
|
28
|
+
function variablesFor(mode: Mode): Map<string, string> {
|
|
29
|
+
const vars = new Map<string, string>();
|
|
30
|
+
for (const rule of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
|
|
31
|
+
const selectors = rule[1].split(",").map((s) => s.trim());
|
|
32
|
+
// <html class="dark"> matches both `:root` and `.dark`; light matches `:root` only.
|
|
33
|
+
const applies = selectors.some((s) => s === ":root" || (mode === "dark" && s === ".dark"));
|
|
34
|
+
if (!applies) continue;
|
|
35
|
+
for (const decl of rule[2].matchAll(/(--[a-z0-9-]+)\s*:\s*([^;]+);/g)) {
|
|
36
|
+
vars.set(decl[1], decl[2].replace(/\s+/g, " ").trim());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return vars;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseColor(value: string, vars: Map<string, string>): Rgba {
|
|
43
|
+
const v = value.trim();
|
|
44
|
+
const ref = /^var\((--[a-z0-9-]+)\)$/.exec(v);
|
|
45
|
+
if (ref) {
|
|
46
|
+
const target = vars.get(ref[1]);
|
|
47
|
+
if (target === undefined) throw new Error(`Undefined variable ${ref[1]}`);
|
|
48
|
+
return parseColor(target, vars);
|
|
49
|
+
}
|
|
50
|
+
const hex = /^#([0-9a-f]{3,8})$/i.exec(v);
|
|
51
|
+
if (hex) {
|
|
52
|
+
let h = hex[1];
|
|
53
|
+
if (h.length === 3 || h.length === 4) h = [...h].map((c) => c + c).join("");
|
|
54
|
+
if (h.length !== 6 && h.length !== 8) throw new Error(`Bad hex colour ${v}`);
|
|
55
|
+
return {
|
|
56
|
+
r: parseInt(h.slice(0, 2), 16),
|
|
57
|
+
g: parseInt(h.slice(2, 4), 16),
|
|
58
|
+
b: parseInt(h.slice(4, 6), 16),
|
|
59
|
+
a: h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const fn = /^rgba?\(([^)]+)\)$/.exec(v);
|
|
63
|
+
if (fn) {
|
|
64
|
+
const [r, g, b, a = "1"] = fn[1].split(",").map((p) => p.trim());
|
|
65
|
+
return { r: Number(r), g: Number(g), b: Number(b), a: Number(a) };
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Unsupported colour syntax: ${v}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function over(top: Rgba, base: Rgba): Rgba {
|
|
71
|
+
const a = top.a + base.a * (1 - top.a);
|
|
72
|
+
const mix = (t: number, b: number) => (t * top.a + b * base.a * (1 - top.a)) / a;
|
|
73
|
+
return { r: mix(top.r, base.r), g: mix(top.g, base.g), b: mix(top.b, base.b), a };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function linearize(channel: number): number {
|
|
77
|
+
const s = channel / 255;
|
|
78
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function luminance({ r, g, b }: Rgba): number {
|
|
82
|
+
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function contrast(a: Rgba, b: Rgba): number {
|
|
86
|
+
const [hi, lo] = [luminance(a), luminance(b)].toSorted((x, y) => y - x);
|
|
87
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const MIN_TEXT = 4.5;
|
|
91
|
+
/** Known and accepted: subtle hover text is below AA for some roles. A floor, not a pass. */
|
|
92
|
+
const MIN_HOVER_TEXT = 4.2;
|
|
93
|
+
const modes: Mode[] = ["light", "dark"];
|
|
94
|
+
const intents = ["info", "success", "warning", "danger"] as const;
|
|
95
|
+
const alertVariantFor = { info: "info", success: "success", warning: "warning", danger: "error" };
|
|
96
|
+
const pages = ["--card", "--background"] as const;
|
|
97
|
+
|
|
98
|
+
describe.each(modes)("semantic colour contrast (%s)", (mode) => {
|
|
99
|
+
const vars = variablesFor(mode);
|
|
100
|
+
const color = (name: string) => parseColor(`var(${name})`, vars);
|
|
101
|
+
/** Foreground drawn on a translucent surface that sits on a page colour. */
|
|
102
|
+
const pair = (fg: string, surface: string, page: string) => {
|
|
103
|
+
const base = color(page);
|
|
104
|
+
const bg = over(color(surface), base);
|
|
105
|
+
return contrast(over(color(fg), bg), bg);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
it("resolves the page colours to opaque values", () => {
|
|
109
|
+
for (const page of pages) expect(color(page).a).toBe(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe.each(intents)("%s", (intent) => {
|
|
113
|
+
const role = (name: string) => `--${intent}-${name}`;
|
|
114
|
+
|
|
115
|
+
it("the contrast role meets 4.5:1 on solid", () => {
|
|
116
|
+
expect(contrast(color(role("contrast")), color(role("solid")))).toBeGreaterThanOrEqual(
|
|
117
|
+
MIN_TEXT,
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("the contrast role meets 4.5:1 on solid-hover", () => {
|
|
122
|
+
expect(contrast(color(role("contrast")), color(role("solid-hover")))).toBeGreaterThanOrEqual(
|
|
123
|
+
MIN_TEXT,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it.each(pages)("text meets 4.5:1 on surface over %s", (page) => {
|
|
128
|
+
expect(pair(role("text"), role("surface"), page)).toBeGreaterThanOrEqual(MIN_TEXT);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it.each(pages)(
|
|
132
|
+
"text stays above the documented hover floor on surface-hover over %s",
|
|
133
|
+
(page) => {
|
|
134
|
+
expect(pair(role("text"), role("surface-hover"), page)).toBeGreaterThanOrEqual(
|
|
135
|
+
MIN_HOVER_TEXT,
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const variant = alertVariantFor[intent];
|
|
141
|
+
|
|
142
|
+
it.each(pages)("alert foreground meets 4.5:1 on alert background over %s", (page) => {
|
|
143
|
+
expect(
|
|
144
|
+
pair(`--alert-${variant}-foreground`, `--alert-${variant}-background`, page),
|
|
145
|
+
).toBeGreaterThanOrEqual(MIN_TEXT);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it.each(pages)("alert description meets 4.5:1 on alert background over %s", (page) => {
|
|
149
|
+
expect(
|
|
150
|
+
pair(`--alert-${variant}-foreground-muted`, `--alert-${variant}-background`, page),
|
|
151
|
+
).toBeGreaterThanOrEqual(MIN_TEXT);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|