@ponchia/ui 0.6.11 → 0.7.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 +113 -0
- package/MIGRATIONS.json +14 -0
- package/README.md +14 -6
- package/behaviors/dialog.d.ts.map +1 -1
- package/behaviors/dialog.js +14 -0
- package/behaviors/modal.d.ts +17 -13
- package/behaviors/modal.d.ts.map +1 -1
- package/behaviors/modal.js +281 -106
- package/behaviors/popover.d.ts.map +1 -1
- package/behaviors/popover.js +50 -2
- package/behaviors/splitter.d.ts +2 -0
- package/behaviors/splitter.d.ts.map +1 -1
- package/behaviors/splitter.js +15 -1
- package/behaviors/theme.d.ts +3 -2
- package/behaviors/theme.d.ts.map +1 -1
- package/behaviors/theme.js +10 -6
- package/bin/bronto-ui-check.mjs +286 -0
- package/classes/classes.json +7 -0
- package/classes/vscode.css-custom-data.json +1 -1
- package/css/disclosure.css +10 -0
- package/css/dots.css +43 -18
- package/css/feedback.css +35 -0
- package/css/report.css +0 -40
- package/css/site.css +10 -0
- package/css/tokens.css +1 -1
- package/dist/bronto.css +1 -1
- package/dist/css/disclosure.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/report-kit.css +1 -1
- package/dist/css/report.css +1 -1
- package/dist/css/site.css +1 -1
- package/dist/css/tokens.css +1 -1
- package/docs/adr/0004-prune-unused-adapters.md +34 -0
- package/docs/architecture.md +15 -11
- package/docs/command.md +18 -4
- package/docs/migrations/0.6-to-0.7.md +85 -0
- package/docs/package-contract.md +10 -5
- package/docs/reference.md +1 -1
- package/docs/reporting.md +8 -8
- package/docs/stability.md +36 -10
- package/docs/theming.md +49 -5
- package/docs/usage.md +68 -18
- package/docs/workbench.md +16 -2
- package/llms.txt +7 -3
- package/package.json +13 -3
- package/qwik/index.d.ts.map +1 -1
- package/qwik/index.js +4 -0
- package/react/index.d.ts.map +1 -1
- package/react/index.js +4 -0
- package/solid/index.d.ts.map +1 -1
- package/solid/index.js +4 -0
- package/svelte/index.d.ts.map +1 -1
- package/svelte/index.js +4 -0
- package/tokens/figma.variables.json +2 -2
- package/tokens/index.js +1 -1
- package/tokens/index.json +2 -2
- package/tokens/resolved.json +1 -1
- package/tokens/tokens.dtcg.json +2508 -399
- package/vue/index.d.ts.map +1 -1
- package/vue/index.js +4 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Validate literal Bronto classes and CSS custom-property references in a consumer. */
|
|
3
|
+
import { readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
4
|
+
import { extname, resolve, relative, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const classesManifest = JSON.parse(
|
|
9
|
+
readFileSync(resolve(packageRoot, 'classes/classes.json'), 'utf8'),
|
|
10
|
+
);
|
|
11
|
+
const tokensManifest = JSON.parse(readFileSync(resolve(packageRoot, 'tokens/index.json'), 'utf8'));
|
|
12
|
+
|
|
13
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
14
|
+
'.astro',
|
|
15
|
+
'.css',
|
|
16
|
+
'.html',
|
|
17
|
+
'.js',
|
|
18
|
+
'.jsx',
|
|
19
|
+
'.mjs',
|
|
20
|
+
'.svelte',
|
|
21
|
+
'.ts',
|
|
22
|
+
'.tsx',
|
|
23
|
+
'.vue',
|
|
24
|
+
]);
|
|
25
|
+
const SKIP_DIRS = new Set([
|
|
26
|
+
'.git',
|
|
27
|
+
'.astro',
|
|
28
|
+
'.next',
|
|
29
|
+
'.nuxt',
|
|
30
|
+
'.output',
|
|
31
|
+
'.pytest_cache',
|
|
32
|
+
'.svelte-kit',
|
|
33
|
+
'.tox',
|
|
34
|
+
'.vercel',
|
|
35
|
+
'.venv',
|
|
36
|
+
'.vite',
|
|
37
|
+
'__pycache__',
|
|
38
|
+
'build',
|
|
39
|
+
'coverage',
|
|
40
|
+
'dist',
|
|
41
|
+
'node_modules',
|
|
42
|
+
'out',
|
|
43
|
+
'playwright-report',
|
|
44
|
+
'public',
|
|
45
|
+
'storybook-static',
|
|
46
|
+
'test-results',
|
|
47
|
+
'vendor',
|
|
48
|
+
'venv',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
const knownClasses = new Set(classesManifest.classes);
|
|
52
|
+
// CSS generic font-family keywords share the ui-* prefix but are never class
|
|
53
|
+
// literals. Keep the scanner broad enough to catch JS-built class strings
|
|
54
|
+
// while excluding these standardized non-class identifiers.
|
|
55
|
+
const nonClassIdentifiers = new Set(['ui-monospace', 'ui-rounded', 'ui-sans-serif', 'ui-serif']);
|
|
56
|
+
const knownTokens = new Set(
|
|
57
|
+
Object.values(tokensManifest.cssVars).flatMap((group) => Object.keys(group)),
|
|
58
|
+
);
|
|
59
|
+
for (const property of classesManifest.customProperties) knownTokens.add(property.name);
|
|
60
|
+
for (const entry of readdirSync(resolve(packageRoot, 'dist/css'), { withFileTypes: true })) {
|
|
61
|
+
if (!entry.isFile() || extname(entry.name) !== '.css') continue;
|
|
62
|
+
const css = readFileSync(resolve(packageRoot, 'dist/css', entry.name), 'utf8');
|
|
63
|
+
for (const match of css.matchAll(/(--[a-z][\w-]*)\s*:/gi)) knownTokens.add(match[1]);
|
|
64
|
+
}
|
|
65
|
+
const reservedTokenPrefixes = new Set(
|
|
66
|
+
[...knownTokens].map((name) => `--${name.slice(2).split('-')[0]}`),
|
|
67
|
+
);
|
|
68
|
+
const LINE_COMMENT_EXTENSIONS = new Set([
|
|
69
|
+
'.astro',
|
|
70
|
+
'.html',
|
|
71
|
+
'.js',
|
|
72
|
+
'.jsx',
|
|
73
|
+
'.mjs',
|
|
74
|
+
'.svelte',
|
|
75
|
+
'.ts',
|
|
76
|
+
'.tsx',
|
|
77
|
+
'.vue',
|
|
78
|
+
]);
|
|
79
|
+
const HTML_COMMENT_EXTENSIONS = new Set(['.astro', '.html', '.svelte', '.vue']);
|
|
80
|
+
|
|
81
|
+
const blankComment = (value) => value.replace(/[^\r\n]/g, ' ');
|
|
82
|
+
|
|
83
|
+
function quoteEnd(text, start, quote) {
|
|
84
|
+
for (let index = start + 1; index < text.length; index += 1) {
|
|
85
|
+
if (text[index] === '\\') index += 1;
|
|
86
|
+
else if (text[index] === quote) return index + 1;
|
|
87
|
+
}
|
|
88
|
+
return text.length;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function stripSlashComments(text, lineComments) {
|
|
92
|
+
let output = '';
|
|
93
|
+
let index = 0;
|
|
94
|
+
while (index < text.length) {
|
|
95
|
+
const char = text[index];
|
|
96
|
+
const next = text[index + 1];
|
|
97
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
98
|
+
const end = quoteEnd(text, index, char);
|
|
99
|
+
output += text.slice(index, end);
|
|
100
|
+
index = end;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (char === '/' && next === '*') {
|
|
104
|
+
const close = text.indexOf('*/', index + 2);
|
|
105
|
+
const end = close === -1 ? text.length : close + 2;
|
|
106
|
+
output += blankComment(text.slice(index, end));
|
|
107
|
+
index = end;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (lineComments && char === '/' && next === '/') {
|
|
111
|
+
const newline = text.indexOf('\n', index + 2);
|
|
112
|
+
const end = newline === -1 ? text.length : newline;
|
|
113
|
+
output += blankComment(text.slice(index, end));
|
|
114
|
+
index = end;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
output += char;
|
|
118
|
+
index += 1;
|
|
119
|
+
}
|
|
120
|
+
return output;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sourceText(text, extension) {
|
|
124
|
+
const withoutHtml = HTML_COMMENT_EXTENSIONS.has(extension)
|
|
125
|
+
? text.replace(/<!--[\s\S]*?-->/g, blankComment)
|
|
126
|
+
: text;
|
|
127
|
+
return stripSlashComments(withoutHtml, LINE_COMMENT_EXTENSIONS.has(extension));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function lineAt(text, index) {
|
|
131
|
+
return text.slice(0, index).split('\n').length;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function filesUnder(input) {
|
|
135
|
+
const absolute = resolve(input);
|
|
136
|
+
const stat = statSync(absolute);
|
|
137
|
+
if (stat.isFile()) return SOURCE_EXTENSIONS.has(extname(absolute)) ? [absolute] : [];
|
|
138
|
+
if (!stat.isDirectory()) return [];
|
|
139
|
+
const files = [];
|
|
140
|
+
for (const entry of readdirSync(absolute, { withFileTypes: true })) {
|
|
141
|
+
if (entry.isSymbolicLink()) continue;
|
|
142
|
+
const child = resolve(absolute, entry.name);
|
|
143
|
+
if (entry.isDirectory()) {
|
|
144
|
+
if (!SKIP_DIRS.has(entry.name)) files.push(...filesUnder(child));
|
|
145
|
+
} else if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
|
|
146
|
+
files.push(child);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return files;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function tokenPrefix(name) {
|
|
153
|
+
return `--${name.slice(2).split('-')[0]}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function checkPaths(inputs, { allowClasses = [], allowTokens = [] } = {}) {
|
|
157
|
+
const allowedClasses = new Set([...knownClasses, ...allowClasses]);
|
|
158
|
+
const allowedTokens = new Set([...knownTokens, ...allowTokens]);
|
|
159
|
+
const files = [...new Set(inputs.flatMap(filesUnder))].sort();
|
|
160
|
+
const documents = files.map((file) => {
|
|
161
|
+
const text = readFileSync(file, 'utf8');
|
|
162
|
+
return { file, text, scanned: sourceText(text, extname(file)) };
|
|
163
|
+
});
|
|
164
|
+
const locallyDefinedTokens = new Set();
|
|
165
|
+
for (const { scanned } of documents) {
|
|
166
|
+
for (const match of scanned.matchAll(/(--[a-z][\w-]*)\s*:/gi)) {
|
|
167
|
+
locallyDefinedTokens.add(match[1]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const findings = [];
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const add = (finding) => {
|
|
174
|
+
const key = `${finding.kind}:${finding.file}:${finding.line}:${finding.name}`;
|
|
175
|
+
if (seen.has(key)) return;
|
|
176
|
+
seen.add(key);
|
|
177
|
+
findings.push(finding);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
for (const { file, text, scanned } of documents) {
|
|
181
|
+
for (const match of scanned.matchAll(/(?<![/\w-])ui-[a-z0-9](?:[\w-]*[a-z0-9])?(?![\w/-])/gi)) {
|
|
182
|
+
if (!allowedClasses.has(match[0]) && !nonClassIdentifiers.has(match[0])) {
|
|
183
|
+
add({ kind: 'class', file, line: lineAt(text, match.index), name: match[0] });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const match of scanned.matchAll(/var\(\s*(--[a-z][\w-]*)/gi)) {
|
|
187
|
+
const name = match[1];
|
|
188
|
+
if (
|
|
189
|
+
reservedTokenPrefixes.has(tokenPrefix(name)) &&
|
|
190
|
+
!allowedTokens.has(name) &&
|
|
191
|
+
!locallyDefinedTokens.has(name)
|
|
192
|
+
) {
|
|
193
|
+
add({ kind: 'token', file, line: lineAt(text, match.index), name });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { files: files.length, findings };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function usage() {
|
|
202
|
+
return (
|
|
203
|
+
`Usage: bronto-ui-check [options] [path ...]\n\n` +
|
|
204
|
+
`Validate literal ui-* classes and unresolved Bronto-like var(--*) references.\n\n` +
|
|
205
|
+
`Options:\n` +
|
|
206
|
+
` --allow-class NAME Allow one consumer-owned ui-* class (repeatable)\n` +
|
|
207
|
+
` --allow-token NAME Allow one consumer-owned --* token (repeatable)\n` +
|
|
208
|
+
` --json Print machine-readable JSON\n` +
|
|
209
|
+
` --help Show this help\n`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseArgs(args) {
|
|
214
|
+
const options = { allowClasses: [], allowTokens: [], json: false, paths: [] };
|
|
215
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
216
|
+
const arg = args[index];
|
|
217
|
+
if (arg === '--help') return { ...options, help: true };
|
|
218
|
+
if (arg === '--json') options.json = true;
|
|
219
|
+
else if (arg === '--allow-class') {
|
|
220
|
+
const value = args[++index];
|
|
221
|
+
if (!/^ui-[a-z0-9][\w-]*$/i.test(value || '')) {
|
|
222
|
+
throw new Error('--allow-class requires one ui-* class name');
|
|
223
|
+
}
|
|
224
|
+
options.allowClasses.push(value);
|
|
225
|
+
} else if (arg === '--allow-token') {
|
|
226
|
+
const value = args[++index];
|
|
227
|
+
if (!/^--[a-z][\w-]*$/i.test(value || '')) {
|
|
228
|
+
throw new Error('--allow-token requires one --* custom-property name');
|
|
229
|
+
}
|
|
230
|
+
options.allowTokens.push(value);
|
|
231
|
+
} else if (arg.startsWith('--')) throw new Error(`Unknown option: ${arg}`);
|
|
232
|
+
else options.paths.push(arg);
|
|
233
|
+
}
|
|
234
|
+
return options;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function main() {
|
|
238
|
+
let options;
|
|
239
|
+
try {
|
|
240
|
+
options = parseArgs(process.argv.slice(2));
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error(error.message);
|
|
243
|
+
console.error(usage());
|
|
244
|
+
process.exitCode = 2;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (options.help) {
|
|
248
|
+
console.log(usage());
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const paths = options.paths.length ? options.paths : ['.'];
|
|
252
|
+
let result;
|
|
253
|
+
try {
|
|
254
|
+
result = checkPaths(paths, options);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error(`[bronto-ui-check] ${error.message}`);
|
|
257
|
+
process.exitCode = 2;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const cwd = process.cwd();
|
|
261
|
+
const output = {
|
|
262
|
+
files: result.files,
|
|
263
|
+
findings: result.findings.map((finding) => ({
|
|
264
|
+
...finding,
|
|
265
|
+
file: relative(cwd, finding.file) || '.',
|
|
266
|
+
})),
|
|
267
|
+
};
|
|
268
|
+
if (options.json) console.log(JSON.stringify(output, null, 2));
|
|
269
|
+
else if (output.findings.length) {
|
|
270
|
+
for (const finding of output.findings) {
|
|
271
|
+
console.error(
|
|
272
|
+
`${finding.file}:${finding.line} unknown Bronto ${finding.kind} ${finding.name}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
console.error(
|
|
276
|
+
`[bronto-ui-check] ${output.findings.length} finding(s) in ${output.files} source file(s)`,
|
|
277
|
+
);
|
|
278
|
+
} else {
|
|
279
|
+
console.log(`[bronto-ui-check] ${output.files} source file(s) match the shipped contract`);
|
|
280
|
+
}
|
|
281
|
+
if (output.findings.length) process.exitCode = 1;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// npm installs bins as symlinks in node_modules/.bin. Compare their real path
|
|
285
|
+
// so the CLI runs both through that public entrypoint and by its package path.
|
|
286
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) main();
|
package/classes/classes.json
CHANGED
|
@@ -2791,6 +2791,13 @@
|
|
|
2791
2791
|
"behavior": "initSplitter",
|
|
2792
2792
|
"note": "keyboard + pointer ARIA window-splitter behavior; updates --splitter-pos and aria-valuenow, then emits bronto:splitter:resize. The host owns persistence and pane state."
|
|
2793
2793
|
},
|
|
2794
|
+
{
|
|
2795
|
+
"name": "data-bronto-splitter-adjust",
|
|
2796
|
+
"on": "a button inside a .ui-splitter host",
|
|
2797
|
+
"value": "signed percentage-point delta, for example -10 or 10",
|
|
2798
|
+
"behavior": "initSplitter",
|
|
2799
|
+
"note": "single-pointer non-drag alternative to moving the separator; activation clamps to aria-valuemin/aria-valuemax and emits bronto:splitter:resize."
|
|
2800
|
+
},
|
|
2794
2801
|
{
|
|
2795
2802
|
"name": "data-bronto-source-ref",
|
|
2796
2803
|
"on": "a button/control that references a source card",
|
package/css/disclosure.css
CHANGED
|
@@ -272,6 +272,16 @@
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
@media (pointer: coarse) {
|
|
276
|
+
.ui-breadcrumb__item a {
|
|
277
|
+
align-items: center;
|
|
278
|
+
display: inline-flex;
|
|
279
|
+
justify-content: center;
|
|
280
|
+
min-block-size: max(24px, 1.6rem);
|
|
281
|
+
min-inline-size: max(24px, 1.6rem);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
275
285
|
/* --- Pagination --- */
|
|
276
286
|
|
|
277
287
|
.ui-pagination {
|
package/css/dots.css
CHANGED
|
@@ -136,7 +136,9 @@
|
|
|
136
136
|
|
|
137
137
|
/* Status dot — the glyph-style state indicator. */
|
|
138
138
|
.ui-dot {
|
|
139
|
-
|
|
139
|
+
--ui-dot-color: var(--text-dim);
|
|
140
|
+
|
|
141
|
+
background: var(--ui-dot-color);
|
|
140
142
|
border-radius: 50%;
|
|
141
143
|
display: inline-block;
|
|
142
144
|
flex: 0 0 auto;
|
|
@@ -145,34 +147,45 @@
|
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
.ui-dot--accent {
|
|
148
|
-
|
|
150
|
+
--ui-dot-color: var(--accent);
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
.ui-dot--success {
|
|
152
|
-
|
|
154
|
+
--ui-dot-color: var(--success);
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
.ui-dot--warning {
|
|
156
|
-
|
|
158
|
+
--ui-dot-color: var(--warning);
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
.ui-dot--danger {
|
|
160
|
-
|
|
162
|
+
--ui-dot-color: var(--danger);
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
.ui-dot--info {
|
|
164
|
-
|
|
166
|
+
--ui-dot-color: var(--info);
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
.ui-dot--live {
|
|
168
|
-
|
|
169
|
-
box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 70%, transparent);
|
|
170
|
+
box-shadow: 0 0 0 0 color-mix(in srgb, var(--ui-dot-color) 70%, transparent);
|
|
170
171
|
position: relative;
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
/* Live describes motion, not status. A standalone live dot defaults to
|
|
175
|
+
success; an explicit tone remains the colour of both the dot and ring. */
|
|
176
|
+
.ui-dot--live:not(
|
|
177
|
+
.ui-dot--accent,
|
|
178
|
+
.ui-dot--success,
|
|
179
|
+
.ui-dot--warning,
|
|
180
|
+
.ui-dot--danger,
|
|
181
|
+
.ui-dot--info
|
|
182
|
+
) {
|
|
183
|
+
--ui-dot-color: var(--success);
|
|
184
|
+
}
|
|
185
|
+
|
|
173
186
|
.ui-dot--live::after {
|
|
174
187
|
animation: pulseRing 1.8s var(--ease-out) infinite;
|
|
175
|
-
border: 1px solid var(--
|
|
188
|
+
border: 1px solid var(--ui-dot-color);
|
|
176
189
|
border-radius: 50%;
|
|
177
190
|
content: '';
|
|
178
191
|
inset: -3px;
|
|
@@ -562,36 +575,48 @@
|
|
|
562
575
|
differentiable. */
|
|
563
576
|
@media (forced-colors: active) {
|
|
564
577
|
.ui-dot--success {
|
|
578
|
+
--ui-dot-color: LinkText;
|
|
579
|
+
|
|
565
580
|
forced-color-adjust: none;
|
|
566
|
-
background: LinkText;
|
|
567
581
|
}
|
|
568
582
|
|
|
569
583
|
.ui-dot--warning {
|
|
584
|
+
--ui-dot-color: Mark;
|
|
585
|
+
|
|
570
586
|
forced-color-adjust: none;
|
|
571
|
-
background: Mark;
|
|
572
587
|
}
|
|
573
588
|
|
|
574
589
|
.ui-dot--danger {
|
|
590
|
+
--ui-dot-color: Highlight;
|
|
591
|
+
|
|
575
592
|
forced-color-adjust: none;
|
|
576
|
-
background: Highlight;
|
|
577
593
|
}
|
|
578
594
|
|
|
579
595
|
.ui-dot--info {
|
|
596
|
+
--ui-dot-color: ButtonText;
|
|
597
|
+
|
|
580
598
|
forced-color-adjust: none;
|
|
581
|
-
background: ButtonText;
|
|
582
599
|
}
|
|
583
600
|
|
|
584
601
|
/* Brand/live dots aren't status tones, but they still encode meaning via
|
|
585
602
|
background-color alone, which HCM flattens. Keep them on a distinct,
|
|
586
603
|
opted-out system colour for completeness. */
|
|
587
|
-
.ui-dot--accent
|
|
588
|
-
|
|
604
|
+
.ui-dot--accent {
|
|
605
|
+
--ui-dot-color: LinkText;
|
|
606
|
+
|
|
589
607
|
forced-color-adjust: none;
|
|
590
|
-
background: LinkText;
|
|
591
608
|
}
|
|
592
609
|
|
|
593
|
-
.ui-dot--live
|
|
594
|
-
|
|
610
|
+
.ui-dot--live:not(
|
|
611
|
+
.ui-dot--accent,
|
|
612
|
+
.ui-dot--success,
|
|
613
|
+
.ui-dot--warning,
|
|
614
|
+
.ui-dot--danger,
|
|
615
|
+
.ui-dot--info
|
|
616
|
+
) {
|
|
617
|
+
--ui-dot-color: LinkText;
|
|
618
|
+
|
|
619
|
+
forced-color-adjust: none;
|
|
595
620
|
}
|
|
596
621
|
|
|
597
622
|
/* The masked one-node icon paints `background: currentcolor` through an SVG
|
package/css/feedback.css
CHANGED
|
@@ -517,6 +517,41 @@
|
|
|
517
517
|
background: var(--info);
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
+
/* A labelled meter is core feedback grammar, not report-only layout. The
|
|
521
|
+
visible value remains the data of record because role=meter support varies. */
|
|
522
|
+
.ui-meter__row {
|
|
523
|
+
align-items: center;
|
|
524
|
+
display: grid;
|
|
525
|
+
gap: var(--space-2xs) var(--space-md);
|
|
526
|
+
grid-template-columns: minmax(9rem, 14rem) 1fr auto;
|
|
527
|
+
margin-block: var(--space-2xs);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.ui-meter__row .ui-meter {
|
|
531
|
+
min-inline-size: 8rem;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.ui-meter__label {
|
|
535
|
+
color: var(--text-soft);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.ui-meter__value {
|
|
539
|
+
color: var(--text);
|
|
540
|
+
font-family: var(--mono);
|
|
541
|
+
font-variant-numeric: tabular-nums;
|
|
542
|
+
text-align: end;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
@media (max-width: 32rem) {
|
|
546
|
+
.ui-meter__row {
|
|
547
|
+
grid-template-columns: 1fr;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.ui-meter__value {
|
|
551
|
+
text-align: start;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
520
555
|
/* --- Steps — progress through a multi-step flow. Use an <ol>; the
|
|
521
556
|
current step is aria-current="step" (no class), completed steps take
|
|
522
557
|
--done. Counter-numbered markers, hairline connectors. --- */
|
package/css/report.css
CHANGED
|
@@ -619,46 +619,6 @@
|
|
|
619
619
|
text-transform: uppercase;
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
/* --- Labelled meter row ---
|
|
623
|
-
A multi-meter block (SLO burn, error budgets, capacity) lays out as
|
|
624
|
-
label | bar | value. The bare `ui-meter` base lives in feedback.css; this is
|
|
625
|
-
the report-document grammar around it so authors stop hand-rolling the grid.
|
|
626
|
-
The bar NEVER carries the reading alone (WCAG 1.4.1) — `ui-meter__value` is
|
|
627
|
-
the data of record, and the bar clamps at 100 so an over-target figure still
|
|
628
|
-
reads correctly in the value text. Collapses to a stack on a narrow screen. */
|
|
629
|
-
.ui-meter__row {
|
|
630
|
-
align-items: center;
|
|
631
|
-
display: grid;
|
|
632
|
-
gap: var(--space-2xs) var(--space-md);
|
|
633
|
-
grid-template-columns: minmax(9rem, 14rem) 1fr auto;
|
|
634
|
-
margin-block: var(--space-2xs);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
.ui-meter__row .ui-meter {
|
|
638
|
-
min-inline-size: 8rem;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
.ui-meter__label {
|
|
642
|
-
color: var(--text-soft);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
.ui-meter__value {
|
|
646
|
-
color: var(--text);
|
|
647
|
-
font-family: var(--mono);
|
|
648
|
-
font-variant-numeric: tabular-nums;
|
|
649
|
-
text-align: end;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
@media (max-width: 32rem) {
|
|
653
|
-
.ui-meter__row {
|
|
654
|
-
grid-template-columns: 1fr;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
.ui-meter__value {
|
|
658
|
-
text-align: start;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
622
|
/* A chart is NOT a bronto component — it needs scales + data binding, which the
|
|
663
623
|
analytical layer refuses to own. Theme Vega-Lite with `@ponchia/ui/vega`
|
|
664
624
|
(docs/vega.md), or hand-author a token-themed inline `<svg>`, and drop it in a
|
package/css/site.css
CHANGED
|
@@ -296,6 +296,16 @@
|
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
@media (pointer: coarse) {
|
|
300
|
+
.ui-sitefooter__links a {
|
|
301
|
+
align-items: center;
|
|
302
|
+
display: inline-flex;
|
|
303
|
+
justify-content: center;
|
|
304
|
+
min-block-size: max(24px, 1.6rem);
|
|
305
|
+
min-inline-size: max(24px, 1.6rem);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
299
309
|
/* --- Tags — neutral, wrapping content labels (NOT ui-chip, which is a
|
|
300
310
|
single interactive token). Use a <ul>. --- */
|
|
301
311
|
|