actual-css 0.11.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 +26 -0
- package/README.md +4 -0
- package/components.json +12 -2
- package/dist/actual.full.css +169 -22
- package/dist/actual.full.min.css +1 -1
- package/docs/navigation.json +2 -1
- package/docs/pages/components/bleed.md +5 -4
- package/docs/pages/components/close.md +9 -3
- package/docs/pages/components/dialog.md +140 -11
- package/docs/pages/components/meter.md +2 -0
- package/docs/pages/examples/overview.md +1 -0
- package/docs/pages/guides/design-tools.md +36 -0
- package/package.json +2 -1
- package/skills/actual-css/references/enhancements.md +35 -0
- package/src/cli/actual-css.js +49 -6
- 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/meter.css +5 -2
- package/src/css/components/modal.css +123 -7
- package/src/css/forms/native.css +5 -1
- 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
package/docs/navigation.json
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
`.bleed` lets a direct child of a padded surface reach its edges. Use it for a
|
|
4
4
|
full-width image, a colored header or footer, or a notice band.
|
|
5
5
|
|
|
6
|
-
It works inside `.card`, `.drawer`,
|
|
7
|
-
owns the padding, so place `.bleed`
|
|
8
|
-
|
|
9
|
-
`actual-css/css/components/bleed`
|
|
6
|
+
It works inside `.card`, `.drawer`, a modal's direct `<form>` or `.stack`, and
|
|
7
|
+
an accordion panel. The accordion panel owns the padding, so place `.bleed`
|
|
8
|
+
directly inside the panel, after the summary. A deeper descendant stays within
|
|
9
|
+
its wrapper. A modular build imports `actual-css/css/components/bleed`
|
|
10
|
+
alongside the surface component.
|
|
10
11
|
|
|
11
12
|
```html demo
|
|
12
13
|
<article class="card stack" style="--card-max-inline-size: 24rem">
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
In a `.modal` or a `.drawer`, the close sits out of the content flow at the
|
|
29
29
|
panel's top inline-end corner. Place it as a direct child of the dialog, of its
|
|
30
30
|
top-level `form` or `header`, or of a `form` inside that header; a `.close`
|
|
31
|
-
deeper in the content keeps its own place
|
|
31
|
+
deeper in the content keeps its own place, so wrapping it in a `.cluster` keeps
|
|
32
|
+
it in the header's row. A header that hosts the corner close reserves inline
|
|
33
|
+
room so a title never runs under it. See [Dialog](dialog.md) and
|
|
32
34
|
[Drawer](drawer.md).
|
|
33
35
|
|
|
34
36
|
```html
|
|
@@ -43,6 +45,10 @@ deeper in the content keeps its own place. See [Dialog](dialog.md) and
|
|
|
43
45
|
|
|
44
46
|
- `--close-size` — inline and block size of the button; `--control-size` unless the container sets it.
|
|
45
47
|
- `--close-icon-size` — size of the X.
|
|
48
|
+
- `--close-bg` — background of the dialog corner close; `--surface-subtle` unless the dialog or a painted band sets it, e.g. `transparent` for a bare X.
|
|
49
|
+
- `--close-color` — ink of the X; `--text-muted` at the dialog corner, otherwise inherited from the context.
|
|
50
|
+
- `--close-hover-color` — ink of the dialog corner close on hover; `--text` unless a band sets its own foreground.
|
|
51
|
+
- `--close-hover-overlay` — hover and active wash that tints the fill the context gave the control; the dialog corner sets it `transparent` so a hover changes only the ink.
|
|
46
52
|
|
|
47
|
-
Alerts and badges declare both hooks on themselves, so set them on the
|
|
48
|
-
or on the button.
|
|
53
|
+
Alerts and badges declare both size hooks on themselves, so set them on the
|
|
54
|
+
container or on the button.
|
|
@@ -14,9 +14,37 @@
|
|
|
14
14
|
| `.dialog-icon` | Component | Circular intent-aware icon well. |
|
|
15
15
|
| `.close` | Composition | Icon-only close button, at the top end. |
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
## Anatomy
|
|
18
|
+
|
|
19
|
+
`.modal` is the outer surface and deliberately has no inner padding. Wrap its
|
|
20
|
+
content in a direct `<form>` or `.stack`; that wrapper owns the padding, rhythm,
|
|
21
|
+
and overflow. Put `.stack` on the wrapper, never on `dialog.modal` itself.
|
|
22
|
+
|
|
23
|
+
For a header band, place `<header>` directly under `dialog.modal`, before the
|
|
24
|
+
body wrapper. Put a flexible `.media` title region first and a `.cluster` of
|
|
25
|
+
controls second. The title has the close control's minimum height; its leading
|
|
26
|
+
icon and every trailing control share the same center axis. A wrapped title
|
|
27
|
+
simply makes the row taller.
|
|
28
|
+
|
|
29
|
+
A direct `dialog > header` is dialog chrome and forms a distinct header band.
|
|
30
|
+
Put an ordinary content heading inside the padded `form` or `.stack` instead.
|
|
31
|
+
|
|
32
|
+
Keep a corner [`.close`](close.md) as a direct child of the dialog and put the
|
|
33
|
+
title in the wrapper's direct `<header>`. The button then stays outside the
|
|
34
|
+
content flow while the header reserves enough inline space to keep its title
|
|
35
|
+
clear.
|
|
36
|
+
|
|
37
|
+
A `.close` placed as a direct child of the chrome header is still the corner
|
|
38
|
+
overlay. That header reserves the same inline room for it, never below its own
|
|
39
|
+
`--modal-pad`, so a wrapping title stays clear either way.
|
|
40
|
+
|
|
41
|
+
The corner close overlays the panel edge, so a body tall enough to scroll would
|
|
42
|
+
run its scrollbar under the button. Give such a dialog the `scrollable` anatomy:
|
|
43
|
+
the close then sits over the fixed header and the scrollbar starts below it.
|
|
44
|
+
|
|
45
|
+
Add `.bleed` to that header when it should form a full-width band. It reaches
|
|
46
|
+
the dialog edges, keeps the modal's inner padding, and can take a surface token
|
|
47
|
+
such as `background: var(--surface-subtle)`.
|
|
20
48
|
|
|
21
49
|
## Usage
|
|
22
50
|
|
|
@@ -85,7 +113,8 @@ For a compact destructive confirmation with a leading status icon, compose
|
|
|
85
113
|
`dialog-confirmation` with the media object. The icon well accepts the shared
|
|
86
114
|
intent and emphasis classes; `dialog-icon danger soft` creates the tinted red
|
|
87
115
|
circle while keeping the glyph centered. The footer becomes a separate action
|
|
88
|
-
band
|
|
116
|
+
band whose two actions sit at its edges, so the cancel-style control never sits
|
|
117
|
+
next to the committing one.
|
|
89
118
|
|
|
90
119
|
```html demo
|
|
91
120
|
<button class="btn danger"
|
|
@@ -121,6 +150,49 @@ band without changing the semantics of the form or its buttons.
|
|
|
121
150
|
</dialog>
|
|
122
151
|
```
|
|
123
152
|
|
|
153
|
+
For a trailing illustration, use a direct `<header>` as the message region and
|
|
154
|
+
compose it as a non-wrapping split cluster. `.grow` keeps the text column
|
|
155
|
+
flexible while `.dialog-icon` stays at the inline end. The confirmation variant
|
|
156
|
+
pads either this header or the leading-icon `.media` shape above.
|
|
157
|
+
|
|
158
|
+
Use the same anatomy in a `.card` instead when the surface belongs in page
|
|
159
|
+
content and does not need modal focus or a backdrop.
|
|
160
|
+
|
|
161
|
+
```html demo
|
|
162
|
+
<button class="btn"
|
|
163
|
+
type="button"
|
|
164
|
+
commandfor="customize-dialog"
|
|
165
|
+
command="show-modal"
|
|
166
|
+
aria-haspopup="dialog"
|
|
167
|
+
aria-controls="customize-dialog">
|
|
168
|
+
Customize workspace
|
|
169
|
+
</button>
|
|
170
|
+
|
|
171
|
+
<dialog class="modal dialog-confirmation"
|
|
172
|
+
id="customize-dialog"
|
|
173
|
+
closedby="none"
|
|
174
|
+
aria-labelledby="customize-dialog-title">
|
|
175
|
+
<form method="dialog">
|
|
176
|
+
<header class="cluster"
|
|
177
|
+
style="--cluster-align: start; --cluster-justify: space-between; --cluster-wrap: nowrap">
|
|
178
|
+
<hgroup class="grow">
|
|
179
|
+
<h3 id="customize-dialog-title">Make this workspace your own</h3>
|
|
180
|
+
<p class="muted">Choose colors and defaults now, or continue with the current settings.</p>
|
|
181
|
+
</hgroup>
|
|
182
|
+
|
|
183
|
+
<span class="dialog-icon success soft" aria-hidden="true">
|
|
184
|
+
<i class="ti ti-check"></i>
|
|
185
|
+
</span>
|
|
186
|
+
</header>
|
|
187
|
+
|
|
188
|
+
<footer>
|
|
189
|
+
<button class="btn primary" value="customize">Customize</button>
|
|
190
|
+
<button class="btn ghost" value="later">Later</button>
|
|
191
|
+
</footer>
|
|
192
|
+
</form>
|
|
193
|
+
</dialog>
|
|
194
|
+
```
|
|
195
|
+
|
|
124
196
|
## Information modal
|
|
125
197
|
|
|
126
198
|
Use this shape for contextual information or lightweight secondary content. It
|
|
@@ -163,6 +235,67 @@ has no action button; the header close button dismisses the dialog.
|
|
|
163
235
|
</dialog>
|
|
164
236
|
```
|
|
165
237
|
|
|
238
|
+
## Action modal
|
|
239
|
+
|
|
240
|
+
Use this shape when contextual information leads to one non-destructive next
|
|
241
|
+
step, such as reviewing a setup before continuing. Unlike an alert dialog, it
|
|
242
|
+
can be dismissed without choosing the action; unlike an information modal, it
|
|
243
|
+
has a primary action in a footer.
|
|
244
|
+
|
|
245
|
+
Use `.dialog-icon` with the media object for a leading title icon. An `.alert`
|
|
246
|
+
belongs in the body only when the dialog contains a distinct status message;
|
|
247
|
+
the dialog title itself is not an alert.
|
|
248
|
+
|
|
249
|
+
```html demo
|
|
250
|
+
<button class="btn primary"
|
|
251
|
+
type="button"
|
|
252
|
+
commandfor="setup-dialog"
|
|
253
|
+
command="show-modal"
|
|
254
|
+
aria-haspopup="dialog"
|
|
255
|
+
aria-controls="setup-dialog">
|
|
256
|
+
Continue setup
|
|
257
|
+
</button>
|
|
258
|
+
|
|
259
|
+
<dialog class="modal"
|
|
260
|
+
id="setup-dialog"
|
|
261
|
+
closedby="any"
|
|
262
|
+
data-dialog-dismissible
|
|
263
|
+
aria-labelledby="setup-dialog-title">
|
|
264
|
+
<header>
|
|
265
|
+
<div class="media items-center grow">
|
|
266
|
+
<span class="dialog-icon warning soft" aria-hidden="true">
|
|
267
|
+
<i class="ti ti-alert-triangle"></i>
|
|
268
|
+
</span>
|
|
269
|
+
|
|
270
|
+
<h2 id="setup-dialog-title">Finish workspace setup</h2>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div class="cluster">
|
|
274
|
+
<button class="close"
|
|
275
|
+
type="button"
|
|
276
|
+
commandfor="setup-dialog"
|
|
277
|
+
command="request-close"
|
|
278
|
+
aria-controls="setup-dialog"
|
|
279
|
+
aria-label="Close dialog"></button>
|
|
280
|
+
</div>
|
|
281
|
+
</header>
|
|
282
|
+
|
|
283
|
+
<form method="dialog" class="stack">
|
|
284
|
+
<div class="card stack">
|
|
285
|
+
<p><strong>Acme workspace</strong></p>
|
|
286
|
+
<p>Team plan</p>
|
|
287
|
+
<p>Starts October 1</p>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<p>Confirm these settings to finish setting up the workspace.</p>
|
|
291
|
+
|
|
292
|
+
<footer>
|
|
293
|
+
<button class="btn primary inline-size-full" value="continue">Continue</button>
|
|
294
|
+
</footer>
|
|
295
|
+
</form>
|
|
296
|
+
</dialog>
|
|
297
|
+
```
|
|
298
|
+
|
|
166
299
|
## Scrollable modal
|
|
167
300
|
|
|
168
301
|
Use `modal scrollable` when the header and footer should stay visible while the
|
|
@@ -371,20 +504,16 @@ Prefer native dialog behavior whenever possible. The framework runtime should
|
|
|
371
504
|
not replace the platform modal system; it should only make dialogs declarative,
|
|
372
505
|
animation-friendly, and consistent across supported browsers.
|
|
373
506
|
|
|
374
|
-
The close button is anchored to the dialog surface, not the content flow: it
|
|
375
|
-
sits at the top `inline-end`, inside the panel. The button itself is out of
|
|
376
|
-
flow, while the header reserves enough inline padding to keep its title clear.
|
|
377
|
-
Keep the button a direct child of `dialog.modal` so the scrolling content
|
|
378
|
-
(`> form` or `> .stack`) never competes with it.
|
|
379
|
-
|
|
380
507
|
## CSS hooks
|
|
381
508
|
|
|
382
509
|
- `--modal-size` — maximum dialog width. Fallback-only, so a class on the
|
|
383
510
|
dialog or an inherited value both reach it. A dialog is shrink-to-fit: a
|
|
384
511
|
composition that must fill this width sets `inline-size` as well.
|
|
512
|
+
- `--modal-pad` — padding of the direct `<form>` or `.stack` content wrapper.
|
|
513
|
+
- `--modal-header-bg` — background of a direct header band.
|
|
385
514
|
- `--dialog-viewport-gap` — distance kept between the dialog and the viewport edges.
|
|
386
515
|
- `--dialog-icon-size` — diameter of the `.dialog-icon` circle.
|
|
387
516
|
- `--dialog-icon-glyph-size` — size of the glyph centered inside `.dialog-icon`.
|
|
388
517
|
- `--close-size` / `--close-icon-size` — size of the corner `.close` and of its
|
|
389
|
-
X. The header reserves the close size
|
|
518
|
+
X. The header reserves the close size, its edge inset, and a content gap so
|
|
390
519
|
the title never runs under it.
|
|
@@ -70,6 +70,8 @@ marker to that background rather than to the neutral intent.
|
|
|
70
70
|
|
|
71
71
|
### Hooks
|
|
72
72
|
|
|
73
|
+
- `--meter-track` — track color; raise it when the meter sits on a
|
|
74
|
+
`--surface-subtle` card, where the default track would disappear.
|
|
73
75
|
- `--bar-height` — bar thickness, shared with `.progress`.
|
|
74
76
|
- `--meter-value` — relative weight of a direct child in a segmented meter.
|
|
75
77
|
|
|
@@ -28,6 +28,7 @@ Whole pages, composed the way a real product would.
|
|
|
28
28
|
- [Marketing](../../demo/templates/marketing.html) - landing page with typography and fluid type
|
|
29
29
|
- [Blog](../../demo/templates/blog.html) - article page with prose
|
|
30
30
|
- [Settings modal](../../demo/templates/settings-modal.html) - two-pane preferences dialog, with the local CSS it needs audited rule by rule
|
|
31
|
+
- [Modal patterns](../../demo/templates/modal-patterns.html) - eight application modals, each the modal surface plus the smallest application CSS it needs
|
|
31
32
|
- [Workspace settings](../../demo/templates/settings-pricing.html) - plans with visible native choices, preferences and a local segmented tab treatment
|
|
32
33
|
- [Card compositions](../../demo/templates/card-compositions.html) - editorial, team, sign-in and horizontal cards composed from existing primitives
|
|
33
34
|
- [Register form](../../demo/templates/register-form.html) - registration page with choice cards, masks, the password toggle, `tel-prefix` validation, an OTP verification dialog, and status feedback
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Design tools (Figma, Penpot)
|
|
2
|
+
|
|
3
|
+
`actual-css design` exports a theme to design tools: its tokens for Figma and Penpot, and a Penpot plugin that builds Button, Input, Badge, Alert and Card bound to those tokens. Use it to hand designers the same palette, scale and components the code ships — a design kit, a UI kit, a Figma or Penpot library generated from your theme.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bunx actual-css design --theme src/theme.css --out design/
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`--theme` is a file declaring one `[data-theme="…"]` block (see [Theming](../foundations/theming.md)); without it the default theme is exported. The command needs Bun: it reads the theme in headless Chrome, so every `light-dark()`, `color-mix()` and `var()` chain resolves exactly as the browser paints it.
|
|
10
|
+
|
|
11
|
+
## What is exported
|
|
12
|
+
|
|
13
|
+
| File | Content |
|
|
14
|
+
| --------------------------------------------- | ------------------------------------ |
|
|
15
|
+
| `figma/foundations.tokens.json` | spacing, radius, borders, type sizes |
|
|
16
|
+
| `figma/light.tokens.json`, `dark.tokens.json` | colors, one file per scheme |
|
|
17
|
+
| `penpot/tokens.json` | all tokens, as sets and themes |
|
|
18
|
+
| `penpot/plugin/` | the plugin and component spec |
|
|
19
|
+
|
|
20
|
+
Token names are the CSS names without `--`: `surface-raised` in the design tool is `var(--surface-raised)` in code. A scheme exists when the theme declares it: `color-scheme: light dark` exports both, anything else one. Lengths are exported in px, with the CSS value kept in `$extensions["actual-css"]`.
|
|
21
|
+
|
|
22
|
+
Colors no theme token paints — the soft fills and borders of `.soft` — are exported as recipe tokens named `<variant>-<intent>-<role>` (`soft-danger-bg`, `soft-danger-border`, `soft-danger-fg`), next to the theme colors so they switch with the scheme. Font stacks, line heights, shadows, motion and z-index have no design-tool equivalent and are not exported.
|
|
23
|
+
|
|
24
|
+
## Figma
|
|
25
|
+
|
|
26
|
+
Import each file from the Variables panel. Figma makes one collection per file: import `light`, then add `dark` to that collection as a second mode (a paid-plan feature; the free plan allows one mode per collection).
|
|
27
|
+
|
|
28
|
+
## Penpot
|
|
29
|
+
|
|
30
|
+
1. Tokens tab → Tools → Import `penpot/tokens.json`. It replaces the file's tokens.
|
|
31
|
+
2. Serve `penpot/plugin/` over http with CORS, and add its `manifest.json` URL in the plugin manager (`Ctrl + Alt + P`).
|
|
32
|
+
3. Run **Build components**.
|
|
33
|
+
|
|
34
|
+
The components are built on their own page, "Actual CSS components"; drag instances from Assets onto any page. Each is a variant set whose properties are the classes an author stacks — `Intent`, `Variant` and `Size` on Button and Badge, `Intent` and `Variant` on Alert, `Size` on Input — defaulting to the bare class (`.btn`, `.badge`). Fills, borders and text colors are bound to tokens, so switching the Penpot theme between Light and Dark repaints every variant. Radius, padding, height and type are the measured values: run the export again after changing them in the theme.
|
|
35
|
+
|
|
36
|
+
The components are measured, not modelled: the browser renders the classes and the plugin replays the result, so a theme's own radius or padding shows up without configuration. States (hover, focus, disabled) stay a code concern.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-css",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.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.4.2",
|
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
"build:js": "bun scripts/build-js.js",
|
|
132
132
|
"build:themes": "bun scripts/build-themes.js",
|
|
133
133
|
"build:size": "bun scripts/build-size.js",
|
|
134
|
+
"export:design": "bun scripts/export-design.js",
|
|
134
135
|
"check:enhance": "bun run scripts/check-enhance.js",
|
|
135
136
|
"generate:reserved": "bun scripts/generate-reserved-classes.js",
|
|
136
137
|
"check:reserved": "bun scripts/check-reserved.js",
|
|
@@ -21,6 +21,41 @@ Self-describing leaf behavior should use the feature's own documented attribute,
|
|
|
21
21
|
|
|
22
22
|
Native `<dialog>` is semantically self-describing and does not need a generic enhancement token merely to be a dialog.
|
|
23
23
|
|
|
24
|
+
## Dialog anatomy
|
|
25
|
+
|
|
26
|
+
Treat `.modal` as the outer surface, not as its content layout. It deliberately
|
|
27
|
+
has no padding. Wrap modal content in a direct `<form>` or `.stack`; do not put
|
|
28
|
+
`.stack` on `dialog.modal` itself.
|
|
29
|
+
|
|
30
|
+
For a header band, put `<header>` directly under the dialog before the body
|
|
31
|
+
wrapper. Compose a flexible `.media` title region and a trailing `.cluster` of
|
|
32
|
+
controls. Nest `.close` in that cluster so it remains in flow; the title,
|
|
33
|
+
leading `.dialog-icon`, close, and any additional control then share one center
|
|
34
|
+
axis and the title uses the close control's minimum height. A direct
|
|
35
|
+
`dialog > header` is dialog chrome and forms a distinct band; put an ordinary
|
|
36
|
+
content heading in the wrapper's `<header>` instead.
|
|
37
|
+
|
|
38
|
+
For a corner close button, keep `.close` as a direct child of the dialog and put
|
|
39
|
+
the title in the content wrapper's direct `<header>`. The close button is then
|
|
40
|
+
anchored to the surface, and the header reserves room so the title cannot run
|
|
41
|
+
under it. A `.close` that is a direct child of the chrome header is that same
|
|
42
|
+
corner overlay, and that header reserves room for it too. Put actions in the
|
|
43
|
+
wrapper's `<footer>`.
|
|
44
|
+
|
|
45
|
+
Use `.bleed` on that direct header when it needs a contrasting full-width band;
|
|
46
|
+
the wrapper relays the modal padding so the band reaches the surface edges while
|
|
47
|
+
its contents keep the normal inset. In `.dialog-confirmation`, keep the icon and
|
|
48
|
+
the text header as the media object's two children; the component centers the
|
|
49
|
+
header's first heading on the icon while following copy stays in that text
|
|
50
|
+
column. For a trailing icon, use the confirmation's direct header as a
|
|
51
|
+
non-wrapping split `.cluster`, with `.grow` on the text group. Do not compensate
|
|
52
|
+
heading alignment with a literal margin.
|
|
53
|
+
|
|
54
|
+
Do not turn the dialog heading into an `.alert` to obtain icon or surface
|
|
55
|
+
styling. Compose `.dialog-icon` with `.media` for a leading title icon; reserve
|
|
56
|
+
`.alert` for a distinct status message in the dialog body. Inspect
|
|
57
|
+
`docs/pages/components/dialog.md` in the installed package for current recipes.
|
|
58
|
+
|
|
24
59
|
## Custom widgets
|
|
25
60
|
|
|
26
61
|
Before implementing lifecycle, focus, Escape handling, keyboard navigation, input rewriting, or floating-surface policy from scratch, inspect the exported Actual JS primitives.
|
package/src/cli/actual-css.js
CHANGED
|
@@ -7,16 +7,23 @@ import { inlineImports, minifyCss } from "../tooling/css-bundle.js";
|
|
|
7
7
|
function usage() {
|
|
8
8
|
return [
|
|
9
9
|
"actual-css bundle INPUT --out FILE [--minify]",
|
|
10
|
+
"actual-css design [--theme FILE] [--name NAME] --out DIR",
|
|
10
11
|
"",
|
|
11
|
-
" -o, --out FILE write the bundle to FILE (required)",
|
|
12
|
+
" -o, --out FILE write the bundle to FILE / the export to DIR (required)",
|
|
12
13
|
" --minify collapse comments and whitespace",
|
|
14
|
+
" --theme FILE export this [data-theme] file instead of the default theme",
|
|
15
|
+
" --name NAME the data-theme name, when the file declares several",
|
|
13
16
|
" -h, --help show this message",
|
|
14
17
|
" --version print the actual-css version",
|
|
15
18
|
"",
|
|
16
|
-
"
|
|
17
|
-
"A layer or layer(name) import is flattened into the @layer block it
|
|
18
|
-
"Remote imports are kept and hoisted to the top of the bundle.",
|
|
19
|
+
"bundle: flatten plain CSS @import chains from relative files and actual-css/css/*",
|
|
20
|
+
"subpaths. A layer or layer(name) import is flattened into the @layer block it",
|
|
21
|
+
"stands for. Remote imports are kept and hoisted to the top of the bundle.",
|
|
19
22
|
"Leaves modern CSS syntax untouched; this is a bundler, not a transpiler.",
|
|
23
|
+
"",
|
|
24
|
+
"design: export a theme's tokens for Figma and Penpot, plus Button, Input, Badge,",
|
|
25
|
+
"Alert and Card measured in headless Chrome and a Penpot plugin that builds them.",
|
|
26
|
+
"Needs Bun (bunx actual-css design ...).",
|
|
20
27
|
].join("\n");
|
|
21
28
|
}
|
|
22
29
|
|
|
@@ -36,6 +43,10 @@ function parseArgs(argv) {
|
|
|
36
43
|
return { version: true };
|
|
37
44
|
}
|
|
38
45
|
|
|
46
|
+
if (command === "design") {
|
|
47
|
+
return parseDesignArgs(rest);
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
if (command !== "bundle") {
|
|
40
51
|
throw new Error(`Unknown command: ${command}`);
|
|
41
52
|
}
|
|
@@ -87,7 +98,39 @@ function parseArgs(argv) {
|
|
|
87
98
|
throw new Error("Missing required --out FILE option.");
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
return { help: false, input, out, minify };
|
|
101
|
+
return { help: false, command: "bundle", input, out, minify };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseDesignArgs(rest) {
|
|
105
|
+
const options = { help: false, command: "design", themeFile: null, name: null, out: "" };
|
|
106
|
+
const valued = { "--theme": "themeFile", "--name": "name", "--out": "out", "-o": "out" };
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
109
|
+
const arg = rest[i];
|
|
110
|
+
if (arg === "--help" || arg === "-h") return { help: true };
|
|
111
|
+
const key = valued[arg];
|
|
112
|
+
if (!key) throw new Error(`Unknown option: ${arg}`);
|
|
113
|
+
const value = rest[i + 1];
|
|
114
|
+
if (!value) throw new Error(`Missing value for ${arg}.`);
|
|
115
|
+
options[key] = value;
|
|
116
|
+
i += 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!options.out) throw new Error("Missing required --out DIR option.");
|
|
120
|
+
return options;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function design({ themeFile, name, out }) {
|
|
124
|
+
// Checked before the import: under Node the module does not even parse
|
|
125
|
+
// (`await using`), and `bundle` must stay usable there.
|
|
126
|
+
if (typeof Bun === "undefined") {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"actual-css design needs Bun: it reads the theme in headless Chrome through Bun.WebView. Run it with bunx.",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const { exportDesign } = await import("../tooling/design-export.js");
|
|
132
|
+
const written = await exportDesign({ themeFile, name, out: resolve(out) });
|
|
133
|
+
console.log(`Exported ${written.length} files to ${resolve(out)}`);
|
|
91
134
|
}
|
|
92
135
|
|
|
93
136
|
async function bundle({ input, out, minify }) {
|
|
@@ -124,7 +167,7 @@ async function main() {
|
|
|
124
167
|
return;
|
|
125
168
|
}
|
|
126
169
|
|
|
127
|
-
await bundle(args);
|
|
170
|
+
await (args.command === "design" ? design(args) : bundle(args));
|
|
128
171
|
}
|
|
129
172
|
|
|
130
173
|
main().catch((error) => {
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* A surface that insets its direct children relays that inset to them as
|
|
7
7
|
* --surface-pad; a direct .bleed child cancels it and reaches the surface
|
|
8
|
-
* edge. Card and drawer pad at the container level,
|
|
9
|
-
*
|
|
10
|
-
* owns it and the item does not.
|
|
8
|
+
* edge. Card and drawer pad at the container level, while modal pads its inner
|
|
9
|
+
* wrapper; each owns the token where that inset lives. The accordion pads its
|
|
10
|
+
* summary and panel instead, so the panel owns it and the item does not.
|
|
11
11
|
*
|
|
12
12
|
* Why the token is handed to the children rather than declared on the surface:
|
|
13
13
|
* custom properties inherit, so a surface-level declaration would also reach a
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
* Framework plumbing:
|
|
29
29
|
* --surface-pad (declared by the surface)
|
|
30
30
|
*/
|
|
31
|
-
:where(.card, .drawer, .accordion details > summary + *) > * * {
|
|
31
|
+
:where(.card, .drawer, dialog.modal > :is(form, .stack), .accordion details > summary + *) > * * {
|
|
32
32
|
--bleed-pad: initial;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
:where(.card, .drawer, .accordion details > summary + *) > * {
|
|
35
|
+
:where(.card, .drawer, dialog.modal > :is(form, .stack), .accordion details > summary + *) > * {
|
|
36
36
|
--bleed-pad: var(--surface-pad);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* --btn-default-hover-bg
|
|
32
32
|
* --btn-border-block-end-color
|
|
33
33
|
* --btn-border-block-end-width
|
|
34
|
+
* --btn-border-block
|
|
34
35
|
* --spinner-width
|
|
35
36
|
*/
|
|
36
37
|
|
|
@@ -56,6 +57,10 @@
|
|
|
56
57
|
--btn-default-hover-bg: var(--btn-bg);
|
|
57
58
|
--btn-radius: var(--radius);
|
|
58
59
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
60
|
+
--btn-border-block: calc(
|
|
61
|
+
var(--border-width) +
|
|
62
|
+
var(--btn-border-block-end-width, var(--border-width))
|
|
63
|
+
);
|
|
59
64
|
|
|
60
65
|
/* Positioning context for indicators, overlays, and grouped states. */
|
|
61
66
|
position: relative;
|
|
@@ -65,7 +70,13 @@
|
|
|
65
70
|
gap: var(--btn-gap, 0.375em);
|
|
66
71
|
min-block-size: var(--btn-min-size);
|
|
67
72
|
padding-block: var(--space-20);
|
|
68
|
-
|
|
73
|
+
/* why: centres one line in --btn-min-size and keeps that rhythm when the
|
|
74
|
+
label wraps. min-block-size is a border-box length, so both borders come
|
|
75
|
+
out of it; without them .btn stood 2px taller than .input. */
|
|
76
|
+
padding-block: max(
|
|
77
|
+
var(--space-10),
|
|
78
|
+
calc((var(--btn-min-size) - 1lh - var(--btn-border-block)) / 2)
|
|
79
|
+
);
|
|
69
80
|
padding-inline: var(--btn-pad-x);
|
|
70
81
|
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
71
82
|
border-block-end-color: var(
|
|
@@ -162,6 +173,8 @@
|
|
|
162
173
|
--ui-fg: var(--intent, currentColor);
|
|
163
174
|
--ui-border: transparent;
|
|
164
175
|
--btn-radius: var(--radius-sm);
|
|
176
|
+
/* border: 0 below; the padding must not subtract a border it lacks. */
|
|
177
|
+
--btn-border-block: 0px;
|
|
165
178
|
|
|
166
179
|
min-block-size: auto;
|
|
167
180
|
padding-inline: 0;
|
|
@@ -16,8 +16,17 @@
|
|
|
16
16
|
* Public hooks:
|
|
17
17
|
* --close-size
|
|
18
18
|
* --close-icon-size
|
|
19
|
+
* --close-bg
|
|
20
|
+
* --close-color
|
|
21
|
+
* --close-hover-color
|
|
22
|
+
* --close-hover-overlay
|
|
19
23
|
*/
|
|
20
24
|
.close {
|
|
25
|
+
/* Routes the hover/active wash, as --btn-hover-overlay does for .btn. A
|
|
26
|
+
context that wants ink-only feedback (the dialog corner) sets it
|
|
27
|
+
transparent. */
|
|
28
|
+
--close-hover-overlay: var(--hover-overlay);
|
|
29
|
+
|
|
21
30
|
display: inline-grid;
|
|
22
31
|
place-items: center;
|
|
23
32
|
flex: none;
|
|
@@ -44,12 +53,12 @@
|
|
|
44
53
|
the control (the dialog corner's --surface-subtle) instead of replacing it. */
|
|
45
54
|
@media (hover: hover) {
|
|
46
55
|
.close:hover {
|
|
47
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
56
|
+
box-shadow: inset 0 0 0 999px var(--close-hover-overlay);
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
.close:active {
|
|
52
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
61
|
+
box-shadow: inset 0 0 0 999px var(--close-hover-overlay);
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
.close:focus-visible {
|
|
@@ -61,23 +70,25 @@
|
|
|
61
70
|
surface rather than to the header or a scroll container inside it. Matched
|
|
62
71
|
on the documented positions (the dialog, its form, its header, or a form
|
|
63
72
|
inside that header) so a .close inside content — an alert in the body —
|
|
64
|
-
keeps its own place. */
|
|
73
|
+
keeps its own place. The corner drops the wash: only the ink changes. */
|
|
65
74
|
:where(dialog):is(.modal, .drawer) > .close,
|
|
66
75
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close,
|
|
67
76
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close {
|
|
77
|
+
--close-hover-overlay: transparent;
|
|
78
|
+
|
|
68
79
|
position: absolute;
|
|
69
80
|
z-index: 1;
|
|
70
81
|
inset-block-start: var(--space-20);
|
|
71
82
|
inset-inline-end: var(--space-20);
|
|
72
|
-
background: var(--surface-subtle);
|
|
73
|
-
color: var(--text-muted);
|
|
83
|
+
background: var(--close-bg, var(--surface-subtle));
|
|
84
|
+
color: var(--close-color, var(--text-muted));
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
@media (hover: hover) {
|
|
77
88
|
:where(dialog):is(.modal, .drawer) > .close:hover,
|
|
78
89
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close:hover,
|
|
79
90
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close:hover {
|
|
80
|
-
color: var(--text);
|
|
91
|
+
color: var(--close-hover-color, var(--text));
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
|
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
* .sm/.lg are deliberately not supported.
|
|
12
12
|
*
|
|
13
13
|
* Public hooks:
|
|
14
|
+
* --meter-track
|
|
14
15
|
* --meter-value
|
|
15
16
|
* --bar-height
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
.meter {
|
|
20
|
+
--meter-track: var(--surface-subtle);
|
|
21
|
+
|
|
19
22
|
appearance: none;
|
|
20
23
|
-webkit-appearance: none;
|
|
21
24
|
display: block;
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
overflow: hidden;
|
|
25
28
|
border: 0;
|
|
26
29
|
border-radius: var(--radius-full);
|
|
27
|
-
background: var(--
|
|
30
|
+
background: var(--meter-track);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/* A non-native meter is a weighted flex container. The element type keeps the
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
block-size: var(--bar-height);
|
|
52
55
|
border: 0;
|
|
53
56
|
border-radius: var(--radius-full);
|
|
54
|
-
background: var(--
|
|
57
|
+
background: var(--meter-track);
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
.meter::-webkit-meter-optimum-value {
|