actual-css 0.9.2 → 0.11.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 +169 -1
- package/README.md +65 -44
- package/components.json +35 -14
- package/dist/actual.full.css +877 -583
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/app-ui.md +1 -1
- package/docs/design-notes/column-layout.md +103 -520
- package/docs/design-notes/enhancement-contract.md +16 -7
- package/docs/design-notes/forced-colors.md +12 -1
- package/docs/design-notes/naming.md +1 -1
- 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/design-notes/surface-reparenting.md +8 -9
- package/docs/navigation.json +4 -0
- package/docs/pages/components/accordion.md +55 -1
- package/docs/pages/components/alert.md +9 -15
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +16 -7
- package/docs/pages/components/bleed.md +42 -0
- package/docs/pages/components/button.md +30 -4
- package/docs/pages/components/card.md +21 -86
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/close.md +48 -0
- package/docs/pages/components/data-list.md +26 -1
- package/docs/pages/components/dialog.md +21 -18
- package/docs/pages/components/drawer.md +9 -9
- package/docs/pages/components/navbar.md +5 -5
- 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 +14 -9
- package/docs/pages/components/table.md +13 -0
- package/docs/pages/enhancements/flyout.md +2 -0
- package/docs/pages/enhancements/overview.md +5 -0
- package/docs/pages/enhancements/tabs.md +4 -0
- package/docs/pages/enhancements/tooltip.md +3 -1
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/choice-cards.md +116 -4
- package/docs/pages/forms/combobox.md +39 -12
- package/docs/pages/forms/joined-controls.md +2 -5
- package/docs/pages/forms/overview.md +10 -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 +110 -18
- package/docs/pages/foundations/tokens.md +41 -13
- package/docs/pages/guides/bootstrap.md +2 -1
- package/docs/pages/guides/introduction.md +15 -23
- package/docs/pages/guides/modular-import.md +54 -7
- package/docs/pages/guides/progressive-enhancement.md +19 -17
- package/docs/pages/guides/tailwind.md +21 -7
- package/docs/pages/layout/column-layout.md +92 -54
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +5 -3
- package/llms.txt +25 -4
- package/package.json +10 -6
- package/reserved-classes.json +6 -30
- package/skills/actual-css/PROJECT.md.example +65 -0
- package/skills/actual-css/README.md +64 -0
- package/skills/actual-css/SKILL.md +234 -0
- package/skills/actual-css/references/accessibility.md +44 -0
- package/skills/actual-css/references/api-and-vocabulary.md +94 -0
- package/skills/actual-css/references/enhancements.md +59 -0
- package/skills/actual-css/references/forms.md +49 -0
- package/skills/actual-css/references/layout-and-theming.md +82 -0
- package/skills/actual-css/references/review-checklist.md +51 -0
- package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
- package/src/css/components/accordion.css +44 -7
- package/src/css/components/alert.css +23 -43
- package/src/css/components/app-nav.css +9 -0
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/badge.css +11 -39
- package/src/css/components/bleed.css +79 -0
- package/src/css/components/button.css +6 -17
- package/src/css/components/card.css +7 -40
- package/src/css/components/close.css +91 -0
- package/src/css/components/data-list.css +18 -0
- package/src/css/components/drawer.css +15 -56
- package/src/css/components/fab.css +10 -0
- package/src/css/components/flyout.css +5 -1
- package/src/css/components/index.css +2 -0
- package/src/css/components/join.css +36 -20
- package/src/css/components/menu.css +12 -0
- package/src/css/components/meter.css +1 -1
- package/src/css/components/modal.css +10 -57
- package/src/css/components/navbar.css +27 -0
- 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 +19 -3
- package/src/css/core/theme.css +33 -43
- package/src/css/core/tokens.css +9 -5
- package/src/css/core/variants.css +5 -34
- 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 +142 -24
- package/src/css/forms/choice.css +2 -3
- package/src/css/forms/control.css +17 -10
- package/src/css/forms/custom-select.css +4 -4
- package/src/css/forms/native.css +4 -4
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +72 -14
- package/src/css/forms/switch.css +2 -3
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/column-layout.css +26 -111
- package/src/css/utilities/extra.css +7 -6
- package/src/js/command.js +61 -32
- package/src/js/dialog.js +103 -47
- package/src/js/dismiss.js +1 -2
- package/src/js/enhance.js +91 -126
- package/src/js/escape.js +1 -0
- package/src/js/flyout.js +10 -1
- package/src/js/focus-group.js +2 -1
- package/src/js/full.js +1 -0
- package/src/js/keys.js +14 -0
- package/src/js/menu.js +2 -1
- 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 +21 -10
- package/src/js/surface.js +12 -8
- package/src/js/tab.js +2 -1
- package/src/js/tooltip.js +29 -2
- package/src/js/validation.js +8 -15
- package/dist/actual.css +0 -876
- package/dist/actual.js +0 -1
- package/dist/actual.min.css +0 -1
|
@@ -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
|
@@ -3,9 +3,25 @@
|
|
|
3
3
|
pointer/mouse focus and interactive components layer their ring on top.
|
|
4
4
|
|
|
5
5
|
Invariant: an interactive component must never rely on box-shadow alone
|
|
6
|
-
for its keyboard focus indicator
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
for its keyboard focus indicator; forced colors keep outlines and drop
|
|
7
|
+
shadows.
|
|
8
|
+
|
|
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
|
|
17
|
+
surface — the theme guarantees --focus at 3:1 against both --surface and
|
|
18
|
+
--surface-solid, so one token reads on the page, on a solid band and on
|
|
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. */
|
|
9
25
|
:focus {
|
|
10
26
|
outline: var(--focus-outline);
|
|
11
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 {
|
|
@@ -72,10 +74,10 @@
|
|
|
72
74
|
|
|
73
75
|
/* Lines and focus */
|
|
74
76
|
--border: hsl(260 15% 88%);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
--focus
|
|
77
|
+
/* A primary-hued mid-tone on purpose, one value for both schemes: focus
|
|
78
|
+
lines must hold 3:1 against --surface and --surface-solid alike (page,
|
|
79
|
+
fields, a solid band), which rules out the primary itself. */
|
|
80
|
+
--focus: hsl(268 30% 55%);
|
|
79
81
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
80
82
|
|
|
81
83
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
@@ -98,7 +100,12 @@
|
|
|
98
100
|
--heading: var(--text);
|
|
99
101
|
--selection-bg: var(--primary);
|
|
100
102
|
--selection-fg: var(--primary-fg);
|
|
101
|
-
|
|
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);
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
|
@@ -107,18 +114,17 @@
|
|
|
107
114
|
default theme: a custom property bakes its var() references where it is
|
|
108
115
|
declared, so these resolve against root/light/dark tokens on every boundary.
|
|
109
116
|
Presets and custom islands keep their --soft-fg-mix / raw-intent ink unless
|
|
110
|
-
they declare hooks themselves
|
|
111
|
-
|
|
112
|
-
:where(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
:root,
|
|
117
|
+
they declare hooks themselves.
|
|
118
|
+
|
|
119
|
+
Why :where(:root) and why the reset comes second: both rules match when a
|
|
120
|
+
custom theme sits on <html> itself. A bare :root is (0,1,0) and would
|
|
121
|
+
outrank the zero-specificity reset, so the same theme resolved a calibrated
|
|
122
|
+
Ink & Terra ink on <html data-theme="brand"> and the raw --soft-fg-mix ink
|
|
123
|
+
on <div data-theme="brand"> — one attribute, two soft palettes. Levelling
|
|
124
|
+
both rules to zero specificity makes source order the tiebreaker, so the
|
|
125
|
+
reset must stay below. Zero specificity is also what lets a theme declare
|
|
126
|
+
its own hooks (e.g. brutalist's 45% soft-fg-mix) and win at (0,1,0). */
|
|
127
|
+
:where(:root),
|
|
122
128
|
[data-theme="light"],
|
|
123
129
|
[data-theme="dark"] {
|
|
124
130
|
--secondary-soft-fg: color-mix(in oklab, var(--text) 10%, var(--secondary) 90%);
|
|
@@ -128,6 +134,15 @@
|
|
|
128
134
|
--neutral-soft-fg: color-mix(in oklab, var(--text) 20%, var(--neutral) 80%);
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
:where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) {
|
|
138
|
+
--primary-soft-fg: initial;
|
|
139
|
+
--secondary-soft-fg: initial;
|
|
140
|
+
--success-soft-fg: initial;
|
|
141
|
+
--warning-soft-fg: initial;
|
|
142
|
+
--danger-soft-fg: initial;
|
|
143
|
+
--neutral-soft-fg: initial;
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
/* Native platform controls (checkbox, radio, range, progress) that are not
|
|
132
147
|
restyled by a component class still follow the theme accent. Declared on
|
|
133
148
|
every island: accent-color inherits as a computed value, so islands must
|
|
@@ -149,20 +164,6 @@
|
|
|
149
164
|
--tone-fg: var(--text);
|
|
150
165
|
}
|
|
151
166
|
|
|
152
|
-
/* Focus ring derivation — color-mix tier. Runs whether or not light-dark()
|
|
153
|
-
is also supported, so it covers the color-mix-without-light-dark browser
|
|
154
|
-
tier on its own; the light-dark block below refines it further with the
|
|
155
|
-
light/dark alpha split once both features are available. Declared on every
|
|
156
|
-
[data-theme] island (not just the built-in light/dark pair) so custom
|
|
157
|
-
islands recolor the ring for free; a preset that re-declares --focus-ring
|
|
158
|
-
later in source order intentionally pins its own ring instead. */
|
|
159
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
160
|
-
:root,
|
|
161
|
-
[data-theme] {
|
|
162
|
-
--focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
167
|
/* Auto dark mode via light-dark(). Resolves based on the element's color-scheme:
|
|
167
168
|
:root (light dark) follows the OS, [data-theme="light"] forces light,
|
|
168
169
|
[data-theme="dark"] forces dark by overriding color-scheme.
|
|
@@ -199,22 +200,11 @@
|
|
|
199
200
|
--text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%));
|
|
200
201
|
|
|
201
202
|
--border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%));
|
|
202
|
-
--focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28));
|
|
203
203
|
--hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06));
|
|
204
204
|
--shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%));
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
@supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) {
|
|
209
|
-
:root,
|
|
210
|
-
[data-theme] {
|
|
211
|
-
--focus-ring: light-dark(
|
|
212
|
-
color-mix(in oklch, var(--focus) 30%, transparent),
|
|
213
|
-
color-mix(in oklch, var(--focus) 28%, transparent)
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
208
|
@media (prefers-contrast: more) {
|
|
219
209
|
:root,
|
|
220
210
|
/* The repeated attribute intentionally outranks single-attribute preset
|
|
@@ -222,7 +212,7 @@
|
|
|
222
212
|
[data-theme][data-theme] {
|
|
223
213
|
--border-width: 2px;
|
|
224
214
|
--focus-outline-offset: 3px;
|
|
225
|
-
--focus-ring-width:
|
|
215
|
+
--focus-ring-width: 3px;
|
|
226
216
|
--text-muted: var(--text);
|
|
227
217
|
--text-subtle: var(--text);
|
|
228
218
|
}
|
package/src/css/core/tokens.css
CHANGED
|
@@ -78,11 +78,15 @@
|
|
|
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
|
-
--focus-ring-width:
|
|
89
|
+
--focus-ring-width: 2px;
|
|
86
90
|
|
|
87
91
|
/* Status dot positioning — used by .avatar > .badge:empty. The offset is
|
|
88
92
|
the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
|
|
@@ -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 {
|
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
* Cross-cutting variant token mapping.
|
|
3
3
|
*
|
|
4
4
|
* Variant scope: .solid/.soft/.outline/.surface set --ui-* for components that
|
|
5
|
-
* opt into shared variants.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* (.card, .navbar, .alert, .badge) invert through their own rules. Loaded
|
|
9
|
-
* before components, so a component with its own explicit surface (e.g.
|
|
10
|
-
* .accordion) naturally keeps that surface — .inverted never forces a
|
|
11
|
-
* non-participant.
|
|
5
|
+
* opt into shared variants. A subtree that needs another palette or scheme is
|
|
6
|
+
* a data-theme island, never a variant: only an island recomputes every token
|
|
7
|
+
* (fields, states, intents), where a remap of a few --ui-* tokens cannot.
|
|
12
8
|
* Component-specific defaults (.btn solid-by-default, .badge
|
|
13
9
|
* soft-by-default, .alert soft-by-default) live with their components:
|
|
14
10
|
* components/button.css — .btn default solid + .btn.ghost + .btn.link
|
|
@@ -64,28 +60,6 @@
|
|
|
64
60
|
--ui-border: var(--border);
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
/* Inverse surface treatment. Paints the element directly (so it works on any
|
|
68
|
-
bare block) and remaps the shared --ui-* tokens so opt-in components
|
|
69
|
-
(.card, .navbar) invert through their own rules. Because this loads before
|
|
70
|
-
component base surfaces, components with their own explicit surface
|
|
71
|
-
(e.g. .accordion) keep it — participation is opt-in, never forced. --heading
|
|
72
|
-
is set so global headings (var(--heading, inherit)) do not inherit an outside
|
|
73
|
-
heading color onto the dark surface; surface-owning descendants derive
|
|
74
|
-
their contextual aliases from their own local surface. */
|
|
75
|
-
.inverted {
|
|
76
|
-
--ui-bg: var(--surface-solid);
|
|
77
|
-
--ui-fg: var(--surface);
|
|
78
|
-
--ui-border: transparent;
|
|
79
|
-
--ui-hover-bg: var(--surface-solid);
|
|
80
|
-
|
|
81
|
-
--heading: var(--surface);
|
|
82
|
-
--busy-overlay-bg: var(--surface-solid);
|
|
83
|
-
|
|
84
|
-
background: var(--ui-bg);
|
|
85
|
-
color: var(--ui-fg);
|
|
86
|
-
border-color: var(--ui-border);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
63
|
.compact {
|
|
90
64
|
--gap: var(--space-20);
|
|
91
65
|
--density-space: var(--space-20);
|
|
@@ -103,7 +77,8 @@
|
|
|
103
77
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
104
78
|
component (field, choice-card, floating-field, input-icon, join, pagination,
|
|
105
79
|
tabs, flyout). A local size declaration beats inherited density because it
|
|
106
|
-
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. */
|
|
107
82
|
:where(
|
|
108
83
|
.btn,
|
|
109
84
|
.input,
|
|
@@ -169,10 +144,6 @@
|
|
|
169
144
|
);
|
|
170
145
|
/* @sync soft-recipe */
|
|
171
146
|
}
|
|
172
|
-
|
|
173
|
-
.inverted {
|
|
174
|
-
--ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black);
|
|
175
|
-
}
|
|
176
147
|
}
|
|
177
148
|
|
|
178
149
|
/* Interactive hover recipes live in the components that consume them:
|
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
|
+
}
|