actual-css 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +217 -0
- package/README.md +13 -11
- package/components.json +668 -0
- package/dist/actual.css +166 -37
- package/dist/actual.full.css +1087 -643
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/accessibility-decisions.md +33 -0
- package/docs/design-notes/browser-support.md +62 -30
- package/docs/design-notes/column-layout.md +3 -3
- package/docs/design-notes/css-anchor.md +75 -21
- package/docs/design-notes/enhancement-contract.md +14 -6
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +6 -1
- package/docs/design-notes/platform-alignment.md +53 -7
- package/docs/design-notes/popover-manual-poc.md +105 -25
- package/docs/design-notes/surface-reparenting.md +56 -47
- package/docs/design-notes/tooltip-coordinate-space.md +132 -0
- package/docs/design-notes/widget-primitives.md +7 -4
- package/docs/pages/components/accordion.md +41 -2
- package/docs/pages/components/alert.md +85 -30
- package/docs/pages/components/app-navigation.md +6 -0
- package/docs/pages/components/avatar.md +6 -6
- package/docs/pages/components/badge.md +101 -16
- package/docs/pages/components/button.md +16 -8
- package/docs/pages/components/card.md +10 -10
- package/docs/pages/components/dialog.md +21 -6
- package/docs/pages/components/drawer.md +13 -0
- package/docs/pages/components/fab.md +4 -3
- package/docs/pages/components/list.md +42 -4
- package/docs/pages/components/navbar.md +17 -1
- package/docs/pages/components/pagination.md +2 -2
- package/docs/pages/components/skeleton.md +3 -1
- package/docs/pages/components/steps.md +19 -0
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/enhancements/flyout.md +82 -38
- package/docs/pages/enhancements/overview.md +25 -0
- package/docs/pages/enhancements/status-bar.md +1 -1
- package/docs/pages/enhancements/tabs.md +45 -37
- package/docs/pages/enhancements/tooltip.md +36 -10
- package/docs/pages/examples/overview.md +38 -5
- package/docs/pages/forms/choice-cards.md +11 -6
- package/docs/pages/forms/combobox.md +1 -1
- package/docs/pages/forms/floating-field.md +4 -3
- package/docs/pages/forms/joined-controls.md +22 -0
- package/docs/pages/forms/otp.md +2 -1
- package/docs/pages/forms/overview.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +16 -18
- package/docs/pages/forms/sticky-actions.md +2 -2
- package/docs/pages/forms/switch.md +1 -1
- package/docs/pages/foundations/philosophy.md +57 -26
- package/docs/pages/foundations/theming.md +2 -2
- package/docs/pages/foundations/tokens.md +118 -21
- package/docs/pages/foundations/typography.md +35 -14
- package/docs/pages/guides/introduction.md +11 -9
- package/docs/pages/guides/progressive-enhancement.md +115 -30
- package/docs/pages/guides/tailwind.md +3 -2
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +11 -1
- package/docs/pages/layout/scroll-snap.md +3 -3
- package/docs/pages/layout/stack.md +17 -9
- package/docs/pages/patterns/icon-input.md +4 -2
- package/docs/pages/patterns/icon-slot.md +2 -0
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/utilities/overview.md +23 -2
- package/docs/pages/utilities/sizing-wrapping.md +1 -1
- package/docs/pages/utilities/spacing.md +10 -8
- package/llms.txt +2 -1
- package/package.json +159 -149
- package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
- package/src/css/components/accordion.css +42 -3
- package/src/css/components/alert.css +25 -13
- package/src/css/components/app-nav.css +18 -8
- package/src/css/components/avatar.css +13 -1
- package/src/css/components/badge.css +78 -12
- package/src/css/components/breadcrumb.css +2 -3
- package/src/css/components/button.css +9 -16
- package/src/css/components/card.css +9 -2
- package/src/css/components/chat.css +1 -0
- package/src/css/components/data-list.css +1 -1
- package/src/css/components/drawer.css +101 -32
- package/src/css/components/fab.css +0 -6
- package/src/css/components/flyout.css +35 -98
- package/src/css/components/index.css +0 -1
- package/src/css/components/indicator.css +21 -0
- package/src/css/components/key.css +10 -0
- package/src/css/components/list.css +40 -4
- package/src/css/components/menu.css +2 -1
- package/src/css/components/modal.css +48 -33
- package/src/css/components/navbar.css +37 -4
- package/src/css/components/pagination.css +1 -0
- package/src/css/components/skeleton.css +15 -4
- package/src/css/components/spinner.css +6 -6
- package/src/css/components/status-bar.css +0 -5
- package/src/css/components/steps.css +45 -59
- package/src/css/components/tab.css +29 -9
- package/src/css/components/table.css +13 -5
- package/src/css/components/tooltip.css +36 -40
- package/src/css/core/base.css +23 -1
- package/src/css/core/intents.css +17 -6
- package/src/css/core/theme.css +27 -0
- package/src/css/core/tokens.css +35 -15
- package/src/css/core/variants.css +64 -15
- package/src/css/forms/choice-card.css +4 -4
- package/src/css/forms/choice.css +76 -29
- package/src/css/forms/control.css +2 -1
- package/src/css/forms/floating-field.css +3 -3
- package/src/css/forms/form.css +4 -3
- package/src/css/forms/input-icon.css +20 -14
- package/src/css/forms/native.css +6 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +2 -0
- package/src/css/forms/select.css +29 -9
- package/src/css/forms/switch.css +28 -8
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/grid.css +50 -76
- package/src/css/layout/scroll-snap.css +11 -7
- package/src/css/layout/scroller.css +3 -4
- package/src/css/layout/stack.css +2 -2
- package/src/css/typography/fluid.css +4 -3
- package/src/css/typography/prose.css +6 -5
- package/src/css/utilities/base.css +2 -1
- package/src/css/utilities/extra.css +20 -17
- package/src/js/context-menu.js +27 -26
- package/src/js/dialog.js +2 -1
- package/src/js/dismiss.js +5 -0
- package/src/js/enhance.js +34 -2
- package/src/js/enhancement-loader.js +36 -43
- package/src/js/events.js +0 -3
- package/src/js/filter.js +0 -1
- package/src/js/floating-space.js +42 -0
- package/src/js/flyout.js +9 -44
- package/src/js/focus-group.js +7 -0
- package/src/js/focus.js +6 -0
- package/src/js/menu.js +10 -1
- package/src/js/selectors.js +5 -9
- package/src/js/status.js +6 -5
- package/src/js/surface.js +97 -139
- package/src/js/tab.js +6 -1
- package/src/js/tooltip.js +150 -51
- package/src/js/validation.js +1 -1
- package/src/css/components/surface.css +0 -49
|
@@ -38,10 +38,31 @@
|
|
|
38
38
|
inset-block-end: 0;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/* Safari before 16.4 has no :dir(); the ancestor-attribute path is the
|
|
42
|
+
Degraded-tier fallback and the :dir() rules are dropped on their own there.
|
|
43
|
+
:dir() follows inherited directionality, so the explicit ltr rules
|
|
44
|
+
neutralize an outer RTL fallback for a nested direction change. The rules
|
|
45
|
+
stay grouped by specificity so the fallback never follows the :dir() rules. */
|
|
46
|
+
[dir="rtl"] .indicator-item {
|
|
47
|
+
--indicator-translate-x: -50%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.indicator-item:dir(ltr) {
|
|
51
|
+
--indicator-translate-x: 50%;
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
.indicator-item:dir(rtl) {
|
|
42
55
|
--indicator-translate-x: -50%;
|
|
43
56
|
}
|
|
44
57
|
|
|
58
|
+
[dir="rtl"] .indicator-item.start {
|
|
59
|
+
--indicator-translate-x: 50%;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.indicator-item.start:dir(ltr) {
|
|
63
|
+
--indicator-translate-x: -50%;
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
.indicator-item.start:dir(rtl) {
|
|
46
67
|
--indicator-translate-x: 50%;
|
|
47
68
|
}
|
|
@@ -27,3 +27,13 @@
|
|
|
27
27
|
vertical-align: 0.08em;
|
|
28
28
|
}
|
|
29
29
|
/* @sync key-recipe */
|
|
30
|
+
|
|
31
|
+
/* .key has a compact typographic default of its own, so its local size roles
|
|
32
|
+
step around that default instead of inheriting the generic mapping. */
|
|
33
|
+
.key.sm {
|
|
34
|
+
font-size: var(--font-size-xs);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.key.lg {
|
|
38
|
+
font-size: var(--font-size-md);
|
|
39
|
+
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
--list-divider: var(--border-width) solid var(--border);
|
|
19
19
|
|
|
20
20
|
display: grid;
|
|
21
|
+
grid-template-columns: minmax(0, 1fr);
|
|
21
22
|
gap: 0;
|
|
22
23
|
margin: 0;
|
|
23
24
|
padding: 0;
|
|
@@ -29,10 +30,17 @@
|
|
|
29
30
|
margin: 0;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/*
|
|
34
|
+
* Flex, not a three-track grid: the leading and trailing regions are optional,
|
|
35
|
+
* and a fixed `auto minmax(0, 1fr) auto` template gives an absent region a
|
|
36
|
+
* track anyway — the content of a row with no leading region lands in the
|
|
37
|
+
* leading track and the trailing control inherits the flexible one, which is
|
|
38
|
+
* the opposite of the intent. Flex drops both the track and its gap when a
|
|
39
|
+
* region is not there, so all four shapes work off one rule.
|
|
40
|
+
*/
|
|
32
41
|
.list > :where(.list-item),
|
|
33
42
|
.list > li > .list-item {
|
|
34
|
-
display:
|
|
35
|
-
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
43
|
+
display: flex;
|
|
36
44
|
/* Regions align to the first line, so leading, title, and trailing controls
|
|
37
45
|
share one horizontal line while supporting text wraps beneath. */
|
|
38
46
|
align-items: start;
|
|
@@ -48,6 +56,7 @@
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
a.list-item {
|
|
59
|
+
position: relative;
|
|
51
60
|
color: inherit;
|
|
52
61
|
text-decoration: none;
|
|
53
62
|
}
|
|
@@ -56,7 +65,31 @@ a.list-item:hover {
|
|
|
56
65
|
background: var(--surface-subtle);
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
/* Current row in a navigational list (master/detail). Generic by design: the
|
|
69
|
+
visual treatment only cares that the row is current, so presence is the
|
|
70
|
+
contract — the attribute is set on the current row and removed everywhere
|
|
71
|
+
else, never serialized as aria-current="false". The value (page, location,
|
|
72
|
+
…) stays free for assistive technology.
|
|
73
|
+
Calm by design: the row keeps its normal text metrics and color on a subtle
|
|
74
|
+
surface, and the selected accent lives on an inset inline-start trait only.
|
|
75
|
+
The trait is a real border, so it survives forced colors without a repaint
|
|
76
|
+
block. The positioning context lives on every row link, so becoming current
|
|
77
|
+
never moves an app-positioned decoration to a new containing block. */
|
|
78
|
+
a.list-item[aria-current] {
|
|
79
|
+
background: var(--surface-subtle);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
a.list-item[aria-current]::before {
|
|
83
|
+
content: "";
|
|
84
|
+
position: absolute;
|
|
85
|
+
inset-block: var(--list-item-pad-block);
|
|
86
|
+
inset-inline-start: 0;
|
|
87
|
+
border-inline-start: 2px solid var(--state-selected);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* The content region is the one that flexes, whichever position it holds. */
|
|
59
91
|
.list-item-content {
|
|
92
|
+
flex: 1 1 0;
|
|
60
93
|
min-inline-size: 0;
|
|
61
94
|
}
|
|
62
95
|
|
|
@@ -64,16 +97,19 @@ a.list-item:hover {
|
|
|
64
97
|
.list-item-text {
|
|
65
98
|
display: block;
|
|
66
99
|
margin: 0;
|
|
100
|
+
overflow-wrap: anywhere;
|
|
67
101
|
}
|
|
68
102
|
|
|
69
103
|
.list-item-text {
|
|
70
104
|
color: var(--text-muted);
|
|
71
105
|
font-size: var(--font-size-sm);
|
|
72
|
-
overflow-wrap: anywhere;
|
|
73
106
|
}
|
|
74
107
|
|
|
108
|
+
/* The auto margin keeps the trailing region at the row's end even in a row
|
|
109
|
+
with no content region to absorb the free space. */
|
|
75
110
|
.list-item-trailing {
|
|
76
|
-
|
|
111
|
+
flex: none;
|
|
112
|
+
margin-inline-start: auto;
|
|
77
113
|
min-inline-size: 0;
|
|
78
114
|
}
|
|
79
115
|
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
background: transparent;
|
|
45
45
|
color: var(--intent, var(--text));
|
|
46
46
|
font: inherit;
|
|
47
|
+
font-size: var(--control-font-size);
|
|
47
48
|
text-align: start;
|
|
48
49
|
text-decoration: none;
|
|
49
50
|
white-space: nowrap;
|
|
@@ -124,7 +125,7 @@
|
|
|
124
125
|
padding: var(--space-20);
|
|
125
126
|
color: var(--text-muted);
|
|
126
127
|
font-size: var(--font-size-xs);
|
|
127
|
-
font-weight: var(--font-weight-
|
|
128
|
+
font-weight: var(--font-weight-semibold);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
.menu-separator {
|
|
@@ -12,21 +12,40 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Internal:
|
|
14
14
|
* --dialog-close-icon-size
|
|
15
|
+
* --dialog-viewport-block
|
|
16
|
+
* --dialog-viewport-gap-default
|
|
15
17
|
* --dialog-viewport-gap-2x
|
|
16
18
|
*/
|
|
17
19
|
dialog.modal {
|
|
18
|
-
--
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
/* --dialog-viewport-gap stays fallback-only for the same reason as
|
|
21
|
+
--modal-size below: declaring the public hook here would give it
|
|
22
|
+
type+class specificity that a single author class cannot outrank. The
|
|
23
|
+
responsive default lives in an internal token instead, so a media query
|
|
24
|
+
can still move it without closing the hook.
|
|
25
|
+
|
|
26
|
+
calc() multiplication is not in the supported browser range, so the
|
|
21
27
|
doubled gap is spelled out with an additive calc. */
|
|
22
|
-
--dialog-viewport-gap-
|
|
28
|
+
--dialog-viewport-gap-default: var(--space-40);
|
|
29
|
+
--dialog-viewport-gap-2x: calc(
|
|
30
|
+
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default)) +
|
|
31
|
+
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default))
|
|
32
|
+
);
|
|
23
33
|
|
|
24
34
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
25
35
|
provides the containing block for the absolutely positioned close button. */
|
|
26
36
|
position: fixed;
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
/* --modal-size stays fallback-only: declaring it on dialog.modal gives it
|
|
39
|
+
type+class specificity, which a single author class cannot outrank, so the
|
|
40
|
+
public hook would silently keep this default. Same reason choice.css never
|
|
41
|
+
declares --choice-control-nudge. */
|
|
42
|
+
max-inline-size: min(calc(100% - var(--dialog-viewport-gap-2x)), var(--modal-size, 32rem));
|
|
43
|
+
|
|
44
|
+
/* Stable viewport cap for the panel: dvh tracks the dynamic browser toolbar
|
|
45
|
+
and makes the dialog reflow on mobile, svh does not. App shells keep the
|
|
46
|
+
dynamic token; only the modal pins the small viewport. */
|
|
47
|
+
--dialog-viewport-block: var(--viewport-block);
|
|
48
|
+
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
30
49
|
border: var(--border-width) solid var(--border);
|
|
31
50
|
border-radius: var(--radius-lg);
|
|
32
51
|
background: var(--surface-raised);
|
|
@@ -36,7 +55,7 @@ dialog.modal {
|
|
|
36
55
|
overflow: visible;
|
|
37
56
|
opacity: 0;
|
|
38
57
|
/*
|
|
39
|
-
* Keep the open dialog root untransformed. Fixed overlays
|
|
58
|
+
* Keep the open dialog root untransformed. Fixed overlays authored inside a
|
|
40
59
|
* modal dialog use viewport coordinates; transform/translate/scale on this
|
|
41
60
|
* element would make them position against the dialog instead. The closed
|
|
42
61
|
* state can still carry a small transform for enter/exit motion. The lift
|
|
@@ -53,16 +72,23 @@ dialog.modal > form,
|
|
|
53
72
|
dialog.modal > .stack {
|
|
54
73
|
display: grid;
|
|
55
74
|
gap: var(--space-40);
|
|
56
|
-
max-block-size: calc(var(--viewport-block) - var(--dialog-viewport-gap-2x));
|
|
75
|
+
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
57
76
|
overflow: auto;
|
|
58
77
|
overscroll-behavior: contain;
|
|
59
78
|
padding: var(--space-40);
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
|
|
81
|
+
@supports (block-size: 100svh) {
|
|
82
|
+
dialog.modal {
|
|
83
|
+
--dialog-viewport-block: 100svh;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Keep the dialog off the viewport edges on small screens. Moves the internal
|
|
88
|
+
default, not the public hook, so an author override still wins here too. */
|
|
63
89
|
@media (max-width: 30rem) {
|
|
64
90
|
dialog.modal {
|
|
65
|
-
--dialog-viewport-gap: var(--space-30);
|
|
91
|
+
--dialog-viewport-gap-default: var(--space-30);
|
|
66
92
|
}
|
|
67
93
|
}
|
|
68
94
|
|
|
@@ -72,8 +98,8 @@ dialog.modal.scrollable > .stack {
|
|
|
72
98
|
overflow: hidden;
|
|
73
99
|
}
|
|
74
100
|
|
|
75
|
-
dialog.modal.scrollable > form > :not(header, footer),
|
|
76
|
-
dialog.modal.scrollable > .stack > :not(header, footer) {
|
|
101
|
+
dialog.modal.scrollable > form > :not(:is(header, footer)),
|
|
102
|
+
dialog.modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
77
103
|
min-block-size: 0;
|
|
78
104
|
overflow: auto;
|
|
79
105
|
overscroll-behavior: contain;
|
|
@@ -128,6 +154,7 @@ dialog.modal.dialog-confirmation > form > footer {
|
|
|
128
154
|
:where(.dialog-icon) {
|
|
129
155
|
--intent: initial;
|
|
130
156
|
--intent-fg: initial;
|
|
157
|
+
--intent-soft-fg: initial;
|
|
131
158
|
--ui-bg: initial;
|
|
132
159
|
--ui-fg: initial;
|
|
133
160
|
}
|
|
@@ -256,14 +283,11 @@ html.has-modal-open.had-scrollbar {
|
|
|
256
283
|
}
|
|
257
284
|
}
|
|
258
285
|
|
|
259
|
-
/* Close
|
|
260
|
-
never participates in the content flow
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
required in the markup. */
|
|
265
|
-
.dialog-close,
|
|
266
|
-
.drawer-close {
|
|
286
|
+
/* Close control. A close button is an action attached to a surface, so it
|
|
287
|
+
never participates in the content flow — absolute at the panel's top
|
|
288
|
+
inline-end, inside the surface. The glyph comes from --icon-close, masked to
|
|
289
|
+
currentColor, so no icon font or text glyph is required in the markup. */
|
|
290
|
+
.dialog-close {
|
|
267
291
|
--dialog-close-icon-size: 1rem;
|
|
268
292
|
|
|
269
293
|
display: inline-grid;
|
|
@@ -287,8 +311,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
287
311
|
background: var(--surface-subtle);
|
|
288
312
|
}
|
|
289
313
|
|
|
290
|
-
.dialog-close::before
|
|
291
|
-
.drawer-close::before {
|
|
314
|
+
.dialog-close::before {
|
|
292
315
|
content: "";
|
|
293
316
|
inline-size: var(--dialog-close-icon-size);
|
|
294
317
|
block-size: var(--dialog-close-icon-size);
|
|
@@ -298,25 +321,17 @@ html.has-modal-open.had-scrollbar {
|
|
|
298
321
|
}
|
|
299
322
|
|
|
300
323
|
@media (hover: hover) {
|
|
301
|
-
.dialog-close:hover
|
|
302
|
-
.drawer-close:hover {
|
|
324
|
+
.dialog-close:hover {
|
|
303
325
|
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
304
326
|
color: var(--text);
|
|
305
327
|
}
|
|
306
328
|
}
|
|
307
329
|
|
|
308
|
-
.dialog-close:active
|
|
309
|
-
.drawer-close:active {
|
|
330
|
+
.dialog-close:active {
|
|
310
331
|
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
311
332
|
}
|
|
312
333
|
|
|
313
|
-
.dialog-close:focus-visible
|
|
314
|
-
.drawer-close:focus-visible {
|
|
334
|
+
.dialog-close:focus-visible {
|
|
315
335
|
outline: 2px solid transparent;
|
|
316
336
|
box-shadow: var(--focus-ring-shadow);
|
|
317
337
|
}
|
|
318
|
-
@media print {
|
|
319
|
-
dialog.modal[open] {
|
|
320
|
-
display: none !important;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
@@ -9,8 +9,15 @@
|
|
|
9
9
|
* --gap (local value for the shared gap token)
|
|
10
10
|
*/
|
|
11
11
|
/* Intent boundary — inherited intents do not tint this component.
|
|
12
|
-
Local intent classes still win because :where() has zero specificity.
|
|
12
|
+
Local intent classes still win because :where() has zero specificity.
|
|
13
|
+
The intent tokens are reset alongside --ui-*: the shared variants
|
|
14
|
+
(.soft/.solid/.outline/.surface) read --intent / --intent-fg / --intent-soft-fg
|
|
15
|
+
when they paint this surface, so an ancestor intent would otherwise leak
|
|
16
|
+
through a variant that has no local intent class. */
|
|
13
17
|
:where(.navbar) {
|
|
18
|
+
--intent: initial;
|
|
19
|
+
--intent-fg: initial;
|
|
20
|
+
--intent-soft-fg: initial;
|
|
14
21
|
--ui-bg: initial;
|
|
15
22
|
--ui-fg: initial;
|
|
16
23
|
--ui-border: initial;
|
|
@@ -66,10 +73,15 @@
|
|
|
66
73
|
text-decoration: none;
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
/* nav-link serves two contracts: plain page navigation (aria-current="page")
|
|
77
|
+
and scrollspy, which toggles aria-current="location" on the active link.
|
|
78
|
+
Presence is the contract: the attribute is set on the current link and
|
|
79
|
+
removed everywhere else, never serialized as aria-current="false".
|
|
80
|
+
Selection never changes text metrics: the shared medium weight is kept and
|
|
81
|
+
the current link is carried by the selected accent. */
|
|
82
|
+
.nav-link[aria-current] {
|
|
70
83
|
background: var(--surface-subtle);
|
|
71
|
-
color: var(--
|
|
72
|
-
font-weight: var(--font-weight-bold);
|
|
84
|
+
color: var(--state-selected);
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
.nav-link:hover {
|
|
@@ -77,11 +89,18 @@
|
|
|
77
89
|
color: var(--text);
|
|
78
90
|
}
|
|
79
91
|
|
|
92
|
+
/* A current link keeps its accent on hover: without this, the hover rule above
|
|
93
|
+
would drop it back to --text. The surface hover still applies. */
|
|
94
|
+
.nav-link[aria-current]:hover {
|
|
95
|
+
color: var(--state-selected);
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
81
99
|
Links fill each row so the whole line is an interactive target; the
|
|
82
100
|
horizontal .navbar-nav links keep their intrinsic size. */
|
|
83
101
|
.nav-list {
|
|
84
102
|
display: grid;
|
|
103
|
+
grid-template-columns: minmax(0, 1fr);
|
|
85
104
|
--gap: var(--space-20);
|
|
86
105
|
gap: var(--gap);
|
|
87
106
|
margin: 0;
|
|
@@ -94,5 +113,19 @@
|
|
|
94
113
|
}
|
|
95
114
|
|
|
96
115
|
.nav-list .nav-link {
|
|
116
|
+
position: relative;
|
|
97
117
|
inline-size: 100%;
|
|
98
118
|
}
|
|
119
|
+
|
|
120
|
+
/* Vertical current indicator: inset inline-start trait in currentColor, so it
|
|
121
|
+
follows the forced palette without a forced-colors block and never touches
|
|
122
|
+
the row separators. Navbar horizontal links keep color + surface only. The
|
|
123
|
+
positioning context lives on every row link, so becoming current never moves
|
|
124
|
+
an app-positioned decoration to a new containing block. */
|
|
125
|
+
.nav-list .nav-link[aria-current]::before {
|
|
126
|
+
content: "";
|
|
127
|
+
position: absolute;
|
|
128
|
+
inset-block: var(--space-20);
|
|
129
|
+
inset-inline-start: 0;
|
|
130
|
+
border-inline-start: 2px solid currentColor;
|
|
131
|
+
}
|
|
@@ -11,16 +11,24 @@
|
|
|
11
11
|
* --skeleton-track
|
|
12
12
|
* --skeleton-highlight
|
|
13
13
|
*/
|
|
14
|
+
/* A placeholder fills its container up to --skeleton-width; the preset widths
|
|
15
|
+
are a cap, never an intrinsic size. Stated the other way round — a rem
|
|
16
|
+
`inline-size` capped by a percentage `max-inline-size` — the cap is ignored
|
|
17
|
+
during intrinsic sizing, so every shrink-to-fit ancestor inherits the
|
|
18
|
+
preset as a min-content floor: a card holding a title placeholder could not
|
|
19
|
+
go below 18rem and pushed a phone's layout sideways. A percentage
|
|
20
|
+
`inline-size` contributes nothing intrinsically and a rem cap only lowers
|
|
21
|
+
the contribution, so the same geometry costs no floor. */
|
|
14
22
|
.skeleton {
|
|
15
23
|
--skeleton-size: 1rem;
|
|
16
|
-
--skeleton-width:
|
|
24
|
+
--skeleton-width: none;
|
|
17
25
|
--skeleton-radius: var(--radius);
|
|
18
26
|
--skeleton-track: var(--surface-subtle);
|
|
19
27
|
--skeleton-highlight: var(--surface-raised);
|
|
20
28
|
|
|
21
29
|
display: block;
|
|
22
|
-
inline-size:
|
|
23
|
-
max-inline-size:
|
|
30
|
+
inline-size: 100%;
|
|
31
|
+
max-inline-size: var(--skeleton-width);
|
|
24
32
|
block-size: var(--skeleton-size);
|
|
25
33
|
border-radius: var(--skeleton-radius);
|
|
26
34
|
background:
|
|
@@ -44,11 +52,14 @@
|
|
|
44
52
|
--skeleton-width: 18rem;
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/* The one preset with a real intrinsic width: a disc is square, so it sizes
|
|
56
|
+
rather than caps. The floor it does carry is the avatar size, which is small
|
|
57
|
+
enough never to widen a layout. */
|
|
47
58
|
.skeleton[data-shape="avatar"] {
|
|
48
59
|
--skeleton-size: var(--avatar-size, 2.25rem);
|
|
49
|
-
--skeleton-width: var(--skeleton-size);
|
|
50
60
|
--skeleton-radius: var(--radius-full);
|
|
51
61
|
|
|
62
|
+
inline-size: var(--skeleton-size);
|
|
52
63
|
flex: 0 0 auto;
|
|
53
64
|
}
|
|
54
65
|
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
border-inline-end-color: var(--spinner-value);
|
|
32
32
|
border-radius: var(--radius-full);
|
|
33
33
|
opacity: 0.85;
|
|
34
|
-
animation: actual-spin var(--duration-spin) linear infinite;
|
|
35
34
|
vertical-align: text-bottom;
|
|
36
35
|
}
|
|
37
36
|
|
|
@@ -52,13 +51,14 @@
|
|
|
52
51
|
--spinner-width: 3px;
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
55
|
+
.spinner {
|
|
56
|
+
animation: actual-spin var(--duration-spin) linear infinite;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
@keyframes actual-spin {
|
|
56
61
|
to {
|
|
57
62
|
transform: rotate(360deg);
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
|
-
@media print {
|
|
61
|
-
.spinner {
|
|
62
|
-
display: none !important;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -8,23 +8,16 @@
|
|
|
8
8
|
*
|
|
9
9
|
* `.steps` is what a step *is* — the counter, the marker, `.step-label`,
|
|
10
10
|
* `.step-complete`, `aria-current="step"`. It carries no orientation, so it is
|
|
11
|
-
* never written alone.
|
|
12
|
-
*
|
|
13
|
-
* has to exclude, so no rule says `:not(...)` and no rule undoes another's
|
|
14
|
-
* layout.
|
|
11
|
+
* never written alone. The two orientations are peers: no rule says
|
|
12
|
+
* `:not(...)` and no rule undoes another's layout.
|
|
15
13
|
*
|
|
16
|
-
* Orientation and labels are independent
|
|
17
|
-
*
|
|
18
|
-
* one:
|
|
14
|
+
* Orientation and labels are independent. `.step-label` is optional in either;
|
|
15
|
+
* when a sequence carries names, every step carries one:
|
|
19
16
|
*
|
|
20
17
|
* empty steps .step-label on every step
|
|
21
18
|
* horizontal compact progression named sequence, stacked or inline
|
|
22
19
|
* vertical compact rail named sequence, labels beside markers
|
|
23
20
|
*
|
|
24
|
-
* An empty step is a marker and nothing else, so it is compact by structure —
|
|
25
|
-
* `> li:empty` in both orientations, no variant class, and nothing authored
|
|
26
|
-
* only to be hidden again. The page around it owns the step name.
|
|
27
|
-
*
|
|
28
21
|
* A label may be an `a[href]`, which is the whole interactive surface: the
|
|
29
22
|
* marker is never navigable, so navigating a flow means naming its
|
|
30
23
|
* destinations. Anything else focusable in a step is out of contract.
|
|
@@ -95,7 +88,7 @@
|
|
|
95
88
|
background: var(--step-marker-bg);
|
|
96
89
|
color: var(--step-marker-fg);
|
|
97
90
|
font-size: var(--font-size-sm);
|
|
98
|
-
font-weight: var(--font-weight-
|
|
91
|
+
font-weight: var(--font-weight-semibold);
|
|
99
92
|
line-height: 1;
|
|
100
93
|
}
|
|
101
94
|
|
|
@@ -155,11 +148,9 @@
|
|
|
155
148
|
border-width: calc(var(--border-width) * 2);
|
|
156
149
|
}
|
|
157
150
|
|
|
158
|
-
/*
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
font-weight: var(--font-weight-strong);
|
|
162
|
-
}
|
|
151
|
+
/* No label weight change for current: the accented marker with its doubled
|
|
152
|
+
border already carries the state, and selection never changes text metrics.
|
|
153
|
+
Complete needs none either: its filled disc already reads as strongly. */
|
|
163
154
|
|
|
164
155
|
.steps > .step-complete:not([aria-current="step"])::before {
|
|
165
156
|
content: var(--step-complete-mark, counter(actual-step));
|
|
@@ -217,8 +208,8 @@
|
|
|
217
208
|
`:empty` says exactly what the markup says, the way `.badge:empty` does, and
|
|
218
209
|
it reads the item's own content: bare text in an `<li>` keeps a reading width
|
|
219
210
|
like its wrapped peers, where `> li:has(.step-label)` would give it a marker
|
|
220
|
-
floor and skew the row.
|
|
221
|
-
|
|
211
|
+
floor and skew the row. `:empty` also keeps the compact form reachable on the
|
|
212
|
+
Degraded tier, where only the inline bonus needs `:has()`. */
|
|
222
213
|
.steps-horizontal > li:empty {
|
|
223
214
|
flex: 1 1 0;
|
|
224
215
|
justify-items: start;
|
|
@@ -249,48 +240,43 @@
|
|
|
249
240
|
}
|
|
250
241
|
|
|
251
242
|
/*
|
|
252
|
-
* The one horizontal container query
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* serves every count. Below it, stacked; a marker row is already the shape
|
|
257
|
-
* this query produces and stays out of it.
|
|
243
|
+
* The one horizontal container query: past 60rem a labelled row can put marker
|
|
244
|
+
* and label inline and still leave the connector a real region between groups.
|
|
245
|
+
* It counts nothing, so it serves every count; below it, stacked. A marker row
|
|
246
|
+
* is already the shape this query produces and stays out of it.
|
|
258
247
|
*
|
|
259
|
-
* Horizontal is a 2-5 step component. Outside that domain the rule
|
|
260
|
-
*
|
|
261
|
-
* curated fallback.
|
|
248
|
+
* Horizontal is a 2-5 step component. Outside that domain the rule applies as
|
|
249
|
+
* written — a row out of contract is not owed a curated fallback.
|
|
262
250
|
*/
|
|
263
|
-
@
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
flex: 0 0 auto;
|
|
293
|
-
}
|
|
251
|
+
@container actual-container (inline-size >= 60rem) {
|
|
252
|
+
.steps-horizontal:has(.step-label) > li {
|
|
253
|
+
display: flex;
|
|
254
|
+
flex: 1 1 auto;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: var(--step-inline-gap);
|
|
257
|
+
min-inline-size: 0;
|
|
258
|
+
text-align: start;
|
|
259
|
+
white-space: nowrap;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.steps-horizontal:has(.step-label) > li::after {
|
|
263
|
+
position: static;
|
|
264
|
+
flex: 1 1 2rem;
|
|
265
|
+
inline-size: auto;
|
|
266
|
+
block-size: var(--step-size);
|
|
267
|
+
margin-inline-end: var(--step-inline-gap);
|
|
268
|
+
background: var(
|
|
269
|
+
--step-inline-connector,
|
|
270
|
+
var(
|
|
271
|
+
--step-connector,
|
|
272
|
+
linear-gradient(var(--step-line), var(--step-line)) center /
|
|
273
|
+
100% var(--step-line-size) no-repeat
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.steps-horizontal:has(.step-label) > li:last-child {
|
|
279
|
+
flex: 0 0 auto;
|
|
294
280
|
}
|
|
295
281
|
}
|
|
296
282
|
|