actual-css 0.11.0 → 0.12.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 +73 -0
- package/README.md +29 -5
- package/components.json +15 -17
- package/dist/actual.full.css +403 -227
- 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/navigation.json +4 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/badge.md +3 -9
- package/docs/pages/components/bleed.md +5 -4
- package/docs/pages/components/card.md +31 -2
- package/docs/pages/components/close.md +9 -3
- package/docs/pages/components/dialog.md +248 -31
- package/docs/pages/components/drawer.md +2 -2
- package/docs/pages/components/meter.md +2 -0
- package/docs/pages/components/navbar.md +8 -9
- package/docs/pages/components/table.md +3 -3
- package/docs/pages/enhancements/flyout.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/status-bar.md +2 -2
- package/docs/pages/examples/overview.md +3 -1
- package/docs/pages/forms/date-picker.md +90 -0
- package/docs/pages/forms/detached-actions.md +37 -30
- package/docs/pages/forms/form-actions.md +61 -28
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/overview.md +6 -5
- package/docs/pages/forms/sticky-actions.md +85 -83
- package/docs/pages/forms/validation.md +5 -5
- package/docs/pages/foundations/philosophy.md +5 -3
- package/docs/pages/foundations/theming.md +15 -4
- package/docs/pages/foundations/tokens.md +50 -33
- package/docs/pages/foundations/typography.md +4 -7
- package/docs/pages/guides/bootstrap.md +2 -2
- package/docs/pages/guides/design-tools.md +36 -0
- package/docs/pages/guides/integrating-actual-css.md +5 -1
- package/docs/pages/guides/introduction.md +3 -4
- package/docs/pages/guides/modular-import.md +3 -0
- package/docs/pages/guides/progressive-enhancement.md +2 -2
- package/docs/pages/guides/tailwind.md +70 -0
- package/docs/pages/guides/upgrading.md +74 -0
- package/docs/pages/layout/cluster.md +28 -2
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/layout/stack.md +21 -16
- package/docs/pages/layout/topbar.md +1 -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 +1 -1
- package/docs/pages/utilities/spacing.md +4 -3
- package/package.json +3 -2
- package/reserved-classes.json +3 -3
- package/skills/actual-css/references/enhancements.md +38 -0
- package/src/cli/actual-css.js +75 -6
- package/src/css/components/avatar.css +16 -9
- package/src/css/components/badge.css +1 -5
- package/src/css/components/bleed.css +5 -5
- package/src/css/components/button.css +17 -10
- package/src/css/components/card.css +18 -10
- package/src/css/components/chat.css +4 -5
- package/src/css/components/close.css +17 -6
- package/src/css/components/drawer.css +19 -3
- package/src/css/components/flyout.css +3 -0
- package/src/css/components/index.css +0 -1
- package/src/css/components/meter.css +5 -2
- package/src/css/components/modal.css +157 -69
- package/src/css/components/navbar.css +14 -25
- package/src/css/core/base.css +1 -1
- package/src/css/core/reset.css +2 -1
- package/src/css/core/theme.css +4 -0
- package/src/css/core/tokens.css +28 -10
- package/src/css/core/variants.css +41 -14
- package/src/css/forms/form-actions.css +15 -10
- package/src/css/forms/native.css +5 -1
- package/src/css/layout/cluster.css +11 -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/tooling/color.js +65 -0
- package/src/tooling/design-components.js +286 -0
- package/src/tooling/design-export.js +69 -0
- package/src/tooling/design-tokens.js +474 -0
- package/src/tooling/penpot-plugin/manifest.json +7 -0
- package/src/tooling/penpot-plugin/plugin.js +272 -0
- package/src/tooling/penpot-plugin/ui.html +58 -0
- package/src/tooling/theme-contrast.js +209 -0
- package/docs/pages/patterns/actions.md +0 -52
- package/src/css/components/actions.css +0 -27
|
@@ -8,13 +8,20 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Public hooks:
|
|
10
10
|
* --modal-size
|
|
11
|
+
* --modal-pad
|
|
11
12
|
* --dialog-viewport-gap
|
|
12
13
|
* --dialog-icon-size
|
|
13
14
|
* --dialog-icon-glyph-size
|
|
14
15
|
*
|
|
15
16
|
* The corner close control is .close (components/close.css).
|
|
16
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
|
+
*
|
|
17
22
|
* Internal:
|
|
23
|
+
* --dialog-inner-radius
|
|
24
|
+
* --dialog-close-reserve (header room for a corner close; zero without a close)
|
|
18
25
|
* --dialog-focus-bleed
|
|
19
26
|
* --dialog-viewport-block
|
|
20
27
|
* --dialog-viewport-gap-default
|
|
@@ -23,16 +30,28 @@
|
|
|
23
30
|
:where(dialog).modal {
|
|
24
31
|
/* --dialog-viewport-gap stays fallback-only so the responsive default can
|
|
25
32
|
live in an internal token: a media query moves it without closing the
|
|
26
|
-
public hook.
|
|
27
|
-
|
|
28
|
-
calc() multiplication is not in the supported browser range, so the
|
|
29
|
-
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. */
|
|
30
35
|
--dialog-viewport-gap-default: var(--space-40);
|
|
31
36
|
--dialog-viewport-gap-2x: calc(
|
|
32
37
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default)) +
|
|
33
38
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default))
|
|
34
39
|
);
|
|
35
40
|
|
|
41
|
+
/* The panel is borderless, so its inner and outer radius are the same. Kept
|
|
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. */
|
|
44
|
+
--dialog-inner-radius: var(--radius-lg);
|
|
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
|
+
|
|
36
55
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
37
56
|
provides the containing block for the absolutely positioned close button. */
|
|
38
57
|
position: fixed;
|
|
@@ -46,7 +65,9 @@
|
|
|
46
65
|
dynamic token; only the modal pins the small viewport. */
|
|
47
66
|
--dialog-viewport-block: var(--viewport-block);
|
|
48
67
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
49
|
-
|
|
68
|
+
/* Zero rather than absent: the UA stylesheet draws `border: solid` on
|
|
69
|
+
dialog, and an authored edge reads as a hard seam against .bleed bands. */
|
|
70
|
+
border: 0;
|
|
50
71
|
border-radius: var(--radius-lg);
|
|
51
72
|
background: var(--surface-raised);
|
|
52
73
|
color: var(--text);
|
|
@@ -63,19 +84,81 @@
|
|
|
63
84
|
* both edges, which reads as horizontal movement.
|
|
64
85
|
*/
|
|
65
86
|
transform: translateY(0.35rem);
|
|
66
|
-
transition:
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
transition-property: opacity, transform;
|
|
88
|
+
transition-duration: var(--duration);
|
|
89
|
+
transition-timing-function: var(--ease-exit);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@media (forced-colors: active) {
|
|
93
|
+
/* With no authored border the panel edge disappears once colors collapse.
|
|
94
|
+
The browser forces the border color, so only the width is authored. */
|
|
95
|
+
:where(dialog).modal {
|
|
96
|
+
border: var(--border-width) solid CanvasText;
|
|
97
|
+
}
|
|
69
98
|
}
|
|
70
99
|
|
|
71
100
|
:where(dialog).modal > form,
|
|
72
101
|
:where(dialog).modal > .stack {
|
|
102
|
+
--surface-pad: var(--modal-pad, var(--space-40));
|
|
103
|
+
|
|
73
104
|
display: grid;
|
|
74
105
|
gap: var(--space-40);
|
|
75
106
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
107
|
+
border-radius: var(--dialog-inner-radius);
|
|
76
108
|
overflow: auto;
|
|
77
109
|
overscroll-behavior: contain;
|
|
78
|
-
padding: var(--
|
|
110
|
+
padding: var(--surface-pad);
|
|
111
|
+
}
|
|
112
|
+
|
|
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. */
|
|
115
|
+
:where(dialog).modal > header {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
gap: var(--space-30);
|
|
120
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
121
|
+
padding: var(--modal-pad, var(--space-40));
|
|
122
|
+
border-start-start-radius: var(--dialog-inner-radius);
|
|
123
|
+
border-start-end-radius: var(--dialog-inner-radius);
|
|
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
|
+
:where(dialog).modal > header > .media {
|
|
135
|
+
flex: 1 1 auto;
|
|
136
|
+
min-inline-size: 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
:where(dialog).modal > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
143
|
+
margin-block: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
:where(dialog).modal > header > .cluster {
|
|
147
|
+
--cluster-wrap: nowrap;
|
|
148
|
+
|
|
149
|
+
flex: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* The header stays fixed while the body wrapper owns overflow. Without :has()
|
|
153
|
+
these drop individually and compact dialogs keep ordinary block flow. */
|
|
154
|
+
:where(dialog).modal[open]:has(> header) {
|
|
155
|
+
display: grid;
|
|
156
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
:where(dialog).modal:has(> header) > :is(form, .stack) {
|
|
160
|
+
min-block-size: 0;
|
|
161
|
+
max-block-size: none;
|
|
79
162
|
}
|
|
80
163
|
|
|
81
164
|
@supports (block-size: 100svh) {
|
|
@@ -94,10 +177,21 @@
|
|
|
94
177
|
|
|
95
178
|
:where(dialog).modal.scrollable > form,
|
|
96
179
|
:where(dialog).modal.scrollable > .stack {
|
|
97
|
-
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: column;
|
|
98
182
|
overflow: hidden;
|
|
99
183
|
}
|
|
100
184
|
|
|
185
|
+
/* Regions carry their own behaviour: a structural header or footer keeps its
|
|
186
|
+
size, every other child absorbs the free space and scrolls. Nothing here has
|
|
187
|
+
to detect which regions exist — an external header leaves the wrapper with
|
|
188
|
+
content + footer and this still holds. Each non-chrome child is its own
|
|
189
|
+
scrollport; wrap the body in one region for a single one. */
|
|
190
|
+
:where(dialog).modal.scrollable > form > :is(header, footer),
|
|
191
|
+
:where(dialog).modal.scrollable > .stack > :is(header, footer) {
|
|
192
|
+
flex: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
101
195
|
:where(dialog).modal.scrollable > form > :not(:is(header, footer)),
|
|
102
196
|
:where(dialog).modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
103
197
|
--dialog-focus-bleed: calc(
|
|
@@ -105,6 +199,7 @@
|
|
|
105
199
|
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
106
200
|
);
|
|
107
201
|
|
|
202
|
+
flex: 1 1 auto;
|
|
108
203
|
min-block-size: 0;
|
|
109
204
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
110
205
|
header and footer edges. The reserve covers the thicker of the two
|
|
@@ -127,39 +222,36 @@
|
|
|
127
222
|
margin-block-end: 0;
|
|
128
223
|
}
|
|
129
224
|
|
|
130
|
-
/*
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
:where(dialog).modal > :where(form, .stack) > header {
|
|
135
|
-
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
225
|
+
/* A media row inside the structural header is title anatomy, so its heading
|
|
226
|
+
does not bring prose margins into the icon alignment. */
|
|
227
|
+
:where(dialog).modal > :where(form, .stack) > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
228
|
+
margin-block: 0;
|
|
136
229
|
}
|
|
137
230
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
gap: var(--gap);
|
|
231
|
+
/* No close anywhere: reclaim the reserve. Deliberately coarse — a close in
|
|
232
|
+
the body keeps the reserve, which costs only room, never overlap. */
|
|
233
|
+
:where(dialog).modal:not(:has(.close)) {
|
|
234
|
+
--dialog-close-reserve: 0px;
|
|
143
235
|
}
|
|
144
236
|
|
|
145
|
-
/*
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
:where(
|
|
149
|
-
|
|
150
|
-
|
|
237
|
+
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
238
|
+
content flow; headers reserve inline room so titles never run under it. A
|
|
239
|
+
.bleed header keeps at least its band inset. The form/.stack anatomy is
|
|
240
|
+
structural, so it stays in :where() and never lends .stack's specificity to
|
|
241
|
+
a bare <form>. */
|
|
242
|
+
:where(dialog).modal > :where(form, .stack) > header {
|
|
243
|
+
padding-inline-end: var(--dialog-close-reserve);
|
|
151
244
|
}
|
|
152
245
|
|
|
153
|
-
:where(dialog).modal
|
|
154
|
-
|
|
155
|
-
padding: var(--space-50);
|
|
246
|
+
:where(dialog).modal > :where(form, .stack) > header:where(.bleed) {
|
|
247
|
+
padding-inline-end: max(var(--bleed-pad, 0px), var(--dialog-close-reserve));
|
|
156
248
|
}
|
|
157
249
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
250
|
+
/* A corner .close that is a direct child of the chrome header needs the same
|
|
251
|
+
reserve, but never below the header's own pad. Kept as its own rule so an
|
|
252
|
+
unsupported :has() drops only this geometry, not the wrapper reserve. */
|
|
253
|
+
:where(dialog).modal > header:has(> .close) {
|
|
254
|
+
padding-inline-end: max(var(--modal-pad, var(--space-40)), var(--dialog-close-reserve));
|
|
163
255
|
}
|
|
164
256
|
|
|
165
257
|
/* Intent boundary — inherited intents do not tint the icon well. A local
|
|
@@ -172,14 +264,17 @@
|
|
|
172
264
|
--ui-fg: initial;
|
|
173
265
|
}
|
|
174
266
|
|
|
267
|
+
/* The size hooks are read here, never declared: an author sets them on the
|
|
268
|
+
icon's media row (or the dialog) so both the circle and the title that
|
|
269
|
+
centers on it inherit one value. A local declaration on .dialog-icon would
|
|
270
|
+
shadow that value for the circle while the title kept the inherited one —
|
|
271
|
+
the two centers would drift. An override on the icon alone is deliberately
|
|
272
|
+
local and moves only the circle. */
|
|
175
273
|
.dialog-icon {
|
|
176
|
-
--dialog-icon-size: 3rem;
|
|
177
|
-
--dialog-icon-glyph-size: 1.5rem;
|
|
178
|
-
|
|
179
274
|
display: inline-grid;
|
|
180
275
|
place-items: center;
|
|
181
|
-
inline-size: var(--dialog-icon-size);
|
|
182
|
-
block-size: var(--dialog-icon-size);
|
|
276
|
+
inline-size: var(--dialog-icon-size, 3rem);
|
|
277
|
+
block-size: var(--dialog-icon-size, 3rem);
|
|
183
278
|
flex: none;
|
|
184
279
|
border-radius: var(--radius-full);
|
|
185
280
|
background: var(--ui-bg, var(--surface-subtle));
|
|
@@ -189,25 +284,27 @@
|
|
|
189
284
|
.dialog-icon > * {
|
|
190
285
|
display: inline-grid;
|
|
191
286
|
place-items: center;
|
|
192
|
-
inline-size: var(--dialog-icon-glyph-size);
|
|
193
|
-
block-size: var(--dialog-icon-glyph-size);
|
|
194
|
-
font-size: var(--dialog-icon-glyph-size);
|
|
287
|
+
inline-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
288
|
+
block-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
289
|
+
font-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
195
290
|
line-height: 1;
|
|
196
291
|
}
|
|
197
292
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
293
|
+
/* A leading icon well centers the first heading of the text column beside it:
|
|
294
|
+
the heading occupies the icon row, so its text centers without a
|
|
295
|
+
font-relative nudge, and following copy stays in the same column. Keyed on
|
|
296
|
+
the media anatomy, not on the dialog, so a card or page row composes it too. */
|
|
297
|
+
.media > .dialog-icon + * > :is(h1, h2, h3, h4, h5, h6):first-child {
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
min-block-size: var(--dialog-icon-size, 3rem);
|
|
301
|
+
margin-block: 0;
|
|
203
302
|
}
|
|
204
303
|
|
|
205
304
|
:where(dialog).modal[open] {
|
|
206
305
|
opacity: 1;
|
|
207
306
|
transform: none;
|
|
208
|
-
transition:
|
|
209
|
-
opacity var(--duration) var(--ease-enter),
|
|
210
|
-
transform var(--duration) var(--ease-enter);
|
|
307
|
+
transition-timing-function: var(--ease-enter);
|
|
211
308
|
}
|
|
212
309
|
|
|
213
310
|
@starting-style {
|
|
@@ -222,31 +319,22 @@
|
|
|
222
319
|
opacity: 0;
|
|
223
320
|
/* Scrim curve stays neutral `ease`; only the panel carries the character
|
|
224
321
|
curves. */
|
|
225
|
-
transition: opacity
|
|
322
|
+
transition-property: opacity;
|
|
323
|
+
transition-duration: var(--duration);
|
|
324
|
+
transition-timing-function: ease;
|
|
226
325
|
}
|
|
227
326
|
|
|
228
327
|
@supports (transition-behavior: allow-discrete) {
|
|
328
|
+
/* Add the discrete top-layer pair to the property list. Duration and timing
|
|
329
|
+
stay on the base rules, so [open] still only swaps the easing. */
|
|
229
330
|
:where(dialog).modal {
|
|
230
|
-
transition:
|
|
231
|
-
|
|
232
|
-
transform var(--duration) var(--ease-exit),
|
|
233
|
-
overlay var(--duration) allow-discrete,
|
|
234
|
-
display var(--duration) allow-discrete;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
:where(dialog).modal[open] {
|
|
238
|
-
transition:
|
|
239
|
-
opacity var(--duration) var(--ease-enter),
|
|
240
|
-
transform var(--duration) var(--ease-enter),
|
|
241
|
-
overlay var(--duration) allow-discrete,
|
|
242
|
-
display var(--duration) allow-discrete;
|
|
331
|
+
transition-property: opacity, transform, overlay, display;
|
|
332
|
+
transition-behavior: allow-discrete;
|
|
243
333
|
}
|
|
244
334
|
|
|
245
335
|
:where(dialog).modal::backdrop {
|
|
246
|
-
transition:
|
|
247
|
-
|
|
248
|
-
overlay var(--duration) allow-discrete,
|
|
249
|
-
display var(--duration) allow-discrete;
|
|
336
|
+
transition-property: opacity, overlay, display;
|
|
337
|
+
transition-behavior: allow-discrete;
|
|
250
338
|
}
|
|
251
339
|
}
|
|
252
340
|
|
|
@@ -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;
|
|
@@ -86,19 +75,16 @@
|
|
|
86
75
|
removed everywhere else, never serialized as aria-current="false".
|
|
87
76
|
Selection never changes text metrics: the shared medium weight is kept and
|
|
88
77
|
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
78
|
.nav-link:hover {
|
|
95
79
|
background: var(--surface-subtle);
|
|
96
80
|
color: var(--text);
|
|
97
81
|
}
|
|
98
82
|
|
|
99
|
-
/*
|
|
100
|
-
|
|
101
|
-
|
|
83
|
+
/* Current last, equal specificity to :hover (0,2,0): a hovered current link
|
|
84
|
+
keeps the selected accent instead of dropping to --text. The surface fill is
|
|
85
|
+
shared with the hover rule above. */
|
|
86
|
+
.nav-link[aria-current] {
|
|
87
|
+
background: var(--surface-subtle);
|
|
102
88
|
color: var(--state-selected);
|
|
103
89
|
}
|
|
104
90
|
|
|
@@ -118,19 +104,22 @@
|
|
|
118
104
|
color: var(--ui-fg, var(--text));
|
|
119
105
|
}
|
|
120
106
|
|
|
121
|
-
.
|
|
122
|
-
.nav-link[aria-current]
|
|
107
|
+
/* After :hover so a hovered current link keeps the selected accent. */
|
|
108
|
+
.nav-link[aria-current] {
|
|
123
109
|
color: var(--ui-fg, var(--state-selected));
|
|
124
110
|
}
|
|
125
111
|
}
|
|
126
112
|
|
|
127
113
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
128
114
|
Links fill each row so the whole line is an interactive target; the
|
|
129
|
-
horizontal .
|
|
115
|
+
horizontal .cluster links keep their intrinsic size. */
|
|
116
|
+
/* A vertical nav reads as one list, not a stack of separate buttons: each row
|
|
117
|
+
is already a control-height target, so the rows sit a hair apart. The grid
|
|
118
|
+
is the component's own layout; for a horizontal row use .cluster instead. */
|
|
130
119
|
.nav-list {
|
|
131
120
|
display: grid;
|
|
132
121
|
grid-template-columns: minmax(0, 1fr);
|
|
133
|
-
--gap: var(--space-
|
|
122
|
+
--gap: var(--space-10);
|
|
134
123
|
gap: var(--gap);
|
|
135
124
|
margin: 0;
|
|
136
125
|
padding: 0;
|
package/src/css/core/base.css
CHANGED
package/src/css/core/reset.css
CHANGED
package/src/css/core/theme.css
CHANGED
|
@@ -79,6 +79,10 @@
|
|
|
79
79
|
fields, a solid band), which rules out the primary itself. */
|
|
80
80
|
--focus: hsl(268 30% 55%);
|
|
81
81
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
82
|
+
/* Solid fills take a stronger wash than the subtle shared overlay. Black in
|
|
83
|
+
both schemes on purpose: a solid button darkens on hover even in dark mode
|
|
84
|
+
(see themes/gradient.css), unlike the neutral washes. */
|
|
85
|
+
--hover-overlay-solid: rgb(0 0 0 / 0.14);
|
|
82
86
|
|
|
83
87
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
84
88
|
--radius: 0.5rem;
|
package/src/css/core/tokens.css
CHANGED
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
21
21
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
22
22
|
--font-width: 100%;
|
|
23
|
-
--font-width-dense: 95%;
|
|
24
23
|
--line-height: 1.5;
|
|
25
24
|
--line-height-tight: 1.25;
|
|
26
25
|
--line-height-relaxed: 1.75;
|
|
@@ -104,8 +103,9 @@
|
|
|
104
103
|
--bar-height: 0.65rem;
|
|
105
104
|
|
|
106
105
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
107
|
-
.compact/.spacious (variants.css) switch geometry
|
|
108
|
-
--control-font-size. Local .sm/.lg
|
|
106
|
+
.compact/.spacious (variants.css) switch geometry (--control-size,
|
|
107
|
+
--control-pad-x) without changing --control-font-size. Local .sm/.lg
|
|
108
|
+
component sizes switch all three.
|
|
109
109
|
|
|
110
110
|
Inline choices (checkbox, radio, switch) are typographic, not field-sized:
|
|
111
111
|
they sit on a line of text, so they follow --control-font-size through
|
|
@@ -116,17 +116,35 @@
|
|
|
116
116
|
|
|
117
117
|
--control-pad-x is the opposite kind of token: a resolved geometry length,
|
|
118
118
|
not an em ratio. Every part of one control must see the same horizontal
|
|
119
|
-
distance, so it is stated in
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
distance, so it is stated in lengths rather than an em whose value would
|
|
120
|
+
change on a descendant with its own font size (the input-icon glyph, made
|
|
121
|
+
larger by --input-icon-size).
|
|
122
|
+
|
|
123
|
+
Like --control-size, it is a scale plus a selection: themes set the
|
|
124
|
+
-sm/-md/-lg steps, and :root, .sm/.lg and .compact/.spacious (variants.css)
|
|
125
|
+
pick one. A theme that wrote --control-pad-x itself would be overwritten
|
|
126
|
+
by every size and density boundary — bootstrap-v6's 12px became a wider
|
|
127
|
+
13px inside .compact. The selection is rebound at each boundary because a
|
|
128
|
+
var() inside a custom property resolves where it is declared.
|
|
129
|
+
|
|
130
|
+
The boundaries also write --control-step-sm/-md/-lg: which step is
|
|
131
|
+
selected, as 0/1 weights that mean the same under every theme. A theme
|
|
132
|
+
that sets its own steps rebuilds the selection from them in its own block
|
|
133
|
+
(bootstrap-v6.css), so a nested island of it reads its steps without
|
|
134
|
+
resetting the density it sits in. Never rebind on every [data-theme]: a
|
|
135
|
+
palette-only island then became a geometry boundary that an inherited
|
|
136
|
+
--control-size or --control-pad-x no longer crossed. */
|
|
125
137
|
--control-size-sm: 2rem;
|
|
126
138
|
--control-size-md: 2.375rem;
|
|
127
139
|
--control-size-lg: 2.75rem;
|
|
128
140
|
--control-size: var(--control-size-md);
|
|
129
|
-
--control-pad-x: var(--font-size-
|
|
141
|
+
--control-pad-x-sm: var(--font-size-xs);
|
|
142
|
+
--control-pad-x-md: var(--font-size-md);
|
|
143
|
+
--control-pad-x-lg: var(--font-size-lg);
|
|
144
|
+
--control-pad-x: var(--control-pad-x-md);
|
|
145
|
+
--control-step-sm: 0;
|
|
146
|
+
--control-step-md: 1;
|
|
147
|
+
--control-step-lg: 0;
|
|
130
148
|
--control-font-size: var(--font-size-md);
|
|
131
149
|
--choice-control-size: 1.125em;
|
|
132
150
|
--disabled-opacity: 0.65;
|
|
@@ -11,17 +11,21 @@
|
|
|
11
11
|
* components/badge.css — .badge default soft behind explicit --ui-*
|
|
12
12
|
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Three axes, each predictable on its own:
|
|
15
|
+
* .sm/.lg size — typography + geometry, local to a component
|
|
16
|
+
* .compact/.spacious density — rhythm + geometry, inherited by a region
|
|
17
|
+
* .tight/.loose rhythm — --gap only, inherited by a region
|
|
18
|
+
*
|
|
19
|
+
* Density never touches typography: no font size, icon size or font width.
|
|
20
|
+
* A width bonus through font-stretch only showed on typefaces with a wdth axis,
|
|
21
|
+
* so .compact meant something different per OS and font; a condensed table is
|
|
22
|
+
* a typographic decision for the application. Controls consume --control-size
|
|
23
|
+
* and --control-pad-x, so a compact control is shorter and narrower (a height
|
|
24
|
+
* change alone read as the same control squashed); non-control components may
|
|
17
25
|
* consume --density-* when the context should affect their local rhythm.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* --font-width, so a spacious island inside a compact context returns to the
|
|
22
|
-
* normal width exactly as it restores its own gap, spacing, and control size.
|
|
23
|
-
* Themes whose typeface has no useful width axis opt out with
|
|
24
|
-
* --font-width-dense: 100%.
|
|
26
|
+
*
|
|
27
|
+
* .tight/.loose reuse the density --gap steps, so a tight row and a compact
|
|
28
|
+
* one share one rhythm.
|
|
25
29
|
*
|
|
26
30
|
* .sm/.lg are local size roles, not shared measurements. Shared control
|
|
27
31
|
* participants below map both typography and geometry; components with
|
|
@@ -60,18 +64,35 @@
|
|
|
60
64
|
--ui-border: var(--border);
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
/* Each boundary selects a control step twice: the values, resolved here, and
|
|
68
|
+
the 0/1 step weights a nested [data-theme] island rebuilds them from
|
|
69
|
+
(tokens.css). */
|
|
63
70
|
.compact {
|
|
64
71
|
--gap: var(--space-20);
|
|
65
72
|
--density-space: var(--space-20);
|
|
66
73
|
--control-size: var(--control-size-sm);
|
|
67
|
-
|
|
74
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
75
|
+
--control-step-sm: 1;
|
|
76
|
+
--control-step-md: 0;
|
|
77
|
+
--control-step-lg: 0;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
.spacious {
|
|
71
81
|
--gap: var(--space-50);
|
|
72
82
|
--density-space: var(--space-50);
|
|
73
83
|
--control-size: var(--control-size-lg);
|
|
74
|
-
|
|
84
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
85
|
+
--control-step-sm: 0;
|
|
86
|
+
--control-step-md: 0;
|
|
87
|
+
--control-step-lg: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tight {
|
|
91
|
+
--gap: var(--space-20);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.loose {
|
|
95
|
+
--gap: var(--space-50);
|
|
75
96
|
}
|
|
76
97
|
|
|
77
98
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
@@ -100,7 +121,10 @@
|
|
|
100
121
|
).sm {
|
|
101
122
|
--control-size: var(--control-size-sm);
|
|
102
123
|
--control-font-size: var(--font-size-sm);
|
|
103
|
-
--control-pad-x: var(--
|
|
124
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
125
|
+
--control-step-sm: 1;
|
|
126
|
+
--control-step-md: 0;
|
|
127
|
+
--control-step-lg: 0;
|
|
104
128
|
}
|
|
105
129
|
|
|
106
130
|
:where(
|
|
@@ -124,7 +148,10 @@
|
|
|
124
148
|
).lg {
|
|
125
149
|
--control-size: var(--control-size-lg);
|
|
126
150
|
--control-font-size: var(--font-size-lg);
|
|
127
|
-
--control-pad-x: var(--
|
|
151
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
152
|
+
--control-step-sm: 0;
|
|
153
|
+
--control-step-md: 0;
|
|
154
|
+
--control-step-lg: 1;
|
|
128
155
|
}
|
|
129
156
|
|
|
130
157
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Form actions —
|
|
2
|
+
* Form actions — the role of a .cluster action row that closes a form.
|
|
3
3
|
*
|
|
4
4
|
* Docs: docs/pages/forms/form-actions.md
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Markup: `<div class="cluster form-actions">`. The cluster owns the row
|
|
7
|
+
* (flex, wrap, gap); this role owns the form-specific alignment policy and the
|
|
8
|
+
* opt-in sticky variant. The distance to the fields above belongs to the
|
|
9
|
+
* parent layout (.stack on the form), never to a margin here: a margin would
|
|
10
|
+
* add to a stack's gap. The sticky variant docks to the viewport bottom with a
|
|
11
|
+
* translucent blur where supported; at rest it stays visually neutral so it
|
|
12
|
+
* does not double as a separator.
|
|
9
13
|
*
|
|
10
14
|
* Public hooks:
|
|
11
15
|
* --form-actions-align
|
|
12
16
|
* --form-actions-justify
|
|
13
|
-
* --form-actions-margin-block-start
|
|
14
17
|
*
|
|
15
18
|
* Internal:
|
|
16
19
|
* --form-actions-sticky-block-offset (owned by .form-actions.sticky)
|
|
17
20
|
* --form-actions-sticky-inline-offset (owned by .form-actions.sticky)
|
|
18
21
|
* --form-actions-sticky-padding (owned by .form-actions.sticky)
|
|
19
22
|
*/
|
|
23
|
+
|
|
24
|
+
/* The policy hooks write the properties themselves rather than relaying
|
|
25
|
+
--cluster-*: a relay would inherit into any .cluster nested in the row.
|
|
26
|
+
These are equal-specificity overrides of .cluster, so they win only because
|
|
27
|
+
forms load after layout in the official bundle (tests/css-audit.test.js
|
|
28
|
+
guards the order); utilities load later and keep the author's last word. On
|
|
29
|
+
this row --cluster-justify/--cluster-align have no effect. */
|
|
20
30
|
.form-actions {
|
|
21
|
-
display: flex;
|
|
22
|
-
flex-direction: row;
|
|
23
|
-
flex-wrap: wrap;
|
|
24
|
-
gap: var(--gap);
|
|
25
31
|
align-items: var(--form-actions-align, center);
|
|
26
32
|
justify-content: var(--form-actions-justify, flex-start);
|
|
27
|
-
margin-block-start: var(--form-actions-margin-block-start, var(--space-50));
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
/* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
|