actual-css 0.11.1 → 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 +47 -0
- package/README.md +26 -6
- package/components.json +4 -16
- package/dist/actual.full.css +296 -267
- 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 +3 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/badge.md +3 -9
- package/docs/pages/components/card.md +31 -2
- package/docs/pages/components/dialog.md +121 -33
- package/docs/pages/components/drawer.md +2 -2
- package/docs/pages/components/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 +2 -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 +2 -2
- 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 +2 -2
- package/reserved-classes.json +3 -3
- package/skills/actual-css/references/enhancements.md +10 -7
- package/src/cli/actual-css.js +39 -13
- package/src/css/components/avatar.css +16 -9
- package/src/css/components/badge.css +1 -5
- package/src/css/components/button.css +3 -9
- package/src/css/components/card.css +18 -10
- package/src/css/components/chat.css +4 -5
- package/src/css/components/drawer.css +19 -3
- package/src/css/components/flyout.css +3 -0
- package/src/css/components/index.css +0 -1
- package/src/css/components/modal.css +96 -124
- 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/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-tokens.js +9 -1
- package/src/tooling/theme-contrast.js +209 -0
- package/docs/pages/patterns/actions.md +0 -52
- package/src/css/components/actions.css +0 -27
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# Navigation List
|
|
2
2
|
|
|
3
|
-
Use `.nav-list` for a
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Use `.nav-list` for a vertical list of navigation links: it removes native list
|
|
4
|
+
chrome, stacks the rows and stretches each `.nav-link` across the row. For a
|
|
5
|
+
horizontal row of links, use `.cluster` on the list, which removes the list
|
|
6
|
+
chrome too.
|
|
6
7
|
|
|
7
8
|
```html demo
|
|
8
9
|
<div class="stack">
|
|
9
10
|
<h2>Navigation</h2>
|
|
10
11
|
<nav aria-label="Main navigation">
|
|
11
|
-
<ul class="
|
|
12
|
+
<ul class="cluster">
|
|
12
13
|
<li><a href="/docs">Docs</a></li>
|
|
13
14
|
<li><a href="/components">Components</a></li>
|
|
14
15
|
<li><a href="/examples">Examples</a></li>
|
|
@@ -16,7 +17,7 @@ It removes native list chrome but does not force a direction. Combine it with la
|
|
|
16
17
|
</nav>
|
|
17
18
|
<h2>Documentation</h2>
|
|
18
19
|
<nav aria-label="Documentation">
|
|
19
|
-
<ul class="nav-list
|
|
20
|
+
<ul class="nav-list">
|
|
20
21
|
<li><a href="/docs/getting-started" aria-current="page">Getting started</a></li>
|
|
21
22
|
<li><a href="/docs/tokens">Tokens</a></li>
|
|
22
23
|
<li><a href="/docs/layout">Layout</a></li>
|
|
@@ -26,14 +27,6 @@ It removes native list chrome but does not force a direction. Combine it with la
|
|
|
26
27
|
</div>
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
Use `.nav-list` for:
|
|
30
|
-
|
|
31
|
-
- header navigation
|
|
32
|
-
- side navigation
|
|
33
|
-
- footer navigation
|
|
34
|
-
- tab-like navigation lists
|
|
35
|
-
- documentation navigation
|
|
36
|
-
|
|
37
30
|
Do not reset every list inside `nav`.
|
|
38
31
|
|
|
39
32
|
```css
|
|
@@ -51,7 +44,7 @@ Prefer an explicit pattern class.
|
|
|
51
44
|
|
|
52
45
|
```html demo
|
|
53
46
|
<nav aria-label="Footer">
|
|
54
|
-
<ul class="
|
|
47
|
+
<ul class="cluster">
|
|
55
48
|
<li><a href="/privacy">Privacy</a></li>
|
|
56
49
|
<li><a href="/terms">Terms</a></li>
|
|
57
50
|
<li><a href="/contact">Contact</a></li>
|
|
@@ -69,7 +62,7 @@ Header navigation is usually a composition of landmarks, layout primitives, and
|
|
|
69
62
|
<a class="brand" href="/">Actual CSS</a>
|
|
70
63
|
|
|
71
64
|
<nav aria-label="Main navigation">
|
|
72
|
-
<ul class="
|
|
65
|
+
<ul class="cluster">
|
|
73
66
|
<li><a href="/docs">Docs</a></li>
|
|
74
67
|
<li><a href="/components">Components</a></li>
|
|
75
68
|
<li><a href="/examples">Examples</a></li>
|
|
@@ -85,8 +78,7 @@ In this example:
|
|
|
85
78
|
- `.center` constrains the content.
|
|
86
79
|
- `.cluster` arranges the brand and navigation.
|
|
87
80
|
- `nav` provides navigation semantics.
|
|
88
|
-
- `.
|
|
89
|
-
- the inner `.cluster` arranges navigation items horizontally.
|
|
81
|
+
- the inner `.cluster` removes the list chrome and arranges the links horizontally.
|
|
90
82
|
|
|
91
83
|
A project may add `.site-header` when it needs a visual shell.
|
|
92
84
|
|
|
@@ -106,7 +98,7 @@ A project may add `.site-header` when it needs a visual shell.
|
|
|
106
98
|
<aside class="shell-sidebar-nav" aria-label="Primary">
|
|
107
99
|
<a class="navbar-brand" href="#"><span class="avatar primary"><abbr>AC</abbr></span> Product</a>
|
|
108
100
|
<nav aria-label="Primary">
|
|
109
|
-
<ul class="nav-list
|
|
101
|
+
<ul class="nav-list">
|
|
110
102
|
<li><a class="nav-link" href="#" aria-current="page">Overview</a></li>
|
|
111
103
|
<li><a class="nav-link" href="#">Settings</a></li>
|
|
112
104
|
</ul>
|
|
@@ -167,7 +159,7 @@ Side navigation is usually a `.nav-list` composed with `.stack`.
|
|
|
167
159
|
```html demo
|
|
168
160
|
<aside>
|
|
169
161
|
<nav aria-label="Documentation">
|
|
170
|
-
<ul class="nav-list
|
|
162
|
+
<ul class="nav-list">
|
|
171
163
|
<li><a href="/docs/getting-started" aria-current="page">Getting started</a></li>
|
|
172
164
|
<li><a href="/docs/tokens">Tokens</a></li>
|
|
173
165
|
<li><a href="/docs/layout">Layout</a></li>
|
|
@@ -182,7 +174,7 @@ A project may add `.side-nav` only when it needs specific visual behavior such a
|
|
|
182
174
|
```html demo
|
|
183
175
|
<aside class="side-nav">
|
|
184
176
|
<nav aria-label="Documentation">
|
|
185
|
-
<ul class="nav-list
|
|
177
|
+
<ul class="nav-list">
|
|
186
178
|
<li><a href="/docs/getting-started" aria-current="page">Getting started</a></li>
|
|
187
179
|
<li><a href="/docs/tokens">Tokens</a></li>
|
|
188
180
|
<li><a href="/docs/layout">Layout</a></li>
|
|
@@ -206,30 +198,8 @@ A project may add `.side-nav` only when it needs specific visual behavior such a
|
|
|
206
198
|
}
|
|
207
199
|
```
|
|
208
200
|
|
|
209
|
-
###
|
|
210
|
-
|
|
211
|
-
`.nav-list` can stay purely structural. If the framework wants navigation links to have a default hit area and active state, this can be added as a lightweight visual pattern.
|
|
212
|
-
|
|
213
|
-
```css
|
|
214
|
-
.nav-list a {
|
|
215
|
-
display: flex;
|
|
216
|
-
align-items: center;
|
|
217
|
-
gap: var(--space-20);
|
|
218
|
-
min-block-size: 2rem;
|
|
219
|
-
padding-inline: var(--space-20);
|
|
220
|
-
border-radius: var(--radius-sm);
|
|
221
|
-
color: inherit;
|
|
222
|
-
text-decoration: none;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.nav-list a:hover {
|
|
226
|
-
background: var(--surface-subtle);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.nav-list a[aria-current="page"] {
|
|
230
|
-
background: var(--surface-subtle);
|
|
231
|
-
font-weight: 600;
|
|
232
|
-
}
|
|
233
|
-
```
|
|
201
|
+
### Link Styling
|
|
234
202
|
|
|
235
|
-
|
|
203
|
+
`.nav-link` gives a link its hit area, hover fill and `aria-current` state, in a
|
|
204
|
+
`.cluster` row or a `.nav-list` (see [Navbar](../components/navbar.md)). Set
|
|
205
|
+
`--gap` on a `.nav-list` to space its rows differently.
|
|
@@ -22,9 +22,8 @@ Use patterns when a semantic element needs a small amount of normalization befor
|
|
|
22
22
|
Patterns often compose with layout primitives.
|
|
23
23
|
|
|
24
24
|
```html
|
|
25
|
-
<menu class="
|
|
26
|
-
<ul class="nav-list
|
|
27
|
-
<ul class="nav-list stack">...</ul>
|
|
25
|
+
<menu class="cluster">...</menu>
|
|
26
|
+
<ul class="nav-list">...</ul>
|
|
28
27
|
<section class="grid">...</section>
|
|
29
28
|
```
|
|
30
29
|
|
|
@@ -51,7 +50,7 @@ Avoid putting semantic resets into layout primitives.
|
|
|
51
50
|
If list chrome should be removed, use an explicit pattern.
|
|
52
51
|
|
|
53
52
|
```html demo
|
|
54
|
-
<ul class="nav-list
|
|
53
|
+
<ul class="nav-list">
|
|
55
54
|
<li><a href="/docs">Docs</a></li>
|
|
56
55
|
<li><a href="/components">Components</a></li>
|
|
57
56
|
</ul>
|
|
@@ -6,24 +6,25 @@ Small flex helpers for grow behavior and other one-line needs.
|
|
|
6
6
|
|
|
7
7
|
Base utilities:
|
|
8
8
|
|
|
9
|
-
| Class
|
|
10
|
-
|
|
|
11
|
-
| `.grow`
|
|
12
|
-
| `.items-start`
|
|
13
|
-
| `.items-center`
|
|
14
|
-
| `.items-end`
|
|
15
|
-
| `.
|
|
9
|
+
| Class | Description |
|
|
10
|
+
| ----------------- | ------------------------------------------------------------------- |
|
|
11
|
+
| `.grow` | Makes an item take available flex space without overflowing. |
|
|
12
|
+
| `.items-start` | Aligns children to the block-start edge; sets `--items-align`. |
|
|
13
|
+
| `.items-center` | Centers children on the cross axis; sets `--items-align`. |
|
|
14
|
+
| `.items-end` | Aligns children to the block-end edge; sets `--items-align`. |
|
|
15
|
+
| `.items-baseline` | Aligns children on their first text baseline; sets `--items-align`. |
|
|
16
|
+
| `.dot` | Inline separator dot for metadata rows. |
|
|
16
17
|
|
|
17
18
|
Extra utilities:
|
|
18
19
|
|
|
19
|
-
| Class | Description
|
|
20
|
-
| -------------------------------- |
|
|
21
|
-
| `.justify-content-start` | `justify-content: flex-start`
|
|
22
|
-
| `.justify-content-center` | `justify-content: center`
|
|
23
|
-
| `.justify-content-end` | `justify-content: flex-end`
|
|
24
|
-
| `.justify-content-space-between` | `justify-content: space-between`
|
|
25
|
-
| `.flex-wrap` | `flex-wrap: wrap` on any flex container.
|
|
26
|
-
| `.flex-nowrap` | `flex-wrap: nowrap` on any flex container.
|
|
20
|
+
| Class | Description |
|
|
21
|
+
| -------------------------------- | ------------------------------------------------------- |
|
|
22
|
+
| `.justify-content-start` | `justify-content: flex-start` on the element itself. |
|
|
23
|
+
| `.justify-content-center` | `justify-content: center` on the element itself. |
|
|
24
|
+
| `.justify-content-end` | `justify-content: flex-end` on the element itself. |
|
|
25
|
+
| `.justify-content-space-between` | `justify-content: space-between` on the element itself. |
|
|
26
|
+
| `.flex-wrap` | `flex-wrap: wrap` on any flex container. |
|
|
27
|
+
| `.flex-nowrap` | `flex-wrap: nowrap` on any flex container. |
|
|
27
28
|
|
|
28
29
|
## Grow
|
|
29
30
|
|
|
@@ -66,14 +67,24 @@ Use `.items-start` on flex or grid layouts when children should keep their natur
|
|
|
66
67
|
|
|
67
68
|
Use `.items-center` when a flex or grid layout should center its children on the cross axis. Use `.items-end` when children should align to the block-end edge.
|
|
68
69
|
|
|
70
|
+
Use `.items-baseline` when a row mixes text sizes — a price and its period, a name and a timestamp — so the words share one line instead of their boxes sharing a center.
|
|
71
|
+
|
|
72
|
+
```html demo
|
|
73
|
+
<p class="cluster items-baseline">
|
|
74
|
+
<strong style="font-size: 2rem">$29</strong>
|
|
75
|
+
<span class="muted">/ month</span>
|
|
76
|
+
</p>
|
|
77
|
+
```
|
|
78
|
+
|
|
69
79
|
These helpers work by setting a custom property that participating components read,
|
|
70
80
|
so they also apply inside a component's own grid anatomy rather than only on plain
|
|
71
81
|
flex containers:
|
|
72
82
|
|
|
73
|
-
- `.items-start` / `.items-center` / `.items-end` set `--items-align`, read by `.alert` and `.card`.
|
|
83
|
+
- `.items-start` / `.items-center` / `.items-end` / `.items-baseline` set `--items-align`, read by `.alert` and `.card`.
|
|
74
84
|
- `.text-start` / `.text-center` / `.text-end` set `--text-align`, read by `.table` cells.
|
|
75
|
-
|
|
76
|
-
- `.flex-wrap` / `.flex-nowrap` (utilities/extra) set
|
|
85
|
+
|
|
86
|
+
`.justify-content-*` and `.flex-wrap` / `.flex-nowrap` (utilities/extra) set the property
|
|
87
|
+
directly, so they never reach a nested `.cluster`.
|
|
77
88
|
|
|
78
89
|
Set the property directly when you need a value the helpers do not ship.
|
|
79
90
|
|
|
@@ -107,6 +118,5 @@ Always mark it `aria-hidden="true"` — it is a visual separator, not content.
|
|
|
107
118
|
|
|
108
119
|
## CSS hooks
|
|
109
120
|
|
|
110
|
-
- `--items-align` — cross-axis alignment, set by
|
|
121
|
+
- `--items-align` — cross-axis alignment, set by the `.items-*` helpers.
|
|
111
122
|
- `--text-align` — text alignment, set by `.text-start` / `.text-center` / `.text-end`.
|
|
112
|
-
- `--cluster-justify` — main-axis distribution, set by the optional `.justify-content-*` helpers and read by `.cluster`.
|
|
@@ -29,7 +29,7 @@ one line (`.badge`). To stop a layout primitive from wrapping — for example to
|
|
|
29
29
|
force a `.cluster` onto a single row — set `--cluster-wrap: nowrap`.
|
|
30
30
|
|
|
31
31
|
```html demo
|
|
32
|
-
<div class="cluster
|
|
32
|
+
<div class="cluster tight" style="--cluster-wrap: nowrap;">
|
|
33
33
|
<select class="select sm fit" aria-label="Segment">
|
|
34
34
|
<option>All segments</option>
|
|
35
35
|
</select>
|
|
@@ -48,9 +48,10 @@ Override the default gap from layout primitives (`.stack`, `.cluster`, `.grid`):
|
|
|
48
48
|
- `.gap-none` → `gap: 0`
|
|
49
49
|
|
|
50
50
|
`.gap-none` sets the element's own gap directly and does not change `--gap`
|
|
51
|
-
for nested layouts. For a
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
for nested layouts. For a tighter or looser rhythm alone — closer items, without
|
|
52
|
+
smaller controls — use `.tight` / `.loose`; `.compact` / `.spacious` also change
|
|
53
|
+
padding and control size. Set the `gap` property on the instance when the change
|
|
54
|
+
must stop there.
|
|
54
55
|
|
|
55
56
|
`--gap` defines the contextual rhythm; `.gap-context` applies that rhythm to a
|
|
56
57
|
container that does not already consume `--gap`. The layout primitives
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-css",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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.4.2",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/lekoala/actual-css.git"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://lekoala.github.io/actual-css/",
|
|
14
14
|
"bugs": {
|
|
15
15
|
"url": "https://github.com/lekoala/actual-css/issues"
|
|
16
16
|
},
|
package/reserved-classes.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
[
|
|
2
2
|
"accordion",
|
|
3
|
-
"actions",
|
|
4
3
|
"admonition",
|
|
5
4
|
"alert",
|
|
6
5
|
"alert-body",
|
|
@@ -56,7 +55,6 @@
|
|
|
56
55
|
"cursor-pointer",
|
|
57
56
|
"danger",
|
|
58
57
|
"data-list",
|
|
59
|
-
"dialog-confirmation",
|
|
60
58
|
"dialog-icon",
|
|
61
59
|
"display-block",
|
|
62
60
|
"display-flex",
|
|
@@ -110,6 +108,7 @@
|
|
|
110
108
|
"intent-color",
|
|
111
109
|
"is-open",
|
|
112
110
|
"is-static",
|
|
111
|
+
"items-baseline",
|
|
113
112
|
"items-center",
|
|
114
113
|
"items-end",
|
|
115
114
|
"items-start",
|
|
@@ -132,6 +131,7 @@
|
|
|
132
131
|
"list-item-title",
|
|
133
132
|
"list-item-trailing",
|
|
134
133
|
"list-reset",
|
|
134
|
+
"loose",
|
|
135
135
|
"margin-block-end-auto",
|
|
136
136
|
"margin-block-start-auto",
|
|
137
137
|
"margin-inline-auto",
|
|
@@ -159,7 +159,6 @@
|
|
|
159
159
|
"nav-list",
|
|
160
160
|
"navbar",
|
|
161
161
|
"navbar-brand",
|
|
162
|
-
"navbar-nav",
|
|
163
162
|
"needs-validation",
|
|
164
163
|
"neutral",
|
|
165
164
|
"otp",
|
|
@@ -248,6 +247,7 @@
|
|
|
248
247
|
"text-uppercase",
|
|
249
248
|
"text-wrap",
|
|
250
249
|
"textarea",
|
|
250
|
+
"tight",
|
|
251
251
|
"tooltip",
|
|
252
252
|
"topbar",
|
|
253
253
|
"touch-target",
|
|
@@ -40,16 +40,19 @@ the title in the content wrapper's direct `<header>`. The close button is then
|
|
|
40
40
|
anchored to the surface, and the header reserves room so the title cannot run
|
|
41
41
|
under it. A `.close` that is a direct child of the chrome header is that same
|
|
42
42
|
corner overlay, and that header reserves room for it too. Put actions in the
|
|
43
|
-
wrapper's `<footer
|
|
43
|
+
wrapper's `<footer>` and compose its row in markup
|
|
44
|
+
(`<footer class="cluster justify-content-end">`); the modal and drawer footers
|
|
45
|
+
have no layout of their own.
|
|
44
46
|
|
|
45
47
|
Use `.bleed` on that direct header when it needs a contrasting full-width band;
|
|
46
48
|
the wrapper relays the modal padding so the band reaches the surface edges while
|
|
47
|
-
its contents keep the normal inset.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
its contents keep the normal inset. A confirmation is composition, not a
|
|
50
|
+
variant: a `.media` row with a leading `.dialog-icon` and the text column (its
|
|
51
|
+
first heading centers on the icon, following copy stays in that column), then a
|
|
52
|
+
`<footer class="bleed background-subtle cluster justify-content-space-between">` action
|
|
53
|
+
band with the cancel-style action first. For a trailing icon, use the wrapper's
|
|
54
|
+
direct header as a non-wrapping split `.cluster`, with `.grow` on the text
|
|
55
|
+
group. Do not compensate heading alignment with a literal margin.
|
|
53
56
|
|
|
54
57
|
Do not turn the dialog heading into an `.alert` to obtain icon or surface
|
|
55
58
|
styling. Compose `.dialog-icon` with `.media` for a leading title icon; reserve
|
package/src/cli/actual-css.js
CHANGED
|
@@ -8,10 +8,11 @@ function usage() {
|
|
|
8
8
|
return [
|
|
9
9
|
"actual-css bundle INPUT --out FILE [--minify]",
|
|
10
10
|
"actual-css design [--theme FILE] [--name NAME] --out DIR",
|
|
11
|
+
"actual-css contrast [--theme FILE] [--name NAME]",
|
|
11
12
|
"",
|
|
12
13
|
" -o, --out FILE write the bundle to FILE / the export to DIR (required)",
|
|
13
14
|
" --minify collapse comments and whitespace",
|
|
14
|
-
" --theme FILE
|
|
15
|
+
" --theme FILE read this [data-theme] file instead of the default theme",
|
|
15
16
|
" --name NAME the data-theme name, when the file declares several",
|
|
16
17
|
" -h, --help show this message",
|
|
17
18
|
" --version print the actual-css version",
|
|
@@ -23,7 +24,13 @@ function usage() {
|
|
|
23
24
|
"",
|
|
24
25
|
"design: export a theme's tokens for Figma and Penpot, plus Button, Input, Badge,",
|
|
25
26
|
"Alert and Card measured in headless Chrome and a Penpot plugin that builds them.",
|
|
26
|
-
"Needs Bun (bunx actual-css design ...).",
|
|
27
|
+
"Needs Bun (bunx --bun actual-css design ...).",
|
|
28
|
+
"",
|
|
29
|
+
"contrast: measure the pairs a theme tunes by hand in headless Chrome, in every",
|
|
30
|
+
"scheme it declares: soft ink on its resting and hovered fill, the focus line on",
|
|
31
|
+
"--surface and --surface-solid, the invalid-field focus line, and the inverse",
|
|
32
|
+
"text. Exits 1 when a pair misses its threshold. Needs Bun (bunx --bun actual-css",
|
|
33
|
+
"contrast ...).",
|
|
27
34
|
].join("\n");
|
|
28
35
|
}
|
|
29
36
|
|
|
@@ -43,8 +50,8 @@ function parseArgs(argv) {
|
|
|
43
50
|
return { version: true };
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
if (command === "design") {
|
|
47
|
-
return
|
|
53
|
+
if (command === "design" || command === "contrast") {
|
|
54
|
+
return parseThemeArgs(command, rest);
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
if (command !== "bundle") {
|
|
@@ -101,9 +108,10 @@ function parseArgs(argv) {
|
|
|
101
108
|
return { help: false, command: "bundle", input, out, minify };
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
function
|
|
105
|
-
const options = { help: false, command
|
|
106
|
-
const valued = { "--theme": "themeFile", "--name": "name"
|
|
111
|
+
function parseThemeArgs(command, rest) {
|
|
112
|
+
const options = { help: false, command, themeFile: null, name: null, out: "" };
|
|
113
|
+
const valued = { "--theme": "themeFile", "--name": "name" };
|
|
114
|
+
if (command === "design") Object.assign(valued, { "--out": "out", "-o": "out" });
|
|
107
115
|
|
|
108
116
|
for (let i = 0; i < rest.length; i += 1) {
|
|
109
117
|
const arg = rest[i];
|
|
@@ -116,23 +124,40 @@ function parseDesignArgs(rest) {
|
|
|
116
124
|
i += 1;
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
if (!options.out) throw new Error("Missing required --out DIR option.");
|
|
127
|
+
if (command === "design" && !options.out) throw new Error("Missing required --out DIR option.");
|
|
120
128
|
return options;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
/* Checked before the tooling import: under Node those modules do not even
|
|
132
|
+
parse (`await using`), and `bundle` must stay usable there. The shebang is
|
|
133
|
+
Node for that reason, so plain `bunx` honors it and lands here: --bun is
|
|
134
|
+
what makes Bun run the script. */
|
|
135
|
+
function requireBun(command) {
|
|
126
136
|
if (typeof Bun === "undefined") {
|
|
127
137
|
throw new Error(
|
|
128
|
-
|
|
138
|
+
`actual-css ${command} needs Bun: it reads the theme in headless Chrome through Bun.WebView. Run it with bunx --bun actual-css ${command} ...`,
|
|
129
139
|
);
|
|
130
140
|
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function design({ themeFile, name, out }) {
|
|
144
|
+
requireBun("design");
|
|
131
145
|
const { exportDesign } = await import("../tooling/design-export.js");
|
|
132
146
|
const written = await exportDesign({ themeFile, name, out: resolve(out) });
|
|
133
147
|
console.log(`Exported ${written.length} files to ${resolve(out)}`);
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
async function contrast({ themeFile, name }) {
|
|
151
|
+
requireBun("contrast");
|
|
152
|
+
const { formatContrast, measureThemeFile } = await import("../tooling/theme-contrast.js");
|
|
153
|
+
const { text, misses } = formatContrast(await measureThemeFile({ themeFile, name }));
|
|
154
|
+
console.log(text);
|
|
155
|
+
if (misses > 0) {
|
|
156
|
+
console.error(`\n${misses} pair${misses === 1 ? "" : "s"} under threshold.`);
|
|
157
|
+
process.exitCode = 1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
136
161
|
async function bundle({ input, out, minify }) {
|
|
137
162
|
const inputPath = resolve(input);
|
|
138
163
|
const outputPath = resolve(out);
|
|
@@ -167,7 +192,8 @@ async function main() {
|
|
|
167
192
|
return;
|
|
168
193
|
}
|
|
169
194
|
|
|
170
|
-
|
|
195
|
+
const commands = { bundle, contrast, design };
|
|
196
|
+
await commands[args.command](args);
|
|
171
197
|
}
|
|
172
198
|
|
|
173
199
|
main().catch((error) => {
|
|
@@ -198,19 +198,26 @@ a.avatar[aria-disabled="true"] {
|
|
|
198
198
|
--avatar-stack-font-size: var(--font-size-lg);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
/* Infer the stack scale from its avatars. Progressive: without :has() the
|
|
202
|
+
explicit .avatar-stack.sm/.lg classes and the default size still size the
|
|
203
|
+
group, only the inference is lost. */
|
|
204
|
+
.avatar-stack:has(> .avatar.sm) {
|
|
205
|
+
--avatar-stack-size: 1.75rem;
|
|
206
|
+
--avatar-stack-font-size: var(--font-size-xs);
|
|
207
|
+
}
|
|
206
208
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
209
|
+
.avatar-stack:has(> .avatar.lg) {
|
|
210
|
+
--avatar-stack-size: 3rem;
|
|
211
|
+
--avatar-stack-font-size: var(--font-size-lg);
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
/* Overlapping avatars hide their neighbour: a translucent fill (--surface-subtle
|
|
215
|
+
on a glass palette) would show the next avatar's edge through it, most of all
|
|
216
|
+
when hover raises one above the other. The fill is layered over the opaque
|
|
217
|
+
role; on an opaque fill the base never shows. */
|
|
213
218
|
.avatar-stack > .avatar {
|
|
219
|
+
background-color: var(--surface-opaque);
|
|
220
|
+
background-image: linear-gradient(var(--avatar-bg), var(--avatar-bg));
|
|
214
221
|
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface-opaque);
|
|
215
222
|
}
|
|
216
223
|
|
|
@@ -169,10 +169,6 @@
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
172
|
-
:is(a.badge, button.badge):is(.solid, :empty) {
|
|
173
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
172
|
:is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
|
|
177
173
|
--badge-default-hover-bg: color-mix(
|
|
178
174
|
in oklab,
|
|
@@ -199,7 +195,7 @@
|
|
|
199
195
|
:where(:disabled, [aria-disabled="true"])
|
|
200
196
|
):hover {
|
|
201
197
|
/* Huge inset spread paints the overlay over the pill radius (see button.css). */
|
|
202
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
198
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay-solid, var(--hover-overlay));
|
|
203
199
|
}
|
|
204
200
|
|
|
205
201
|
button.badge {
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
* --btn-bg
|
|
29
29
|
* --btn-fg
|
|
30
30
|
* --btn-border
|
|
31
|
-
* --btn-default-hover-bg
|
|
32
31
|
* --btn-border-block-end-color
|
|
33
32
|
* --btn-border-block-end-width
|
|
34
33
|
* --btn-border-block
|
|
@@ -54,9 +53,8 @@
|
|
|
54
53
|
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
55
54
|
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
56
55
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
57
|
-
--btn-default-hover-bg: var(--btn-bg);
|
|
58
56
|
--btn-radius: var(--radius);
|
|
59
|
-
--btn-hover-overlay: var(--hover-overlay,
|
|
57
|
+
--btn-hover-overlay: var(--hover-overlay-solid, var(--hover-overlay));
|
|
60
58
|
--btn-border-block: calc(
|
|
61
59
|
var(--border-width) +
|
|
62
60
|
var(--btn-border-block-end-width, var(--border-width))
|
|
@@ -136,10 +134,6 @@
|
|
|
136
134
|
}
|
|
137
135
|
|
|
138
136
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
139
|
-
.btn {
|
|
140
|
-
--btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
137
|
.btn.soft {
|
|
144
138
|
--ui-hover-bg: color-mix(
|
|
145
139
|
in oklab,
|
|
@@ -155,9 +149,9 @@
|
|
|
155
149
|
}
|
|
156
150
|
|
|
157
151
|
/* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
|
|
158
|
-
the overlay darkening for solid
|
|
152
|
+
the overlay darkening for solid is .btn-specific. */
|
|
159
153
|
.btn:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
160
|
-
background: var(--ui-hover-bg, var(--btn-
|
|
154
|
+
background: var(--ui-hover-bg, var(--btn-bg));
|
|
161
155
|
/* Huge inset spread paints the overlay over any border-radius without a
|
|
162
156
|
pseudo-element; a plain background would replace the fill instead of
|
|
163
157
|
darkening it. */
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Docs: docs/pages/components/card.md
|
|
5
5
|
*
|
|
6
6
|
* Surface, border, radius, padding, density, and elevation stay local here.
|
|
7
|
-
* Direct header/footer children are structural slots
|
|
8
|
-
* anchored to the card's bottom edge when extra block space
|
|
9
|
-
* price/action rows align across equal-height cards in a
|
|
10
|
-
* owns its direct-child rhythm
|
|
11
|
-
* over that layout. Page layout remains the job of markup and layout
|
|
7
|
+
* Direct header/footer children are structural slots with a default split row;
|
|
8
|
+
* a direct footer is anchored to the card's bottom edge when extra block space
|
|
9
|
+
* is available (so price/action rows align across equal-height cards in a
|
|
10
|
+
* grid). A bare card owns its direct-child rhythm and its slot rows; a layout
|
|
11
|
+
* primitive on the card or on a slot takes over that layout. Page layout remains the job of markup and layout
|
|
12
12
|
* primitives outside the card.
|
|
13
13
|
*
|
|
14
14
|
* Public hooks:
|
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
|
|
47
47
|
.card {
|
|
48
48
|
--card-pad: var(--space-40);
|
|
49
|
+
/* Local anatomy, like the padding. Never var(--gap): that token is
|
|
50
|
+
inherited, so the --gap of a parent grid became the rhythm inside every
|
|
51
|
+
card (+79px per card on a 38px grid). */
|
|
49
52
|
--card-gap: var(--space-30);
|
|
50
53
|
--card-radius: var(--radius-lg);
|
|
51
54
|
--card-max-inline-size: none;
|
|
@@ -87,7 +90,11 @@
|
|
|
87
90
|
margin-block: 0;
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
/* The slot row is a convenience default, not anatomy: like the root box it
|
|
94
|
+
lives at zero specificity, so a primitive, role or utility on the slot
|
|
95
|
+
(.cluster, .media, .form-actions, .justify-content-*) replaces it instead of
|
|
96
|
+
losing to components/ on source order. */
|
|
97
|
+
:where(.card > header, .card > footer) {
|
|
91
98
|
display: flex;
|
|
92
99
|
flex-wrap: wrap;
|
|
93
100
|
align-items: var(--items-align, center);
|
|
@@ -96,15 +103,15 @@
|
|
|
96
103
|
min-inline-size: 0;
|
|
97
104
|
}
|
|
98
105
|
|
|
106
|
+
:where(.card > header) {
|
|
107
|
+
align-items: var(--items-align, flex-start);
|
|
108
|
+
}
|
|
109
|
+
|
|
99
110
|
.card > :where(header, footer) > * {
|
|
100
111
|
min-inline-size: 0;
|
|
101
112
|
margin-block: 0;
|
|
102
113
|
}
|
|
103
114
|
|
|
104
|
-
.card > header {
|
|
105
|
-
align-items: var(--items-align, flex-start);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
115
|
/* A direct footer is pinned to the bottom when the card has free space.
|
|
109
116
|
Specificity beats .stack > * so a .card.stack keeps this contract. */
|
|
110
117
|
.card > footer {
|
|
@@ -119,6 +126,7 @@
|
|
|
119
126
|
}
|
|
120
127
|
.card.compact {
|
|
121
128
|
--card-pad: var(--space-20);
|
|
129
|
+
--card-gap: var(--space-20);
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
@media print {
|
|
@@ -94,9 +94,8 @@
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/* Remove the reserved avatar gutter when a message has no avatar. This is a
|
|
97
|
-
cosmetic enhancement; the two-column baseline remains fully readable
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
97
|
+
cosmetic enhancement; the two-column baseline remains fully readable, and a
|
|
98
|
+
browser without :has() drops only this rule. */
|
|
99
|
+
.chat:not(:has(> .chat-avatar)) {
|
|
100
|
+
column-gap: 0;
|
|
102
101
|
}
|