actual-css 0.3.0 → 0.3.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 +11 -0
- package/dist/actual.css +344 -255
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/cascade-layer.md +6 -5
- package/docs/pages/components/button.md +6 -1
- package/docs/pages/components/card.md +16 -4
- package/docs/pages/examples/overview.md +2 -0
- package/docs/pages/foundations/theming.md +13 -6
- package/docs/pages/layout/cluster.md +4 -2
- package/docs/pages/layout/media.md +9 -2
- package/docs/pages/layout/stack.md +38 -1
- package/docs/pages/utilities/list.md +2 -1
- package/package.json +2 -1
- package/src/css/actual.css +4 -1
- package/src/css/components/alert.css +17 -7
- package/src/css/components/badge.css +20 -13
- package/src/css/components/button.css +16 -12
- package/src/css/components/card.css +10 -2
- package/src/css/components/navbar.css +13 -5
- package/src/css/components/overline.css +8 -2
- package/src/css/forms/control.css +2 -0
- package/src/css/grid.css +13 -0
- package/src/css/layout.css +18 -3
- package/src/css/themes/index.css +5 -1
- package/src/css/variants.css +24 -11
- package/src/js/surface.js +3 -1
- package/src/js/tooltip.js +25 -12
|
@@ -15,4 +15,41 @@ Vertical flow with consistent spacing, ideal for forms, card content, and conten
|
|
|
15
15
|
</section>
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Use stack for forms, card content, modal bodies, side panels, and documentation blocks.
|
|
18
|
+
Use stack for forms, card content, modal bodies, side panels, and documentation blocks.
|
|
19
|
+
|
|
20
|
+
Like `.cluster`, stack owns its children's block-axis spacing: direct-child
|
|
21
|
+
block margins are reset, so vertical rhythm always comes from `--gap`. Inline
|
|
22
|
+
margins stay free — for example `margin-inline: auto` on a child centers it
|
|
23
|
+
without fighting the primitive.
|
|
24
|
+
|
|
25
|
+
## Grouping items tightly
|
|
26
|
+
|
|
27
|
+
Stack spacing is a `--gap` between every direct child; the primitive cannot
|
|
28
|
+
tighten two specific children by itself. To group a few items into one unit
|
|
29
|
+
(a title and its description), nest a `.stack gap-none`:
|
|
30
|
+
|
|
31
|
+
```html demo
|
|
32
|
+
<div class="stack">
|
|
33
|
+
<div class="stack gap-none">
|
|
34
|
+
<strong>Ada Meridian</strong>
|
|
35
|
+
<span class="muted">Senior designer working on design systems.</span>
|
|
36
|
+
</div>
|
|
37
|
+
<span class="badge success soft">Active</span>
|
|
38
|
+
</div>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The outer stack keeps its rhythm between groups; the inner `gap-none` unit owns
|
|
42
|
+
the tight pairing.
|
|
43
|
+
|
|
44
|
+
For a one-off adjacency — pull a single child closer to its neighbour without
|
|
45
|
+
adding a wrapper — the escape hatch is app CSS. `gap` and `margin-block` are
|
|
46
|
+
additive, so a negative block margin on the child cancels the following gap:
|
|
47
|
+
|
|
48
|
+
```css
|
|
49
|
+
.stack > .profile-row {
|
|
50
|
+
margin-block-end: calc(var(--gap) * -1);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The stack resets child block margins to zero, so this re-adds a margin on that
|
|
55
|
+
one child only; the rest of the stack keeps its rhythm.
|
|
@@ -28,4 +28,5 @@ The overline exists because categories, kicker labels, and section markers recur
|
|
|
28
28
|
|
|
29
29
|
## CSS hooks
|
|
30
30
|
|
|
31
|
-
- `--prose-measure` — the readable measure `.measure` caps at; defaults to `70ch`.
|
|
31
|
+
- `--prose-measure` — the readable measure `.measure` caps at; defaults to `70ch`.
|
|
32
|
+
- `--overline-radius` — the pill chip's corner radius; defaults to `--radius-full`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-css",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Plain CSS component framework with semantic classes, universal variants, small tokens, theme hooks, and progressive enhancements.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.14",
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"check:links": "bun scripts/check-doc-links.js",
|
|
102
102
|
"check:templates": "bun scripts/check-templates.js",
|
|
103
103
|
"shot:forced": "bun scripts/forced-colors-shot.js",
|
|
104
|
+
"shot:multi": "bun scripts/multi-shot.js",
|
|
104
105
|
"shot:page": "bun scripts/page-shot.js",
|
|
105
106
|
"probe": "bun scripts/probe.js",
|
|
106
107
|
"check:sync": "bun scripts/check-sync.js",
|
package/src/css/actual.css
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
@import "./theme.css";
|
|
4
4
|
@import "./base.css";
|
|
5
5
|
@import "./intents.css";
|
|
6
|
+
/* Prose before layout: layout primitives (.stack, .cluster) reset child
|
|
7
|
+
margins and must win over .prose's flow margins at equal specificity —
|
|
8
|
+
source order is the tie-breaker, so prose loads first. */
|
|
9
|
+
@import "./prose.css";
|
|
6
10
|
@import "./layout.css";
|
|
7
11
|
@import "./grid.css";
|
|
8
12
|
@import "./variants.css";
|
|
9
|
-
@import "./prose.css";
|
|
10
13
|
@import "./components/index.css";
|
|
11
14
|
@import "./forms/index.css";
|
|
12
15
|
@import "./utilities.css";
|
|
@@ -15,8 +15,12 @@
|
|
|
15
15
|
* --alert-radius
|
|
16
16
|
* --alert-icon-size
|
|
17
17
|
* --alert-dismiss-size
|
|
18
|
+
* --alert-border-inline-start-color
|
|
19
|
+
* --alert-border-inline-start-width
|
|
18
20
|
*
|
|
19
21
|
* Internal:
|
|
22
|
+
* --alert-default-bg
|
|
23
|
+
* --alert-default-border
|
|
20
24
|
* --alert-bg
|
|
21
25
|
* --alert-fg
|
|
22
26
|
* --alert-border
|
|
@@ -37,9 +41,11 @@
|
|
|
37
41
|
--alert-pad-inline: var(--space-40);
|
|
38
42
|
--alert-font-size: var(--font-size-md);
|
|
39
43
|
--alert-icon-size: 1.25rem;
|
|
40
|
-
--alert-bg: var(--
|
|
44
|
+
--alert-default-bg: var(--surface-subtle);
|
|
45
|
+
--alert-default-border: var(--border);
|
|
46
|
+
--alert-bg: var(--ui-bg, var(--alert-default-bg));
|
|
41
47
|
--alert-fg: var(--ui-fg, var(--intent, var(--text)));
|
|
42
|
-
--alert-border: var(--ui-border, var(--border));
|
|
48
|
+
--alert-border: var(--ui-border, var(--alert-default-border));
|
|
43
49
|
|
|
44
50
|
padding-block: var(--alert-pad-block);
|
|
45
51
|
padding-inline: var(--alert-pad-inline);
|
|
@@ -64,13 +70,17 @@
|
|
|
64
70
|
margin-block-end: 0;
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
/* Alerts are soft by default.
|
|
68
|
-
|
|
73
|
+
/* Alerts are soft by default. The recipe writes local defaults rather than
|
|
74
|
+
--ui-* so an explicit shared treatment always wins without exclusions. */
|
|
69
75
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
70
|
-
.alert
|
|
76
|
+
.alert {
|
|
71
77
|
/* @sync soft-recipe */
|
|
72
|
-
--
|
|
73
|
-
|
|
78
|
+
--alert-default-bg: color-mix(
|
|
79
|
+
in oklch,
|
|
80
|
+
var(--surface) var(--soft-bg-mix),
|
|
81
|
+
var(--intent, var(--neutral))
|
|
82
|
+
);
|
|
83
|
+
--alert-default-border: color-mix(
|
|
74
84
|
in oklch,
|
|
75
85
|
var(--surface) var(--soft-border-mix),
|
|
76
86
|
var(--intent, var(--neutral))
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* --badge-radius
|
|
14
14
|
*
|
|
15
15
|
* Internal:
|
|
16
|
+
* --badge-default-bg
|
|
17
|
+
* --badge-default-fg
|
|
18
|
+
* --badge-default-border
|
|
19
|
+
* --badge-default-hover-bg
|
|
16
20
|
* --badge-bg
|
|
17
21
|
* --badge-fg
|
|
18
22
|
* --badge-border
|
|
@@ -33,9 +37,13 @@
|
|
|
33
37
|
--badge-pad-x: 0.55em;
|
|
34
38
|
--badge-font-size: 0.8125rem;
|
|
35
39
|
--badge-dot-size: 0.5rem;
|
|
36
|
-
--badge-bg: var(--
|
|
37
|
-
--badge-fg: var(--
|
|
38
|
-
--badge-border: var(--
|
|
40
|
+
--badge-default-bg: var(--surface-subtle);
|
|
41
|
+
--badge-default-fg: var(--intent, var(--text));
|
|
42
|
+
--badge-default-border: var(--border);
|
|
43
|
+
--badge-default-hover-bg: var(--badge-bg);
|
|
44
|
+
--badge-bg: var(--ui-bg, var(--badge-default-bg));
|
|
45
|
+
--badge-fg: var(--ui-fg, var(--badge-default-fg));
|
|
46
|
+
--badge-border: var(--ui-border, var(--badge-default-border));
|
|
39
47
|
--badge-radius: var(--radius-full);
|
|
40
48
|
|
|
41
49
|
display: inline-flex;
|
|
@@ -62,18 +70,17 @@
|
|
|
62
70
|
box-shadow var(--duration);
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
/* Soft by default
|
|
66
|
-
|
|
73
|
+
/* Soft by default. Local defaults remain behind --ui-* in the resolution
|
|
74
|
+
chain, so explicit shared treatments compose without selector exclusions. */
|
|
67
75
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
68
|
-
.badge
|
|
76
|
+
.badge {
|
|
69
77
|
/* @sync soft-recipe */
|
|
70
|
-
--badge-bg: color-mix(
|
|
78
|
+
--badge-default-bg: color-mix(
|
|
71
79
|
in oklch,
|
|
72
80
|
var(--surface) var(--soft-bg-mix),
|
|
73
81
|
var(--intent, var(--neutral))
|
|
74
82
|
);
|
|
75
|
-
--badge-
|
|
76
|
-
--badge-border: color-mix(
|
|
83
|
+
--badge-default-border: color-mix(
|
|
77
84
|
in oklch,
|
|
78
85
|
var(--surface) var(--soft-border-mix),
|
|
79
86
|
var(--intent, var(--neutral))
|
|
@@ -119,7 +126,7 @@
|
|
|
119
126
|
/* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
|
|
120
127
|
Declared outside :hover like button.css for the same reason. */
|
|
121
128
|
:is(a.badge, button.badge).outline {
|
|
122
|
-
--ui-hover-bg:
|
|
129
|
+
--ui-hover-bg: transparent;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
@@ -128,7 +135,7 @@
|
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
:is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
|
|
131
|
-
--
|
|
138
|
+
--badge-default-hover-bg: color-mix(
|
|
132
139
|
in oklch,
|
|
133
140
|
var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
|
|
134
141
|
var(--intent, var(--neutral))
|
|
@@ -136,12 +143,12 @@
|
|
|
136
143
|
}
|
|
137
144
|
|
|
138
145
|
:is(a.badge, button.badge).outline {
|
|
139
|
-
--ui-hover-bg: color-mix(in oklch,
|
|
146
|
+
--ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
:where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
144
|
-
background: var(--ui-hover-bg, var(--badge-bg));
|
|
151
|
+
background: var(--ui-hover-bg, var(--badge-default-hover-bg));
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
:where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
|
|
@@ -18,11 +18,16 @@
|
|
|
18
18
|
* --btn-focus-color
|
|
19
19
|
* --btn-gap
|
|
20
20
|
* --btn-hover-overlay
|
|
21
|
+
* --btn-default-bg
|
|
22
|
+
* --btn-default-fg
|
|
23
|
+
* --btn-default-border
|
|
24
|
+
* --btn-border-color
|
|
21
25
|
*
|
|
22
26
|
* Internal:
|
|
23
27
|
* --btn-bg
|
|
24
28
|
* --btn-fg
|
|
25
29
|
* --btn-border
|
|
30
|
+
* --btn-default-hover-bg
|
|
26
31
|
* --btn-focus-ring-color
|
|
27
32
|
* --btn-border-block-end-color
|
|
28
33
|
* --btn-border-block-end-width
|
|
@@ -47,6 +52,7 @@
|
|
|
47
52
|
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
48
53
|
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
49
54
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
55
|
+
--btn-default-hover-bg: var(--btn-bg);
|
|
50
56
|
--btn-radius: var(--radius);
|
|
51
57
|
--btn-focus-color: var(--intent, var(--focus));
|
|
52
58
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
@@ -99,7 +105,7 @@
|
|
|
99
105
|
fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
|
|
100
106
|
.btn.ghost {
|
|
101
107
|
--ui-bg: transparent;
|
|
102
|
-
--ui-fg: var(--intent,
|
|
108
|
+
--ui-fg: var(--intent, currentColor);
|
|
103
109
|
--ui-border: transparent;
|
|
104
110
|
}
|
|
105
111
|
|
|
@@ -117,12 +123,12 @@
|
|
|
117
123
|
Declared outside :hover: the variables resolve only when the hover rule
|
|
118
124
|
reads them, so the values are inert until interaction. */
|
|
119
125
|
.btn:is(.outline, .ghost) {
|
|
120
|
-
--ui-hover-bg:
|
|
126
|
+
--ui-hover-bg: transparent;
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
124
|
-
.btn
|
|
125
|
-
--
|
|
130
|
+
.btn {
|
|
131
|
+
--btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
.btn.soft {
|
|
@@ -133,19 +139,16 @@
|
|
|
133
139
|
);
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
.btn.outline
|
|
137
|
-
--ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
|
|
138
|
-
}
|
|
139
|
-
|
|
142
|
+
.btn.outline,
|
|
140
143
|
.btn.ghost {
|
|
141
|
-
--ui-hover-bg: color-mix(in oklch,
|
|
144
|
+
--ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
/* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
|
|
146
149
|
the overlay darkening for solid/soft is .btn-specific. */
|
|
147
150
|
.btn:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
148
|
-
background: var(--ui-hover-bg, var(--btn-bg));
|
|
151
|
+
background: var(--ui-hover-bg, var(--btn-default-hover-bg));
|
|
149
152
|
box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
|
|
150
153
|
}
|
|
151
154
|
|
|
@@ -155,7 +158,7 @@
|
|
|
155
158
|
|
|
156
159
|
.btn.link {
|
|
157
160
|
--ui-bg: transparent;
|
|
158
|
-
--ui-fg: var(--intent,
|
|
161
|
+
--ui-fg: var(--intent, currentColor);
|
|
159
162
|
--ui-border: transparent;
|
|
160
163
|
--btn-radius: var(--radius-sm);
|
|
161
164
|
--btn-border-block-end-color: transparent;
|
|
@@ -163,6 +166,8 @@
|
|
|
163
166
|
|
|
164
167
|
min-block-size: auto;
|
|
165
168
|
padding-inline: 0;
|
|
169
|
+
inline-size: fit-content;
|
|
170
|
+
max-inline-size: 100%;
|
|
166
171
|
border-color: transparent;
|
|
167
172
|
text-decoration: underline;
|
|
168
173
|
text-underline-offset: 0.18em;
|
|
@@ -173,7 +178,6 @@
|
|
|
173
178
|
|
|
174
179
|
.btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
175
180
|
background: transparent;
|
|
176
|
-
color: var(--intent, var(--text));
|
|
177
181
|
text-decoration-thickness: 0.12em;
|
|
178
182
|
}
|
|
179
183
|
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* edge children inherit the card corners. .bleed must be a direct child.
|
|
16
16
|
*
|
|
17
17
|
* Internal:
|
|
18
|
+
* --card-bg
|
|
19
|
+
* --card-fg
|
|
18
20
|
* --heading
|
|
19
21
|
* --busy-overlay-bg
|
|
20
22
|
*/
|
|
@@ -30,11 +32,17 @@
|
|
|
30
32
|
--card-pad: var(--space-40);
|
|
31
33
|
--card-radius: var(--radius-lg);
|
|
32
34
|
--card-max-inline-size: none;
|
|
35
|
+
--card-bg: var(--ui-bg, var(--surface-raised));
|
|
36
|
+
--card-fg: var(--ui-fg, var(--text));
|
|
37
|
+
|
|
38
|
+
/* Keep contextual descendants tied to the surface this card actually owns. */
|
|
39
|
+
--heading: var(--card-fg);
|
|
40
|
+
--busy-overlay-bg: var(--card-bg);
|
|
33
41
|
|
|
34
42
|
max-inline-size: var(--card-max-inline-size);
|
|
35
43
|
padding: var(--card-pad);
|
|
36
|
-
background: var(--
|
|
37
|
-
color: var(--
|
|
44
|
+
background: var(--card-bg);
|
|
45
|
+
color: var(--card-fg);
|
|
38
46
|
border: var(--border-width) solid var(--ui-border, var(--border));
|
|
39
47
|
border-radius: var(--card-radius);
|
|
40
48
|
}
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
* Internal:
|
|
9
9
|
* --gap (local value for the shared gap token)
|
|
10
10
|
*/
|
|
11
|
+
/* Intent boundary — inherited intents do not tint this component.
|
|
12
|
+
Local intent classes still win because :where() has zero specificity. */
|
|
13
|
+
:where(.navbar) {
|
|
14
|
+
--ui-bg: initial;
|
|
15
|
+
--ui-fg: initial;
|
|
16
|
+
--ui-border: initial;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
.navbar {
|
|
12
20
|
display: flex;
|
|
13
21
|
flex-wrap: wrap;
|
|
@@ -16,17 +24,17 @@
|
|
|
16
24
|
gap: var(--gap);
|
|
17
25
|
padding-block: var(--space-30);
|
|
18
26
|
padding-inline: var(--space-40);
|
|
19
|
-
border: var(--border-width) solid var(--border);
|
|
27
|
+
border: var(--border-width) solid var(--ui-border, var(--border));
|
|
20
28
|
border-radius: var(--radius-lg);
|
|
21
|
-
background: var(--surface-raised);
|
|
22
|
-
color: var(--text);
|
|
29
|
+
background: var(--ui-bg, var(--surface-raised));
|
|
30
|
+
color: var(--ui-fg, var(--text));
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
.navbar-brand {
|
|
26
34
|
display: inline-flex;
|
|
27
35
|
align-items: center;
|
|
28
36
|
gap: var(--space-20);
|
|
29
|
-
color: var(--text);
|
|
37
|
+
color: var(--ui-fg, var(--text));
|
|
30
38
|
font-weight: var(--font-weight-bold);
|
|
31
39
|
text-decoration: none;
|
|
32
40
|
}
|
|
@@ -51,7 +59,7 @@
|
|
|
51
59
|
min-block-size: var(--control-size-sm);
|
|
52
60
|
padding-inline: var(--space-20);
|
|
53
61
|
border-radius: var(--radius);
|
|
54
|
-
color: var(--text-muted);
|
|
62
|
+
color: var(--ui-fg, var(--text-muted));
|
|
55
63
|
font-weight: var(--font-weight-medium);
|
|
56
64
|
text-decoration: none;
|
|
57
65
|
}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Base form is unstyled chrome that takes its surrounding color. Add .pill for
|
|
5
5
|
* the bordered rounded shape, and combine with an intent class (.primary,
|
|
6
6
|
* .success, etc.) to tint the text, border, and background.
|
|
7
|
+
*
|
|
8
|
+
* Public hooks:
|
|
9
|
+
* --overline-radius
|
|
7
10
|
*/
|
|
8
11
|
/* Intent boundary — inherited intents do not tint this component.
|
|
9
12
|
Local intent classes still win because :where() has zero specificity. */
|
|
@@ -12,7 +15,6 @@
|
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
.overline {
|
|
15
|
-
align-self: flex-start;
|
|
16
18
|
color: var(--text-muted);
|
|
17
19
|
font-size: var(--font-size-sm);
|
|
18
20
|
font-weight: var(--font-weight-medium);
|
|
@@ -21,14 +23,18 @@
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
.overline.pill {
|
|
26
|
+
--overline-radius: var(--radius-full);
|
|
27
|
+
|
|
24
28
|
display: inline-flex;
|
|
25
29
|
align-items: center;
|
|
26
30
|
gap: 0.5em;
|
|
27
31
|
padding-inline: 0.6em;
|
|
28
32
|
padding-block: 0.25em;
|
|
33
|
+
inline-size: fit-content;
|
|
34
|
+
max-inline-size: 100%;
|
|
29
35
|
color: var(--intent, var(--text-muted));
|
|
30
36
|
border: var(--border-width) solid var(--border);
|
|
31
|
-
border-radius: var(--radius
|
|
37
|
+
border-radius: var(--overline-radius);
|
|
32
38
|
background: var(--surface);
|
|
33
39
|
}
|
|
34
40
|
|
package/src/css/grid.css
CHANGED
|
@@ -5,6 +5,19 @@
|
|
|
5
5
|
* --grid-min
|
|
6
6
|
* --grid-columns
|
|
7
7
|
*/
|
|
8
|
+
|
|
9
|
+
/* --grid-columns replaces the intrinsic auto-fit template: once set, the
|
|
10
|
+
grid no longer collapses on its own — the author owns narrow-container
|
|
11
|
+
collapse. Prefer .sidebar-layout inside .container-query for content +
|
|
12
|
+
aside layouts; reserve --grid-columns for genuinely fixed or asymmetric
|
|
13
|
+
templates. */
|
|
14
|
+
|
|
15
|
+
/* Recipe:
|
|
16
|
+
- N equal cards that collapse when narrow -> .grid (auto-fit, tune
|
|
17
|
+
--grid-min) or .grid-2/3/4/6 inside .container-query
|
|
18
|
+
- content + aside -> .sidebar-layout inside .container-query
|
|
19
|
+
- fixed or asymmetric columns -> --grid-columns with an explicit collapse
|
|
20
|
+
rule of your own */
|
|
8
21
|
.grid {
|
|
9
22
|
display: grid;
|
|
10
23
|
gap: var(--gap);
|
package/src/css/layout.css
CHANGED
|
@@ -13,13 +13,26 @@
|
|
|
13
13
|
* --sidebar-layout-size
|
|
14
14
|
*
|
|
15
15
|
* Child contracts:
|
|
16
|
-
* .stack owns vertical rhythm: direct-child margins are reset,
|
|
17
|
-
* from --gap.
|
|
16
|
+
* .stack owns vertical rhythm: direct-child block-axis margins are reset,
|
|
17
|
+
* spacing comes from --gap, inline margins stay free.
|
|
18
18
|
* .cluster owns inline rhythm: direct-child margins are reset, spacing comes
|
|
19
19
|
* from --gap.
|
|
20
20
|
* .frame sizes direct media children (img/video/iframe/canvas/svg) to fill the
|
|
21
21
|
* frame.
|
|
22
22
|
*/
|
|
23
|
+
|
|
24
|
+
/* Responsive recipes:
|
|
25
|
+
- avatar/icon + flexible content -> .media
|
|
26
|
+
- inline items that wrap when space runs out -> .cluster (keep wrapping)
|
|
27
|
+
- content + aside -> .sidebar-layout inside .container-query
|
|
28
|
+
- row that becomes a column when constrained -> .switcher (optional module)
|
|
29
|
+
|
|
30
|
+
Anti-patterns:
|
|
31
|
+
- viewport breakpoints for ordinary composition
|
|
32
|
+
- fixed grid templates (--grid-columns) where .grid/.sidebar-layout express
|
|
33
|
+
the layout
|
|
34
|
+
- nowrap on page-level compositions; prefer wrapping and container-aware
|
|
35
|
+
primitives */
|
|
23
36
|
.container-query {
|
|
24
37
|
container-type: inline-size;
|
|
25
38
|
}
|
|
@@ -30,8 +43,10 @@
|
|
|
30
43
|
gap: var(--gap);
|
|
31
44
|
}
|
|
32
45
|
|
|
46
|
+
/* .stack owns the block axis only: block margins reset, inline margins stay
|
|
47
|
+
free (self-centering children via margin-inline: auto keep working). */
|
|
33
48
|
.stack > * {
|
|
34
|
-
margin: 0;
|
|
49
|
+
margin-block: 0;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
.cluster {
|
package/src/css/themes/index.css
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/*
|
|
2
|
+
* Reference theme presets. Shipped for inspection and copying; not public
|
|
3
|
+
* package entry points (package.json blocks ./css/themes exports).
|
|
4
|
+
*
|
|
5
|
+
* Opt-in theme bundle. Importing this file pulls in every theme below, each
|
|
2
6
|
* gated by [data-theme="…"]. The light baseline is :root in src/css/theme.css —
|
|
3
7
|
* there is no light.css here.
|
|
4
8
|
*
|
package/src/css/variants.css
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
* Cross-cutting variant token mapping.
|
|
3
3
|
*
|
|
4
4
|
* Variant scope: .solid/.soft/.outline set --ui-* for components that opt into
|
|
5
|
-
* shared variants. .inverted is a surface modifier, not a variant: it
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* shared variants. .inverted is a surface modifier, not a variant: it paints
|
|
6
|
+
* the element directly (background/color/border-color) so it works on any bare
|
|
7
|
+
* block, and remaps the shared --ui-* tokens so participating components
|
|
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.
|
|
8
12
|
* Component-specific defaults (.btn solid-by-default, .badge
|
|
9
13
|
* soft-by-default, .alert soft-by-default) live with their components:
|
|
10
14
|
* components/button.css — .btn default solid + .btn.ghost + .btn.link
|
|
11
|
-
* components/badge.css — .badge default soft
|
|
12
|
-
* components/alert.css — .alert default soft
|
|
15
|
+
* components/badge.css — .badge default soft behind explicit --ui-*
|
|
16
|
+
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
13
17
|
*
|
|
14
18
|
* .sm/.lg set shared density tokens: spacing rhythm and geometry only.
|
|
15
19
|
* Density never changes typography — controls keep their font-size and
|
|
@@ -37,18 +41,23 @@
|
|
|
37
41
|
|
|
38
42
|
.outline {
|
|
39
43
|
--ui-bg: transparent;
|
|
40
|
-
--ui-fg: var(--intent,
|
|
41
|
-
--ui-border: var(--intent,
|
|
44
|
+
--ui-fg: var(--intent, currentColor);
|
|
45
|
+
--ui-border: var(--intent, currentColor);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
/* Inverse surface treatment.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
/* Inverse surface treatment. Paints the element directly (so it works on any
|
|
49
|
+
bare block) and remaps the shared --ui-* tokens so opt-in components
|
|
50
|
+
(.card, .navbar) invert through their own rules. Because this loads before
|
|
51
|
+
component base surfaces, components with their own explicit surface
|
|
52
|
+
(e.g. .accordion) keep it — participation is opt-in, never forced. --heading
|
|
53
|
+
is set so global headings (var(--heading, inherit)) do not inherit an outside
|
|
54
|
+
heading color onto the dark surface; surface-owning descendants derive
|
|
55
|
+
their contextual aliases from their own local surface. */
|
|
48
56
|
.inverted {
|
|
49
57
|
--ui-bg: var(--surface-solid);
|
|
50
58
|
--ui-fg: var(--surface);
|
|
51
59
|
--ui-border: transparent;
|
|
60
|
+
--ui-hover-bg: var(--surface-solid);
|
|
52
61
|
|
|
53
62
|
--heading: var(--surface);
|
|
54
63
|
--busy-overlay-bg: var(--surface-solid);
|
|
@@ -84,6 +93,10 @@
|
|
|
84
93
|
);
|
|
85
94
|
/* @sync soft-recipe */
|
|
86
95
|
}
|
|
96
|
+
|
|
97
|
+
.inverted {
|
|
98
|
+
--ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black);
|
|
99
|
+
}
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
/* Interactive hover recipes live in the components that consume them:
|
package/src/js/surface.js
CHANGED
|
@@ -274,7 +274,9 @@ export function retainSurface(panel) {
|
|
|
274
274
|
released = true;
|
|
275
275
|
entry.count--;
|
|
276
276
|
if (entry.count <= 0) {
|
|
277
|
-
|
|
277
|
+
// A released retainer may leave a mounted panel whose original parent
|
|
278
|
+
// either still needs it back or has disappeared along with the trigger.
|
|
279
|
+
disconnectSurface(panel);
|
|
278
280
|
surfaceRetainers.delete(panel);
|
|
279
281
|
}
|
|
280
282
|
};
|