@tensor_1/tokens 2.47.0 → 2.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.
Files changed (77) hide show
  1. package/build/css/components.css +1 -1
  2. package/build/css/styles.css +1 -1
  3. package/build/css/tokens.css +4 -0
  4. package/build/ts/tokens.d.ts +1 -1
  5. package/build/ts/tokens.js +8 -0
  6. package/package.json +1 -1
  7. package/styles/components/alert.css +6 -4
  8. package/styles/components/app-shell.css +40 -0
  9. package/styles/components/arrival.css +7 -1
  10. package/styles/components/attachment.css +4 -1
  11. package/styles/components/audit-log.css +29 -0
  12. package/styles/components/breadcrumbs.css +2 -0
  13. package/styles/components/busy-region.css +10 -2
  14. package/styles/components/button.css +7 -0
  15. package/styles/components/card.css +22 -0
  16. package/styles/components/change-highlight.css +58 -0
  17. package/styles/components/checkbox.css +18 -0
  18. package/styles/components/code-display.css +43 -0
  19. package/styles/components/combobox.css +4 -1
  20. package/styles/components/command-palette.css +16 -2
  21. package/styles/components/contact-card.css +17 -6
  22. package/styles/components/count.css +23 -0
  23. package/styles/components/data-workspace.css +40 -0
  24. package/styles/components/date-picker.css +14 -1
  25. package/styles/components/detail-layout.css +9 -0
  26. package/styles/components/diff-viewer.css +5 -0
  27. package/styles/components/display-preferences.css +38 -0
  28. package/styles/components/drag.css +63 -0
  29. package/styles/components/drawer.css +6 -5
  30. package/styles/components/editorial-home.css +24 -2
  31. package/styles/components/flow.css +5 -0
  32. package/styles/components/heartbeat.css +8 -0
  33. package/styles/components/icon.css +14 -0
  34. package/styles/components/inline-edit.css +19 -3
  35. package/styles/components/input.css +62 -0
  36. package/styles/components/kanban.css +10 -1
  37. package/styles/components/kbd.css +25 -0
  38. package/styles/components/lightbox.css +13 -0
  39. package/styles/components/list-motion.css +27 -0
  40. package/styles/components/list.css +2 -0
  41. package/styles/components/match-review.css +40 -0
  42. package/styles/components/media.css +4 -2
  43. package/styles/components/metric-input.css +3 -1
  44. package/styles/components/modal.css +10 -5
  45. package/styles/components/page-header.css +12 -0
  46. package/styles/components/pagination.css +2 -0
  47. package/styles/components/peek.css +6 -5
  48. package/styles/components/popover.css +14 -10
  49. package/styles/components/print-layout.css +177 -0
  50. package/styles/components/progress.css +7 -0
  51. package/styles/components/qr-code.css +30 -0
  52. package/styles/components/qr-scanner.css +61 -0
  53. package/styles/components/relationship-graph.css +71 -15
  54. package/styles/components/rule-builder.css +2 -1
  55. package/styles/components/save-status.css +5 -0
  56. package/styles/components/scheduling-calendar.css +82 -1
  57. package/styles/components/search-suggest.css +8 -6
  58. package/styles/components/segmented.css +31 -1
  59. package/styles/components/select.css +20 -2
  60. package/styles/components/sidebar.css +51 -1
  61. package/styles/components/sortable-list.css +42 -0
  62. package/styles/components/spinner.css +5 -0
  63. package/styles/components/stat-tile.css +36 -12
  64. package/styles/components/states.css +50 -0
  65. package/styles/components/status-pill.css +28 -6
  66. package/styles/components/steps.css +2 -0
  67. package/styles/components/table-toolbar.css +1 -1
  68. package/styles/components/table.css +48 -28
  69. package/styles/components/tabs.css +32 -3
  70. package/styles/components/tag-input.css +21 -0
  71. package/styles/components/textarea.css +3 -0
  72. package/styles/components/toast.css +23 -8
  73. package/styles/components/tooltip.css +7 -3
  74. package/styles/components/tree.css +3 -1
  75. package/styles/components/view-menu.css +3 -0
  76. package/styles/motion.css +23 -4
  77. package/tokens/base/effect.json +6 -0
@@ -170,7 +170,7 @@
170
170
  .uix-scheduling-calendar__legend-list { display:flex; flex-wrap:wrap; gap:var(--uix-space-2) var(--uix-space-3); margin:0; padding:0; list-style:none; }
171
171
  .uix-scheduling-calendar__legend-list > li { display:inline-flex; align-items:center; gap:var(--uix-space-1); margin:0; }
172
172
  .uix-scheduling-calendar__legend-caption { flex:1 1 100%; margin:0; }
173
- .uix-scheduling-calendar__swatch { flex:none; display:inline-flex; align-items:center; justify-content:center; width:1.5rem; height:0.875rem; border:1px solid var(--calendar-status-edge); border-radius:var(--uix-radius-xs); background:var(--calendar-status-surface); color:var(--calendar-status-text); }
173
+ .uix-scheduling-calendar__swatch { flex:none; display:inline-flex; align-items:center; justify-content:center; width:1.5rem; height:0.875rem; border:1px solid var(--calendar-status-edge); border-radius:var(--uix-radius-xs); background:var(--calendar-status-surface); color:var(--calendar-chrome-text); }
174
174
  .uix-scheduling-calendar__swatch > svg { width:0.75rem; height:0.75rem; }
175
175
  /* the built-in legend of the deprecated `state` values: the same shapes, no hue */
176
176
  .uix-scheduling-calendar__legend > span[data-state]::before { content:""; display:inline-block; width:0.625rem; height:0.625rem; margin-right:var(--uix-space-1); border:1px solid var(--calendar-status-edge-strong); border-radius:var(--uix-radius-xs); vertical-align:middle; }
@@ -313,6 +313,87 @@
313
313
  whatever theme the screen has). The calendar asks the browser to print its surfaces as they
314
314
  are; a browser may refuse, so no meaning rests on a fill or a pattern: each is an edge too.
315
315
  The first rule is the colour map again. */
316
+ /* ── Resource day (HAR-983): one column per resource on a slot axis ──────────────────────────
317
+ A free span is cut into bookable slots (a button each); a booking and time off stay whole;
318
+ time no span covers is "no schedule": hatched, with no button and no "free". No kind takes a
319
+ hue (the band alone does): free is open paper with a thin bar, a booking is filled with a heavy
320
+ bar and its words, time off is quiet italic words, no schedule is hatched. The grid scrolls
321
+ inside its own box, so the time gutter (left) and the heads (top) stay in view. */
322
+ .uix-scheduling-calendar__resources { --calendar-slot:1.75rem; --calendar-rs-column:9rem; position:relative; min-width:0; }
323
+ /* a refetch keeps every span readable and says it is loading in words, above the grid */
324
+ .uix-scheduling-calendar__rs-busy { display:block; margin-bottom:var(--uix-space-2); color:var(--calendar-chrome-text-quiet); font-size:var(--uix-text-meta); }
325
+ .uix-scheduling-calendar__rs-scroll { max-height:var(--uix-scheduling-calendar-rs-height,min(70vh,46rem)); overflow:auto; border:1px solid var(--calendar-chrome-line); border-radius:var(--uix-radius-sm); background:var(--calendar-chrome-surface); overscroll-behavior:contain; }
326
+ .uix-scheduling-calendar__rs-grid { display:grid; grid-template-columns:var(--calendar-gutter) repeat(var(--uix-scheduling-calendar-rs-columns,1),minmax(var(--calendar-rs-column),1fr)); grid-template-rows:auto calc(var(--uix-scheduling-calendar-rs-slots,1) * var(--calendar-slot)); min-width:calc(var(--calendar-gutter) + var(--uix-scheduling-calendar-rs-columns,1) * var(--calendar-rs-column)); }
327
+ .uix-scheduling-calendar__rs-corner,.uix-scheduling-calendar__rs-head { position:sticky; top:0; z-index:var(--uix-z-raised); padding:var(--uix-space-2); border-bottom:1px solid var(--calendar-chrome-line); background:var(--calendar-chrome-surface-quiet); }
328
+ .uix-scheduling-calendar__rs-corner { left:0; z-index:var(--uix-z-sticky); display:flex; align-items:flex-end; padding-inline:var(--uix-space-1); color:var(--calendar-chrome-text-quiet); font-size:var(--uix-text-eyebrow); overflow-wrap:anywhere; }
329
+ .uix-scheduling-calendar__rs-head { display:grid; align-content:end; gap:var(--calendar-gap); min-width:0; border-left:1px solid var(--calendar-chrome-line); }
330
+ .uix-scheduling-calendar__rs-name { font-weight:600; overflow-wrap:anywhere; }
331
+ .uix-scheduling-calendar__rs-description { display:block; color:var(--calendar-chrome-text-quiet); font-size:var(--uix-text-meta); font-weight:400; overflow-wrap:anywhere; }
332
+ .uix-scheduling-calendar__rs-gutter { position:sticky; left:0; z-index:var(--uix-z-raised); background:var(--calendar-chrome-surface); }
333
+ .uix-scheduling-calendar__rs-tick { position:absolute; top:calc(var(--uix-scheduling-calendar-rs-offset,0) * var(--calendar-slot)); right:var(--uix-space-1); padding-top:var(--calendar-gap); color:var(--calendar-chrome-text-quiet); font-size:var(--uix-text-meta); font-variant-numeric:tabular-nums; line-height:1; }
334
+ /* The column's own background is "no schedule" (hatched); a free span covers it with paper.
335
+ Lines mark the hours only, drawn over the paper; the slots are felt on hover, not drawn
336
+ (review of PR #166: per-slot boxes with a label each read as a form, not a day). A booking
337
+ is the time grid's own entry chip, at the time grid's own lanes. */
338
+ /* One hour line per hour, as a tile one hour tall: the column and every free slot draw it,
339
+ each shifted by its own offset, so the lines run unbroken over paper and hatch alike. The
340
+ column is its own stacking context: what is raised inside it never rises over the sticky
341
+ heads and gutter. */
342
+ .uix-scheduling-calendar__rs-column { --calendar-hour:var(--calendar-slot); --calendar-rs-hour:calc(var(--uix-scheduling-calendar-rs-per-hour,1) * var(--calendar-slot));
343
+ position:relative; isolation:isolate; min-width:0; border-left:1px solid var(--calendar-chrome-line);
344
+ background:linear-gradient(to bottom,var(--calendar-chrome-line) 0 1px,transparent 1px) 0 calc(var(--uix-scheduling-calendar-rs-hour-at,0) * var(--calendar-slot)) / 100% var(--calendar-rs-hour) repeat-y,repeating-linear-gradient(135deg,var(--calendar-chrome-line) 0 1px,transparent 1px 8px),var(--calendar-chrome-surface-quiet); }
345
+ /* until the first spans arrive nothing is known: no hatch that would say "no schedule" */
346
+ .uix-scheduling-calendar__resources[data-first-load] .uix-scheduling-calendar__rs-column { background:var(--calendar-chrome-surface); }
347
+ .uix-scheduling-calendar__rs-skeleton { position:absolute; top:calc(var(--uix-scheduling-calendar-rs-offset,0) * var(--calendar-slot) + 1px); left:var(--calendar-gap); right:var(--calendar-gap); height:calc(var(--uix-scheduling-calendar-rs-length,1) * var(--calendar-slot) - 2px); }
348
+ /* a free slot: exactly its slot, so a free span tiles into one sheet of paper */
349
+ .uix-scheduling-calendar__rs-item { --calendar-rs-paper:var(--calendar-chrome-surface); position:absolute; top:calc(var(--uix-scheduling-calendar-rs-offset,0) * var(--calendar-slot)); left:0; right:0; height:calc(var(--uix-scheduling-calendar-rs-length,1) * var(--calendar-slot)); display:flex; align-items:flex-start; min-width:0; padding:var(--calendar-gap) var(--uix-space-2); overflow:hidden; border:0; border-radius:0;
350
+ background:linear-gradient(to bottom,var(--calendar-chrome-line) 0 1px,transparent 1px) 0 calc((var(--uix-scheduling-calendar-rs-hour-at,0) - var(--uix-scheduling-calendar-rs-offset,0)) * var(--calendar-slot)) / 100% var(--calendar-rs-hour) repeat-y,var(--calendar-rs-paper); color:var(--calendar-chrome-text); font:inherit; font-size:var(--uix-text-meta); font-variant-numeric:tabular-nums; line-height:1.35; text-align:left; cursor:pointer; }
351
+ .uix-scheduling-calendar__rs-add { display:inline-flex; align-items:center; gap:var(--calendar-gap); visibility:hidden; white-space:nowrap; }
352
+ .uix-scheduling-calendar__rs-add > svg { flex:none; width:1em; height:1em; }
353
+ .uix-scheduling-calendar__rs-item:focus-visible,.uix-scheduling-calendar__rs-item[data-selected] { z-index:var(--uix-z-raised); --calendar-rs-paper:var(--calendar-chrome-ghost-surface); }
354
+ .uix-scheduling-calendar__rs-item:is(:focus-visible,[data-selected]) > .uix-scheduling-calendar__rs-add { visibility:visible; }
355
+ .uix-scheduling-calendar__rs-item:focus-visible { outline:2px solid var(--calendar-chrome-text); outline-offset:-2px; }
356
+ .uix-scheduling-calendar__rs-item[data-selected] { box-shadow:inset 0 0 0 2px var(--calendar-chrome-text); }
357
+ /* time off: quiet words on a quiet sheet, over the paper and under nothing interactive */
358
+ .uix-scheduling-calendar__rs-off { position:absolute; top:calc(var(--uix-scheduling-calendar-offset,0) * var(--calendar-slot)); left:0; right:0; height:calc(var(--uix-scheduling-calendar-length,1) * var(--calendar-slot)); padding:var(--calendar-gap) var(--uix-space-2); overflow:hidden; border-top:1px solid var(--calendar-chrome-line); background:var(--calendar-chrome-surface-quiet); color:var(--calendar-chrome-text-quiet); font-size:var(--uix-text-meta); font-style:italic; line-height:1.35; }
359
+ .uix-scheduling-calendar__rs-text { display:block; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
360
+ /* a booking: the entry chip (its plain edge), heavy only when it is the selected one */
361
+ .uix-scheduling-calendar__rs-booking { top:calc(var(--uix-scheduling-calendar-offset,0) * var(--calendar-slot) + 1px); height:max(calc(var(--uix-scheduling-calendar-length,1) * var(--calendar-slot) - 2px),0.875rem); padding:var(--calendar-gap) var(--uix-space-1); font:inherit; font-size:var(--uix-text-meta); }
362
+ .uix-scheduling-calendar__rs-booking[data-continues-before] { border-top-style:dashed; border-top-left-radius:0; border-top-right-radius:0; }
363
+ .uix-scheduling-calendar__rs-booking[data-continues-after] { border-bottom-style:dashed; border-bottom-left-radius:0; border-bottom-right-radius:0; }
364
+ .uix-scheduling-calendar__rs-booking[data-selected] { border-width:2px; border-color:var(--calendar-chrome-text); }
365
+ .uix-scheduling-calendar__rs-booking[data-selected] .uix-scheduling-calendar__title { font-weight:600; }
366
+ .uix-scheduling-calendar__rs-booking:focus-visible { z-index:var(--uix-z-raised); outline:2px solid var(--calendar-chrome-text); outline-offset:1px; }
367
+ .uix-scheduling-calendar__rs-row:focus-visible { outline:2px solid var(--calendar-chrome-text); outline-offset:1px; z-index:var(--uix-z-raised); }
368
+ .uix-scheduling-calendar__rs-row[data-selected] { box-shadow:inset 0 0 0 2px var(--calendar-chrome-text); }
369
+ @media (hover:hover) {
370
+ .uix-scheduling-calendar__rs-item:hover { --calendar-rs-paper:var(--calendar-chrome-ghost-surface); }
371
+ .uix-scheduling-calendar__rs-item:hover > .uix-scheduling-calendar__rs-add { visibility:visible; }
372
+ .uix-scheduling-calendar__rs-booking:hover { background:var(--calendar-chrome-surface-quiet); }
373
+ button.uix-scheduling-calendar__rs-row:hover { background:var(--calendar-chrome-ghost-surface); }
374
+ }
375
+ /* a finger needs a taller slot than a pointer (WCAG 2.5.8) */
376
+ @media (pointer:coarse) { .uix-scheduling-calendar__resources { --calendar-slot:2.75rem; } }
377
+ .uix-scheduling-calendar__rs-skeleton { display:block; }
378
+
379
+ /* the list: one resource after another, each time range on its own row */
380
+ .uix-scheduling-calendar__resources[data-layout="list"] { display:grid; gap:var(--uix-space-4); }
381
+ .uix-scheduling-calendar__rs-section { display:grid; gap:var(--uix-space-2); min-width:0; }
382
+ .uix-scheduling-calendar__rs-heading { margin:0; font-size:inherit; font-weight:600; overflow-wrap:anywhere; }
383
+ .uix-scheduling-calendar__rs-list { display:grid; gap:var(--calendar-gap); margin:0; padding:0; list-style:none; }
384
+ .uix-scheduling-calendar__rs-list > li { min-width:0; }
385
+ .uix-scheduling-calendar__rs-row { display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--uix-space-1) var(--uix-space-3); width:100%; min-height:2.75rem; padding:var(--uix-space-2) var(--uix-space-3); border:1px solid var(--calendar-chrome-line); border-radius:var(--uix-radius-xs); background:var(--calendar-chrome-surface); color:var(--calendar-chrome-text); font:inherit; text-align:left; }
386
+ button.uix-scheduling-calendar__rs-row { cursor:pointer; }
387
+ .uix-scheduling-calendar__rs-row[data-kind="free"] { border-left:3px solid var(--calendar-chrome-line-strong); }
388
+ .uix-scheduling-calendar__rs-row[data-kind="booked"] { border-color:var(--calendar-chrome-text); border-left-width:3px; background:var(--calendar-chrome-surface-quiet); }
389
+ .uix-scheduling-calendar__rs-row[data-kind="off"] { background:var(--calendar-chrome-surface-quiet); color:var(--calendar-chrome-text-quiet); font-style:italic; }
390
+ .uix-scheduling-calendar__rs-row[data-kind="none"] { background:repeating-linear-gradient(135deg,var(--calendar-chrome-line) 0 1px,transparent 1px 8px),var(--calendar-chrome-surface-quiet); color:var(--calendar-chrome-text-quiet); }
391
+ .uix-scheduling-calendar__rs-range { font-variant-numeric:tabular-nums; font-weight:600; white-space:nowrap; }
392
+ .uix-scheduling-calendar__rs-kind { color:var(--calendar-chrome-text-quiet); }
393
+ .uix-scheduling-calendar__rs-label { min-width:0; overflow-wrap:anywhere; }
394
+ .uix-scheduling-calendar__rs-section .uix-scheduling-calendar__rs-skeleton { position:static; height:6rem; }
395
+ .uix-scheduling-calendar__resources > .uix-scheduling-calendar__empty { position:static; border:0; }
396
+
316
397
  @media print {
317
398
  .uix-scheduling-calendar {
318
399
  --calendar-chrome-text: var(--uix-print-ink);
@@ -23,6 +23,8 @@
23
23
  .uix-search-suggest__clear:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: 1px; }
24
24
 
25
25
  .uix-search-suggest--lg .uix-search-suggest__input { height: calc(var(--uix-control-h) + 8px); padding-left: 44px; font-size: var(--uix-text-h3); }
26
+ /* free text (HAR-2465): a value field, not a search, so no magnifier and no room for one */
27
+ .uix-search-suggest--free-text .uix-search-suggest__input { padding-left: var(--uix-space-3); }
26
28
  .uix-search-suggest--lg .uix-search-suggest__lead { left: 14px; }
27
29
  .uix-search-suggest--lg .uix-search-suggest__lead svg { width: var(--uix-icon-lg); height: var(--uix-icon-lg); }
28
30
 
@@ -32,9 +34,13 @@
32
34
  display: flex; flex-direction: column; max-height: min(28rem, 70vh); overflow-y: auto; overscroll-behavior: contain;
33
35
  padding: var(--uix-space-1); background: var(--uix-surface-2); border: 1px solid var(--uix-border);
34
36
  border-radius: var(--uix-radius-md); box-shadow: var(--uix-shadow-popover);
35
- animation: uix-search-suggest-in var(--uix-dur-fast) var(--uix-ease-out);
37
+ /* Enters once per opening, growing from the field (a transition from @starting-style, not a
38
+ keyframe): the React SearchSuggest sets [data-settled] after the first show, so a list that
39
+ hides and reappears while the user types comes back in place (HAR-2578). */
40
+ transform-origin: var(--uix-anchor-origin, top center);
41
+ transition: opacity var(--uix-dur-fast) var(--uix-ease-out), transform var(--uix-dur-fast) var(--uix-ease-out);
36
42
  }
37
- @keyframes uix-search-suggest-in { from { opacity: 0; transform: translateY(-4px); } }
43
+ @starting-style { .uix-search-suggest__popup:not([hidden], [data-settled]) { opacity: 0; transform: scale(.95); } }
38
44
 
39
45
  .uix-search-suggest__header {
40
46
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--uix-space-3);
@@ -107,10 +113,6 @@
107
113
  .uix-search-suggest__option { min-height: 44px; padding-block: var(--uix-space-3); }
108
114
  }
109
115
 
110
- @media (prefers-reduced-motion: reduce) {
111
- .uix-search-suggest__popup:not([hidden]) { animation: none; }
112
- }
113
-
114
116
  @media (forced-colors: active) {
115
117
  .uix-search-suggest__option[data-active], .uix-search-suggest__footer[data-active] { outline: 2px solid Highlight; outline-offset: -2px; }
116
118
  .uix-search-suggest__match { text-decoration: underline; }
@@ -16,6 +16,36 @@
16
16
  }
17
17
  .uix-segmented__option:hover { color: var(--uix-text); }
18
18
  /* aria-checked: a `selection="radio"` group (HAR-1604) */
19
- .uix-segmented__option:is([aria-pressed="true"], [aria-checked="true"]) { background: var(--uix-surface); color: var(--uix-text); box-shadow: var(--uix-shadow-sm); }
19
+ /* selection="link" (HAR-1380): a <nav> around ul.uix-segmented of links; the current page lifts. */
20
+ ul.uix-segmented { margin: 0; list-style: none; }
21
+ ul.uix-segmented > li { display: flex; min-inline-size: 0; margin: 0; }
22
+ a.uix-segmented__option { display: inline-flex; align-items: center; text-decoration: none; }
23
+ a.uix-segmented__option[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
24
+ /* the selected segment is lifted AND ringed: the surface on the track is only 1.05:1 in dark and
25
+ 1.09:1 in light, so an inset --uix-text-muted hairline (4.9:1 light, 7:1 dark on the track) makes
26
+ it a 3:1 boundary (WCAG 1.4.11). No weight change: a bolder label would widen the option and
27
+ shift its neighbours on every selection. */
28
+ .uix-segmented__option:is([aria-pressed="true"], [aria-checked="true"], [aria-current="page"]) { background: var(--uix-surface); color: var(--uix-text); box-shadow: var(--uix-shadow-sm), inset 0 0 0 1px var(--uix-text-muted); }
20
29
  .uix-segmented__option:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: 1px; }
30
+
31
+ /* Sliding indicator (React Segmented, HAR-2589). At rest the chosen option shows its own lift.
32
+ When a click changes it, one lifted segment slides from the old option to the new one
33
+ (transform only); while it slides ([data-uix-indicator]) the new option's own lift steps
34
+ aside, then the indicator hides again. Keyboard changes jump. */
35
+ /* the indicator is the last child: isolate + z-index -1 paint it above the track and beneath
36
+ the options */
37
+ .uix-segmented { position: relative; isolation: isolate; }
38
+ /* A window that narrows mid-slide wraps the options before the ResizeObserver that ends the
39
+ slide has run, so for that frame the indicator sits at its old place, past the control's edge,
40
+ and widened the page by 19 px at 320 px in CI (display-preferences.spec, red on master since
41
+ #170). The track clips it; the margin keeps the options' focus ring and lift. */
42
+ .uix-segmented { overflow: clip; overflow-clip-margin: var(--uix-space-1); }
43
+ .uix-segmented__option { position: relative; }
44
+ /* the same look as a selected option: surface, lift and the 3:1 inset ring */
45
+ .uix-segmented__indicator { position: absolute; z-index: calc(-1 * var(--uix-z-raised)); pointer-events: none; transform-origin: 0 0; background: var(--uix-surface); box-shadow: var(--uix-shadow-sm), inset 0 0 0 1px var(--uix-text-muted); border-radius: calc(var(--uix-radius-md) - 3px); }
46
+ .uix-segmented__indicator[data-moving] { transition: transform var(--uix-dur) var(--uix-ease-in-out); }
47
+ @media (forced-colors: none) {
48
+ .uix-segmented[data-uix-indicator] .uix-segmented__option:is([aria-pressed="true"], [aria-checked="true"]) { background: transparent; box-shadow: none; }
49
+ }
50
+ @media (forced-colors: active) { .uix-segmented__indicator { display: none; } }
21
51
  }
@@ -14,6 +14,8 @@
14
14
  .uix-select:focus { outline: none; border-color: var(--uix-ring); box-shadow: 0 0 0 3px var(--uix-brand-muted); }
15
15
  .uix-select:disabled { opacity: .55; cursor: not-allowed; background-color: var(--uix-bg-subtle); }
16
16
  .uix-select--sm { height: 28px; font-size: var(--uix-text-meta); }
17
+ /* lg: 44 px, beside Input and Button lg on a public page (HAR-1812) */
18
+ .uix-select--lg { height: 44px; padding: 0 calc(var(--uix-space-8) + var(--uix-space-1)) 0 var(--uix-space-4); background-position: right 14px center; font-size: var(--uix-text-h3); }
17
19
 
18
20
  /* command-palette-style rich select: a trigger that opens a .uix-cmdk popover (search + options) */
19
21
  .uix-select-trigger { display: inline-flex; align-items: center; gap: var(--uix-space-2); height: var(--uix-control-h); padding: 0 var(--uix-space-3); min-width: 220px;
@@ -29,6 +31,15 @@
29
31
  .uix-select[aria-invalid="true"] { border-color: var(--uix-danger); }
30
32
  .uix-select[aria-invalid="true"]:focus, .uix-select[aria-invalid="true"][aria-expanded="true"] { box-shadow: 0 0 0 3px var(--uix-danger-bg); }
31
33
  .uix-select[aria-readonly="true"] { cursor: default; background-color: var(--uix-bg-subtle); background-image: none; padding-right: var(--uix-space-3); }
34
+ /* the trigger's chevron is the icon set's chevron-down through a mask, in the muted text colour of
35
+ the theme (HAR-1576). A native <select> cannot hold a pseudo-element: it keeps the image above,
36
+ a mid grey that clears 3:1 on the light and the dark surface. */
37
+ button.uix-select { position: relative; background-image: none; }
38
+ button.uix-select::after {
39
+ content: ""; position: absolute; inset-inline-end: var(--uix-space-3); top: 50%; width: var(--uix-icon-sm); height: var(--uix-icon-sm); translate: 0 -50%;
40
+ background: var(--uix-text-muted); pointer-events: none; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat;
41
+ }
42
+ button.uix-select[aria-readonly="true"]::after { content: none; }
32
43
  .uix-select__value { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--uix-space-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
33
44
  .uix-select__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
34
45
  .uix-select__icon { display: inline-flex; flex: none; color: var(--uix-text-muted); }
@@ -55,8 +66,15 @@
55
66
  }
56
67
  .uix-select__popup:not([hidden]) { display: flex; }
57
68
  [popover].uix-select__popup:not(:popover-open) { display: none; }
58
- [popover].uix-select__popup { opacity: 1; transition: opacity var(--uix-dur-fast) var(--uix-ease-out), overlay var(--uix-dur-fast) allow-discrete, display var(--uix-dur-fast) allow-discrete; }
59
- @starting-style { [popover].uix-select__popup:popover-open { opacity: 0; } }
69
+ /* grows out of the trigger (--uix-anchor-origin from useAnchoredPosition) at --uix-dur-fast; the
70
+ closed state carries the faster exit. Opened from the keyboard it does not animate (motion.css). */
71
+ [popover].uix-select__popup {
72
+ opacity: 0; transform: scale(.95); transform-origin: var(--uix-anchor-origin, top left);
73
+ transition: opacity var(--uix-dur-exit-fast) var(--uix-ease-out), transform var(--uix-dur-exit-fast) var(--uix-ease-out),
74
+ overlay var(--uix-dur-exit-fast) allow-discrete, display var(--uix-dur-exit-fast) allow-discrete;
75
+ }
76
+ [popover].uix-select__popup:popover-open { opacity: 1; transform: none; transition-duration: var(--uix-dur-fast); }
77
+ @starting-style { [popover].uix-select__popup:popover-open { opacity: 0; transform: scale(.95); } }
60
78
  .uix-select__listbox { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; position: relative; }
61
79
  .uix-select__popup .uix-listbox { min-width: 0; padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; }
62
80
  .uix-select__popup .uix-listbox:focus { outline: none; }
@@ -35,7 +35,8 @@
35
35
  .uix-navitem__badge { font-size: var(--uix-text-meta); color: var(--uix-text-muted); }
36
36
  /* two-line row: a label plus a wrapping second line (HAR-1355). min-height above lets it grow. */
37
37
  .uix-navitem[data-multiline] { align-items: flex-start; padding-block: var(--uix-space-2); }
38
- .uix-navitem__text { flex: 1; min-width: 0; display: grid; }
38
+ /* label over description, --uix-space-1 apart: the kit's one title -> secondary rhythm (HAR-1577) */
39
+ .uix-navitem__text { flex: 1; min-width: 0; display: grid; gap: var(--uix-space-1); }
39
40
  .uix-navitem__desc { font-size: var(--uix-text-meta); line-height: 1.35; color: var(--uix-text-muted); overflow-wrap: anywhere; white-space: normal; font-weight: 400; }
40
41
  .uix-navitem[aria-current="page"] .uix-navitem__desc { color: var(--uix-text-hushed); }
41
42
  /* on the active tint, muted badge dips below AA — hushed composites darker on the tint */
@@ -142,3 +143,52 @@
142
143
  .uix-sidebar[data-nav="rail"] .uix-navitem { justify-content: center; align-items: center; padding: 0; gap: 0; }
143
144
  .uix-sidebar[data-nav="rail"] .uix-sidebar__head { justify-content: center; padding-inline: 0; }
144
145
  }
146
+
147
+ /* Head actions, the search row, the foot and AccountMenu (HAR-1371). Hover tints only where the
148
+ device can hover; nothing here moves, so a rail switch is instant. */
149
+ @layer uix.components {
150
+ .uix-sidebar__head .uix-sidebar__brand { flex: 1 1 auto; }
151
+ .uix-sidebar__actions { display: flex; align-items: center; gap: var(--uix-space-1); flex: none; }
152
+
153
+ .uix-sidebar__search {
154
+ display: flex; align-items: center; gap: var(--uix-space-2); width: 100%; min-height: 32px; margin-block-end: var(--uix-space-1);
155
+ padding: 0 var(--uix-space-2) 0 var(--uix-space-3); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md);
156
+ background: var(--uix-surface); color: var(--uix-text-muted); font: inherit; font-size: var(--uix-text-body); text-align: start; cursor: pointer;
157
+ }
158
+ .uix-sidebar__search:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: -2px; }
159
+ .uix-sidebar__search-icon { width: var(--uix-icon-sm); height: var(--uix-icon-sm); flex: none; }
160
+ .uix-sidebar__search-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
161
+ .uix-sidebar__search-keys { flex: none; }
162
+
163
+ .uix-sidebar__foot { margin-block-start: auto; padding-block-start: var(--uix-space-2); border-block-start: 1px solid var(--uix-border); }
164
+
165
+ .uix-account-menu {
166
+ display: flex; align-items: center; gap: var(--uix-space-3); width: 100%; min-height: 44px; padding: var(--uix-space-1) var(--uix-space-2);
167
+ border: 0; border-radius: var(--uix-radius-sm); background: transparent; color: var(--uix-text); font: inherit; text-align: start; cursor: pointer;
168
+ }
169
+ .uix-account-menu:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: -2px; }
170
+ .uix-account-menu[aria-expanded="true"] { background: var(--uix-bg-active); }
171
+ .uix-account-menu__avatar { flex: none; }
172
+ .uix-account-menu__text { display: grid; flex: 1 1 auto; gap: var(--uix-space-1); min-width: 0; } /* the kit's one title -> secondary rhythm (HAR-1577) */
173
+ .uix-account-menu__name { font-size: var(--uix-text-body); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
174
+ /* the second line is hushed, not muted: muted dips under 4.5:1 on the hover and open tints */
175
+ .uix-account-menu__sub { font-size: var(--uix-text-meta); color: var(--uix-text-hushed); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
176
+ .uix-account-menu__chevron { width: var(--uix-icon-sm); height: var(--uix-icon-sm); flex: none; color: var(--uix-text-muted); }
177
+
178
+ @media (hover: hover) {
179
+ .uix-sidebar__search:hover { border-color: var(--uix-border-strong); color: var(--uix-text-hushed); }
180
+ .uix-account-menu:hover { background: var(--uix-bg-hover); }
181
+ }
182
+
183
+ /* the rail: the search row and the account trigger become centred icon buttons; their words
184
+ stay as the accessible name (the clip pattern the nav labels use) */
185
+ :is(.uix-sidebar[data-collapsed], .uix-sidebar[data-nav="rail"]) .uix-sidebar__actions,
186
+ :is(.uix-sidebar[data-collapsed], .uix-sidebar[data-nav="rail"]) .uix-sidebar__search-keys,
187
+ .uix-account-menu--collapsed .uix-account-menu__chevron { display: none; }
188
+ :is(.uix-sidebar[data-collapsed], .uix-sidebar[data-nav="rail"]) .uix-sidebar__search { justify-content: center; min-height: 36px; padding: 0; }
189
+ :is(.uix-sidebar[data-collapsed], .uix-sidebar[data-nav="rail"]) .uix-sidebar__search-label,
190
+ .uix-account-menu--collapsed .uix-account-menu__text {
191
+ position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
192
+ }
193
+ .uix-account-menu--collapsed { justify-content: center; padding-inline: 0; }
194
+ }
@@ -0,0 +1,42 @@
1
+ /* uix sortable list — a list the person reorders (React `SortableList`, HAR-1375).
2
+ A row is grip · content · move up / move down. Depends only on --uix-* tokens. */
3
+ @layer uix.components {
4
+ .uix-sortable { display: grid; gap: var(--uix-space-2); min-width: 0; margin: 0; padding: 0; list-style: none; }
5
+ .uix-sortable__row {
6
+ display: flex; align-items: center; gap: var(--uix-space-2); min-width: 0; margin: 0;
7
+ padding: var(--uix-space-2) var(--uix-space-2) var(--uix-space-2) var(--uix-space-1);
8
+ border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); background: var(--uix-surface);
9
+ }
10
+ /* The dragged row lifts and its neighbours make room by reordering, frame by frame: no motion of its own. */
11
+ .uix-sortable__row[data-dragging] { position: relative; z-index: var(--uix-z-raised); border-color: var(--uix-ring); box-shadow: var(--uix-shadow-popover); }
12
+ .uix-sortable[data-dragging] { cursor: grabbing; user-select: none; }
13
+ .uix-sortable__content { flex: 1; min-width: 0; overflow-wrap: break-word; }
14
+ /* 24 × 32: the grip is a pointer target, wide enough without taking the content's room. */
15
+ .uix-sortable__grip {
16
+ display: grid; place-items: center; flex: none; align-self: stretch; width: 24px; min-height: 32px; padding: 0;
17
+ border: 0; border-radius: var(--uix-radius-sm); background: transparent; color: var(--uix-text-muted); cursor: grab; touch-action: none;
18
+ }
19
+ .uix-sortable__grip svg { width: var(--uix-icon-sm); height: var(--uix-icon-sm); }
20
+ .uix-sortable__moves { display: inline-flex; flex: none; gap: var(--uix-space-1); }
21
+ .uix-sortable__move {
22
+ display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0;
23
+ border: 1px solid var(--uix-border); border-radius: var(--uix-radius-sm); background: var(--uix-surface); color: var(--uix-text); cursor: pointer;
24
+ transition: background-color var(--uix-dur-fast) var(--uix-ease-out);
25
+ }
26
+ .uix-sortable__move svg { width: var(--uix-icon-sm); height: var(--uix-icon-sm); }
27
+ .uix-sortable__move:disabled { color: var(--uix-text-muted); opacity: .45; cursor: default; }
28
+ @media (hover: hover) and (pointer: fine) {
29
+ .uix-sortable__move:not(:disabled):hover, .uix-sortable__grip:hover { background: var(--uix-bg-hover); color: var(--uix-text); }
30
+ }
31
+ .uix-sortable__move:not(:disabled):active { background: var(--uix-bg-active); }
32
+ :is(.uix-sortable__grip, .uix-sortable__move):focus-visible { outline: 2px solid var(--uix-ring); outline-offset: 1px; }
33
+ .uix-sortable__row:focus-within { border-color: var(--uix-border-strong); }
34
+ /* On a phone Move up stacks over Move down, so the content keeps the width its words need (at
35
+ 320 px they broke inside every word). A viewport query, not a container: an inline-size
36
+ container has no width of its own, and the list would collapse in a popover or any
37
+ shrink-to-fit parent (it did in the docs preview). */
38
+ @media (max-width: 30rem) {
39
+ .uix-sortable__moves { flex-direction: column; }
40
+ .uix-sortable__move { width: 32px; height: 28px; }
41
+ }
42
+ }
@@ -12,3 +12,8 @@
12
12
  background: color-mix(in srgb, var(--uix-bg-app) 60%, transparent); backdrop-filter: blur(2px);
13
13
  }
14
14
  }
15
+
16
+ /* the in-app reduced-motion preference (HAR-1372, <html data-motion="reduce">): the same pulse */
17
+ @layer uix.components {
18
+ @media (prefers-reduced-motion: no-preference) { :root[data-motion="reduce"] .uix-spinner { animation: uix-pulse var(--uix-loop-pulse) var(--uix-ease-in-out) infinite !important; } }
19
+ }
@@ -2,21 +2,30 @@
2
2
  @layer uix.components {
3
3
  .uix-stat { background: var(--uix-surface); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-lg); padding: var(--uix-space-6); }
4
4
  .uix-stat__label { font-size: var(--uix-text-eyebrow); letter-spacing: var(--uix-tracking-eyebrow); text-transform: uppercase; color: var(--uix-text-muted); display: flex; align-items: center; justify-content: space-between; gap: var(--uix-space-2); }
5
- .uix-stat__label > span { min-width: 0; overflow-wrap: anywhere; }
5
+ /* break-word, not anywhere: a word wraps whole and only breaks when it alone is wider than the tile */
6
+ .uix-stat__label > span { min-width: 0; overflow-wrap: break-word; }
6
7
  .uix-stat__label svg { width: var(--uix-icon-md); height: var(--uix-icon-md); }
7
8
  .uix-stat__value { display: block; font-size: var(--uix-text-data-hero); font-weight: 600; line-height: var(--uix-leading-data-hero); margin-top: var(--uix-space-3); overflow-wrap: anywhere; }
8
9
  .uix-stat__meta { font-size: var(--uix-text-meta); color: var(--uix-text-muted); margin-top: var(--uix-space-2); display: flex; flex-wrap: wrap; align-items: center; gap: var(--uix-space-2); overflow-wrap: anywhere; }
9
- .uix-stat__trend { display: inline-flex; align-items: center; gap: var(--uix-space-1); font-size: var(--uix-text-meta); font-weight: 600; padding: var(--uix-space-1) var(--uix-space-2); border-radius: var(--uix-radius-pill); }
10
+ /* the chip is neutral unless the product says what the change means (HAR-1563): a rise can be
11
+ bad (breached SLAs) and a fall good (open incidents), so direction never picks the colour */
12
+ .uix-stat__trend { display: inline-flex; align-items: center; gap: var(--uix-space-1); font-size: var(--uix-text-meta); font-weight: 600; padding: var(--uix-space-1) var(--uix-space-2); border-radius: var(--uix-radius-pill); color: var(--uix-neutral-text); background: var(--uix-neutral-bg); }
10
13
  /* direction glyph inside the chip (UIX-A11Y-4) — sized to sit on the text baseline row */
11
14
  .uix-stat__trend svg { width: 10px; height: 10px; flex: none; }
12
- .uix-stat__trend--up { color: var(--uix-success-text); background: var(--uix-success-bg); }
13
- .uix-stat__trend--down { color: var(--uix-danger-text); background: var(--uix-danger-bg); }
15
+ /* the direction classes name the arrow, not a colour; their figures are tabular so deltas line up across tiles */
16
+ .uix-stat__trend:is(.uix-stat__trend--up, .uix-stat__trend--down) { font-variant-numeric: tabular-nums; }
17
+ .uix-stat__trend--positive { color: var(--uix-success-text); background: var(--uix-success-bg); }
18
+ .uix-stat__trend--negative { color: var(--uix-danger-text); background: var(--uix-danger-bg); }
14
19
 
15
- /* tones — an outlined tile whose value carries the tone; the label keeps its muted colour */
16
- .uix-stat--warning { border-color: var(--uix-warning-border); }
17
- .uix-stat--warning .uix-stat__value { color: var(--uix-warning-text); }
18
- .uix-stat--danger { border-color: var(--uix-danger-border); }
19
- .uix-stat--danger .uix-stat__value { color: var(--uix-danger-text); }
20
+ /* tones (HAR-1563) — the value carries the tone and a dot of its solid leads it; the border stays
21
+ neutral and the label keeps its muted colour, so a row of tiles has one quiet frame */
22
+ .uix-stat--warning { --stat-tone-text: var(--uix-warning-text); --stat-tone-solid: var(--uix-warning-solid); }
23
+ .uix-stat--danger { --stat-tone-text: var(--uix-danger-text); --stat-tone-solid: var(--uix-danger-solid); }
24
+ .uix-stat:is(.uix-stat--warning, .uix-stat--danger) .uix-stat__value { color: var(--stat-tone-text); }
25
+ .uix-stat:is(.uix-stat--warning, .uix-stat--danger) .uix-stat__value::before {
26
+ content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
27
+ margin-inline-end: var(--uix-space-2); vertical-align: middle; background: var(--stat-tone-solid);
28
+ }
20
29
 
21
30
  /* compact — for dense fact bands */
22
31
  .uix-stat--compact { padding: var(--uix-space-3) var(--uix-space-4); }
@@ -29,9 +38,7 @@
29
38
  display: flex; flex-direction: column; width: 100%; font: inherit; color: inherit; text-align: start; cursor: pointer;
30
39
  transition: background-color var(--uix-dur-fast) var(--uix-ease-out), border-color var(--uix-dur-fast) var(--uix-ease-out);
31
40
  }
32
- .uix-stat--interactive:hover:where(:not([aria-busy="true"])) { background: var(--uix-bg-hover); border-color: var(--uix-border-strong); }
33
- .uix-stat--interactive.uix-stat--warning:hover { border-color: var(--uix-warning-border); }
34
- .uix-stat--interactive.uix-stat--danger:hover { border-color: var(--uix-danger-border); }
41
+ @media (hover: hover) { .uix-stat--interactive:hover:where(:not([aria-busy="true"])) { background: var(--uix-bg-hover); border-color: var(--uix-border-strong); } }
35
42
  .uix-stat--interactive:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: 2px; }
36
43
  .uix-stat--interactive[aria-expanded="true"] { border-color: var(--uix-ring); }
37
44
  .uix-stat__chevron { display: inline-flex; flex: none; margin-inline-start: auto; color: var(--uix-text-muted); }
@@ -41,7 +48,24 @@
41
48
  .uix-stat--interactive[aria-busy="true"] { cursor: progress; opacity: .6; }
42
49
  /* link — the whole tile is an <a> to a filtered list or a record (HAR-1350) */
43
50
  .uix-stat--link { text-decoration: none; }
51
+ /* resting affordance (HAR-1803): a tile that goes somewhere looks it before it is touched, since
52
+ a tablet has no hover. A link carries a trailing chevron in its label row; an in-place action
53
+ (no popup, so no chevron) underlines its value. Both are shapes, never colour alone. */
54
+ .uix-stat__go { display: inline-flex; flex: none; margin-inline-start: auto; color: var(--uix-text-muted); }
55
+ .uix-stat__label .uix-stat__go svg { width: var(--uix-icon-sm); height: var(--uix-icon-sm); }
56
+ .uix-stat--action .uix-stat__value { text-decoration: underline dotted var(--uix-text-muted); text-decoration-thickness: from-font; text-underline-offset: .2em; }
44
57
  /* current: the ring outline only; a brand tint would drop the muted label below AA */
45
58
  .uix-stat--link[aria-current="page"] { border-color: var(--uix-ring); box-shadow: inset 0 0 0 1px var(--uix-ring); }
46
59
  @media (prefers-reduced-motion: reduce) { .uix-stat--interactive, .uix-stat__label .uix-stat__chevron svg { transition: none; } }
47
60
  }
61
+
62
+ /* Stat row (HAR-1803 review): tiles side by side while each keeps a readable width, then fewer per
63
+ row, down to one on a phone. A fixed six-column grid broke labels letter by letter at 320 px
64
+ ("AS/SI/GN/EE"). --stat-row-min sets the narrowest tile: 8.5rem, and 12rem with --hero (or once
65
+ a hero tile, with its 34 px value, is in the row); min(100%, …) keeps a single tile inside a
66
+ container narrower than that. */
67
+ @layer uix.components {
68
+ .uix-stat-row { --stat-row-auto: 8.5rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--stat-row-min, var(--stat-row-auto))), 1fr)); gap: var(--uix-space-3); min-width: 0; }
69
+ .uix-stat-row--hero, .uix-stat-row:has(> .uix-stat:not(.uix-stat--compact)) { --stat-row-auto: 12rem; }
70
+ }
71
+
@@ -22,3 +22,53 @@
22
22
  .uix-operation__description { color: var(--uix-text-muted); font-size: var(--uix-text-meta); }
23
23
  .uix-operation__action { justify-self: start; }
24
24
  }
25
+
26
+ /* the in-app reduced-motion preference (HAR-1372, <html data-motion="reduce">): the same pulse */
27
+ @layer uix.components {
28
+ @media (prefers-reduced-motion: no-preference) { :root[data-motion="reduce"] .uix-skeleton { animation: uix-pulse var(--uix-loop-pulse) var(--uix-ease-in-out) infinite !important; } }
29
+ }
30
+
31
+ /* uix success state (HAR-2588): the outcome of a rare, important journey — a request
32
+ submitted. The mark draws once (the disc settles in, then the check is drawn), never repeats,
33
+ never bounces; reduced motion shows it at once. Then the title (focused on mount), the
34
+ reference, what happens next and the next action. */
35
+ @layer uix.components {
36
+ .uix-success { gap: var(--uix-space-4); color: var(--uix-text); }
37
+ .uix-success__mark { inline-size: var(--uix-space-12); block-size: var(--uix-space-12); }
38
+ .uix-success__mark svg { inline-size: 100%; block-size: 100%; overflow: visible; }
39
+ .uix-success__disc { fill: var(--uix-success-bg); stroke: var(--uix-success); stroke-width: 1.5; transform-box: fill-box; transform-origin: center; }
40
+ .uix-success__check { stroke: var(--uix-success-text); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 1; stroke-dashoffset: 0; }
41
+ @keyframes uix-success-disc { from { opacity: 0; transform: scale(.85); } }
42
+ /* hidden until it starts drawing: a round cap on the zero-length dash would show as a dot */
43
+ @keyframes uix-success-check { 0% { stroke-dashoffset: 1; opacity: 0; } 1% { opacity: 1; } }
44
+ .uix-success__disc { animation: uix-success-disc var(--uix-dur-slow) var(--uix-ease-out) both; }
45
+ .uix-success__check { animation: uix-success-check var(--uix-dur-slower) var(--uix-ease-out) var(--uix-dur-fast) both; }
46
+ @media (prefers-reduced-motion: reduce) {
47
+ /* no settle, no drawing: the disc fades in with the check already there */
48
+ .uix-success__disc { animation: uix-fade-in var(--uix-dur) var(--uix-ease-out) both !important; }
49
+ .uix-success__check { animation: none !important; }
50
+ }
51
+ .uix-success__title { margin: 0; }
52
+ .uix-success__title:focus { outline: none; }
53
+ .uix-success__title:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: var(--uix-space-1); border-radius: var(--uix-radius-sm); }
54
+ .uix-success__body { max-inline-size: 52ch; color: var(--uix-text-muted); }
55
+ .uix-success__body > p { margin: 0; }
56
+ .uix-success__reference { margin: 0; color: var(--uix-text-muted); }
57
+ .uix-success__reference-label { font-size: var(--uix-text-meta); }
58
+ .uix-success__reference-value { display: inline-flex; align-items: center; gap: var(--uix-space-1); font-family: var(--uix-font-mono); font-weight: 600; color: var(--uix-text); }
59
+ .uix-success__next { inline-size: min(100%, 28rem); padding: var(--uix-space-4); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); background: var(--uix-surface); text-align: start; }
60
+ .uix-success__next-title { margin: 0 0 var(--uix-space-2); font-size: var(--uix-text-body); font-weight: 600; }
61
+ .uix-success__steps { margin: 0; padding-inline-start: var(--uix-space-5); display: grid; gap: var(--uix-space-2); color: var(--uix-text-muted); }
62
+ @media (forced-colors: active) {
63
+ .uix-success__disc { fill: Canvas; stroke: CanvasText; }
64
+ .uix-success__check { stroke: CanvasText; }
65
+ }
66
+ }
67
+
68
+ /* the in-app reduced-motion preference (HAR-1372, <html data-motion="reduce">): the mark is simply there */
69
+ @layer uix.components {
70
+ @media (prefers-reduced-motion: no-preference) {
71
+ :root[data-motion="reduce"] .uix-success__disc { animation: uix-fade-in var(--uix-dur) var(--uix-ease-out) both !important; }
72
+ :root[data-motion="reduce"] .uix-success__check { animation: none !important; }
73
+ }
74
+ }
@@ -28,12 +28,32 @@
28
28
  .uix-pill--p4 { --tone-bg: var(--uix-info-bg); --tone-fg: var(--uix-info-text); --tone-dot: var(--uix-info); }
29
29
  .uix-pill--p5 { --tone-bg: var(--uix-bg-active); --tone-fg: var(--uix-text-hushed); --tone-dot: var(--uix-text-muted); }
30
30
 
31
- /* outline treatment — transparent fill, tone-tinted border, tone-colored text. The border tints
32
- the tone hue (--tone-fg) at 40%; neutral/muted use the structural borders, critical reads strongest. */
33
- .uix-pill--outline { background: transparent; border: 1px solid var(--tone-line, color-mix(in srgb, var(--tone-fg) 40%, transparent)); }
34
- .uix-pill--outline.uix-pill--neutral { --tone-line: var(--uix-border-strong); }
35
- .uix-pill--outline.uix-pill--muted { --tone-line: var(--uix-border); }
36
- .uix-pill--outline.uix-pill--critical { color: var(--uix-danger-text); --tone-dot: var(--uix-danger); --tone-line: color-mix(in srgb, var(--uix-danger) 60%, transparent); }
31
+ /* in flight (HAR-1563): the same blue as info, under the name the colour policy uses */
32
+ .uix-pill--progress { --tone-bg: var(--uix-info-bg); --tone-fg: var(--uix-info-text); --tone-dot: var(--uix-info); }
33
+
34
+ /* each tone's solid, for the quiet treatments below, where the dot is the only hue (HAR-1563) */
35
+ .uix-pill--neutral, .uix-pill--muted, .uix-pill--p5 { --tone-solid: var(--uix-neutral-solid); }
36
+ /* the pale hues mix 40 % of their text role in, so a light-mode dot clears 3:1 on the surface
37
+ (success 2.54 -> 3.38, warning 2.15 -> 3.32, amber 2.94 -> 4.09) and a dark one stays calm */
38
+ .uix-pill--success, .uix-pill--sla-ok { --tone-solid: color-mix(in srgb, var(--uix-success-solid) 60%, var(--uix-success-text)); }
39
+ .uix-pill--info, .uix-pill--progress, .uix-pill--p4 { --tone-solid: var(--uix-info-solid); }
40
+ .uix-pill--warning, .uix-pill--p2 { --tone-solid: color-mix(in srgb, var(--uix-warning-solid) 60%, var(--uix-warning-text)); }
41
+ .uix-pill--p3 { --tone-solid: color-mix(in srgb, var(--uix-amber) 60%, var(--uix-amber-text)); }
42
+ .uix-pill--danger, .uix-pill--critical, .uix-pill--p1 { --tone-solid: var(--uix-danger-solid); }
43
+ .uix-pill--attention, .uix-pill--sla-at-risk { --tone-solid: var(--uix-attention-solid); }
44
+ .uix-pill--overdue, .uix-pill--sla-breached { --tone-solid: var(--uix-overdue-solid); }
45
+
46
+ /* dot treatment (HAR-1563) — for dense tables: no fill and no padding, a neutral label, and the
47
+ tone only in the dot. The label is the meaning; the dot is the glance. */
48
+ .uix-pill--dot { background: transparent; padding-inline: 0; color: var(--uix-neutral-text); }
49
+ .uix-pill--dot .uix-pill__dot { background: var(--tone-solid, var(--uix-neutral-solid)); }
50
+
51
+ /* outline treatment (HAR-1563) — a neutral hairline, a hushed label and the tone's dot. Only
52
+ critical keeps a toned border, so the one thing that needs action still stands out. */
53
+ .uix-pill--outline { background: transparent; border: 1px solid var(--uix-neutral-border); color: var(--uix-neutral-text); }
54
+ .uix-pill--outline .uix-pill__dot { background: var(--tone-solid, var(--uix-neutral-solid)); }
55
+ .uix-pill--outline.uix-pill--muted { border-color: var(--uix-border); }
56
+ .uix-pill--outline.uix-pill--critical { border-color: var(--uix-danger-solid); color: var(--uix-danger-text); }
37
57
  }
38
58
 
39
59
  /* Sizes (HAR-1614): sm for dense cells, lg for a page header. The default is unchanged. */
@@ -42,4 +62,6 @@
42
62
  .uix-pill--sm .uix-pill__dot { width: 6px; height: 6px; }
43
63
  .uix-pill--lg { padding: var(--uix-space-2) var(--uix-space-4); font-size: var(--uix-text-body); }
44
64
  .uix-pill--lg .uix-pill__dot { width: 9px; height: 9px; }
65
+ /* the dot treatment has no box at any size: its label lines up with the cell's other text */
66
+ .uix-pill--dot:is(.uix-pill--sm, .uix-pill--lg) { padding-inline: 0; }
45
67
  }
@@ -32,6 +32,8 @@
32
32
  .uix-steps--horizontal > .uix-step > .uix-step__text { flex: 0 0 auto; max-width: 12rem; }
33
33
  .uix-step[data-state="waiting"] .uix-step__marker { border-style: dashed; border-color: var(--uix-warning-border); color: var(--uix-warning-text); }
34
34
  .uix-step[data-state="error"] { color: var(--uix-text); }
35
+ /* the error marker is the icon set's X (HAR-1576), the size of the done check */
36
+ .uix-step__marker > .uix-icon { width: 12px; height: 12px; }
35
37
  .uix-step[data-state="error"] .uix-step__marker { background: var(--uix-danger-bg); border-color: var(--uix-danger-border); color: var(--uix-danger-text); }
36
38
  /* vertical (and horizontal below 40rem): a column, the connector a line under the marker */
37
39
  .uix-steps--vertical { flex-direction: column; gap: 0; }
@@ -60,7 +60,7 @@
60
60
  /* FilterEditor (React, HAR-1365): typed filter content inside .uix-filter-popover */
61
61
  @layer uix.components {
62
62
  .uix-filter-editor { width: min(22rem, calc(100vw - 4rem)); min-width: 0; gap: var(--uix-space-3); }
63
- .uix-filter-editor[data-kind="number"] .uix-filter-editor__row > .uix-select { flex-basis: 100%; }
63
+ .uix-filter-editor:is([data-kind="number"], [data-kind="date"]) .uix-filter-editor__row > .uix-select { flex-basis: 100%; }
64
64
  .uix-filter-editor__label { font-weight: 600; color: var(--uix-text); }
65
65
  .uix-filter-editor__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--uix-space-2); }
66
66
  .uix-filter-editor__row > .uix-input, .uix-filter-editor__row > .uix-select { flex: 1 1 8rem; min-width: 0; }