actual-css 0.10.0 → 0.11.1
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 +99 -1
- package/README.md +6 -2
- package/components.json +14 -3
- package/dist/actual.full.css +483 -128
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/enhancement-contract.md +16 -7
- package/docs/design-notes/platform-alignment.md +8 -4
- package/docs/design-notes/rich-text.md +61 -0
- package/docs/design-notes/steps.md +2 -2
- package/docs/navigation.json +4 -1
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +10 -0
- package/docs/pages/components/bleed.md +5 -4
- package/docs/pages/components/button.md +1 -1
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/close.md +9 -3
- package/docs/pages/components/dialog.md +145 -13
- package/docs/pages/components/meter.md +2 -0
- package/docs/pages/components/pagination.md +15 -15
- package/docs/pages/components/shimmer.md +57 -0
- package/docs/pages/components/skeleton.md +2 -1
- package/docs/pages/components/spinner.md +11 -6
- package/docs/pages/components/table.md +13 -0
- package/docs/pages/enhancements/flyout.md +2 -0
- package/docs/pages/enhancements/tabs.md +4 -0
- package/docs/pages/examples/overview.md +1 -0
- package/docs/pages/forms/choice-cards.md +77 -4
- package/docs/pages/forms/combobox.md +16 -1
- package/docs/pages/forms/overview.md +4 -1
- package/docs/pages/forms/range.md +110 -5
- package/docs/pages/forms/rich-text.md +110 -0
- package/docs/pages/forms/validation.md +5 -1
- package/docs/pages/foundations/theming.md +49 -3
- package/docs/pages/foundations/tokens.md +15 -5
- package/docs/pages/guides/bootstrap.md +2 -1
- package/docs/pages/guides/design-tools.md +36 -0
- package/docs/pages/guides/introduction.md +1 -1
- package/docs/pages/guides/modular-import.md +1 -1
- package/docs/pages/guides/progressive-enhancement.md +13 -14
- package/docs/pages/guides/tailwind.md +4 -2
- package/llms.txt +4 -3
- package/package.json +6 -1
- package/reserved-classes.json +1 -0
- package/skills/actual-css/references/enhancements.md +35 -0
- package/src/cli/actual-css.js +49 -6
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/bleed.css +5 -5
- package/src/css/components/button.css +14 -1
- package/src/css/components/close.css +17 -6
- package/src/css/components/flyout.css +4 -1
- package/src/css/components/meter.css +6 -3
- package/src/css/components/modal.css +123 -7
- package/src/css/components/pagination.css +3 -2
- package/src/css/components/rating.css +1 -1
- package/src/css/components/status-bar.css +2 -2
- package/src/css/components/steps.css +3 -3
- package/src/css/components/tab.css +3 -3
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +1 -1
- package/src/css/core/focus.css +14 -4
- package/src/css/core/theme.css +8 -0
- package/src/css/core/tokens.css +8 -4
- package/src/css/core/variants.css +2 -1
- package/src/css/effects/aura.css +51 -33
- package/src/css/effects/index.css +1 -0
- package/src/css/effects/shimmer.css +57 -0
- package/src/css/forms/choice-card.css +71 -23
- package/src/css/forms/control.css +2 -2
- package/src/css/forms/native.css +5 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +64 -21
- package/src/js/command.js +61 -32
- package/src/js/dialog.js +8 -23
- package/src/js/dismiss.js +1 -2
- package/src/js/enhance.js +84 -123
- package/src/js/full.js +1 -0
- package/src/js/password.js +1 -8
- package/src/js/range.js +151 -0
- package/src/js/scrollspy.js +4 -5
- package/src/js/status.js +2 -10
- package/src/js/tooltip.js +11 -1
- package/src/js/validation.js +4 -10
- package/src/tooling/design-components.js +286 -0
- package/src/tooling/design-export.js +69 -0
- package/src/tooling/design-tokens.js +466 -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
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Public hooks:
|
|
10
10
|
* --modal-size
|
|
11
|
+
* --modal-pad
|
|
12
|
+
* --modal-header-bg
|
|
11
13
|
* --dialog-viewport-gap
|
|
12
14
|
* --dialog-icon-size
|
|
13
15
|
* --dialog-icon-glyph-size
|
|
@@ -15,6 +17,7 @@
|
|
|
15
17
|
* The corner close control is .close (components/close.css).
|
|
16
18
|
*
|
|
17
19
|
* Internal:
|
|
20
|
+
* --dialog-inner-radius
|
|
18
21
|
* --dialog-focus-bleed
|
|
19
22
|
* --dialog-viewport-block
|
|
20
23
|
* --dialog-viewport-gap-default
|
|
@@ -33,6 +36,11 @@
|
|
|
33
36
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default))
|
|
34
37
|
);
|
|
35
38
|
|
|
39
|
+
/* The panel is borderless, so its inner and outer radius are the same. Kept
|
|
40
|
+
as one name for the padded wrapper and any edge band (header,
|
|
41
|
+
confirmation footer) so none of them drifts from the corner. */
|
|
42
|
+
--dialog-inner-radius: var(--radius-lg);
|
|
43
|
+
|
|
36
44
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
37
45
|
provides the containing block for the absolutely positioned close button. */
|
|
38
46
|
position: fixed;
|
|
@@ -46,7 +54,9 @@
|
|
|
46
54
|
dynamic token; only the modal pins the small viewport. */
|
|
47
55
|
--dialog-viewport-block: var(--viewport-block);
|
|
48
56
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
49
|
-
|
|
57
|
+
/* Zero rather than absent: the UA stylesheet draws `border: solid` on
|
|
58
|
+
dialog, and an authored edge reads as a hard seam against .bleed bands. */
|
|
59
|
+
border: 0;
|
|
50
60
|
border-radius: var(--radius-lg);
|
|
51
61
|
background: var(--surface-raised);
|
|
52
62
|
color: var(--text);
|
|
@@ -68,14 +78,73 @@
|
|
|
68
78
|
transform var(--duration) var(--ease-exit);
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
@media (forced-colors: active) {
|
|
82
|
+
/* With no authored border the panel edge disappears once colors collapse.
|
|
83
|
+
The browser forces the border color, so only the width is authored. */
|
|
84
|
+
:where(dialog).modal {
|
|
85
|
+
border: var(--border-width) solid CanvasText;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
:where(dialog).modal > form,
|
|
72
90
|
:where(dialog).modal > .stack {
|
|
91
|
+
--surface-pad: var(--modal-pad, var(--space-40));
|
|
92
|
+
|
|
73
93
|
display: grid;
|
|
74
94
|
gap: var(--space-40);
|
|
75
95
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
96
|
+
border-radius: var(--dialog-inner-radius);
|
|
76
97
|
overflow: auto;
|
|
77
98
|
overscroll-behavior: contain;
|
|
78
|
-
padding: var(--
|
|
99
|
+
padding: var(--surface-pad);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Optional dialog chrome lives beside the padded body wrapper. Its leading
|
|
103
|
+
region and trailing controls share one center axis; wrapping the close in
|
|
104
|
+
the trailing cluster keeps it in flow instead of invoking the absolute
|
|
105
|
+
corner treatment from close.css. */
|
|
106
|
+
:where(dialog).modal > header {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: space-between;
|
|
110
|
+
gap: var(--space-30);
|
|
111
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
112
|
+
padding: var(--modal-pad, var(--space-40));
|
|
113
|
+
border-start-start-radius: var(--dialog-inner-radius);
|
|
114
|
+
border-start-end-radius: var(--dialog-inner-radius);
|
|
115
|
+
background: var(--modal-header-bg, var(--surface-subtle));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
:where(dialog).modal > header > .media {
|
|
119
|
+
flex: 1 1 auto;
|
|
120
|
+
min-inline-size: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:where(dialog).modal > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
127
|
+
margin-block: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
:where(dialog).modal > header > .cluster {
|
|
131
|
+
--cluster-wrap: nowrap;
|
|
132
|
+
|
|
133
|
+
flex: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@supports selector(dialog:has(> header)) {
|
|
137
|
+
/* The header stays fixed while the body wrapper owns overflow. Without
|
|
138
|
+
:has(), compact dialogs keep ordinary block flow as the fallback. */
|
|
139
|
+
:where(dialog).modal[open]:has(> header) {
|
|
140
|
+
display: grid;
|
|
141
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
:where(dialog).modal:has(> header) > :is(form, .stack) {
|
|
145
|
+
min-block-size: 0;
|
|
146
|
+
max-block-size: none;
|
|
147
|
+
}
|
|
79
148
|
}
|
|
80
149
|
|
|
81
150
|
@supports (block-size: 100svh) {
|
|
@@ -127,12 +196,32 @@
|
|
|
127
196
|
margin-block-end: 0;
|
|
128
197
|
}
|
|
129
198
|
|
|
199
|
+
/* A media row inside the structural header is title anatomy, so its heading
|
|
200
|
+
does not bring prose margins into the icon alignment. */
|
|
201
|
+
:where(dialog).modal > :where(form, .stack) > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
202
|
+
margin-block: 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
130
205
|
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
131
206
|
content flow; headers reserve inline room so titles never run under it.
|
|
132
207
|
The form/.stack anatomy is structural, so it stays in :where() and never
|
|
133
208
|
lends .stack's specificity to a bare <form>. */
|
|
134
209
|
:where(dialog).modal > :where(form, .stack) > header {
|
|
135
|
-
padding-inline-end: calc(
|
|
210
|
+
padding-inline-end: calc(
|
|
211
|
+
var(--close-size, var(--control-size)) +
|
|
212
|
+
var(--space-20) +
|
|
213
|
+
var(--space-30)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* A corner .close that is a direct child of the chrome header needs the same
|
|
218
|
+
reserve, but never below the header's own pad. Kept as its own rule so an
|
|
219
|
+
unsupported :has() drops only this geometry, not the wrapper reserve. */
|
|
220
|
+
: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
|
+
);
|
|
136
225
|
}
|
|
137
226
|
|
|
138
227
|
:where(dialog).modal > :where(form, .stack) > footer {
|
|
@@ -146,19 +235,25 @@
|
|
|
146
235
|
action band. The media primitive owns icon/content alignment; this variant
|
|
147
236
|
only removes the standard form inset so both regions can own their padding. */
|
|
148
237
|
:where(dialog).modal.dialog-confirmation > form {
|
|
238
|
+
--surface-pad: 0px;
|
|
239
|
+
|
|
149
240
|
gap: 0;
|
|
150
241
|
padding: 0;
|
|
151
242
|
}
|
|
152
243
|
|
|
153
|
-
:where(dialog).modal.dialog-confirmation > form > .media {
|
|
244
|
+
:where(dialog).modal.dialog-confirmation > form > :is(.media, header) {
|
|
154
245
|
gap: var(--space-40);
|
|
155
246
|
padding: var(--space-50);
|
|
156
247
|
}
|
|
157
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. */
|
|
158
252
|
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
253
|
+
justify-content: space-between;
|
|
159
254
|
padding: var(--space-40) var(--space-50);
|
|
160
|
-
border-end-start-radius:
|
|
161
|
-
border-end-end-radius:
|
|
255
|
+
border-end-start-radius: var(--dialog-inner-radius);
|
|
256
|
+
border-end-end-radius: var(--dialog-inner-radius);
|
|
162
257
|
background: var(--surface-subtle);
|
|
163
258
|
}
|
|
164
259
|
|
|
@@ -195,8 +290,29 @@
|
|
|
195
290
|
line-height: 1;
|
|
196
291
|
}
|
|
197
292
|
|
|
293
|
+
:where(dialog).modal.dialog-confirmation > form > .media > header {
|
|
294
|
+
display: grid;
|
|
295
|
+
gap: var(--space-20);
|
|
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 {
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
min-block-size: var(--dialog-icon-size, 3rem);
|
|
312
|
+
}
|
|
313
|
+
|
|
198
314
|
@media (max-width: 30rem) {
|
|
199
|
-
:where(dialog).modal.dialog-confirmation > form > .media,
|
|
315
|
+
:where(dialog).modal.dialog-confirmation > form > :is(.media, header),
|
|
200
316
|
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
201
317
|
padding: var(--space-40);
|
|
202
318
|
}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Docs: docs/pages/components/pagination.md
|
|
5
5
|
*
|
|
6
|
-
* Render .btn elements inside a .pagination list. The active page is
|
|
7
|
-
* with aria-current="page"; .btn owns the active state styling
|
|
6
|
+
* Render .btn.ghost elements inside a .pagination list. The active page is
|
|
7
|
+
* marked with aria-current="page"; .btn owns the active state styling, so the
|
|
8
|
+
* current page is the only filled item.
|
|
8
9
|
*
|
|
9
10
|
* Truncated ranges are plain text (`.muted`, `aria-hidden="true"`), not .btn
|
|
10
11
|
* — they are decorative, not actionable. They still get the same box sizing
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.status-bar {
|
|
36
|
-
--status-bg: var(--intent, var(--
|
|
37
|
-
--status-fg: var(--intent-fg, var(--surface));
|
|
36
|
+
--status-bg: var(--intent, var(--surface-solid));
|
|
37
|
+
--status-fg: var(--intent-fg, var(--surface-solid-fg));
|
|
38
38
|
|
|
39
39
|
position: fixed;
|
|
40
40
|
/* Fixed positioning tracks the layout viewport, which the mobile software
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
--step-size: 1.75rem;
|
|
59
59
|
--step-inline-gap: var(--space-20);
|
|
60
60
|
--step-line-size: var(--border-width);
|
|
61
|
-
--step-marker-bg: var(--surface);
|
|
61
|
+
--step-marker-bg: var(--surface-opaque);
|
|
62
62
|
--step-marker-border: var(--border);
|
|
63
63
|
--step-marker-fg: var(--text-muted);
|
|
64
64
|
--step-marker-radius: var(--radius-full);
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
connector leaving current goes back to neutral. Declared after
|
|
139
139
|
.step-complete at equal specificity: current wins on every channel. */
|
|
140
140
|
.steps > [aria-current="step"] {
|
|
141
|
-
--step-marker-bg: var(--surface);
|
|
141
|
+
--step-marker-bg: var(--surface-opaque);
|
|
142
142
|
--step-marker-border: var(--state-selected);
|
|
143
143
|
--step-marker-fg: var(--state-selected);
|
|
144
144
|
--step-line: var(--border);
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
focus ring's own width and offset. Only this composition scrolls, and only a
|
|
178
178
|
link has a ring to protect. */
|
|
179
179
|
.steps-horizontal:has(a[href]) {
|
|
180
|
-
padding-block: calc(var(--focus-outline-offset) + var(--
|
|
180
|
+
padding-block: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/* One explicit row, the marker's: a `.step-label` is auto-placed into an
|
|
@@ -76,10 +76,10 @@
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/* The ring is square on the edge the tab is attached to, so it reads as part
|
|
79
|
-
of the strip rather than a floating chip.
|
|
79
|
+
of the strip rather than a floating chip. Color and width follow the
|
|
80
|
+
component ring contract (focus.css); only the geometry is tab-specific. */
|
|
80
81
|
.tab:focus-visible {
|
|
81
|
-
outline: var(--focus-
|
|
82
|
-
outline-color: var(--focus);
|
|
82
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
83
83
|
outline-offset: -2px;
|
|
84
84
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
85
85
|
}
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* .table-wrap provides the scroll container and outer chrome; .table owns row
|
|
7
7
|
* and cell rules. Tokens are sourced from the shared scale.
|
|
8
8
|
*
|
|
9
|
+
* Responsive strategy is horizontal overflow: the wrap scrolls, the table
|
|
10
|
+
* keeps its columns. Restructuring rows into cards would destroy the
|
|
11
|
+
* column scanability tables exist for, and needs data-label duplication
|
|
12
|
+
* because CSS cannot join a cell to its header — that representation choice
|
|
13
|
+
* belongs to the application, not the framework.
|
|
14
|
+
*
|
|
9
15
|
* Public hooks:
|
|
10
16
|
* --table-min
|
|
11
17
|
* --table-cell-pad
|
package/src/css/core/focus.css
CHANGED
|
@@ -6,12 +6,22 @@
|
|
|
6
6
|
for its keyboard focus indicator; forced colors keep outlines and drop
|
|
7
7
|
shadows.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Two contracts share the job, both --focus-ring-width wide. Text-like
|
|
10
|
+
elements, whose ink is the thing being focused (links, summary, .nav-link,
|
|
11
|
+
.menu-item, breadcrumb links, .navbar-brand), declare no outline and take
|
|
12
|
+
the generic fallback below: a currentColor line that follows whatever the
|
|
13
|
+
application painted. Boxed components, with a fill or border of their own,
|
|
14
|
+
draw the themed ring instead — a solid line in --focus: inset on fields
|
|
15
|
+
(inside their border box), offset by --focus-outline-offset on discrete
|
|
16
|
+
actions. The color never derives from an intent or a local
|
|
12
17
|
surface — the theme guarantees --focus at 3:1 against both --surface and
|
|
13
18
|
--surface-solid, so one token reads on the page, on a solid band and on
|
|
14
|
-
any nested surface.
|
|
19
|
+
any nested surface.
|
|
20
|
+
|
|
21
|
+
Documented exception: an invalid field focuses in --form-invalid-border
|
|
22
|
+
(danger), so the error stays visible at the point of correction. The border
|
|
23
|
+
carries the same hook; report:theme-contrast measures that line at 3:1
|
|
24
|
+
against the field's background alongside --focus. */
|
|
15
25
|
:focus {
|
|
16
26
|
outline: var(--focus-outline);
|
|
17
27
|
outline-offset: var(--focus-outline-offset);
|
package/src/css/core/theme.css
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* Shadow color, Focus ring derivation.
|
|
8
8
|
* Derived: "Theme-derived aliases", Tone subsystem, Backdrop fill — reference
|
|
9
9
|
* other tokens; hard-coded overrides on a custom theme will break the intent.
|
|
10
|
+
* Exception: --surface-opaque and --surface-solid-fg are public; a theme with
|
|
11
|
+
* translucent surfaces overrides them with opaque values.
|
|
10
12
|
* Internal: --hgroup-gap.
|
|
11
13
|
*/
|
|
12
14
|
:root {
|
|
@@ -98,6 +100,12 @@
|
|
|
98
100
|
--heading: var(--text);
|
|
99
101
|
--selection-bg: var(--primary);
|
|
100
102
|
--selection-fg: var(--primary-fg);
|
|
103
|
+
/* Surface roles that must stay opaque when a theme paints translucent
|
|
104
|
+
surfaces; both equal --surface on an opaque palette. --surface-opaque
|
|
105
|
+
hides or separates what is painted beneath a ring, separator or marker —
|
|
106
|
+
never a fill. --surface-solid-fg is the ink on --surface-solid. */
|
|
107
|
+
--surface-opaque: var(--surface);
|
|
108
|
+
--surface-solid-fg: var(--surface);
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
package/src/css/core/tokens.css
CHANGED
|
@@ -78,9 +78,13 @@
|
|
|
78
78
|
--ease-enter: cubic-bezier(0.2, 0, 0, 1);
|
|
79
79
|
--ease-exit: cubic-bezier(0.4, 0, 1, 1);
|
|
80
80
|
|
|
81
|
-
/*
|
|
81
|
+
/* Generic focus fallback — an adaptive currentColor line for elements
|
|
82
|
+
without a component ring (links, summary, plain items). Components draw
|
|
83
|
+
the themed ring instead: --focus-ring-width solid --focus. See focus.css
|
|
84
|
+
for the two contracts. Both share --focus-ring-width: they differ in
|
|
85
|
+
color only. */
|
|
82
86
|
--focus-outline-color: currentColor;
|
|
83
|
-
--focus-outline:
|
|
87
|
+
--focus-outline: var(--focus-ring-width) solid var(--focus-outline-color);
|
|
84
88
|
--focus-outline-offset: 2px;
|
|
85
89
|
--focus-ring-width: 2px;
|
|
86
90
|
|
|
@@ -143,8 +147,8 @@
|
|
|
143
147
|
--state-selected-fg: var(--primary-fg);
|
|
144
148
|
--state-disabled: var(--text-subtle);
|
|
145
149
|
|
|
146
|
-
/* Status dot ring —
|
|
147
|
-
--indicator-ring: var(--surface);
|
|
150
|
+
/* Status dot ring — cuts the dot out of what it overlaps. */
|
|
151
|
+
--indicator-ring: var(--surface-opaque);
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
:root {
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
78
78
|
component (field, choice-card, floating-field, input-icon, join, pagination,
|
|
79
79
|
tabs, flyout). A local size declaration beats inherited density because it
|
|
80
|
-
is declared on the sized component itself.
|
|
80
|
+
is declared on the sized component itself. Guarded by check:sync
|
|
81
|
+
(size-scale-participants): .sm and .lg must list the same components. */
|
|
81
82
|
:where(
|
|
82
83
|
.btn,
|
|
83
84
|
.input,
|
package/src/css/effects/aura.css
CHANGED
|
@@ -35,36 +35,55 @@
|
|
|
35
35
|
|
|
36
36
|
.aura {
|
|
37
37
|
--aura-color: var(--intent, var(--primary));
|
|
38
|
-
--aura-width:
|
|
38
|
+
--aura-width: 1px;
|
|
39
39
|
--aura-blur: 0.25rem;
|
|
40
|
-
--aura-intensity:
|
|
40
|
+
--aura-intensity: 0.4;
|
|
41
41
|
--aura-radius: var(--radius-lg);
|
|
42
42
|
--aura-duration: 6s;
|
|
43
43
|
|
|
44
44
|
position: relative;
|
|
45
45
|
isolation: isolate;
|
|
46
46
|
display: inline-grid;
|
|
47
|
-
padding: var(--aura-width);
|
|
48
47
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
49
|
-
|
|
48
|
+
|
|
49
|
+
/* The frame is the border itself, so --aura-width is the painted thickness.
|
|
50
|
+
A transparent border stays for forced colors, where it becomes the visible
|
|
51
|
+
boundary; a padding frame inside it would paint both, doubling the line. */
|
|
52
|
+
border: var(--aura-width) solid transparent;
|
|
50
53
|
background: var(--aura-color);
|
|
51
54
|
|
|
52
55
|
/* A short feathered accent rather than a long wedge with a hard edge, which
|
|
53
56
|
reads as one lit side. Nothing tints the frame outside it: a constant tint
|
|
54
|
-
across
|
|
57
|
+
across the frame draws an outline, the opposite of the effect. `border-box`
|
|
58
|
+
sizes the gradient to the border box: the default padding-box origin would
|
|
59
|
+
tile a neighbouring copy of the gradient into the border, which is now the
|
|
60
|
+
whole frame. */
|
|
55
61
|
background: conic-gradient(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
from var(--aura-angle),
|
|
63
|
+
transparent 0 78%,
|
|
64
|
+
var(--aura-color) 93%,
|
|
65
|
+
transparent
|
|
66
|
+
)
|
|
67
|
+
border-box;
|
|
68
|
+
|
|
69
|
+
/* Far halo — flat and static. At this blur the rotating detail is no longer
|
|
70
|
+
legible, so there is nothing to gain from animating a second wide blurred
|
|
71
|
+
layer. This is the layer that stays lit at all times, so it — not the
|
|
72
|
+
frame — is what keeps the object present between two passes of the
|
|
73
|
+
accent. An outer shadow never paints under the box, so it stays out of a
|
|
74
|
+
translucent child. min() because a color-mix() share above 100% is
|
|
75
|
+
invalid, where the opacity it replaces clamped. */
|
|
76
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 8) var(--aura-blur)
|
|
77
|
+
color-mix(in oklab, var(--aura-color) min(100%, calc(15% * var(--aura-intensity))), transparent);
|
|
61
78
|
}
|
|
62
79
|
|
|
63
|
-
/* Near halo — inherits the frame gradient, so it tracks the lit accent.
|
|
80
|
+
/* Near halo — inherits the frame gradient, so it tracks the lit accent. An
|
|
81
|
+
absolute inset starts at the padding edge, inside the border that is the
|
|
82
|
+
frame, so the negative inset puts the halo back on the frame. */
|
|
64
83
|
.aura::before {
|
|
65
84
|
position: absolute;
|
|
66
85
|
z-index: -1;
|
|
67
|
-
inset:
|
|
86
|
+
inset: calc(-1 * var(--aura-width));
|
|
68
87
|
border-radius: inherit;
|
|
69
88
|
background: inherit;
|
|
70
89
|
filter: blur(var(--aura-blur));
|
|
@@ -73,29 +92,22 @@
|
|
|
73
92
|
content: "";
|
|
74
93
|
}
|
|
75
94
|
|
|
76
|
-
/*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
passes of the accent. */
|
|
95
|
+
/* Occlusion — the frame and the near halo paint across the whole box, and only
|
|
96
|
+
their edge is meant to show. This mask sits above both, under the child, so
|
|
97
|
+
a transparent or translucent child (an .outline alert, a glass surface) is
|
|
98
|
+
framed rather than filled with the frame color. It follows the child's
|
|
99
|
+
shape through --aura-radius. */
|
|
82
100
|
.aura::after {
|
|
83
101
|
position: absolute;
|
|
84
102
|
z-index: -1;
|
|
85
|
-
inset:
|
|
86
|
-
border-radius:
|
|
87
|
-
background: var(--
|
|
88
|
-
filter: blur(calc(var(--aura-blur) * 4));
|
|
89
|
-
opacity: calc(0.15 * var(--aura-intensity));
|
|
103
|
+
inset: 0;
|
|
104
|
+
border-radius: var(--aura-radius);
|
|
105
|
+
background: var(--surface-opaque);
|
|
90
106
|
pointer-events: none;
|
|
91
107
|
content: "";
|
|
92
108
|
}
|
|
93
109
|
|
|
94
|
-
/* Lifts the child above
|
|
95
|
-
across the whole box and only its edge is meant to show, so a transparent
|
|
96
|
-
child (an .outline alert, a .ghost button) is filled with the frame color
|
|
97
|
-
rather than framed by it. Documented as a constraint, not worked around —
|
|
98
|
-
masking a ring would cost more than the case is worth. */
|
|
110
|
+
/* Lifts the child above the halo and the mask. */
|
|
99
111
|
.aura > * {
|
|
100
112
|
position: relative;
|
|
101
113
|
}
|
|
@@ -118,7 +130,7 @@
|
|
|
118
130
|
animation: aura-breathe-near var(--aura-duration) ease-in-out infinite;
|
|
119
131
|
}
|
|
120
132
|
|
|
121
|
-
.aura-glow
|
|
133
|
+
.aura-glow {
|
|
122
134
|
animation: aura-breathe-far var(--aura-duration) ease-in-out infinite;
|
|
123
135
|
}
|
|
124
136
|
}
|
|
@@ -130,8 +142,8 @@
|
|
|
130
142
|
@media (prefers-reduced-motion: reduce) {
|
|
131
143
|
.aura:not(.aura-glow) {
|
|
132
144
|
background:
|
|
133
|
-
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%),
|
|
134
|
-
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%);
|
|
145
|
+
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%) border-box,
|
|
146
|
+
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%) border-box;
|
|
135
147
|
}
|
|
136
148
|
}
|
|
137
149
|
|
|
@@ -152,10 +164,16 @@
|
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
166
|
|
|
167
|
+
/* The far halo is the element's own shadow: opening its spread and share
|
|
168
|
+
breathes it without scaling the child. */
|
|
155
169
|
@keyframes aura-breathe-far {
|
|
156
170
|
50% {
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 10) calc(var(--aura-blur) * 2)
|
|
172
|
+
color-mix(
|
|
173
|
+
in oklab,
|
|
174
|
+
var(--aura-color) min(100%, calc(28% * var(--aura-intensity))),
|
|
175
|
+
transparent
|
|
176
|
+
);
|
|
159
177
|
}
|
|
160
178
|
}
|
|
161
179
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Highlight sweeping across a text label — an activity in progress.
|
|
3
|
+
*
|
|
4
|
+
* Docs: docs/pages/components/shimmer.md
|
|
5
|
+
*
|
|
6
|
+
* Presentation only: it carries no state and selects no ARIA attribute, so the
|
|
7
|
+
* label stays whatever the author makes it (a role="status" message, a list
|
|
8
|
+
* item, a heading).
|
|
9
|
+
*
|
|
10
|
+
* Public hooks:
|
|
11
|
+
* --shimmer-color
|
|
12
|
+
* --shimmer-highlight
|
|
13
|
+
*/
|
|
14
|
+
.shimmer {
|
|
15
|
+
--shimmer-color: var(--text-muted);
|
|
16
|
+
--shimmer-highlight: var(--text);
|
|
17
|
+
|
|
18
|
+
color: var(--shimmer-color);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* The sweep paints the glyphs through a clipped background, which needs the
|
|
22
|
+
glyph fill transparent. Without background-clip: text that would erase the
|
|
23
|
+
label, so the transparency lives under the same query as the clip. It is
|
|
24
|
+
the fill, not `color`: a label class that sets its own color (.muted, a
|
|
25
|
+
caption style) would otherwise win the cascade and paint opaque glyphs over
|
|
26
|
+
the sweep. Reduced motion keeps the plain label — a frozen band would read
|
|
27
|
+
as a random tint. */
|
|
28
|
+
@supports (background-clip: text) {
|
|
29
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
30
|
+
.shimmer {
|
|
31
|
+
-webkit-text-fill-color: transparent;
|
|
32
|
+
background:
|
|
33
|
+
linear-gradient(
|
|
34
|
+
90deg,
|
|
35
|
+
var(--shimmer-color) 40%,
|
|
36
|
+
var(--shimmer-highlight) 50%,
|
|
37
|
+
var(--shimmer-color) 60%
|
|
38
|
+
)
|
|
39
|
+
0 0 / 300% 100%;
|
|
40
|
+
background-clip: text;
|
|
41
|
+
animation: actual-shimmer var(--duration-shimmer) linear infinite;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* The band occupies the middle third of a background three times the label's
|
|
47
|
+
width, so each pass leaves the end of the label before the next one enters
|
|
48
|
+
at the start. */
|
|
49
|
+
@keyframes actual-shimmer {
|
|
50
|
+
from {
|
|
51
|
+
background-position: 100% 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
to {
|
|
55
|
+
background-position: 0% 0;
|
|
56
|
+
}
|
|
57
|
+
}
|