@tensor_1/tokens 2.45.1 → 2.47.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.
@@ -47,9 +47,14 @@
47
47
  .uix-table[data-density="compact"] td, .uix-table--compact td { height: var(--uix-row-h-compact); }
48
48
  .uix-table[data-density="comfortable"] td { height: var(--uix-row-h-comfortable); }
49
49
 
50
- .uix-table__pin { border: 0; background: transparent; color: var(--uix-text-muted); cursor: pointer; padding: var(--uix-space-1); border-radius: var(--uix-radius-xs); opacity: 0; transition: opacity var(--uix-dur-fast) var(--uix-ease-out); }
51
- /* :focus-visible reveal mirrors .uix-rowact so keyboard users can find the pin (UIX-A11Y-2) */
52
- .uix-table tbody tr:hover .uix-table__pin, .uix-table__pin:focus-visible, .uix-table__pin[data-on] { opacity: 1; }
50
+ .uix-table__pin { border: 0; background: transparent; color: var(--uix-text-muted); cursor: pointer; padding: var(--uix-space-1); border-radius: var(--uix-radius-xs); transition: opacity var(--uix-dur-fast) var(--uix-ease-out); }
51
+ /* Hidden until hover only where hover exists; a touch screen always shows the pin. The
52
+ :focus-visible reveal mirrors .uix-rowact so keyboard users can find it (UIX-A11Y-2). */
53
+ @media (hover: hover) and (pointer: fine) {
54
+ .uix-table__pin { opacity: 0; }
55
+ .uix-table tbody tr:hover .uix-table__pin { opacity: 1; }
56
+ }
57
+ .uix-table__pin:focus-visible, .uix-table__pin[data-on] { opacity: 1; }
53
58
  .uix-table__pin:hover { background: var(--uix-bg-active); color: var(--uix-text); }
54
59
  .uix-table__pin[data-on] { color: var(--uix-accent); }
55
60
  .uix-table__pin svg { width: 15px; height: 15px; }
@@ -112,10 +117,14 @@
112
117
  .uix-id-cell__btn:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: 2px; border-radius: var(--uix-radius-xs); }
113
118
  .uix-id-cell__arrow {
114
119
  width: var(--uix-icon-sm); height: var(--uix-icon-sm); flex-shrink: 0; color: var(--uix-text-muted);
115
- opacity: 0; transition: opacity var(--uix-dur-fast) var(--uix-ease-out);
120
+ transition: opacity var(--uix-dur-fast) var(--uix-ease-out);
116
121
  }
117
122
  /* :focus-within as well: a keyboard user on the id link sees the arrow too (HAR-1814). */
118
- .uix-table tbody tr:hover .uix-id-cell__arrow, .uix-table tbody tr:focus-within .uix-id-cell__arrow { opacity: 1; }
123
+ @media (hover: hover) and (pointer: fine) {
124
+ .uix-id-cell__arrow { opacity: 0; }
125
+ .uix-table tbody tr:hover .uix-id-cell__arrow { opacity: 1; }
126
+ }
127
+ .uix-table tbody tr:focus-within .uix-id-cell__arrow { opacity: 1; }
119
128
 
120
129
  /* ── selection column + select-all ────────────────────────────────────────────── */
121
130
  .uix-table th.uix-table__selcol, .uix-table td.uix-table__selcell { width: 44px; padding-left: var(--uix-space-3); padding-right: 0; text-align: center; }
@@ -136,10 +145,15 @@
136
145
 
137
146
  /* ── row hover actions + kebab ─────────────────────────────────────────────────── */
138
147
  .uix-table__actions { width: 84px; text-align: right; white-space: nowrap; }
139
- .uix-rowact { border: 0; background: transparent; color: var(--uix-text-muted); cursor: pointer; padding: var(--uix-space-1); border-radius: var(--uix-radius-sm); opacity: 0; transition: opacity var(--uix-dur-fast) var(--uix-ease-out), background-color var(--uix-dur-fast) var(--uix-ease-out); }
148
+ .uix-rowact { border: 0; background: transparent; color: var(--uix-text-muted); cursor: pointer; padding: var(--uix-space-1); border-radius: var(--uix-radius-sm); transition: opacity var(--uix-dur-fast) var(--uix-ease-out), background-color var(--uix-dur-fast) var(--uix-ease-out); }
140
149
  .uix-rowact svg { width: 16px; height: 16px; display: block; }
141
- /* A row the keyboard is in shows its actions, as a hovered one does (HAR-1814). */
142
- .uix-table tbody tr:hover .uix-rowact, .uix-table tbody tr:focus-within .uix-rowact, .uix-rowact:focus-visible, .uix-rowact[aria-expanded="true"] { opacity: 1; }
150
+ /* Row actions hide until hover only where hover exists: on a touch screen they stay visible, so
151
+ hover is never the only way to reach them. A row the keyboard is in shows them too (HAR-1814). */
152
+ @media (hover: hover) and (pointer: fine) {
153
+ .uix-rowact { opacity: 0; }
154
+ .uix-table tbody tr:hover .uix-rowact { opacity: 1; }
155
+ }
156
+ .uix-table tbody tr:focus-within .uix-rowact, .uix-rowact:focus-visible, .uix-rowact[aria-expanded="true"] { opacity: 1; }
143
157
  .uix-rowact:hover { background: var(--uix-bg-active); color: var(--uix-text); }
144
158
 
145
159
  /* ── expandable inline row ─────────────────────────────────────────────────────── */
@@ -155,10 +169,13 @@
155
169
  @media (prefers-reduced-motion: reduce) { .uix-table__expand svg { transition: none; } }
156
170
 
157
171
  /* ── inline cell edit ──────────────────────────────────────────────────────────── */
158
- .uix-cell-edit { display: inline-flex; align-items: center; gap: var(--uix-space-2); cursor: text; border-radius: var(--uix-radius-sm); padding: var(--uix-space-1) var(--uix-space-2); margin: calc(-1 * var(--uix-space-1)) calc(-1 * var(--uix-space-2)); transition: background-color var(--uix-dur-fast) var(--uix-ease-out), box-shadow var(--uix-dur-fast) var(--uix-ease-out); }
172
+ .uix-cell-edit { display: inline-flex; align-items: center; gap: var(--uix-space-2); cursor: text; border-radius: var(--uix-radius-sm); padding: var(--uix-space-1) var(--uix-space-2); margin: calc(-1 * var(--uix-space-1)) calc(-1 * var(--uix-space-2)); transition: background-color var(--uix-dur-fast) var(--uix-ease-out); }
159
173
  .uix-cell-edit:hover { background: var(--uix-bg-active); }
160
- .uix-cell-edit__pencil { width: 13px; height: 13px; color: var(--uix-text-muted); opacity: 0; transition: opacity var(--uix-dur-fast) var(--uix-ease-out); }
161
- .uix-table tbody tr:hover .uix-cell-edit__pencil { opacity: 1; }
174
+ .uix-cell-edit__pencil { width: 13px; height: 13px; color: var(--uix-text-muted); transition: opacity var(--uix-dur-fast) var(--uix-ease-out); }
175
+ @media (hover: hover) and (pointer: fine) {
176
+ .uix-cell-edit__pencil { opacity: 0; }
177
+ .uix-table tbody tr:hover .uix-cell-edit__pencil { opacity: 1; }
178
+ }
162
179
  .uix-cell-edit[data-editing] { background: var(--uix-surface); box-shadow: 0 0 0 2px var(--uix-ring); cursor: default; }
163
180
  .uix-cell-edit__ok { color: var(--uix-success); width: 14px; height: 14px; opacity: 0; transition: opacity var(--uix-dur-fast) var(--uix-ease-out); }
164
181
  .uix-cell-edit[data-saved] .uix-cell-edit__ok { opacity: 1; }
@@ -5,7 +5,7 @@
5
5
  border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md);
6
6
  background: var(--uix-surface); color: var(--uix-text); font: inherit; font-size: var(--uix-text-body);
7
7
  line-height: var(--uix-leading-body);
8
- transition: border-color var(--uix-dur-fast) var(--uix-ease-out), box-shadow var(--uix-dur-fast) var(--uix-ease-out);
8
+ transition: border-color var(--uix-dur-fast) var(--uix-ease-out);
9
9
  }
10
10
  .uix-textarea::placeholder { color: var(--uix-text-muted); }
11
11
  .uix-textarea:hover { border-color: var(--uix-border-strong); }
@@ -6,7 +6,12 @@
6
6
  background: var(--uix-surface-2); border: 1px solid var(--uix-border); box-shadow: var(--uix-shadow-popover);
7
7
  animation: uix-slide-in-right var(--uix-dur-slow) var(--uix-ease-out);
8
8
  }
9
- .uix-toast[data-leaving] { animation: uix-toast-out var(--uix-dur) var(--uix-ease-in) forwards; }
9
+ .uix-toast[data-leaving] { animation: uix-toast-out var(--uix-dur-exit) var(--uix-ease-out) forwards; }
10
+ @media (prefers-reduced-motion: reduce) {
11
+ .uix-toast { animation: uix-fade-in var(--uix-dur) var(--uix-ease-out) !important; }
12
+ .uix-toast[data-leaving] { animation: uix-toast-fade-out var(--uix-dur-exit) var(--uix-ease-out) forwards !important; }
13
+ }
14
+ @keyframes uix-toast-fade-out { to { opacity: 0; } }
10
15
  @keyframes uix-toast-out { to { opacity: 0; transform: translateX(16px); } }
11
16
  .uix-toast__icon { flex: none; margin-top: 1px; }
12
17
  .uix-toast__icon svg { width: var(--uix-icon-md); height: var(--uix-icon-md); }
@@ -40,7 +40,8 @@
40
40
  opacity: 0; pointer-events: none; z-index: var(--uix-z-tooltip);
41
41
  transition: opacity var(--uix-dur-fast) var(--uix-ease-out), transform var(--uix-dur-fast) var(--uix-ease-out);
42
42
  }
43
- [data-uix-tip]:hover::after, [data-uix-tip]:focus-visible::after { opacity: 1; transform: translateX(-50%) translateY(0); }
43
+ [data-uix-tip]:focus-visible::after { opacity: 1; transform: translateX(-50%) translateY(0); }
44
+ @media (hover: hover) and (pointer: fine) { [data-uix-tip]:hover::after { opacity: 1; transform: translateX(-50%) translateY(0); } }
44
45
  /* when JS upgrades [data-uix-tip] to a top-layer bubble, drop the clipped CSS-only one */
45
46
  .uix-has-js-tip [data-uix-tip]::after { content: none; }
46
47
  }
@@ -46,7 +46,8 @@
46
46
  /* Grip and ⋯ are quiet until the row is hovered or holds focus, the grip is in use, or the menu is
47
47
  open — at 75%, the same WCAG 1.4.11 floor as the saved-views row (see below). */
48
48
  :is(.uix-view-menu__grip, .uix-view-menu__actions) { opacity: .75; transition: opacity var(--uix-dur-fast) var(--uix-ease-out); }
49
- .uix-view-menu__col:is(:hover, :focus-within, [data-dragging]) :is(.uix-view-menu__grip, .uix-view-menu__actions),
49
+ @media (hover: hover) and (pointer: fine) { .uix-view-menu__col:hover :is(.uix-view-menu__grip, .uix-view-menu__actions) { opacity: 1; } }
50
+ .uix-view-menu__col:is(:focus-within, [data-dragging]) :is(.uix-view-menu__grip, .uix-view-menu__actions),
50
51
  .uix-view-menu__actions:has([aria-expanded="true"]) { opacity: 1; }
51
52
  /* Saved views: optional titled sections in one scrolling menu; a row is grip · name · overflow.
52
53
  Hover and selection paint the WHOLE row; the selected row is an accent tint, never a check glyph. */
@@ -69,7 +70,8 @@
69
70
  the grip is in use, or the overflow menu is open. Idle is 75% (not lower): the muted grip on the
70
71
  panel surface needs it to keep WCAG 1.4.11 3:1 non-text contrast at rest (3.19:1 light, 4.45:1 dark). */
71
72
  :is(.uix-saved-views__grip, .uix-saved-views__actions) { opacity: .75; transition: opacity var(--uix-dur-fast) var(--uix-ease-out); }
72
- .uix-saved-views__row:is(:hover, :focus-within, [data-dragging]) :is(.uix-saved-views__grip, .uix-saved-views__actions),
73
+ @media (hover: hover) and (pointer: fine) { .uix-saved-views__row:hover :is(.uix-saved-views__grip, .uix-saved-views__actions) { opacity: 1; } }
74
+ .uix-saved-views__row:is(:focus-within, [data-dragging]) :is(.uix-saved-views__grip, .uix-saved-views__actions),
73
75
  .uix-saved-views__actions:has([aria-expanded="true"]) { opacity: 1; }
74
76
  .uix-saved-views__pin { display: grid; place-items: center; width: 28px; height: 28px; border: 0; border-radius: var(--uix-radius-sm); background: transparent; color: var(--uix-text-muted); cursor: pointer; }
75
77
  .uix-saved-views__pin:is(:hover, :focus-visible), .uix-saved-views__pin[data-on] { color: var(--uix-warning-text); background: var(--uix-bg-hover); }
package/styles/motion.css CHANGED
@@ -1,12 +1,23 @@
1
- /* uix motion — shared keyframes, enter/exit helpers, and the reduced-motion guard.
2
- Components reference these tokens/keyframes; overlays pair the helpers with @starting-style. */
1
+ /* uix motion — shared keyframes, enter/exit helpers, the instant-surface rule and the reduced-motion policy.
2
+ Components reference these tokens/keyframes; overlays pair the helpers with @starting-style.
3
+ The contract (docs → Foundations → Motion) is checked by packages/tokens/scripts/check-motion.mjs. */
3
4
 
4
- /* Reduced-motion guard — UNLAYERED so its !important beats every component transition. */
5
+ /* Reduced-motion policy — UNLAYERED. `prefers-reduced-motion: reduce` removes MOVEMENT, not feedback:
6
+ - Transitions keep only fades and colour changes. Transform, translate, scale, rotate and any layout
7
+ glide drop out of every transition list, so a popover or drawer still fades but no longer travels.
8
+ allow-discrete keeps display/overlay exits painted while their fade runs.
9
+ - Keyframe animations end at once (we cannot tell a product's keyframes from a fade). UIx's own
10
+ still-working indicators (Spinner, Button loading, skeleton, indeterminate progress, SaveStatus,
11
+ a running flow node, Heartbeat) opt back in from their component layer with a slow opacity pulse
12
+ (`uix-pulse`, --uix-loop-pulse) declared !important — a layered !important beats this unlayered one —
13
+ so the status stays visibly alive instead of freezing. */
5
14
  @media (prefers-reduced-motion: reduce) {
6
15
  *, *::before, *::after {
16
+ transition-property: opacity, color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, visibility, overlay, display !important;
17
+ transition-behavior: allow-discrete !important;
18
+ /* motion-allow: ends product keyframes at once while still firing animationend (a 0s duration is skipped by some engines' event paths) */
7
19
  animation-duration: .001ms !important;
8
20
  animation-iteration-count: 1 !important;
9
- transition-duration: .001ms !important;
10
21
  scroll-behavior: auto !important;
11
22
  }
12
23
  }
@@ -21,15 +32,27 @@
21
32
  @keyframes uix-slide-in-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
22
33
  @keyframes uix-spin { to { transform: rotate(1turn); } }
23
34
  @keyframes uix-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
35
+ /* The reduced-motion stand-in for every looping indicator: no movement, a slow breath of opacity. */
36
+ @keyframes uix-pulse { 50% { opacity: .35; } }
24
37
 
25
38
  @layer uix.motion {
26
39
  .uix-anim-fade { animation: uix-fade-in var(--uix-dur) var(--uix-ease-out); }
27
40
  .uix-anim-pop { animation: uix-pop-in var(--uix-dur-fast) var(--uix-ease-out); }
28
41
  .uix-anim-up { animation: uix-slide-in-up var(--uix-dur) var(--uix-ease-out); }
42
+ @media (prefers-reduced-motion: reduce) {
43
+ /* the entrance helpers keep their fade and lose their travel */
44
+ .uix-anim-fade, .uix-anim-pop, .uix-anim-up { animation: uix-fade-in var(--uix-dur) var(--uix-ease-out) !important; }
45
+ }
29
46
 
30
- /* Hover-lift affordance for interactive cards/tiles. Gated so touch devices don't get a stuck hover. */
31
- .uix-lift-hover { transition: transform var(--uix-dur-fast) var(--uix-ease-out), box-shadow var(--uix-dur-fast) var(--uix-ease-out); }
47
+ /* Hover-lift affordance for interactive cards/tiles. Gated so touch devices don't get a stuck hover.
48
+ The shadow steps; only the lift animates (box-shadow is never animated). */
49
+ .uix-lift-hover { transition: transform var(--uix-dur-fast) var(--uix-ease-out); }
32
50
  @media (hover: hover) and (pointer: fine) {
33
51
  .uix-lift-hover:hover { transform: translateY(calc(-1 * var(--uix-lift))); box-shadow: var(--uix-shadow-popover); }
34
52
  }
53
+
54
+ /* Instant surfaces. Keyboard-initiated and 100×-a-day surfaces (the command palette, an overlay opened
55
+ from the keyboard) open and close with no animation — the frequency budget is "none". Overlays set
56
+ [data-uix-instant] themselves (Modal `instant`, CommandPalette, keyboard-opened menus and selects). */
57
+ [data-uix-instant], [data-uix-instant]::backdrop { transition: none !important; animation: none !important; }
35
58
  }
@@ -7,8 +7,8 @@
7
7
 
8
8
  "ease-out": { "$type": "cubicBezier", "$value": "cubic-bezier(.23,1,.32,1)" },
9
9
  "ease-out-strong": { "$type": "cubicBezier", "$value": "cubic-bezier(.2,0,0,1)" },
10
- "ease-in": { "$type": "cubicBezier", "$value": "cubic-bezier(.4,0,1,1)" },
11
- "ease-in-out": { "$type": "cubicBezier", "$value": "cubic-bezier(.77,0,.175,1)" },
10
+ "ease-in": { "$type": "cubicBezier", "$value": "cubic-bezier(.4,0,1,1)", "$description": "Kept for compatibility only. An accelerating curve on something the user watches reads as lag, so exits use --uix-ease-out at a --uix-dur-exit-* duration; check-motion.mjs fails component CSS that uses ease-in." },
11
+ "ease-in-out": { "$type": "cubicBezier", "$value": "cubic-bezier(.77,0,.175,1)", "$description": "Movement already on screen (a strong in-out, easeInOutQuart). The spec's older cubic-bezier(.4,0,.2,1) was never shipped; this value is canonical." },
12
12
  "ease-spring": { "$type": "cubicBezier", "$value": "cubic-bezier(.34,1.56,.64,1)" },
13
13
 
14
14
  "dur-fast": { "$type": "duration", "$value": "120ms" },
@@ -16,6 +16,21 @@
16
16
  "dur-slow": { "$type": "duration", "$value": "240ms" },
17
17
  "dur-slower": { "$type": "duration", "$value": "320ms" },
18
18
 
19
+ "dur-exit-fast": { "$type": "duration", "$value": "80ms", "$description": "Exit paired with --uix-dur-fast (about two-thirds of the enter). Exits are faster than enters and never ease-in." },
20
+ "dur-exit": { "$type": "duration", "$value": "110ms", "$description": "Exit paired with --uix-dur (popovers, menus, select, toasts)." },
21
+ "dur-exit-slow": { "$type": "duration", "$value": "160ms", "$description": "Exit paired with --uix-dur-slow (modal, drawer, side peek)." },
22
+ "dur-highlight": { "$type": "duration", "$value": "2400ms", "$description": "How long an arrival or change tint takes to fade back to the row colour. Not a transition budget: the tint is a held signal, not movement." },
23
+
24
+ "delay-tooltip": { "$type": "duration", "$value": "400ms", "$description": "Hover delay before a tooltip opens. Keyboard focus opens it at once." },
25
+ "delay-tooltip-skip": { "$type": "duration", "$value": "300ms", "$description": "Delay-group window: a neighbouring tooltip hovered within this long after one closed opens at once." },
26
+
27
+ "loop-spin": { "$type": "duration", "$value": "600ms", "$description": "One turn of a spinner (Spinner, Button loading, a running flow node)." },
28
+ "loop-progress": { "$type": "duration", "$value": "1200ms", "$description": "One sweep of indeterminate progress." },
29
+ "loop-shimmer": { "$type": "duration", "$value": "1400ms", "$description": "One pass of the skeleton shimmer." },
30
+ "loop-ping": { "$type": "duration", "$value": "1600ms", "$description": "One heartbeat ping." },
31
+ "loop-blink": { "$type": "duration", "$value": "1100ms", "$description": "One caret blink cycle." },
32
+ "loop-pulse": { "$type": "duration", "$value": "2000ms", "$description": "One cycle of the reduced-motion opacity pulse that replaces spinners, shimmer and indeterminate progress, so a still-working indicator stays visibly alive without movement." },
33
+
19
34
  "lift": { "$type": "dimension", "$value": "4px" },
20
35
 
21
36
  "z-raised": { "$type": "number", "$value": "1", "$description": "z-index ladder (semantic stacking order). raised = locally lifted within a component: a sticky table header & first column, a flow node title over its diamond, a heartbeat dot over its ping." },