actual-css 0.11.1 → 0.13.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 +96 -0
- package/README.md +25 -6
- package/components.json +12 -18
- package/dist/actual.full.css +780 -479
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/brand/favicon.svg +5 -0
- package/docs/brand/lockup.svg +12 -0
- package/docs/brand/logo-dark.svg +9 -0
- package/docs/brand/logo-light.svg +9 -0
- package/docs/brand/logo.svg +11 -0
- package/docs/brand/mark.svg +11 -0
- package/docs/design-notes/architecture.md +67 -0
- package/docs/design-notes/steps.md +45 -28
- package/docs/navigation.json +3 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/avatar.md +17 -11
- package/docs/pages/components/badge.md +3 -9
- package/docs/pages/components/breadcrumb.md +16 -2
- package/docs/pages/components/card.md +31 -2
- package/docs/pages/components/dialog.md +121 -33
- package/docs/pages/components/drawer.md +2 -2
- package/docs/pages/components/list.md +9 -13
- package/docs/pages/components/navbar.md +49 -30
- package/docs/pages/components/pagination.md +19 -0
- package/docs/pages/components/steps.md +24 -20
- package/docs/pages/components/table.md +3 -3
- package/docs/pages/enhancements/flyout.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +7 -7
- package/docs/pages/enhancements/status-bar.md +2 -2
- package/docs/pages/enhancements/tabs.md +74 -3
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/date-picker.md +90 -0
- package/docs/pages/forms/detached-actions.md +37 -30
- package/docs/pages/forms/floating-field.md +21 -7
- package/docs/pages/forms/form-actions.md +58 -28
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/overview.md +6 -5
- package/docs/pages/forms/states-and-sizes.md +15 -15
- package/docs/pages/forms/sticky-actions.md +85 -83
- package/docs/pages/forms/validation.md +5 -5
- package/docs/pages/foundations/philosophy.md +102 -4
- package/docs/pages/foundations/theming.md +15 -4
- package/docs/pages/foundations/tokens.md +89 -36
- package/docs/pages/foundations/typography.md +4 -7
- package/docs/pages/guides/bootstrap.md +2 -2
- package/docs/pages/guides/design-tools.md +2 -2
- package/docs/pages/guides/integrating-actual-css.md +79 -9
- package/docs/pages/guides/introduction.md +5 -5
- package/docs/pages/guides/modular-import.md +3 -0
- package/docs/pages/guides/progressive-enhancement.md +27 -9
- package/docs/pages/guides/tailwind.md +70 -0
- package/docs/pages/guides/upgrading.md +151 -0
- package/docs/pages/layout/center.md +2 -1
- package/docs/pages/layout/cluster.md +37 -4
- package/docs/pages/layout/grid.md +2 -0
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/layout/sidebar-layout.md +3 -0
- package/docs/pages/layout/stack.md +21 -16
- package/docs/pages/layout/switcher.md +2 -0
- package/docs/pages/layout/topbar.md +7 -5
- package/docs/pages/patterns/icon-slot.md +17 -1
- package/docs/pages/patterns/navigation-list.md +16 -46
- package/docs/pages/patterns/overview.md +3 -4
- package/docs/pages/utilities/flex.md +30 -20
- package/docs/pages/utilities/sizing-wrapping.md +52 -7
- package/docs/pages/utilities/spacing.md +4 -3
- package/package.json +6 -6
- package/reserved-classes.json +3 -3
- package/skills/actual-css/references/enhancements.md +10 -7
- package/src/cli/actual-css.js +39 -13
- package/src/css/components/alert.css +34 -34
- package/src/css/components/avatar.css +8 -17
- package/src/css/components/badge.css +1 -6
- package/src/css/components/breadcrumb.css +74 -8
- package/src/css/components/button.css +3 -9
- package/src/css/components/card.css +18 -10
- package/src/css/components/chat.css +4 -5
- package/src/css/components/drawer.css +42 -45
- package/src/css/components/flyout.css +3 -1
- package/src/css/components/index.css +0 -1
- package/src/css/components/join.css +2 -2
- package/src/css/components/list.css +10 -10
- package/src/css/components/modal.css +113 -134
- package/src/css/components/navbar.css +22 -31
- package/src/css/components/pagination.css +5 -0
- package/src/css/components/scrollspy.css +1 -1
- package/src/css/components/steps.css +66 -12
- package/src/css/components/tab.css +134 -4
- package/src/css/core/base.css +1 -1
- package/src/css/core/focus.css +8 -6
- package/src/css/core/reset.css +2 -1
- package/src/css/core/theme.css +10 -1
- package/src/css/core/tokens.css +32 -12
- package/src/css/core/variants.css +41 -14
- package/src/css/forms/choice-card.css +1 -1
- package/src/css/forms/choice.css +1 -1
- package/src/css/forms/control.css +1 -1
- package/src/css/forms/floating-field.css +20 -18
- package/src/css/forms/form-actions.css +14 -12
- package/src/css/forms/native.css +2 -2
- package/src/css/forms/otp.css +4 -5
- package/src/css/forms/switch.css +1 -1
- package/src/css/forms/validation.css +23 -30
- package/src/css/layout/center.css +2 -0
- package/src/css/layout/cluster.css +25 -0
- package/src/css/layout/grid.css +7 -0
- package/src/css/layout/scroller.css +19 -3
- package/src/css/layout/sidebar-layout.css +3 -0
- package/src/css/layout/switcher.css +3 -0
- package/src/css/layout/topbar.css +12 -6
- package/src/css/utilities/base.css +5 -0
- package/src/css/utilities/extra.css +3 -7
- package/src/js/full.js +4 -0
- package/src/js/inline-visible.js +54 -0
- package/src/js/parsed.js +21 -0
- package/src/js/tab.js +33 -7
- package/src/tooling/color.js +65 -0
- package/src/tooling/design-export.js +1 -1
- package/src/tooling/design-tokens.js +53 -34
- package/src/tooling/theme-contrast.js +217 -0
- package/docs/pages/patterns/actions.md +0 -52
- package/src/css/components/actions.css +0 -27
|
@@ -50,6 +50,9 @@
|
|
|
50
50
|
position: absolute;
|
|
51
51
|
inset-block-start: calc(100% + var(--space-10));
|
|
52
52
|
inset-inline-start: 0;
|
|
53
|
+
/* Anatomy, not a default to replace: `flyout grid-3` (mega menu) takes its
|
|
54
|
+
columns from the grid preset and keeps this tight gap, because each
|
|
55
|
+
section pads itself — the visible gutter is section padding + gap. */
|
|
53
56
|
display: grid;
|
|
54
57
|
gap: var(--space-10);
|
|
55
58
|
inline-size: var(--flyout-inline-size, max-content);
|
|
@@ -90,7 +93,6 @@
|
|
|
90
93
|
.flyout[popover] {
|
|
91
94
|
position: fixed;
|
|
92
95
|
inset: auto;
|
|
93
|
-
margin: 0;
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
/* A presentation class may own its *visible* display type, but must not
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
.join-addon {
|
|
43
|
-
--control-border: var(--border);
|
|
43
|
+
--control-border: var(--border-control);
|
|
44
44
|
--control-bg: var(--surface-subtle);
|
|
45
45
|
--control-fg: var(--text-muted);
|
|
46
46
|
|
|
@@ -145,5 +145,5 @@
|
|
|
145
145
|
The second :has() covers a control nested one wrapper level deep. */
|
|
146
146
|
.join:has(> :is(.input, .textarea, .select)) > .btn.outline,
|
|
147
147
|
.join:has(> * > :is(.input, .textarea, .select)) > .btn.outline {
|
|
148
|
-
--btn-border-color: var(--intent, var(--border));
|
|
148
|
+
--btn-border-color: var(--intent, var(--border-control));
|
|
149
149
|
}
|
|
@@ -43,9 +43,10 @@
|
|
|
43
43
|
.list > :where(.list-item),
|
|
44
44
|
.list > li > .list-item {
|
|
45
45
|
display: flex;
|
|
46
|
-
/*
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
/* Centered fits the short rows lists mostly hold. Read through
|
|
47
|
+
--items-align so .items-start on the row or the list wins without
|
|
48
|
+
outranking this selector — the opt-out for long supporting text. */
|
|
49
|
+
align-items: var(--items-align, center);
|
|
49
50
|
gap: var(--list-item-gap);
|
|
50
51
|
min-block-size: var(--list-item-min-size);
|
|
51
52
|
padding-block: var(--list-item-pad-block);
|
|
@@ -67,21 +68,20 @@ a.list-item:hover {
|
|
|
67
68
|
background: var(--surface-subtle);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/* Current row in a navigational list
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
…) stays free for assistive technology.
|
|
71
|
+
/* Current row in a navigational list: aria-current="page" when the row opens
|
|
72
|
+
a page, "true" when it is the current item of a master/detail view (a
|
|
73
|
+
conversation, a thread, a record) that is no page of its own. Any other
|
|
74
|
+
value, "false" included, is inert.
|
|
75
75
|
Calm by design: the row keeps its normal text metrics and color on a subtle
|
|
76
76
|
surface, and the selected accent lives on an inset inline-start trait only.
|
|
77
77
|
The trait is a real border, so it survives forced colors without a repaint
|
|
78
78
|
block. The positioning context lives on every row link, so becoming current
|
|
79
79
|
never moves an app-positioned decoration to a new containing block. */
|
|
80
|
-
a.list-item[aria-current] {
|
|
80
|
+
a.list-item:is([aria-current="page"], [aria-current="true"]) {
|
|
81
81
|
background: var(--surface-subtle);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
a.list-item[aria-current]::before {
|
|
84
|
+
a.list-item:is([aria-current="page"], [aria-current="true"])::before {
|
|
85
85
|
content: "";
|
|
86
86
|
position: absolute;
|
|
87
87
|
inset-block: var(--list-item-pad-block);
|
|
@@ -9,15 +9,19 @@
|
|
|
9
9
|
* Public hooks:
|
|
10
10
|
* --modal-size
|
|
11
11
|
* --modal-pad
|
|
12
|
-
* --modal-header-bg
|
|
13
12
|
* --dialog-viewport-gap
|
|
14
13
|
* --dialog-icon-size
|
|
15
14
|
* --dialog-icon-glyph-size
|
|
16
15
|
*
|
|
17
16
|
* The corner close control is .close (components/close.css).
|
|
18
17
|
*
|
|
18
|
+
* The body footer is a structural slot with no layout of its own: its action
|
|
19
|
+
* row is composed in markup (`<footer class="cluster justify-content-end">`),
|
|
20
|
+
* as in the drawer. A default row here would fight every composed one.
|
|
21
|
+
*
|
|
19
22
|
* Internal:
|
|
20
23
|
* --dialog-inner-radius
|
|
24
|
+
* --dialog-close-reserve (header room for a corner close; zero without a close)
|
|
21
25
|
* --dialog-focus-bleed
|
|
22
26
|
* --dialog-viewport-block
|
|
23
27
|
* --dialog-viewport-gap-default
|
|
@@ -26,10 +30,8 @@
|
|
|
26
30
|
:where(dialog).modal {
|
|
27
31
|
/* --dialog-viewport-gap stays fallback-only so the responsive default can
|
|
28
32
|
live in an internal token: a media query moves it without closing the
|
|
29
|
-
public hook.
|
|
30
|
-
|
|
31
|
-
calc() multiplication is not in the supported browser range, so the
|
|
32
|
-
doubled gap is spelled out with an additive calc. */
|
|
33
|
+
public hook. `-2x` is the gap on the two viewport edges, shared by the
|
|
34
|
+
width and height caps. */
|
|
33
35
|
--dialog-viewport-gap-default: var(--space-40);
|
|
34
36
|
--dialog-viewport-gap-2x: calc(
|
|
35
37
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default)) +
|
|
@@ -37,10 +39,19 @@
|
|
|
37
39
|
);
|
|
38
40
|
|
|
39
41
|
/* The panel is borderless, so its inner and outer radius are the same. Kept
|
|
40
|
-
as one name for the padded wrapper and
|
|
41
|
-
|
|
42
|
+
as one name for the padded wrapper and the header band; a .bleed band
|
|
43
|
+
inherits it from the wrapper, so none of them drifts from the corner. */
|
|
42
44
|
--dialog-inner-radius: var(--radius-lg);
|
|
43
45
|
|
|
46
|
+
/* Header room for a corner close. Unconditional baseline: without :has()
|
|
47
|
+
(Degraded tier) a header keeps too much room rather than a title running
|
|
48
|
+
under the close. Zeroed when the dialog has no close. */
|
|
49
|
+
--dialog-close-reserve: calc(
|
|
50
|
+
var(--close-size, var(--control-size)) +
|
|
51
|
+
var(--space-20) +
|
|
52
|
+
var(--space-30)
|
|
53
|
+
);
|
|
54
|
+
|
|
44
55
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
45
56
|
provides the containing block for the absolutely positioned close button. */
|
|
46
57
|
position: fixed;
|
|
@@ -73,9 +84,9 @@
|
|
|
73
84
|
* both edges, which reads as horizontal movement.
|
|
74
85
|
*/
|
|
75
86
|
transform: translateY(0.35rem);
|
|
76
|
-
transition:
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
transition-property: opacity, transform;
|
|
88
|
+
transition-duration: var(--duration);
|
|
89
|
+
transition-timing-function: var(--ease-exit);
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
@media (forced-colors: active) {
|
|
@@ -99,10 +110,8 @@
|
|
|
99
110
|
padding: var(--surface-pad);
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
/*
|
|
103
|
-
|
|
104
|
-
the trailing cluster keeps it in flow instead of invoking the absolute
|
|
105
|
-
corner treatment from close.css. */
|
|
113
|
+
/* Dialog chrome beside the padded body wrapper. Wrapping the close in the
|
|
114
|
+
trailing cluster keeps it in flow, not the absolute corner overlay. */
|
|
106
115
|
:where(dialog).modal > header {
|
|
107
116
|
display: flex;
|
|
108
117
|
align-items: center;
|
|
@@ -112,7 +121,20 @@
|
|
|
112
121
|
padding: var(--modal-pad, var(--space-40));
|
|
113
122
|
border-start-start-radius: var(--dialog-inner-radius);
|
|
114
123
|
border-start-end-radius: var(--dialog-inner-radius);
|
|
115
|
-
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Paint is not anatomy: the default band fill has zero specificity, so a
|
|
127
|
+
.background-* utility on the header replaces it. The layout above stays at
|
|
128
|
+
the component's weight — a composed .cluster must not reset the title/close
|
|
129
|
+
alignment. */
|
|
130
|
+
:where(dialog.modal > header) {
|
|
131
|
+
background: var(--surface-subtle);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* The chrome header is a centered row, so prose margins on a bare title only
|
|
135
|
+
push the band taller and pull the title off the close axis. */
|
|
136
|
+
:where(dialog).modal > header > :is(h1, h2, h3, h4, h5, h6) {
|
|
137
|
+
margin-block: 0;
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
:where(dialog).modal > header > .media {
|
|
@@ -133,18 +155,16 @@
|
|
|
133
155
|
flex: none;
|
|
134
156
|
}
|
|
135
157
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
158
|
+
/* The header stays fixed while the body wrapper owns overflow. Without :has()
|
|
159
|
+
these drop individually and compact dialogs keep ordinary block flow. */
|
|
160
|
+
:where(dialog).modal[open]:has(> header) {
|
|
161
|
+
display: grid;
|
|
162
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
163
|
+
}
|
|
143
164
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
165
|
+
:where(dialog).modal:has(> header) > :is(form, .stack) {
|
|
166
|
+
min-block-size: 0;
|
|
167
|
+
max-block-size: none;
|
|
148
168
|
}
|
|
149
169
|
|
|
150
170
|
@supports (block-size: 100svh) {
|
|
@@ -163,24 +183,37 @@
|
|
|
163
183
|
|
|
164
184
|
:where(dialog).modal.scrollable > form,
|
|
165
185
|
:where(dialog).modal.scrollable > .stack {
|
|
166
|
-
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
167
188
|
overflow: hidden;
|
|
168
189
|
}
|
|
169
190
|
|
|
191
|
+
/* Regions carry their own behaviour: a structural header or footer keeps its
|
|
192
|
+
size, every other child absorbs the free space and scrolls. Nothing here has
|
|
193
|
+
to detect which regions exist — an external header leaves the wrapper with
|
|
194
|
+
content + footer and this still holds. Each non-chrome child is its own
|
|
195
|
+
scrollport; wrap the body in one region for a single one. */
|
|
196
|
+
:where(dialog).modal.scrollable > form > :is(header, footer),
|
|
197
|
+
:where(dialog).modal.scrollable > .stack > :is(header, footer) {
|
|
198
|
+
flex: none;
|
|
199
|
+
}
|
|
200
|
+
|
|
170
201
|
:where(dialog).modal.scrollable > form > :not(:is(header, footer)),
|
|
171
202
|
:where(dialog).modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
);
|
|
203
|
+
/* Declared here, not as a :root alias: an inherited alias arrives already
|
|
204
|
+
computed, so a --focus-ring-width override on the dialog would not reach
|
|
205
|
+
it. */
|
|
206
|
+
--dialog-focus-bleed: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
176
207
|
|
|
208
|
+
flex: 1 1 auto;
|
|
177
209
|
min-block-size: 0;
|
|
178
|
-
/* Keep focus paint inside the clip
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
210
|
+
/* Keep focus paint inside the clip, on all four sides (overflow: auto clips
|
|
211
|
+
both axes), without moving the content. Every outside indicator is
|
|
212
|
+
--focus-ring-width wide (core/focus.css). On the block axis the reserve
|
|
213
|
+
reaches into the slot gap, which is wider, so the scrollport never slides
|
|
214
|
+
under the header or footer. */
|
|
215
|
+
margin: calc(var(--dialog-focus-bleed) * -1);
|
|
216
|
+
padding: var(--dialog-focus-bleed);
|
|
184
217
|
overflow: auto;
|
|
185
218
|
overscroll-behavior: contain;
|
|
186
219
|
}
|
|
@@ -202,59 +235,30 @@
|
|
|
202
235
|
margin-block: 0;
|
|
203
236
|
}
|
|
204
237
|
|
|
238
|
+
/* No close anywhere: reclaim the reserve. Deliberately coarse — a close in
|
|
239
|
+
the body keeps the reserve, which costs only room, never overlap. */
|
|
240
|
+
:where(dialog).modal:not(:has(.close)) {
|
|
241
|
+
--dialog-close-reserve: 0px;
|
|
242
|
+
}
|
|
243
|
+
|
|
205
244
|
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
206
|
-
content flow; headers reserve inline room so titles never run under it.
|
|
207
|
-
|
|
208
|
-
lends .stack's specificity to
|
|
245
|
+
content flow; headers reserve inline room so titles never run under it. A
|
|
246
|
+
.bleed header keeps at least its band inset. The form/.stack anatomy is
|
|
247
|
+
structural, so it stays in :where() and never lends .stack's specificity to
|
|
248
|
+
a bare <form>. */
|
|
209
249
|
:where(dialog).modal > :where(form, .stack) > header {
|
|
210
|
-
padding-inline-end:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
);
|
|
250
|
+
padding-inline-end: var(--dialog-close-reserve);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
:where(dialog).modal > :where(form, .stack) > header:where(.bleed) {
|
|
254
|
+
padding-inline-end: max(var(--bleed-pad, 0px), var(--dialog-close-reserve));
|
|
215
255
|
}
|
|
216
256
|
|
|
217
257
|
/* A corner .close that is a direct child of the chrome header needs the same
|
|
218
258
|
reserve, but never below the header's own pad. Kept as its own rule so an
|
|
219
259
|
unsupported :has() drops only this geometry, not the wrapper reserve. */
|
|
220
260
|
:where(dialog).modal > header:has(> .close) {
|
|
221
|
-
padding-inline-end: max(
|
|
222
|
-
var(--modal-pad, var(--space-40)),
|
|
223
|
-
calc(var(--close-size, var(--control-size)) + var(--space-20) + var(--space-30))
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
:where(dialog).modal > :where(form, .stack) > footer {
|
|
228
|
-
display: flex;
|
|
229
|
-
flex-wrap: wrap;
|
|
230
|
-
justify-content: flex-end;
|
|
231
|
-
gap: var(--gap);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/* Confirmation layout — a focused message followed by a visually distinct
|
|
235
|
-
action band. The media primitive owns icon/content alignment; this variant
|
|
236
|
-
only removes the standard form inset so both regions can own their padding. */
|
|
237
|
-
:where(dialog).modal.dialog-confirmation > form {
|
|
238
|
-
--surface-pad: 0px;
|
|
239
|
-
|
|
240
|
-
gap: 0;
|
|
241
|
-
padding: 0;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
:where(dialog).modal.dialog-confirmation > form > :is(.media, header) {
|
|
245
|
-
gap: var(--space-40);
|
|
246
|
-
padding: var(--space-50);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/* The action band splits a decision pair: the cancel-style action holds the
|
|
250
|
-
leading edge and the committing action the trailing one, so the destructive
|
|
251
|
-
choice is never adjacent to the dismiss. */
|
|
252
|
-
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
253
|
-
justify-content: space-between;
|
|
254
|
-
padding: var(--space-40) var(--space-50);
|
|
255
|
-
border-end-start-radius: var(--dialog-inner-radius);
|
|
256
|
-
border-end-end-radius: var(--dialog-inner-radius);
|
|
257
|
-
background: var(--surface-subtle);
|
|
261
|
+
padding-inline-end: max(var(--modal-pad, var(--space-40)), var(--dialog-close-reserve));
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
/* Intent boundary — inherited intents do not tint the icon well. A local
|
|
@@ -267,14 +271,17 @@
|
|
|
267
271
|
--ui-fg: initial;
|
|
268
272
|
}
|
|
269
273
|
|
|
274
|
+
/* The size hooks are read here, never declared: an author sets them on the
|
|
275
|
+
icon's media row (or the dialog) so both the circle and the title that
|
|
276
|
+
centers on it inherit one value. A local declaration on .dialog-icon would
|
|
277
|
+
shadow that value for the circle while the title kept the inherited one —
|
|
278
|
+
the two centers would drift. An override on the icon alone is deliberately
|
|
279
|
+
local and moves only the circle. */
|
|
270
280
|
.dialog-icon {
|
|
271
|
-
--dialog-icon-size: 3rem;
|
|
272
|
-
--dialog-icon-glyph-size: 1.5rem;
|
|
273
|
-
|
|
274
281
|
display: inline-grid;
|
|
275
282
|
place-items: center;
|
|
276
|
-
inline-size: var(--dialog-icon-size);
|
|
277
|
-
block-size: var(--dialog-icon-size);
|
|
283
|
+
inline-size: var(--dialog-icon-size, 3rem);
|
|
284
|
+
block-size: var(--dialog-icon-size, 3rem);
|
|
278
285
|
flex: none;
|
|
279
286
|
border-radius: var(--radius-full);
|
|
280
287
|
background: var(--ui-bg, var(--surface-subtle));
|
|
@@ -284,46 +291,27 @@
|
|
|
284
291
|
.dialog-icon > * {
|
|
285
292
|
display: inline-grid;
|
|
286
293
|
place-items: center;
|
|
287
|
-
inline-size: var(--dialog-icon-glyph-size);
|
|
288
|
-
block-size: var(--dialog-icon-glyph-size);
|
|
289
|
-
font-size: var(--dialog-icon-glyph-size);
|
|
294
|
+
inline-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
295
|
+
block-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
296
|
+
font-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
290
297
|
line-height: 1;
|
|
291
298
|
}
|
|
292
299
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
:where(dialog).modal.dialog-confirmation > form > .media > header > * {
|
|
299
|
-
margin-block: 0;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/* The first heading occupies the icon row, so its text centers on the icon
|
|
303
|
-
without a font-relative nudge. Following copy stays in the same column. */
|
|
304
|
-
:where(dialog).modal.dialog-confirmation
|
|
305
|
-
> form
|
|
306
|
-
> .media
|
|
307
|
-
> header
|
|
308
|
-
> :is(h1, h2, h3, h4, h5, h6):first-child {
|
|
300
|
+
/* A leading icon well centers the first heading of the text column beside it:
|
|
301
|
+
the heading occupies the icon row, so its text centers without a
|
|
302
|
+
font-relative nudge, and following copy stays in the same column. Keyed on
|
|
303
|
+
the media anatomy, not on the dialog, so a card or page row composes it too. */
|
|
304
|
+
.media > .dialog-icon + * > :is(h1, h2, h3, h4, h5, h6):first-child {
|
|
309
305
|
display: flex;
|
|
310
306
|
align-items: center;
|
|
311
307
|
min-block-size: var(--dialog-icon-size, 3rem);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
@media (max-width: 30rem) {
|
|
315
|
-
:where(dialog).modal.dialog-confirmation > form > :is(.media, header),
|
|
316
|
-
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
317
|
-
padding: var(--space-40);
|
|
318
|
-
}
|
|
308
|
+
margin-block: 0;
|
|
319
309
|
}
|
|
320
310
|
|
|
321
311
|
:where(dialog).modal[open] {
|
|
322
312
|
opacity: 1;
|
|
323
313
|
transform: none;
|
|
324
|
-
transition:
|
|
325
|
-
opacity var(--duration) var(--ease-enter),
|
|
326
|
-
transform var(--duration) var(--ease-enter);
|
|
314
|
+
transition-timing-function: var(--ease-enter);
|
|
327
315
|
}
|
|
328
316
|
|
|
329
317
|
@starting-style {
|
|
@@ -338,31 +326,22 @@
|
|
|
338
326
|
opacity: 0;
|
|
339
327
|
/* Scrim curve stays neutral `ease`; only the panel carries the character
|
|
340
328
|
curves. */
|
|
341
|
-
transition: opacity
|
|
329
|
+
transition-property: opacity;
|
|
330
|
+
transition-duration: var(--duration);
|
|
331
|
+
transition-timing-function: ease;
|
|
342
332
|
}
|
|
343
333
|
|
|
344
334
|
@supports (transition-behavior: allow-discrete) {
|
|
335
|
+
/* Add the discrete top-layer pair to the property list. Duration and timing
|
|
336
|
+
stay on the base rules, so [open] still only swaps the easing. */
|
|
345
337
|
:where(dialog).modal {
|
|
346
|
-
transition:
|
|
347
|
-
|
|
348
|
-
transform var(--duration) var(--ease-exit),
|
|
349
|
-
overlay var(--duration) allow-discrete,
|
|
350
|
-
display var(--duration) allow-discrete;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
:where(dialog).modal[open] {
|
|
354
|
-
transition:
|
|
355
|
-
opacity var(--duration) var(--ease-enter),
|
|
356
|
-
transform var(--duration) var(--ease-enter),
|
|
357
|
-
overlay var(--duration) allow-discrete,
|
|
358
|
-
display var(--duration) allow-discrete;
|
|
338
|
+
transition-property: opacity, transform, overlay, display;
|
|
339
|
+
transition-behavior: allow-discrete;
|
|
359
340
|
}
|
|
360
341
|
|
|
361
342
|
:where(dialog).modal::backdrop {
|
|
362
|
-
transition:
|
|
363
|
-
|
|
364
|
-
overlay var(--duration) allow-discrete,
|
|
365
|
-
display var(--duration) allow-discrete;
|
|
343
|
+
transition-property: opacity, overlay, display;
|
|
344
|
+
transition-behavior: allow-discrete;
|
|
366
345
|
}
|
|
367
346
|
}
|
|
368
347
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Docs: docs/pages/components/navbar.md
|
|
5
5
|
*
|
|
6
|
-
* .navbar is a horizontal bar; .navbar-brand is the brand link;
|
|
7
|
-
*
|
|
6
|
+
* .navbar is a horizontal bar; .navbar-brand is the brand link; the inline
|
|
7
|
+
* links are a .cluster using .nav-link items. .nav-list is the vertical nav
|
|
8
8
|
* list shared with .drawer, fed by the shared --gap token.
|
|
9
9
|
*
|
|
10
10
|
* Internal:
|
|
@@ -53,17 +53,6 @@
|
|
|
53
53
|
border-radius: var(--radius-sm);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
.navbar-nav {
|
|
57
|
-
display: flex;
|
|
58
|
-
flex-direction: row;
|
|
59
|
-
flex-wrap: wrap;
|
|
60
|
-
align-items: center;
|
|
61
|
-
gap: var(--space-10);
|
|
62
|
-
margin: 0;
|
|
63
|
-
padding: 0;
|
|
64
|
-
list-style: none;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
56
|
.nav-link {
|
|
68
57
|
display: inline-flex;
|
|
69
58
|
align-items: center;
|
|
@@ -82,23 +71,19 @@
|
|
|
82
71
|
|
|
83
72
|
/* nav-link serves two contracts: plain page navigation (aria-current="page")
|
|
84
73
|
and scrollspy, which toggles aria-current="location" on the active link.
|
|
85
|
-
|
|
86
|
-
removed everywhere else, never serialized as aria-current="false".
|
|
74
|
+
Those two values are the contract; any other, "false" included, is inert.
|
|
87
75
|
Selection never changes text metrics: the shared medium weight is kept and
|
|
88
76
|
the current link is carried by the selected accent. */
|
|
89
|
-
.nav-link[aria-current] {
|
|
90
|
-
background: var(--surface-subtle);
|
|
91
|
-
color: var(--state-selected);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
77
|
.nav-link:hover {
|
|
95
78
|
background: var(--surface-subtle);
|
|
96
79
|
color: var(--text);
|
|
97
80
|
}
|
|
98
81
|
|
|
99
|
-
/*
|
|
100
|
-
|
|
101
|
-
|
|
82
|
+
/* Current last, equal specificity to :hover (0,2,0): a hovered current link
|
|
83
|
+
keeps the selected accent instead of dropping to --text. The surface fill is
|
|
84
|
+
shared with the hover rule above. */
|
|
85
|
+
.nav-link:is([aria-current="page"], [aria-current="location"]) {
|
|
86
|
+
background: var(--surface-subtle);
|
|
102
87
|
color: var(--state-selected);
|
|
103
88
|
}
|
|
104
89
|
|
|
@@ -110,7 +95,7 @@
|
|
|
110
95
|
above keeps the light chip where color-mix is missing, with ink that is
|
|
111
96
|
legible on it. */
|
|
112
97
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
113
|
-
.nav-link:is(:hover, [aria-current]) {
|
|
98
|
+
.nav-link:is(:hover, [aria-current="page"], [aria-current="location"]) {
|
|
114
99
|
background: color-mix(in oklch, currentColor 10%, transparent);
|
|
115
100
|
}
|
|
116
101
|
|
|
@@ -118,19 +103,22 @@
|
|
|
118
103
|
color: var(--ui-fg, var(--text));
|
|
119
104
|
}
|
|
120
105
|
|
|
121
|
-
.
|
|
122
|
-
.nav-link[aria-current]
|
|
106
|
+
/* After :hover so a hovered current link keeps the selected accent. */
|
|
107
|
+
.nav-link:is([aria-current="page"], [aria-current="location"]) {
|
|
123
108
|
color: var(--ui-fg, var(--state-selected));
|
|
124
109
|
}
|
|
125
110
|
}
|
|
126
111
|
|
|
127
112
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
128
113
|
Links fill each row so the whole line is an interactive target; the
|
|
129
|
-
horizontal .
|
|
114
|
+
horizontal .cluster links keep their intrinsic size. */
|
|
115
|
+
/* A vertical nav reads as one list, not a stack of separate buttons: each row
|
|
116
|
+
is already a control-height target, so the rows sit a hair apart. The grid
|
|
117
|
+
is the component's own layout; for a horizontal row use .cluster instead. */
|
|
130
118
|
.nav-list {
|
|
131
119
|
display: grid;
|
|
132
120
|
grid-template-columns: minmax(0, 1fr);
|
|
133
|
-
--gap: var(--space-
|
|
121
|
+
--gap: var(--space-10);
|
|
134
122
|
gap: var(--gap);
|
|
135
123
|
margin: 0;
|
|
136
124
|
padding: 0;
|
|
@@ -150,11 +138,14 @@
|
|
|
150
138
|
follows the forced palette without a forced-colors block and never touches
|
|
151
139
|
the row separators. Navbar horizontal links keep color + surface only. The
|
|
152
140
|
positioning context lives on every row link, so becoming current never moves
|
|
153
|
-
an app-positioned decoration to a new containing block.
|
|
154
|
-
|
|
141
|
+
an app-positioned decoration to a new containing block.
|
|
142
|
+
The trait sits centered in the link's inline padding with rounded ends: flush
|
|
143
|
+
on the edge, a square line read as pasted onto the rounded fill. */
|
|
144
|
+
.nav-list .nav-link:is([aria-current="page"], [aria-current="location"])::before {
|
|
155
145
|
content: "";
|
|
156
146
|
position: absolute;
|
|
157
147
|
inset-block: var(--space-20);
|
|
158
|
-
inset-inline-start:
|
|
148
|
+
inset-inline-start: calc((var(--space-20) - 2px) / 2);
|
|
159
149
|
border-inline-start: 2px solid currentColor;
|
|
150
|
+
border-radius: var(--radius-full);
|
|
160
151
|
}
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
* Truncated ranges are plain text (`.muted`, `aria-hidden="true"`), not .btn
|
|
11
11
|
* — they are decorative, not actionable. They still get the same box sizing
|
|
12
12
|
* as .btn below so the row stays visually aligned.
|
|
13
|
+
*
|
|
14
|
+
* The row wraps when it does not fit: every page stays visible and
|
|
15
|
+
* clickable. A strip that scrolled hid pages behind a scrollbar that clicking
|
|
16
|
+
* cannot move, since a click changes the page. Keeping the rendered range
|
|
17
|
+
* compact is the application's job.
|
|
13
18
|
*/
|
|
14
19
|
.pagination {
|
|
15
20
|
display: flex;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* <nav class="scrollspy">
|
|
9
9
|
* containing <a href="#section"> links and toggles aria-current="location" on
|
|
10
10
|
* the link whose target is in view. Pair with .nav-list and .nav-link, or style
|
|
11
|
-
* [aria-current] yourself.
|
|
11
|
+
* [aria-current="location"] yourself.
|
|
12
12
|
*
|
|
13
13
|
* Progressive enhancement: on browsers that support :target-current,
|
|
14
14
|
* this file enables the native target group and marks the active link.
|