@tensor_1/tokens 2.48.0 → 2.50.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/build/css/components.css +1 -1
- package/build/css/styles.css +1 -1
- package/build/css/tokens.css +4 -0
- package/build/ts/tokens.d.ts +1 -1
- package/build/ts/tokens.js +8 -0
- package/package.json +1 -1
- package/styles/components/card.css +12 -0
- package/styles/components/data-workspace.css +106 -6
- package/styles/components/drag.css +63 -0
- package/styles/components/drawer.css +6 -5
- package/styles/components/kanban.css +10 -1
- package/styles/components/kbd.css +25 -0
- package/styles/components/list-motion.css +27 -0
- package/styles/components/modal.css +6 -5
- package/styles/components/peek.css +6 -5
- package/styles/components/popover.css +10 -10
- package/styles/components/relationship-graph.css +32 -11
- package/styles/components/search-suggest.css +6 -6
- package/styles/components/segmented.css +21 -0
- package/styles/components/select.css +9 -2
- package/styles/components/states.css +45 -0
- package/styles/components/table.css +35 -24
- package/styles/components/tabs.css +24 -0
- package/styles/components/toast.css +18 -15
- package/styles/components/tooltip.css +7 -3
- package/styles/motion.css +5 -4
- package/tokens/base/effect.json +6 -0
|
@@ -27,4 +27,25 @@
|
|
|
27
27
|
shift its neighbours on every selection. */
|
|
28
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); }
|
|
29
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; } }
|
|
30
51
|
}
|
|
@@ -66,8 +66,15 @@
|
|
|
66
66
|
}
|
|
67
67
|
.uix-select__popup:not([hidden]) { display: flex; }
|
|
68
68
|
[popover].uix-select__popup:not(:popover-open) { display: none; }
|
|
69
|
-
|
|
70
|
-
|
|
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); } }
|
|
71
78
|
.uix-select__listbox { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; position: relative; }
|
|
72
79
|
.uix-select__popup .uix-listbox { min-width: 0; padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; }
|
|
73
80
|
.uix-select__popup .uix-listbox:focus { outline: none; }
|
|
@@ -27,3 +27,48 @@
|
|
|
27
27
|
@layer uix.components {
|
|
28
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
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
|
+
}
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
/* multi-column sort ordinal (⇧+click adds a secondary key) */
|
|
28
28
|
.uix-table th[data-sort-order]::before { content: attr(data-sort-order); display: inline-block; margin-right: var(--uix-space-1); font-size: var(--uix-text-eyebrow); font-variant-numeric: tabular-nums; color: var(--uix-accent); }
|
|
29
29
|
|
|
30
|
+
/* With Th sortOrderLabel the ordinal is said in words inside the button (2.50, HAR-2623), so the
|
|
31
|
+
drawn number gets empty alt text; an engine without the alt syntax keeps the rule above. */
|
|
32
|
+
.uix-table th[data-sort-order][data-sort-labelled]::before { content: attr(data-sort-order) / ""; }
|
|
33
|
+
|
|
30
34
|
/* sortable header's real <button> (UIX-A11Y-2): visually indistinguishable from the old
|
|
31
35
|
plain-text th — inherited font/colour, no chrome — with an inset overlay so the whole
|
|
32
36
|
cell stays the click target (the sticky th is its containing block). The sort arrow and
|
|
@@ -37,14 +41,19 @@
|
|
|
37
41
|
.uix-th__sortbtn::before { content: ""; position: absolute; inset: 0; }
|
|
38
42
|
|
|
39
43
|
.uix-table td { padding: 0 var(--uix-space-4); height: var(--uix-row-h); border-bottom: 1px solid var(--uix-border); color: var(--uix-text); vertical-align: middle; }
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
/* The row's current state is one value, --uix-row-bg: the row paints it, and a frozen cell paints
|
|
45
|
+
it too (below), so a frozen cell can never keep the zebra stripe on a selected row (operator
|
|
46
|
+
review 2026-10-23). Every state rule has the same specificity (:where), so the order decides:
|
|
47
|
+
hover > selected > pinned > zebra > plain. --uix-row-bg is a registered colour (@property at
|
|
48
|
+
the end of this file), so the row eases the value itself and its frozen cells ease with it.
|
|
49
|
+
[data-selected] carries row-selection styling; aria-selected is invalid on a native
|
|
45
50
|
table row — AT state comes from the row checkbox (UIX-A11Y-2). */
|
|
46
|
-
.uix-table tbody tr
|
|
47
|
-
.uix-table tbody tr:
|
|
51
|
+
.uix-table tbody tr { --uix-row-bg: var(--uix-row); background: var(--uix-row-bg); transition: --uix-row-bg var(--uix-dur-fast) var(--uix-ease-out); }
|
|
52
|
+
.uix-table tbody tr:where(:nth-child(even)) { --uix-row-bg: var(--uix-row-alt); }
|
|
53
|
+
.uix-table--no-zebra tbody tr:where(:nth-child(even)) { --uix-row-bg: var(--uix-row); }
|
|
54
|
+
.uix-table tbody tr:where([data-pinned]) { --uix-row-bg: var(--uix-row-pinned-bg); }
|
|
55
|
+
.uix-table tbody tr:where([data-selected]) { --uix-row-bg: var(--uix-row-selected-bg); }
|
|
56
|
+
@media (hover: hover) { .uix-table tbody tr:where(:hover) { --uix-row-bg: var(--uix-bg-hover); } }
|
|
48
57
|
/* Row links stay calm on hover/focus: the row tint is the affordance, so neither the base
|
|
49
58
|
layer's `a:hover` underline nor link.css reaches them. `:not(.uix-btn)` keeps buttons
|
|
50
59
|
styled; the global :focus-visible ring still marks keyboard focus. */
|
|
@@ -76,21 +85,17 @@
|
|
|
76
85
|
sideways overflow (data-overflow="false") draws none. Without the measurement they stay. */
|
|
77
86
|
.uix-table-wrap[data-overflow="false"] :is(.uix-table--pinned-col :is(th, td):first-child, .uix-table--pinned-actions :is(th, td):last-child) { box-shadow: none; }
|
|
78
87
|
/* A pinned cell paints its row's state itself and must be opaque, or the cells scrolling
|
|
79
|
-
under it show through. The dark
|
|
80
|
-
over the surface
|
|
81
|
-
.uix-table--pinned-col tbody tr td:first-child, .uix-table--pinned-actions tbody tr td:last-child { background: var(--uix-row)
|
|
82
|
-
.uix-table--pinned-col tbody tr:nth-child(even) td:first-child, .uix-table--pinned-actions tbody tr:nth-child(even) td:last-child { background: var(--uix-row-alt); }
|
|
83
|
-
/* same specificity as the even-row rule above and the state rules below, so the order decides:
|
|
84
|
-
an :nth-child(even) here outranked hover, pinned and selected on every even row */
|
|
85
|
-
.uix-table--pinned-col.uix-table--no-zebra tbody tr td:first-child,
|
|
86
|
-
.uix-table--pinned-actions.uix-table--no-zebra tbody tr td:last-child { background: var(--uix-row); }
|
|
87
|
-
.uix-table--pinned-col tbody tr[data-pinned] td:first-child, .uix-table--pinned-actions tbody tr[data-pinned] td:last-child { background: linear-gradient(var(--uix-row-pinned-bg), var(--uix-row-pinned-bg)) var(--uix-surface); }
|
|
88
|
-
.uix-table--pinned-col tbody tr[data-selected] td:first-child, .uix-table--pinned-actions tbody tr[data-selected] td:last-child { background: linear-gradient(var(--uix-row-selected-bg), var(--uix-row-selected-bg)) var(--uix-surface); }
|
|
89
|
-
.uix-table--pinned-col tbody tr:hover td:first-child, .uix-table--pinned-actions tbody tr:hover td:last-child { background: var(--uix-bg-hover); }
|
|
88
|
+
under it show through. The dark tints are translucent, so the row's --uix-row-bg is layered
|
|
89
|
+
over the surface rather than painted bare. */
|
|
90
|
+
.uix-table--pinned-col tbody tr td:first-child, .uix-table--pinned-actions tbody tr td:last-child { background: linear-gradient(var(--uix-row-bg), var(--uix-row-bg)), var(--uix-surface); }
|
|
90
91
|
/* an expanded row's full-width detail cell is both first and last child: keep its own tint */
|
|
91
92
|
.uix-table--pinned-col tbody tr.uix-table__detail > td, .uix-table--pinned-actions tbody tr.uix-table__detail > td { background: var(--uix-bg-subtle); }
|
|
92
93
|
@media (prefers-reduced-motion: reduce) {
|
|
93
|
-
.uix-table tbody tr
|
|
94
|
+
.uix-table tbody tr { transition: none; }
|
|
95
|
+
}
|
|
96
|
+
/* forced colours keep a gradient image: a frozen cell is plain Canvas there, like its row */
|
|
97
|
+
@media (forced-colors: active) {
|
|
98
|
+
.uix-table--pinned-col tbody tr td:first-child, .uix-table--pinned-actions tbody tr td:last-child { background: Canvas; }
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
/* ── column sizing + cell behaviour (a shared vocabulary, not per-app px) ──────── */
|
|
@@ -142,19 +147,21 @@
|
|
|
142
147
|
.uix-table th.uix-table__selcol, .uix-table td.uix-table__selcell { width: var(--uix-selcol-w); padding-left: var(--uix-space-3); padding-right: 0; text-align: center; }
|
|
143
148
|
|
|
144
149
|
/* contextual bulk-action bar — appears when >=1 row is selected */
|
|
150
|
+
/* It wraps: on a phone the actions take a second row rather than run off the bar's edge, and
|
|
151
|
+
the count stays on one line (operator review 2026-10-23). */
|
|
145
152
|
.uix-bulkbar {
|
|
146
|
-
display: flex; align-items: center; gap: var(--uix-space-3);
|
|
153
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: var(--uix-space-2) var(--uix-space-3); min-width: 0;
|
|
147
154
|
padding: var(--uix-space-2) var(--uix-space-3); margin-bottom: var(--uix-space-3);
|
|
148
155
|
border: 1px solid var(--uix-accent); border-radius: var(--uix-radius-md);
|
|
149
156
|
background: var(--uix-brand-muted); color: var(--uix-text);
|
|
150
|
-
|
|
157
|
+
/* enters from @starting-style on a transition (interruptible), not a keyframe (HAR-2578) */
|
|
158
|
+
transition: opacity var(--uix-dur) var(--uix-ease-out), transform var(--uix-dur) var(--uix-ease-out);
|
|
151
159
|
}
|
|
160
|
+
@starting-style { .uix-bulkbar:not([hidden]) { opacity: 0; transform: translateY(calc(-1 * var(--uix-lift))); } }
|
|
152
161
|
.uix-bulkbar[hidden] { display: none; }
|
|
153
162
|
/* --uix-accent-text, not --uix-accent: the plain accent on the accent tint is 3.4:1 in dark (HAR-1388 specimen) */
|
|
154
|
-
.uix-bulkbar__count { font-size: var(--uix-text-meta); font-weight: 600; color: var(--uix-accent-text); font-variant-numeric: tabular-nums; }
|
|
163
|
+
.uix-bulkbar__count { font-size: var(--uix-text-meta); font-weight: 600; color: var(--uix-accent-text); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
155
164
|
.uix-bulkbar__spacer { flex: 1; }
|
|
156
|
-
@keyframes uix-bulkbar-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
|
|
157
|
-
@media (prefers-reduced-motion: reduce) { .uix-bulkbar { animation: none; } }
|
|
158
165
|
|
|
159
166
|
/* ── row hover actions + kebab ─────────────────────────────────────────────────── */
|
|
160
167
|
.uix-table__actions { width: 84px; text-align: right; white-space: nowrap; }
|
|
@@ -227,3 +234,7 @@
|
|
|
227
234
|
.uix-compare__head > div + div, .uix-compare__cell + .uix-compare__cell { border-left: 1px solid var(--uix-border); }
|
|
228
235
|
.uix-compare__attr { grid-column: 1 / -1; background: var(--uix-bg-subtle); padding: var(--uix-space-1) var(--uix-space-3); border-top: 1px solid var(--uix-border); font-size: var(--uix-text-eyebrow); letter-spacing: var(--uix-tracking-eyebrow); text-transform: uppercase; color: var(--uix-text-muted); }
|
|
229
236
|
}
|
|
237
|
+
|
|
238
|
+
/* The row's state colour (above) is registered so it can be eased: the row and its frozen cells,
|
|
239
|
+
which paint the same value, change together. Outside the layer: @property is global. */
|
|
240
|
+
@property --uix-row-bg { syntax: '<color>'; inherits: true; initial-value: transparent; }
|
|
@@ -68,5 +68,29 @@
|
|
|
68
68
|
.uix-tabs-scroller__prev svg, .uix-tabs-scroller__next svg { width: var(--uix-icon-sm); height: var(--uix-icon-sm); }
|
|
69
69
|
.uix-tabs-scroller__prev:dir(rtl) svg, .uix-tabs-scroller__next:dir(rtl) svg { transform: scaleX(-1); }
|
|
70
70
|
|
|
71
|
+
/* Sliding indicator (React Tabs, HAR-2589). At rest each tab shows its own mark. When a click
|
|
72
|
+
changes the tab, one element carries the line's underline or the pill's fill from the old
|
|
73
|
+
tab to the new one (transform only, about 160 ms); while it slides ([data-uix-indicator])
|
|
74
|
+
the new tab's own mark steps aside, then it hides again. Keyboard changes jump. */
|
|
75
|
+
/* the indicator is the last child: isolate + z-index -1 paint it above the row's own
|
|
76
|
+
background and beneath the tabs */
|
|
77
|
+
.uix-tabs { position: relative; isolation: isolate; }
|
|
78
|
+
/* clipped while it slides (a resize must not widen the page); a scrolling row contains it */
|
|
79
|
+
.uix-tabs[data-uix-indicator]:not(.uix-tabs--scroll) { overflow: clip; overflow-clip-margin: var(--uix-space-1); }
|
|
80
|
+
.uix-tabs__indicator { position: absolute; z-index: calc(-1 * var(--uix-z-raised)); pointer-events: none; transform-origin: 0 0; }
|
|
81
|
+
.uix-tabs__indicator[data-moving] { transition: transform var(--uix-dur) var(--uix-ease-in-out); }
|
|
82
|
+
.uix-tabs--line > .uix-tabs__indicator::after {
|
|
83
|
+
content: ""; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2px;
|
|
84
|
+
background: var(--uix-accent); border-radius: var(--uix-radius-pill);
|
|
85
|
+
}
|
|
86
|
+
.uix-tabs--scroll.uix-tabs--line > .uix-tabs__indicator::after { bottom: 0; }
|
|
87
|
+
.uix-tabs--pill > .uix-tabs__indicator { background: var(--uix-surface); box-shadow: var(--uix-shadow-sm); border-radius: calc(var(--uix-radius-md) - 3px); }
|
|
88
|
+
@media (forced-colors: none) {
|
|
89
|
+
.uix-tabs--line[data-uix-indicator] .uix-tab::after { visibility: hidden; }
|
|
90
|
+
.uix-tabs--pill[data-uix-indicator] .uix-tab[aria-selected="true"] { background: transparent; box-shadow: none; }
|
|
91
|
+
}
|
|
92
|
+
/* forced colours: the tabs' own marks are clearer than a painted box */
|
|
93
|
+
@media (forced-colors: active) { .uix-tabs__indicator { display: none; } }
|
|
94
|
+
|
|
71
95
|
@media (prefers-reduced-motion: reduce) { .uix-tabs--scroll { scroll-behavior: auto; } }
|
|
72
96
|
}
|
|
@@ -4,15 +4,18 @@
|
|
|
4
4
|
.uix-toast {
|
|
5
5
|
display: flex; gap: var(--uix-space-3); align-items: flex-start; padding: var(--uix-space-3) var(--uix-space-4); border-radius: var(--uix-radius-md);
|
|
6
6
|
background: var(--uix-surface-2); border: 1px solid var(--uix-border); box-shadow: var(--uix-shadow-popover);
|
|
7
|
-
|
|
7
|
+
/* Transitions, not keyframes, so a toast that is updated or re-shown mid-exit turns around
|
|
8
|
+
instead of restarting (HAR-2578). Enters from the toaster's edge at --uix-dur-slow; leaves the
|
|
9
|
+
same way at --uix-dur-exit (faster, still ease-out). `translate` is the swipe offset the React
|
|
10
|
+
Toast writes while a finger drags it; the Toaster glides the neighbours when one leaves. */
|
|
11
|
+
transition: opacity var(--uix-dur-slow) var(--uix-ease-out), transform var(--uix-dur-slow) var(--uix-ease-out),
|
|
12
|
+
translate var(--uix-dur) var(--uix-ease-out);
|
|
13
|
+
touch-action: pan-y;
|
|
8
14
|
}
|
|
9
|
-
.uix-toast
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
@keyframes uix-toast-fade-out { to { opacity: 0; } }
|
|
15
|
-
@keyframes uix-toast-out { to { opacity: 0; transform: translateX(16px); } }
|
|
15
|
+
@starting-style { .uix-toast { opacity: 0; transform: var(--uix-toast-from, translateX(var(--uix-space-4))); } }
|
|
16
|
+
.uix-toast[data-leaving] { opacity: 0; transform: var(--uix-toast-from, translateX(var(--uix-space-4))); pointer-events: none; transition-duration: var(--uix-dur-exit); }
|
|
17
|
+
/* while dragged the toast follows the finger; on release it snaps back or flies out */
|
|
18
|
+
.uix-toast[data-swiping] { transition: none; user-select: none; }
|
|
16
19
|
.uix-toast__icon { flex: none; margin-top: 1px; }
|
|
17
20
|
.uix-toast__icon svg { width: var(--uix-icon-md); height: var(--uix-icon-md); }
|
|
18
21
|
.uix-toast__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--uix-space-1); }
|
|
@@ -40,15 +43,15 @@
|
|
|
40
43
|
.uix-toast__action:focus-visible, .uix-toast__close:focus-visible { outline: 2px solid var(--uix-ring); outline-offset: 1px; }
|
|
41
44
|
.uix-toast__spinner { width: var(--uix-icon-md); height: var(--uix-icon-md); }
|
|
42
45
|
.uix-toaster[data-position^="top"] { top: var(--uix-space-4); bottom: auto; }
|
|
46
|
+
/* a toast enters from, and leaves toward, the toaster's own edge */
|
|
47
|
+
.uix-toaster[data-position$="start"] { --uix-toast-from: translateX(calc(-1 * var(--uix-space-4))); }
|
|
48
|
+
.uix-toaster[data-position="bottom-center"] { --uix-toast-from: translateY(var(--uix-space-4)); }
|
|
49
|
+
.uix-toaster[data-position="top-center"] { --uix-toast-from: translateY(calc(-1 * var(--uix-space-4))); }
|
|
43
50
|
.uix-toaster[data-position$="start"] { left: var(--uix-space-4); right: auto; }
|
|
44
51
|
.uix-toaster[data-position$="center"] { left: 50%; right: auto; transform: translateX(-50%); }
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
/*
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
:root[data-motion="reduce"] .uix-toast { animation: uix-fade-in var(--uix-dur) var(--uix-ease-out) !important; }
|
|
51
|
-
:root[data-motion="reduce"] .uix-toast[data-leaving] { animation: uix-toast-fade-out var(--uix-dur-exit) var(--uix-ease-out) forwards !important; }
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
+
/* The in-app reduced-motion preference (HAR-1372, <html data-motion="reduce">) needs no rule here: toasts
|
|
55
|
+
move on transitions (HAR-2578), which the motion.css policy narrows to fades for both the OS setting
|
|
56
|
+
and data-motion="reduce". */
|
|
54
57
|
|
|
@@ -15,12 +15,16 @@
|
|
|
15
15
|
padding: var(--uix-space-2) var(--uix-space-3); border-radius: var(--uix-radius-sm);
|
|
16
16
|
/* no pointer-events:none — the bubble must be hoverable (WCAG 1.4.13, UIX-A11Y-1) */
|
|
17
17
|
box-shadow: var(--uix-shadow-popover); white-space: normal;
|
|
18
|
-
|
|
18
|
+
/* grows out of the trigger (--uix-anchor-origin); the closed state carries the faster exit. The
|
|
19
|
+
React Tooltip waits --uix-delay-tooltip before it opens on hover, and a neighbour hovered within
|
|
20
|
+
--uix-delay-tooltip-skip of one closing opens at once with [data-uix-instant] (no fade). */
|
|
21
|
+
opacity: 0; transform: scale(.97); transform-origin: var(--uix-anchor-origin, bottom center);
|
|
22
|
+
transition: opacity var(--uix-dur-exit-fast) var(--uix-ease-out), transform var(--uix-dur-exit-fast) var(--uix-ease-out);
|
|
19
23
|
}
|
|
20
24
|
/* rich content (HAR-1349): a short list or a few lines, never interactive */
|
|
21
25
|
.uix-tooltip__bubble :is(ul, ol) { margin: 0; padding-left: 1.1em; }
|
|
22
26
|
.uix-tooltip__bubble :is(p, ul, ol) + :is(p, ul, ol) { margin-top: var(--uix-space-1); }
|
|
23
|
-
.uix-tooltip__bubble:popover-open { opacity: 1; }
|
|
27
|
+
.uix-tooltip__bubble:popover-open { opacity: 1; transform: none; transition-duration: var(--uix-dur-fast); }
|
|
24
28
|
/* A shortcut in the bubble (HAR-2031: Tooltip `shortcut`, Button `shortcut`): after the text,
|
|
25
29
|
the keys drawn as outlines in the bubble's own colour, so they keep its contrast in both
|
|
26
30
|
themes. A Button's hint bubble is one line: the name of an icon button, then the keys. */
|
|
@@ -28,7 +32,7 @@
|
|
|
28
32
|
.uix-tooltip__shortcut:first-child { margin-inline-start: 0; }
|
|
29
33
|
.uix-tooltip__shortcut .uix-kbd { color: inherit; background: transparent; border-color: color-mix(in srgb, currentColor 45%, transparent); }
|
|
30
34
|
.uix-tooltip__bubble--hint { white-space: nowrap; }
|
|
31
|
-
@starting-style { .uix-tooltip__bubble:popover-open { opacity: 0; } }
|
|
35
|
+
@starting-style { .uix-tooltip__bubble:popover-open { opacity: 0; transform: scale(.97); } }
|
|
32
36
|
|
|
33
37
|
/* legacy CSS-only tooltip — clipped by overflow ancestors; superseded by .uix-tooltip__bubble */
|
|
34
38
|
[data-uix-tip] { position: relative; }
|
package/styles/motion.css
CHANGED
|
@@ -69,8 +69,9 @@
|
|
|
69
69
|
.uix-lift-hover:hover { transform: translateY(calc(-1 * var(--uix-lift))); box-shadow: var(--uix-shadow-popover); }
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
/* Instant surfaces. Keyboard-initiated and 100×-a-day surfaces
|
|
73
|
-
|
|
74
|
-
[data-uix-
|
|
75
|
-
|
|
72
|
+
/* Instant surfaces. Keyboard-initiated and 100×-a-day surfaces open and close with no animation — the
|
|
73
|
+
frequency budget is "none". [data-uix-instant] is a standing choice (Modal `instant`, CommandPalette,
|
|
74
|
+
a tooltip opened inside its delay group); [data-uix-key-opened] is set per open or close by the React
|
|
75
|
+
overlays (Modal, Drawer, Peek, Popover and Menu, Select) when a key press led to it. */
|
|
76
|
+
:is([data-uix-instant], [data-uix-key-opened]), :is([data-uix-instant], [data-uix-key-opened])::backdrop { transition: none !important; animation: none !important; }
|
|
76
77
|
}
|
package/tokens/base/effect.json
CHANGED
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
"dur-exit-slow": { "$type": "duration", "$value": "160ms", "$description": "Exit paired with --uix-dur-slow (modal, drawer, side peek)." },
|
|
22
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
23
|
|
|
24
|
+
"dur-change": { "$type": "duration", "$value": "1200ms", "$description": "How long a change tint (ChangeHighlight, .uix-changed) takes to fade on the row or field that changed. Shorter than --uix-dur-highlight: a change in place, not a landing." },
|
|
25
|
+
|
|
26
|
+
"delay-pending": { "$type": "duration", "$value": "300ms", "$description": "How long a pending action waits before it shows a pending indicator: a spinner that would flash for less is noise." },
|
|
27
|
+
"hold-pending": { "$type": "duration", "$value": "500ms", "$description": "Once a pending indicator shows, the least time it stays, so it never flickers." },
|
|
28
|
+
"hold-result": { "$type": "duration", "$value": "1500ms", "$description": "How long an in-place result (a button's success or error state, a copied check) holds before it returns to rest." },
|
|
29
|
+
|
|
24
30
|
"delay-tooltip": { "$type": "duration", "$value": "400ms", "$description": "Hover delay before a tooltip opens. Keyboard focus opens it at once." },
|
|
25
31
|
"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
32
|
|