@ponchia/ui 0.7.0 → 0.8.1

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +3 -3
  3. package/classes/classes.json +79 -7
  4. package/classes/index.d.ts +53 -1
  5. package/classes/index.js +60 -0
  6. package/classes/vscode.css-custom-data.json +24 -0
  7. package/css/app.css +14 -3
  8. package/css/disclosure.css +7 -7
  9. package/css/feedback.css +11 -8
  10. package/css/forms.css +3 -3
  11. package/css/navigation.css +1 -1
  12. package/css/overlay.css +19 -5
  13. package/css/primitives.css +101 -6
  14. package/css/site.css +11 -5
  15. package/css/skins.css +97 -3
  16. package/css/state.css +161 -0
  17. package/css/table.css +1 -1
  18. package/css/tokens.css +6 -0
  19. package/css/workbench.css +151 -0
  20. package/dist/bronto.css +1 -1
  21. package/dist/css/app.css +1 -1
  22. package/dist/css/disclosure.css +1 -1
  23. package/dist/css/feedback.css +1 -1
  24. package/dist/css/forms.css +1 -1
  25. package/dist/css/navigation.css +1 -1
  26. package/dist/css/overlay.css +1 -1
  27. package/dist/css/primitives.css +1 -1
  28. package/dist/css/report-kit.css +1 -1
  29. package/dist/css/site.css +1 -1
  30. package/dist/css/skins.css +1 -1
  31. package/dist/css/state.css +1 -1
  32. package/dist/css/table.css +1 -1
  33. package/dist/css/tokens.css +1 -1
  34. package/dist/css/workbench.css +1 -1
  35. package/docs/adr/0001-color-system.md +32 -3
  36. package/docs/contrast.md +102 -18
  37. package/docs/migrations/0.7-to-0.8.md +216 -0
  38. package/docs/package-contract.md +1 -0
  39. package/docs/reference.md +52 -1
  40. package/docs/reporting.md +8 -8
  41. package/docs/stability.md +31 -2
  42. package/docs/state.md +51 -1
  43. package/docs/theming.md +18 -0
  44. package/docs/usage.md +62 -2
  45. package/docs/workbench.md +83 -4
  46. package/llms.txt +1 -1
  47. package/package.json +13 -12
  48. package/tokens/figma.variables.json +84 -0
  49. package/tokens/index.d.ts +2 -2
  50. package/tokens/index.js +23 -0
  51. package/tokens/index.json +12 -0
  52. package/tokens/resolved.json +6 -0
  53. package/tokens/skins.js +117 -7
  54. package/tokens/tokens.dtcg.json +7 -1
package/css/state.css CHANGED
@@ -223,6 +223,153 @@
223
223
  }
224
224
  }
225
225
 
226
+ /* ==========================================================================
227
+ Severity — the second axis of system state.
228
+
229
+ `.ui-state` answers "what is this thing doing". This answers "how bad is
230
+ it". Bronto already shipped the TONES (`--danger`, `--warning`, `--info`,
231
+ `--success`) as per-component modifiers, but never the SCALE: the tier names,
232
+ their order, and the attribute that carries them. So every consumer invents
233
+ the ladder, and inside a single app it drifts — one surface saying
234
+ `critical|error|warning|note`, the next `bad|warn`, a third
235
+ `critical|warning|info|ok`, under two different attribute names. Findings
236
+ then do not sort against alerts, and a filter written for one list silently
237
+ misses the other.
238
+
239
+ THE LADDER, worst to best:
240
+
241
+ critical broken now, and still losing something
242
+ error something failed; it is not currently getting worse
243
+ warning a threshold was crossed; nothing has failed yet
244
+ notice worth reading, no action implied
245
+ ok checked and healthy — an ASSERTION, not the absence of news
246
+ unknown not measured, stale, or the check itself failed
247
+
248
+ `unknown` sits OUTSIDE the ordering deliberately. It is not "slightly worse
249
+ than ok", it is "we do not know", and collapsing it into `ok` is how a dead
250
+ collector reads as a healthy system.
251
+
252
+ The level is carried by `data-level` — one attribute name, so the same
253
+ selector works on a chip, a row, a dot, or the host's own element:
254
+
255
+ <span class="ui-severity" data-level="critical">Critical</span>
256
+ <li class="ui-severity-row" data-level="warning">…</li>
257
+
258
+ BOUNDARY: the host owns thresholds, ranking, filtering, and the wording of
259
+ every label. Colour is never the only channel (WCAG 1.4.1) — `.ui-severity`
260
+ carries an author-written label, and `.ui-severity-dot` is only for rows that
261
+ ALSO name their level in text.
262
+ ========================================================================== */
263
+
264
+ /* Declared once so a host putting the level on its own element gets the same
265
+ mapping from `data-level` + `var(--severity-tone)`, without copying a table. */
266
+ .ui-severity,
267
+ .ui-severity-row,
268
+ .ui-severity-dot {
269
+ --severity-tone: var(--text-dim);
270
+ }
271
+
272
+ :is(.ui-severity, .ui-severity-row, .ui-severity-dot):is(
273
+ [data-level='critical'],
274
+ [data-level='error']
275
+ ) {
276
+ --severity-tone: var(--danger);
277
+ }
278
+
279
+ :is(.ui-severity, .ui-severity-row, .ui-severity-dot)[data-level='warning'] {
280
+ --severity-tone: var(--warning);
281
+ }
282
+
283
+ :is(.ui-severity, .ui-severity-row, .ui-severity-dot)[data-level='notice'] {
284
+ --severity-tone: var(--info);
285
+ }
286
+
287
+ :is(.ui-severity, .ui-severity-row, .ui-severity-dot)[data-level='ok'] {
288
+ --severity-tone: var(--success);
289
+ }
290
+
291
+ /* `unknown` keeps the neutral default tone — it must not read as a verdict. */
292
+
293
+ /* The chip — a short uppercase tier label, tinted rather than filled, because
294
+ a list of findings is mostly chips and filled ones turn it into stripes.
295
+ `critical` is the exception and fills: in a scrolling list the worst tier has
296
+ to survive peripheral vision. */
297
+ .ui-severity {
298
+ background: color-mix(in oklch, var(--severity-tone) 16%, transparent);
299
+ border-radius: var(--radius-pill);
300
+ color: var(--severity-tone);
301
+ display: inline-block;
302
+ flex: none;
303
+ font-family: var(--mono);
304
+ font-size: var(--text-2xs);
305
+ font-weight: 700;
306
+ letter-spacing: var(--tracking-wide);
307
+ line-height: 1.2;
308
+ padding: 0.1rem 0.4rem;
309
+ text-transform: uppercase;
310
+ }
311
+
312
+ .ui-severity[data-level='critical'] {
313
+ background: var(--danger);
314
+ color: var(--button-text);
315
+ }
316
+
317
+ /* A tint over an unknown host surface cannot be contrast-gated, so the neutral
318
+ tier keeps a border instead and stays legible anywhere. */
319
+ .ui-severity[data-level='unknown'] {
320
+ background: none;
321
+ border: 1px solid var(--line-strong);
322
+ padding-block: calc(0.1rem - 1px);
323
+ }
324
+
325
+ /* The dot — for dense rows where a chip costs more width than the level is
326
+ worth. An accelerator for scanning, not the label. */
327
+ .ui-severity-dot {
328
+ background: var(--severity-tone);
329
+ border-radius: 50%;
330
+ block-size: 0.5rem;
331
+ display: inline-block;
332
+ flex: none;
333
+ inline-size: 0.5rem;
334
+ print-color-adjust: exact;
335
+ }
336
+
337
+ .ui-severity-dot[data-level='unknown'] {
338
+ background: none;
339
+ box-shadow: inset 0 0 0 1px var(--line-strong);
340
+ }
341
+
342
+ /* The row — a list item that carries a level. The inline-start gutter is what
343
+ makes a long list readable without reading it. */
344
+ .ui-severity-row {
345
+ align-items: baseline;
346
+ border-block-end: 1px solid var(--line);
347
+ border-inline-start: 2px solid var(--severity-tone);
348
+ display: flex;
349
+ gap: var(--space-xs);
350
+ padding: var(--space-2xs) var(--space-xs);
351
+ }
352
+
353
+ .ui-severity-row:last-child {
354
+ border-block-end: 0;
355
+ }
356
+
357
+ /* Gives up space first, so the level and any trailing metadata stay put. */
358
+ .ui-severity-row__title {
359
+ flex: 1 1 auto;
360
+ min-inline-size: 0;
361
+ overflow: hidden;
362
+ text-overflow: ellipsis;
363
+ white-space: nowrap;
364
+ }
365
+
366
+ .ui-severity-row__meta {
367
+ color: var(--text-dim);
368
+ flex: none;
369
+ font-family: var(--mono);
370
+ font-size: var(--text-2xs);
371
+ }
372
+
226
373
  /* Forced colours: the tone dot collapses to a system colour, so the
227
374
  author-written label remains the state channel (it already must be). */
228
375
  @media (forced-colors: active) {
@@ -237,4 +384,18 @@
237
384
  .ui-job__bar {
238
385
  background: CanvasText;
239
386
  }
387
+
388
+ /* Same contract for severity: the tint and the gutter both vanish under a
389
+ replaced palette, so keep the structure and let the label carry meaning. */
390
+ .ui-severity {
391
+ border: 1px solid currentColor;
392
+ }
393
+
394
+ .ui-severity-row {
395
+ border-inline-start-color: CanvasText;
396
+ }
397
+
398
+ .ui-severity-dot {
399
+ background: CanvasText;
400
+ }
240
401
  }
package/css/table.css CHANGED
@@ -136,7 +136,7 @@
136
136
  .ui-table__sort {
137
137
  box-sizing: border-box;
138
138
  margin-block: -0.7rem;
139
- min-block-size: 24px;
139
+ min-block-size: var(--tap-target-min);
140
140
  padding-block: 0.7rem;
141
141
  }
142
142
 
package/css/tokens.css CHANGED
@@ -25,6 +25,12 @@
25
25
  --space-lg: 1.35rem;
26
26
  --space-xl: 1.75rem;
27
27
  --space-2xl: 2.5rem;
28
+ --tap-target: max(44px, 2.9rem);
29
+ --tap-target-min: max(24px, 1.6rem);
30
+ --safe-area-top: env(safe-area-inset-top, 0px);
31
+ --safe-area-right: env(safe-area-inset-right, 0px);
32
+ --safe-area-bottom: env(safe-area-inset-bottom, 0px);
33
+ --safe-area-left: env(safe-area-inset-left, 0px);
28
34
 
29
35
  /* Type — Doto is the dot-matrix display face; body stays mono-grotesque. */
30
36
  --mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', ui-monospace, monospace;
package/css/workbench.css CHANGED
@@ -56,6 +56,36 @@
56
56
  padding: 0.25rem;
57
57
  }
58
58
 
59
+ /* Pane-scale — a control bar belonging to ONE pane rather than to the app. The
60
+ difference from `--compact` is not density but framing: this bar is a row
61
+ inside a surface that already has a border, so it drops its own frame and
62
+ corners and rules off from the content below instead. It also refuses to
63
+ wrap: a pane bar sits directly above content that may be a live terminal or
64
+ an editor, and a second row would resize that content on every state change.
65
+ The row scrolls instead — see `__fill` for what gives up the space. */
66
+ .ui-toolstrip--pane {
67
+ background: none;
68
+ border: 0;
69
+ border-block-end: 1px solid var(--line);
70
+ border-radius: 0;
71
+ flex-wrap: nowrap;
72
+ gap: var(--space-2xs);
73
+ overflow-x: auto;
74
+ padding: 0.3rem var(--space-xs);
75
+ scrollbar-width: thin;
76
+ }
77
+
78
+ /* The element that absorbs slack and gives it back first — a title, a path, a
79
+ filter input. Everything else in the bar keeps its size, so the control that
80
+ would otherwise be pushed past the clipped edge stays reachable. */
81
+ .ui-toolstrip__fill {
82
+ flex: 1 1 auto;
83
+ min-inline-size: 0;
84
+ overflow: hidden;
85
+ text-overflow: ellipsis;
86
+ white-space: nowrap;
87
+ }
88
+
59
89
  .ui-toolstrip__brand {
60
90
  align-items: baseline;
61
91
  display: inline-flex;
@@ -168,6 +198,96 @@
168
198
  min-inline-size: 0;
169
199
  }
170
200
 
201
+ /* --- Pane — a movable, renameable surface with a grab header.
202
+
203
+ `.ui-panel` is a padded card and `.ui-inspector` is head-plus-body; neither
204
+ is a window. A pane is what a canvas node, a floating tool window, or a
205
+ dockable panel needs: a header you can drag, a title that renames in place,
206
+ an actions slot that survives a narrow pane, and a body that owns the rest.
207
+
208
+ Bronto paints the frame only. The host owns dragging, z-order, focus policy,
209
+ persistence, and what the actions do — this leaf has no behavior. --- */
210
+ .ui-pane {
211
+ background: var(--panel);
212
+ border: 1px solid var(--line);
213
+ border-radius: var(--radius-md);
214
+ display: flex;
215
+ flex-direction: column;
216
+ min-block-size: 0;
217
+ min-inline-size: 0;
218
+ overflow: hidden;
219
+ }
220
+
221
+ /* `cursor: grab` states the affordance; the host wires the gesture. */
222
+ .ui-pane__head {
223
+ align-items: center;
224
+ background: var(--bg-elevated);
225
+ border-block-end: 1px solid var(--line);
226
+ cursor: grab;
227
+ display: flex;
228
+ flex: none;
229
+ gap: var(--space-2xs);
230
+ min-block-size: var(--pane-head, 2.5rem);
231
+ padding: var(--space-2xs) var(--space-xs);
232
+ }
233
+
234
+ .ui-pane__head:active {
235
+ cursor: grabbing;
236
+ }
237
+
238
+ /* The title gives up space FIRST — it is the one thing in the header that can
239
+ be truncated without losing a function. */
240
+ .ui-pane__title {
241
+ flex: 1 1 auto;
242
+ font-size: var(--text-xs);
243
+ min-inline-size: 0;
244
+ overflow: hidden;
245
+ text-overflow: ellipsis;
246
+ white-space: nowrap;
247
+ }
248
+
249
+ /* Rename in place. It inherits the type it replaces so the swap from label to
250
+ input does not move a single pixel of layout; only the accent border says
251
+ "you are typing a name now". Pair with `.ui-pane__title` on the input too. */
252
+ .ui-pane__title-input {
253
+ background: var(--bg-elevated);
254
+ border: 1px solid var(--accent);
255
+ border-radius: var(--radius-sm);
256
+ color: inherit;
257
+ font: inherit;
258
+ letter-spacing: inherit;
259
+ margin-block: -1px;
260
+ max-inline-size: 100%;
261
+ min-inline-size: 0;
262
+ padding: 0 0.25rem;
263
+ }
264
+
265
+ /* Shrinks only AFTER the title is gone, and then scrolls rather than pushing
266
+ its last control past the pane's clipped edge — the failure that hides a
267
+ control the user needs (close, focus, disconnect) with no way to reach it. */
268
+ .ui-pane__actions {
269
+ display: flex;
270
+ flex: 0 1 auto;
271
+ flex-wrap: nowrap;
272
+ gap: var(--space-2xs);
273
+ min-inline-size: 0;
274
+ overflow-x: auto;
275
+ scrollbar-width: thin;
276
+ }
277
+
278
+ .ui-pane__actions:empty {
279
+ display: none;
280
+ }
281
+
282
+ .ui-pane__body {
283
+ display: flex;
284
+ flex: 1;
285
+ flex-direction: column;
286
+ min-block-size: 0;
287
+ min-inline-size: 0;
288
+ overflow: auto;
289
+ }
290
+
171
291
  /* --- Selection bar — actions on the current selection ("3 selected"). --- */
172
292
  .ui-selectionbar {
173
293
  align-items: center;
@@ -194,6 +314,37 @@
194
314
  gap: var(--space-xs);
195
315
  }
196
316
 
317
+ /* Anchor a floating bar to a viewport edge, clear of any display cutout or
318
+ gesture area. Both `--floating` bars above are shadowed but position-less,
319
+ so every consumer re-derives this — including the `max(offset, inset)` shape,
320
+ which is the part people get wrong by writing a bare offset that a phone then
321
+ swallows. `--anchor-block-end` is the default because a bar the thumb must
322
+ reach belongs at the bottom; `--anchor-block-start` is for the bar that must
323
+ not be under the thumb (a destructive-action or recovery bar).
324
+
325
+ The host still owns z-index — it knows what else is on the canvas. */
326
+ .ui-selectionbar--anchored,
327
+ .ui-toolstrip--anchored {
328
+ --anchor-offset: var(--space-sm);
329
+
330
+ inset-inline-start: 50%;
331
+ max-inline-size: calc(100% - 2 * var(--space-md));
332
+ position: absolute;
333
+ transform: translateX(-50%);
334
+ }
335
+
336
+ .ui-selectionbar--anchored,
337
+ .ui-toolstrip--anchored,
338
+ .ui-selectionbar--anchor-block-end,
339
+ .ui-toolstrip--anchor-block-end {
340
+ inset-block: auto max(var(--anchor-offset), var(--safe-area-bottom));
341
+ }
342
+
343
+ .ui-selectionbar--anchor-block-start,
344
+ .ui-toolstrip--anchor-block-start {
345
+ inset-block: max(var(--anchor-offset), var(--safe-area-top)) auto;
346
+ }
347
+
197
348
  @media (hover: hover) {
198
349
  .ui-segmented-buttons__button:hover:not(:disabled, [aria-disabled='true'], [aria-pressed='true'], .is-active) {
199
350
  background: var(--panel);