actual-css 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -1
- package/README.md +6 -2
- package/components.json +14 -3
- package/dist/actual.full.css +483 -128
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/enhancement-contract.md +16 -7
- package/docs/design-notes/platform-alignment.md +8 -4
- package/docs/design-notes/rich-text.md +61 -0
- package/docs/design-notes/steps.md +2 -2
- package/docs/navigation.json +4 -1
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +10 -0
- package/docs/pages/components/bleed.md +5 -4
- package/docs/pages/components/button.md +1 -1
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/close.md +9 -3
- package/docs/pages/components/dialog.md +145 -13
- package/docs/pages/components/meter.md +2 -0
- package/docs/pages/components/pagination.md +15 -15
- package/docs/pages/components/shimmer.md +57 -0
- package/docs/pages/components/skeleton.md +2 -1
- package/docs/pages/components/spinner.md +11 -6
- package/docs/pages/components/table.md +13 -0
- package/docs/pages/enhancements/flyout.md +2 -0
- package/docs/pages/enhancements/tabs.md +4 -0
- package/docs/pages/examples/overview.md +1 -0
- package/docs/pages/forms/choice-cards.md +77 -4
- package/docs/pages/forms/combobox.md +16 -1
- package/docs/pages/forms/overview.md +4 -1
- package/docs/pages/forms/range.md +110 -5
- package/docs/pages/forms/rich-text.md +110 -0
- package/docs/pages/forms/validation.md +5 -1
- package/docs/pages/foundations/theming.md +49 -3
- package/docs/pages/foundations/tokens.md +15 -5
- package/docs/pages/guides/bootstrap.md +2 -1
- package/docs/pages/guides/design-tools.md +36 -0
- package/docs/pages/guides/introduction.md +1 -1
- package/docs/pages/guides/modular-import.md +1 -1
- package/docs/pages/guides/progressive-enhancement.md +13 -14
- package/docs/pages/guides/tailwind.md +4 -2
- package/llms.txt +4 -3
- package/package.json +6 -1
- package/reserved-classes.json +1 -0
- package/skills/actual-css/references/enhancements.md +35 -0
- package/src/cli/actual-css.js +49 -6
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/bleed.css +5 -5
- package/src/css/components/button.css +14 -1
- package/src/css/components/close.css +17 -6
- package/src/css/components/flyout.css +4 -1
- package/src/css/components/meter.css +6 -3
- package/src/css/components/modal.css +123 -7
- package/src/css/components/pagination.css +3 -2
- package/src/css/components/rating.css +1 -1
- package/src/css/components/status-bar.css +2 -2
- package/src/css/components/steps.css +3 -3
- package/src/css/components/tab.css +3 -3
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +1 -1
- package/src/css/core/focus.css +14 -4
- package/src/css/core/theme.css +8 -0
- package/src/css/core/tokens.css +8 -4
- package/src/css/core/variants.css +2 -1
- package/src/css/effects/aura.css +51 -33
- package/src/css/effects/index.css +1 -0
- package/src/css/effects/shimmer.css +57 -0
- package/src/css/forms/choice-card.css +71 -23
- package/src/css/forms/control.css +2 -2
- package/src/css/forms/native.css +5 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +64 -21
- package/src/js/command.js +61 -32
- package/src/js/dialog.js +8 -23
- package/src/js/dismiss.js +1 -2
- package/src/js/enhance.js +84 -123
- package/src/js/full.js +1 -0
- package/src/js/password.js +1 -8
- package/src/js/range.js +151 -0
- package/src/js/scrollspy.js +4 -5
- package/src/js/status.js +2 -10
- package/src/js/tooltip.js +11 -1
- package/src/js/validation.js +4 -10
- package/src/tooling/design-components.js +286 -0
- package/src/tooling/design-export.js +69 -0
- package/src/tooling/design-tokens.js +466 -0
- package/src/tooling/penpot-plugin/manifest.json +7 -0
- package/src/tooling/penpot-plugin/plugin.js +272 -0
- package/src/tooling/penpot-plugin/ui.html +58 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Shimmer
|
|
2
|
+
|
|
3
|
+
> **Module** — import `actual-css/css/effects/shimmer` or `actual-css/css/effects`.
|
|
4
|
+
|
|
5
|
+
Shimmer sweeps a highlight across a text label to show an activity in
|
|
6
|
+
progress: an agent working, a chatbot composing, a job processing.
|
|
7
|
+
|
|
8
|
+
```html demo
|
|
9
|
+
<div class="stack">
|
|
10
|
+
<span class="shimmer" role="status">Working…</span>
|
|
11
|
+
<p class="cluster">
|
|
12
|
+
<span class="spinner" aria-hidden="true"></span>
|
|
13
|
+
<span class="shimmer" role="status">Generating the report…</span>
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Related terms:** shimmering text, loading text, thinking indicator, pending
|
|
19
|
+
label, typing indicator.
|
|
20
|
+
|
|
21
|
+
Shimmer is presentation only and selects no ARIA attribute. Put
|
|
22
|
+
`role="status"` on a message that should be announced, and `aria-busy="true"`
|
|
23
|
+
on the region whose content is being updated — not on the message because it
|
|
24
|
+
looks busy. Assistive technology may hold back changes inside a busy region
|
|
25
|
+
until it is released.
|
|
26
|
+
|
|
27
|
+
The label rests on `--shimmer-color` and the band passes in
|
|
28
|
+
`--shimmer-highlight`; one pass takes `--duration-shimmer`, the pace shared
|
|
29
|
+
with `.skeleton` and indeterminate `.progress`. With reduced motion, or in a
|
|
30
|
+
browser without `background-clip: text`, the label is plain
|
|
31
|
+
`--shimmer-color` text. Recolor a label through these hooks: its own `color`
|
|
32
|
+
shows only where the sweep does not run.
|
|
33
|
+
|
|
34
|
+
## Choosing a waiting signal
|
|
35
|
+
|
|
36
|
+
| Primitive | Signals |
|
|
37
|
+
| -------------------------- | -------------------------------------------- |
|
|
38
|
+
| [`.spinner`](spinner.md) | An operation takes time |
|
|
39
|
+
| [`.skeleton`](skeleton.md) | Content is not available yet |
|
|
40
|
+
| `.shimmer` | This label describes an activity in progress |
|
|
41
|
+
| [`.aura`](aura.md) | This object is emphasized |
|
|
42
|
+
|
|
43
|
+
They compose rather than imply one another: `aria-busy` never adds an aura or
|
|
44
|
+
a shimmer. A card being processed can carry several, each for its own reason:
|
|
45
|
+
|
|
46
|
+
```html demo
|
|
47
|
+
<span class="aura primary">
|
|
48
|
+
<article class="card stack" style="inline-size: 18rem">
|
|
49
|
+
<p><strong>Quarterly import</strong></p>
|
|
50
|
+
<p class="shimmer" role="status">Processing 1,204 rows…</p>
|
|
51
|
+
<div class="stack" aria-busy="true">
|
|
52
|
+
<div class="skeleton" data-shape="text" aria-hidden="true"></div>
|
|
53
|
+
<div class="skeleton" data-shape="text" aria-hidden="true"></div>
|
|
54
|
+
</div>
|
|
55
|
+
</article>
|
|
56
|
+
</span>
|
|
57
|
+
```
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
- Use layout utilities for placeholder arrangement.
|
|
9
9
|
- Only relevant for simple placeholders - use more structural solutions like phantom-ui for complex cases.
|
|
10
10
|
|
|
11
|
-
**Related terms:** skeleton screen, shimmer, loading placeholder.
|
|
11
|
+
**Related terms:** skeleton screen, shimmer, loading placeholder. For a text
|
|
12
|
+
label showing an activity in progress, see [Shimmer](shimmer.md).
|
|
12
13
|
|
|
13
14
|
## Class reference
|
|
14
15
|
|
|
@@ -12,11 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
## Class reference
|
|
14
14
|
|
|
15
|
-
| Class
|
|
16
|
-
|
|
|
17
|
-
| `.spinner`
|
|
18
|
-
|
|
|
19
|
-
|
|
|
15
|
+
| Class | Kind | Description |
|
|
16
|
+
| -------------------- | --------- | ------------------------------------------------ |
|
|
17
|
+
| `.spinner` | Component | Circular loading indicator using `currentColor`. |
|
|
18
|
+
| `[aria-busy="true"]` | State | Loading region; see Busy state below. |
|
|
19
|
+
| `.sm` / `.lg` | Size | Smaller (`0.75em`) or larger (`2rem`) spinner. |
|
|
20
|
+
| Shared intents | Intent | `.primary`, `.secondary`, `.success`, … |
|
|
21
|
+
|
|
22
|
+
The shared intents are `.primary`, `.secondary`, `.success`, `.warning` and
|
|
23
|
+
`.danger`.
|
|
20
24
|
|
|
21
25
|
## Basic usage
|
|
22
26
|
|
|
@@ -68,7 +72,8 @@ Size follows the font (`1em`); use `.sm` / `.lg` rather than sizing it directly.
|
|
|
68
72
|
|
|
69
73
|
## Busy state
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
The spinner is the indicator; `aria-busy` is the state. A container-wide
|
|
76
|
+
loading state keeps the underlying content in place. It is
|
|
72
77
|
driven by `aria-busy="true"` plus a direct last-child `.spinner` — there is no
|
|
73
78
|
separate `.busy` class. The spinner is shown as a centered overlay over a faded
|
|
74
79
|
surface; buttons keep their spinners inline instead (see Button).
|
|
@@ -124,6 +124,19 @@ same vocabulary as `.card.compact`.
|
|
|
124
124
|
</div>
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
## When a table stops reading as a table
|
|
128
|
+
|
|
129
|
+
Responsive strategy is horizontal overflow: the wrap scrolls, the table keeps
|
|
130
|
+
its columns and its column scanability. No `data-label` restructuring, no
|
|
131
|
+
second representation of the data.
|
|
132
|
+
|
|
133
|
+
For datasets that no longer read well as a table on narrow screens, compose a
|
|
134
|
+
different view instead of restructuring the table: a `.list` / `.media` row
|
|
135
|
+
with badges and actions for a CRUD collection, [`.data-list`](data-list.md)
|
|
136
|
+
for one object's properties, or a dedicated data grid for interactive tables
|
|
137
|
+
(sorting, filtering, selection). Two views for two contexts is a feature, not
|
|
138
|
+
a duplication.
|
|
139
|
+
|
|
127
140
|
## CSS hooks
|
|
128
141
|
|
|
129
142
|
- `--table-cell-pad` — cell padding; `.compact` re-declares it tighter.
|
|
@@ -522,6 +522,8 @@ delay; a number sets the delay in milliseconds.
|
|
|
522
522
|
|
|
523
523
|
The repetition is the point: the two hooks are different contracts, and only
|
|
524
524
|
the cap may enter the runtime's `min()`.
|
|
525
|
+
- `--flyout-pad` — panel padding. Defaults to `var(--space-10)`, tight for
|
|
526
|
+
action menus; rich panels opt into dialog air per instance.
|
|
525
527
|
- `--menu-item-size` — minimum row height of `.menu-item`.
|
|
526
528
|
- `--menu-item-icon-size` — shared leading-column width for icons and checked-state indicators.
|
|
527
529
|
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
- JavaScript owns roving `tabindex`, `aria-selected`, `hidden`, and keyboard behavior.
|
|
23
23
|
- Left/Right select tabs and wrap at the ends. Home/End jump to first/last. Down moves focus into the selected panel.
|
|
24
24
|
- A tab list needs both `.tabs` and `role="tablist"`; `.tab` styles each trigger.
|
|
25
|
+
- Naming follows the container/item convention (like `menu` / `menu-item`):
|
|
26
|
+
`.tabs` is the strip, `.tab` is one trigger. The JS module
|
|
27
|
+
(`actual-css/js/tab`) is named for the file; the behavior token
|
|
28
|
+
(`data-enhance="tabs"`) is named for what it wires.
|
|
25
29
|
- A `.tab` with an icon uses `--tab-gap` (default `0.375em`) for the space between icon and label.
|
|
26
30
|
- A tab that is `hidden`, `disabled`, `aria-disabled`, or has no panel is skipped
|
|
27
31
|
by the arrow keys. An application filtering a tab strip only has to set
|
|
@@ -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
|
|
@@ -11,9 +11,31 @@
|
|
|
11
11
|
| `.primary` / `.secondary` | Intent | Accent for the selected state. |
|
|
12
12
|
|
|
13
13
|
- Use `.choice-card` on a `<label>` wrapping a native `<input type="radio">` or `<input type="checkbox">`. The input is visually hidden; in browsers without `:has()` it stays visible so selection state never disappears.
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
14
|
+
- The control shows before any choice: a ring (radio) or a rounded box (checkbox) on the leading edge, level with the first line. Checked, it fills with a check, and the card takes a primary border and a subtle tint.
|
|
15
|
+
- Each child element of the label sits on its own row beside the indicator — a title and a `.muted` detail need no wrapper. Wrap loose text in an element.
|
|
16
|
+
- Stack cards for a list of options (a person, an address, a delivery method), or pair them with grid utilities for options compared side by side.
|
|
17
|
+
- Supports disabled, checked, focus-visible, and invalid states; the resting ring turns danger with the border.
|
|
18
|
+
|
|
19
|
+
```html demo
|
|
20
|
+
<fieldset class="field-group stack">
|
|
21
|
+
<legend class="field-label">Deliver to</legend>
|
|
22
|
+
<label class="choice-card">
|
|
23
|
+
<input type="radio" name="address" value="home" checked />
|
|
24
|
+
<strong>Home</strong>
|
|
25
|
+
<span class="muted">12 Rue des Tanneurs, 1000 Brussels</span>
|
|
26
|
+
</label>
|
|
27
|
+
<label class="choice-card">
|
|
28
|
+
<input type="radio" name="address" value="work" />
|
|
29
|
+
<strong>Work</strong>
|
|
30
|
+
<span class="muted">Avenue Louise 54, 1050 Ixelles</span>
|
|
31
|
+
</label>
|
|
32
|
+
<label class="choice-card">
|
|
33
|
+
<input type="radio" name="address" value="pickup" />
|
|
34
|
+
<strong>Pickup point</strong>
|
|
35
|
+
<span class="muted">Collect within 7 days</span>
|
|
36
|
+
</label>
|
|
37
|
+
</fieldset>
|
|
38
|
+
```
|
|
17
39
|
|
|
18
40
|
```html demo
|
|
19
41
|
<div class="grid-3">
|
|
@@ -94,6 +116,52 @@ follows through its `em` sizing. Padding is density, so use a `.compact` or
|
|
|
94
116
|
`.spacious` context to change it. Intent classes (`.primary`, `.secondary`) are
|
|
95
117
|
supported on the `.choice-card` element.
|
|
96
118
|
|
|
119
|
+
## Corner badge
|
|
120
|
+
|
|
121
|
+
A tile whose content is the choice itself — a product image, a centred plan
|
|
122
|
+
name — may want no indicator column, and a check badge over its corner once
|
|
123
|
+
selected (radio/checkbox tile, selected tick, check in the corner). That is a
|
|
124
|
+
recipe on the card's own `::before` (disc) and `::after` (check) layers,
|
|
125
|
+
written in application CSS after the framework:
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
.choice-tile {
|
|
129
|
+
--choice-card-check-size: 1.5em;
|
|
130
|
+
|
|
131
|
+
display: block;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.choice-tile::before,
|
|
135
|
+
.choice-tile::after {
|
|
136
|
+
position: absolute;
|
|
137
|
+
inset-inline-end: -0.375em;
|
|
138
|
+
inset-block-start: -0.375em;
|
|
139
|
+
margin: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.choice-tile::before {
|
|
143
|
+
border: 0;
|
|
144
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
145
|
+
transform: scale(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.choice-tile:has(:checked)::before {
|
|
149
|
+
transform: scale(1);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<label class="choice-card choice-tile">
|
|
155
|
+
<input type="radio" name="cpu" value="1" checked />
|
|
156
|
+
<strong>8-core CPU</strong><br />
|
|
157
|
+
<span class="muted">32 GB RAM</span>
|
|
158
|
+
</label>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The badge overhangs the corner, so no ancestor up to the grid may clip with
|
|
162
|
+
`overflow: hidden`. The [Neon Ramen item page](../../demo/sites/neon-ramen/item.html)
|
|
163
|
+
uses it for its sizes and toppings.
|
|
164
|
+
|
|
97
165
|
## Chips
|
|
98
166
|
|
|
99
167
|
Add `.chip` for the compact pill form: filter chips, selectable tags, toggle
|
|
@@ -101,6 +169,9 @@ pills, multi-select facets. A radio group gives single-choice chips. A leading
|
|
|
101
169
|
dot turns into the check in a fixed slot, so checking never moves the label.
|
|
102
170
|
Use `.cluster` for a wrapping row. A chip is sized like a control: `.btn` and
|
|
103
171
|
`.input` height, `--control-pad-x` inline padding, `.sm` / `.lg` scale.
|
|
172
|
+
A chip is a `<label>` around its checkbox/radio — never nest a `.close` or any
|
|
173
|
+
other button inside it. A removable value (combobox tag) belongs to the widget
|
|
174
|
+
that produced it, or to a sibling `.badge` + `.close`, not to the chip label.
|
|
104
175
|
|
|
105
176
|
```html demo
|
|
106
177
|
<fieldset class="field-group">
|
|
@@ -129,8 +200,10 @@ by widening the clip, as the `material` theme does.
|
|
|
129
200
|
## CSS hooks
|
|
130
201
|
|
|
131
202
|
- `--choice-card-pad` — inner padding.
|
|
203
|
+
- `--choice-card-gap` — space between the indicator column and the label.
|
|
132
204
|
- `--choice-card-radius` — corner radius.
|
|
133
|
-
- `--choice-card-check-size` — size of the
|
|
205
|
+
- `--choice-card-check-size` — size of the indicator, and the width of its
|
|
206
|
+
column.
|
|
134
207
|
- `--choice-card-border` / `--choice-card-bg` — resting border and background;
|
|
135
208
|
the checked state and hover rebind them, so a checked override needs
|
|
136
209
|
`:has(:checked)`.
|
|
@@ -10,7 +10,7 @@ real product needs a JavaScript combobox (searchable select, removable tags,
|
|
|
10
10
|
autocomplete suggestions), load a dedicated component and bridge its custom
|
|
11
11
|
properties onto Actual's tokens.
|
|
12
12
|
|
|
13
|
-
**Related terms:** combo box, combobox, autocomplete, datalist, searchable select, enhanced select, multiple select, tags, chips, tag input, pill input.
|
|
13
|
+
**Related terms:** combo box, combobox, autocomplete, datalist, select, custom select, native select, searchable select, enhanced select, multiple select, dropdown, form controls, tags, chips, tag input, pill input.
|
|
14
14
|
|
|
15
15
|
## Theme bridge
|
|
16
16
|
|
|
@@ -36,6 +36,21 @@ mix tokens.
|
|
|
36
36
|
|
|
37
37
|
The card in the template documents the full token map of this recipe.
|
|
38
38
|
|
|
39
|
+
## Bridge contract
|
|
40
|
+
|
|
41
|
+
Actual owns the visual language; the widget owns the behavior. Actual provides
|
|
42
|
+
surface, border, radius, inset focus outline, danger state, `.join` corners,
|
|
43
|
+
and the contrast contracts those tokens carry. The widget provides search,
|
|
44
|
+
keyboard navigation, multi-value state, removable tags, and option rendering.
|
|
45
|
+
The bridge CSS stays in docs/demo — it is a recipe, not package API, so no
|
|
46
|
+
`actual-combobox.css` ships with the framework and no widget version is pinned
|
|
47
|
+
by it.
|
|
48
|
+
|
|
49
|
+
Multi-value and i18n (for example a "n selected" summary string) are widget
|
|
50
|
+
configuration: pass locale strings and tag rendering through the widget's own
|
|
51
|
+
options, and keep skinning on the `--cb-*` tokens so light/dark mode and preset
|
|
52
|
+
themes keep applying for free.
|
|
53
|
+
|
|
39
54
|
## Lightweight pass-through
|
|
40
55
|
|
|
41
56
|
Most of the skin is three one-line token bridges. The visible vocabulary —
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
- `.field-group` and `.choice` provide layout without depending on parent scope.
|
|
11
11
|
- Customizable select is progressive enhancement only — the native select remains the baseline.
|
|
12
12
|
- Searchable selects, tags and autocomplete are a JavaScript widget concern; theme an external combobox with Actual tokens — see the [combobox template](../../demo/templates/combobox.html).
|
|
13
|
+
- Formatted text (bold, lists, links) is a hosted-editor concern; the native textarea stays the baseline and an external engine is themed the same way — see the [rich-text template](../../demo/templates/rich-text.html) and the [composer template](../../demo/templates/composer.html) for the plain-text case.
|
|
14
|
+
|
|
15
|
+
**Related terms:** form controls, select, custom select, dropdown, input, textarea, checkbox, radio.
|
|
13
16
|
|
|
14
17
|
## Class reference
|
|
15
18
|
|
|
@@ -181,7 +184,7 @@ it a stronger fill.
|
|
|
181
184
|
- Use native `<optgroup>` and `disabled` options in `.select` controls when applicable; the customizable picker preserves both as a progressive enhancement.
|
|
182
185
|
- `.choice` is the choice-label API. Use `.check` or `.radio` on the nested control. The control goes first, the label group second, so multi-line labels align the control with the first line.
|
|
183
186
|
- For a switch, use `class="switch"` and `role="switch"`.
|
|
184
|
-
-
|
|
187
|
+
- `.range` skins the native range control (`appearance: none` track, thumb, and focus ring). Add `data-enhance="range"` for the fill share, named `datalist` values, and output sync.
|
|
185
188
|
- `.form-actions` carries a default top margin. Override it with `--form-actions-margin-block-start`, `--form-actions-align`, or `--form-actions-justify`. It is class-only and may live inside or outside `<form>`. See Detached Actions below.
|
|
186
189
|
- `.join` visually joins adjacent controls into a single unit. Use `.join-addon` for static prefix/suffix content. The container still carries `role="group"` for accessibility.
|
|
187
190
|
|
|
@@ -12,14 +12,117 @@ rather than the CSS box.
|
|
|
12
12
|
```html demo
|
|
13
13
|
<label class="field">
|
|
14
14
|
<span class="field-label">Volume</span>
|
|
15
|
-
<input class="range" type="range" min="0" max="100" value="50" />
|
|
15
|
+
<input class="range" data-enhance="range" type="range" min="0" max="100" value="50" />
|
|
16
16
|
</label>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
## Fill
|
|
20
|
+
|
|
21
|
+
`data-enhance="range"` opts into the `actual-css/js/range` behavior. It writes
|
|
22
|
+
`--range-progress`, the fill share the track gradient reads, and keeps it in
|
|
23
|
+
sync on input and after a form reset, including inputs associated through
|
|
24
|
+
`form="…"`. Without it the variable stays at its `0%` default and the track
|
|
25
|
+
renders plain — the control stays fully usable without JavaScript. The fill
|
|
26
|
+
follows the writing direction, and a bare `input[type="range"]` without the
|
|
27
|
+
`.range` class can opt in the same way.
|
|
28
|
+
|
|
29
|
+
## Named values
|
|
30
|
+
|
|
31
|
+
A `datalist` is the source of truth for discrete named steps: positions plus
|
|
32
|
+
names. Browsers render its ticks and labels inconsistently, so Actual never
|
|
33
|
+
depends on their presentation — the enhancement only reads `option` values and
|
|
34
|
+
their `label`. A matching label sets `aria-valuetext` on the input and mirrors
|
|
35
|
+
into each `<output for="…">`. The output is the visible value, not the input's
|
|
36
|
+
name — give the input its own accessible name, since `for` only declares the
|
|
37
|
+
output's operands.
|
|
38
|
+
|
|
39
|
+
```html demo
|
|
40
|
+
<div class="field">
|
|
41
|
+
<output class="field-label" for="effort">High</output>
|
|
42
|
+
<input
|
|
43
|
+
class="range"
|
|
44
|
+
data-enhance="range"
|
|
45
|
+
id="effort"
|
|
46
|
+
type="range"
|
|
47
|
+
min="0"
|
|
48
|
+
max="3"
|
|
49
|
+
step="1"
|
|
50
|
+
value="2"
|
|
51
|
+
list="effort-levels"
|
|
52
|
+
aria-label="Effort"
|
|
53
|
+
/>
|
|
54
|
+
<datalist id="effort-levels">
|
|
55
|
+
<option value="0" label="Minimal"></option>
|
|
56
|
+
<option value="1" label="Standard"></option>
|
|
57
|
+
<option value="2" label="High"></option>
|
|
58
|
+
<option value="3" label="Maximum"></option>
|
|
59
|
+
</datalist>
|
|
60
|
+
</div>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`aria-valuetext` is the announced value when a number really means a text; keep
|
|
64
|
+
it even if the visible `output` changes shape. An `output` is a live region,
|
|
65
|
+
so verify with a screen reader that the two do not double-announce.
|
|
66
|
+
|
|
67
|
+
## In a flyout
|
|
68
|
+
|
|
69
|
+
The range stays an `input`, the flyout stays a flyout — together they read as
|
|
70
|
+
a richer picker with no new component. `data-flyout-auto-close="outside"` keeps
|
|
71
|
+
dragging the slider from closing the panel.
|
|
72
|
+
|
|
73
|
+
```html demo
|
|
74
|
+
<div class="flyout-trigger">
|
|
75
|
+
<button
|
|
76
|
+
class="btn outline"
|
|
77
|
+
type="button"
|
|
78
|
+
data-enhance="flyout"
|
|
79
|
+
aria-expanded="false"
|
|
80
|
+
aria-controls="effort-picker"
|
|
81
|
+
>
|
|
82
|
+
Reasoning effort
|
|
83
|
+
</button>
|
|
84
|
+
|
|
85
|
+
<div
|
|
86
|
+
class="flyout"
|
|
87
|
+
id="effort-picker"
|
|
88
|
+
data-flyout-auto-close="outside"
|
|
89
|
+
style="--flyout-inline-size: 16rem; --flyout-pad: var(--space-40)"
|
|
90
|
+
hidden
|
|
91
|
+
>
|
|
92
|
+
<div class="field">
|
|
93
|
+
<output class="field-label text-center" for="effort-flyout">High</output>
|
|
94
|
+
|
|
95
|
+
<input
|
|
96
|
+
class="range"
|
|
97
|
+
data-enhance="range"
|
|
98
|
+
id="effort-flyout"
|
|
99
|
+
type="range"
|
|
100
|
+
min="0"
|
|
101
|
+
max="3"
|
|
102
|
+
step="1"
|
|
103
|
+
value="2"
|
|
104
|
+
list="effort-flyout-levels"
|
|
105
|
+
aria-label="Reasoning effort"
|
|
106
|
+
>
|
|
107
|
+
|
|
108
|
+
<datalist id="effort-flyout-levels">
|
|
109
|
+
<option value="0" label="Minimal"></option>
|
|
110
|
+
<option value="1" label="Standard"></option>
|
|
111
|
+
<option value="2" label="High"></option>
|
|
112
|
+
<option value="3" label="Maximum"></option>
|
|
113
|
+
</datalist>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="cluster" style="--cluster-justify: center">
|
|
117
|
+
<button class="btn primary sm" type="button" data-flyout-close>Done</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Actual deliberately does not emulate tick marks or multi-thumb ranges. Ticks
|
|
124
|
+
still need engine-specific pseudo-elements; the fill above is designed for
|
|
125
|
+
deletion once `::slider-fill` is interoperable — see
|
|
23
126
|
[Enhanced range controls](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/platform-alignment.md).
|
|
24
127
|
|
|
25
128
|
## CSS hooks
|
|
@@ -28,3 +131,5 @@ styling becomes interoperable — see
|
|
|
28
131
|
- `--range-track-height` — track thickness.
|
|
29
132
|
- `--range-thumb-bg` — thumb color. Defaults to the local intent, then `--primary`.
|
|
30
133
|
- `--range-track-bg` — track color.
|
|
134
|
+
- `--range-fill-bg` — filled portion color. Defaults to the local intent, then `--primary`.
|
|
135
|
+
- `--range-progress` — fill share. Written by `actual-css/js/range`, never set by hand.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Rich text
|
|
2
|
+
|
|
3
|
+
> A rich-text editor is not shipped by the framework — the native `<textarea>` stays the baseline. Enhance it with a dedicated engine, themed with Actual tokens.
|
|
4
|
+
|
|
5
|
+
Use a `<textarea>` while editing text, and a hosted editing engine as soon as
|
|
6
|
+
editing means a document (formatting, lists, links, mentions). The
|
|
7
|
+
[rich-text template](../../demo/templates/rich-text.html) enhances a textarea
|
|
8
|
+
with [@lekoala/rich-text](https://github.com/lekoala/rich-text) (Squire
|
|
9
|
+
underneath): the package generates the toolbar with its roving tabindex,
|
|
10
|
+
states, commands and lifecycle, while Actual maps its visual language onto the
|
|
11
|
+
package's `--rt-*` tokens. No adapter, no compatibility layer.
|
|
12
|
+
|
|
13
|
+
**Related terms:** rich text, richtext, WYSIWYG, HTML editor, formatted text, Squire, ProseMirror, Tiptap, text formatting, bold italic, mentions, slash commands.
|
|
14
|
+
|
|
15
|
+
## Theme bridge
|
|
16
|
+
|
|
17
|
+
The template loads the package JS and CSS from a CDN (`@0.2` caret range) and
|
|
18
|
+
declares one editor per field — Actual never builds toolbar buttons or calls
|
|
19
|
+
into the editing engine:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<div class="field actual-rich-text">
|
|
23
|
+
<label class="field-label" for="note">Team note</label>
|
|
24
|
+
<rich-text id="note-editor">
|
|
25
|
+
<textarea class="textarea" id="note" name="note" required placeholder="Write a note…"></textarea>
|
|
26
|
+
</rich-text>
|
|
27
|
+
<span class="field-help">Bold, italic, lists, quotes and links.</span>
|
|
28
|
+
</div>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
editor.options = {
|
|
33
|
+
buttons, // Tabler icons as Nodes, never HTML strings
|
|
34
|
+
requestLink, // an Actual <dialog> replacing window.prompt()
|
|
35
|
+
suggestions, // static @people and /snippets providers
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The bridge maps tokens; it does not restyle the editor's internals:
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
.actual-rich-text .rt-shell {
|
|
43
|
+
--rt-bg: var(--surface);
|
|
44
|
+
--rt-fg: var(--text);
|
|
45
|
+
--rt-border: var(--form-invalid-border, var(--control-border, var(--border)));
|
|
46
|
+
--rt-divider: var(--border);
|
|
47
|
+
--rt-focus: var(--form-invalid-border, var(--focus));
|
|
48
|
+
--rt-toolbar-bg: var(--surface-subtle);
|
|
49
|
+
--rt-button-pressed-bg: var(--state-selected);
|
|
50
|
+
--rt-link: var(--primary);
|
|
51
|
+
--rt-suggestion-bg: var(--surface-raised);
|
|
52
|
+
--rt-suggestion-shadow: var(--shadow-popout);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The card in the template documents the full token map of this recipe. The
|
|
57
|
+
suggestion popover is a child of `.rt-shell`, so it inherits the tokens like
|
|
58
|
+
the rest of the editor. `--rt-border` paints only the field frame, so the
|
|
59
|
+
invalid and hover hooks never reach the toolbar separators or the quote bar,
|
|
60
|
+
which follow `--rt-divider`.
|
|
61
|
+
|
|
62
|
+
## Bridge contract
|
|
63
|
+
|
|
64
|
+
`@lekoala/rich-text` owns the editor UI semantics — progressive enhancement,
|
|
65
|
+
sanitization, toolbar behaviour and keyboard, links, `@mentions`, `/`
|
|
66
|
+
suggestions, form/reset/readonly/disabled integration, selection and undo
|
|
67
|
+
lifecycle. Actual owns presentation and composer chrome — tokens, visual
|
|
68
|
+
states, dialogs, attachments, send, `Ctrl/Cmd+Enter`.
|
|
69
|
+
|
|
70
|
+
Formatting buttons stay local to the editor instance. The stateless `command`
|
|
71
|
+
router never drives bold/italic: those depend on selection, schema and history,
|
|
72
|
+
which only the engine knows. Reserve `command` for the chrome around the
|
|
73
|
+
editor (attach, template, dictate, send).
|
|
74
|
+
|
|
75
|
+
A rich composer reuses `.composer` and `.composer-actions` for the outer
|
|
76
|
+
surface; the bridge only lifts the editor and toolbar into the composer grid
|
|
77
|
+
with `display: contents` and grid areas. `composer.css` itself never knows
|
|
78
|
+
about `.rt-*`. The plain-text [composer template](../../demo/templates/composer.html)
|
|
79
|
+
stays the canonical pattern when editing means text, not a document.
|
|
80
|
+
|
|
81
|
+
People lookup and templates are application concerns: the demo wires static
|
|
82
|
+
arrays into the package's suggestion seam, and the composer buttons type
|
|
83
|
+
`@`/`/` so the providers do the rest. Attachments stay composer-owned —
|
|
84
|
+
logged, never serialized into the HTML.
|
|
85
|
+
|
|
86
|
+
## Storage and sanitization
|
|
87
|
+
|
|
88
|
+
The form submits limited HTML, not engine state: portable, readable,
|
|
89
|
+
editor-independent, reversible. Sanitization is mandatory inside the package
|
|
90
|
+
and not configurable; the client-side sanitizer is a UI boundary, not the
|
|
91
|
+
persistence security boundary — sanitize again on the server before storing or
|
|
92
|
+
rendering untrusted content. An editor holding only the empty block serializes
|
|
93
|
+
to `""`, so native `required` keeps working.
|
|
94
|
+
|
|
95
|
+
If the product later needs truly structural content (custom document nodes,
|
|
96
|
+
annotations, collaboration), a schema-based engine becomes interesting — see
|
|
97
|
+
the [rich-text design note](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/rich-text.md).
|
|
98
|
+
|
|
99
|
+
## Baseline traps
|
|
100
|
+
|
|
101
|
+
The textarea is the value owner and the no-JS fallback, holding raw HTML. Two
|
|
102
|
+
details keep it honest:
|
|
103
|
+
|
|
104
|
+
- Without the integration script the page remains a plain textarea — the link
|
|
105
|
+
dialog wiring and the suggestion providers only exist once the package
|
|
106
|
+
loads, and the composer falls back to plain-text send.
|
|
107
|
+
- `required` cannot sit usefully on a hidden textarea: mirror validity with
|
|
108
|
+
`aria-invalid` on submit (the package emits `richtext:invalid` and focuses
|
|
109
|
+
the editor when it is the form's first invalid control), and let the bridge
|
|
110
|
+
re-own the danger hook from there.
|
|
@@ -55,7 +55,11 @@ Native validation first; the enhancer only adds state and focus behavior.
|
|
|
55
55
|
|
|
56
56
|
Actual CSS ships validation *styles*. Invalid fields are marked with `aria-invalid="true"` (or the manual `.field.danger` wrapper class). The default forms bundle imports `forms/validation.css`; custom builds may omit that file when validation styling is app-owned. A small enhancer (`actual-css/js/validation`), included in the full runtime (`actual-css/js/full`) and also importable on its own, prevents premature error display, marks invalid fields on blur and submit, focuses the first invalid field on submit, and supports a few custom rules. It is not a validation framework — server and AJAX validation stay in app code.
|
|
57
57
|
|
|
58
|
-
Opt in with
|
|
58
|
+
Opt in with `data-enhance="validation"` on the form; `.needs-validation` alone
|
|
59
|
+
only enables the CSS feedback. Importing the module registers the behavior;
|
|
60
|
+
there is no init call. To opt in forms you cannot mark up, call
|
|
61
|
+
`applyEnhancement("validation", "form.needs-validation")` from
|
|
62
|
+
`actual-css/js/enhance`.
|
|
59
63
|
|
|
60
64
|
```html demo
|
|
61
65
|
<form class="needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
|
|
@@ -122,9 +122,9 @@ application CSS and keep forms and intent ink out of it:
|
|
|
122
122
|
|
|
123
123
|
```css
|
|
124
124
|
.site-band {
|
|
125
|
-
--heading: var(--surface);
|
|
125
|
+
--heading: var(--surface-solid-fg);
|
|
126
126
|
background: var(--surface-solid);
|
|
127
|
-
color: var(--surface);
|
|
127
|
+
color: var(--surface-solid-fg);
|
|
128
128
|
}
|
|
129
129
|
```
|
|
130
130
|
|
|
@@ -135,6 +135,49 @@ there: the theme contract keeps `--focus` at 3:1 on `--surface-solid`.
|
|
|
135
135
|
|
|
136
136
|
→ Foundations · Tokens (theme contract) · Examples · Contrast contexts
|
|
137
137
|
|
|
138
|
+
### Scope a theme to one section / microtheme / theme island
|
|
139
|
+
|
|
140
|
+
Any `data-theme` value is an island, not only `light` and `dark`:
|
|
141
|
+
`<aside data-theme="promo">` paints its subtree with that theme, nested inside
|
|
142
|
+
the page theme, without changing the markup. The same painting rule applies as
|
|
143
|
+
for a dark section. A theme can change radius, typography, shadow and motion as
|
|
144
|
+
well as color.
|
|
145
|
+
|
|
146
|
+
→ Foundations · Tokens (theme contract)
|
|
147
|
+
|
|
148
|
+
### Dark mode toggle / theme switcher / remember the user's choice
|
|
149
|
+
|
|
150
|
+
Actual CSS ships the mechanism, not the control. Without `data-theme` the page
|
|
151
|
+
follows the OS; `data-theme="light"` or `"dark"` on `<html>` forces a scheme,
|
|
152
|
+
and removing the attribute returns to the OS. The switcher UI and where the
|
|
153
|
+
choice is stored belong to the application; a page with no setting already
|
|
154
|
+
follows the OS. Apply a stored choice from an inline script in `<head>` so the
|
|
155
|
+
first paint does not flash the OS scheme. The demo's theme picker is an
|
|
156
|
+
inspection tool, not a component to copy.
|
|
157
|
+
|
|
158
|
+
### Force light or dark on a theme that has both / theme and color scheme
|
|
159
|
+
|
|
160
|
+
With a single brand theme, `data-theme="light"` and `"dark"` already are that
|
|
161
|
+
choice once you redefine them as your brand's two schemes (see the dark section
|
|
162
|
+
above).
|
|
163
|
+
|
|
164
|
+
A named theme that pairs both schemes through `light-dark()` follows the OS. To
|
|
165
|
+
pin one scheme, set `color-scheme` on the element that carries `data-theme`,
|
|
166
|
+
inline or in a rule more specific than the theme's own. `color-scheme` is the
|
|
167
|
+
scheme axis and `data-theme` the design axis, so there is no separate
|
|
168
|
+
`data-color-scheme` attribute.
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<html data-theme="ocean" style="color-scheme: dark">
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
On an ancestor it has no effect: the theme declares its own `color-scheme` on
|
|
175
|
+
its boundary. Leave single-scheme themes (`color-scheme: dark` only) alone —
|
|
176
|
+
their tokens have one value, so forcing the other scheme only flips native
|
|
177
|
+
controls and scrollbars.
|
|
178
|
+
|
|
179
|
+
→ Foundations · Tokens (theme contract)
|
|
180
|
+
|
|
138
181
|
### Replace the palette with my own brand
|
|
139
182
|
|
|
140
183
|
Override the whole minimal recolor set (Tokens · Theme contract), not just
|
|
@@ -165,4 +208,7 @@ line. Use `.badge.soft` for a subtle badge. It is a visual theme, not a
|
|
|
165
208
|
Bootstrap compatibility layer. The `gradient` example keeps a Bootstrap 4 style
|
|
166
209
|
outer halo on focused fields, for comparison. The `material` example restyles
|
|
167
210
|
fields as filled underlines with pill buttons — tokens plus three small rules,
|
|
168
|
-
and `.join` still needs no adaptation.
|
|
211
|
+
and `.join` still needs no adaptation. The `glass` example paints translucent
|
|
212
|
+
surfaces over its own backdrop: it sets `--surface-opaque` and
|
|
213
|
+
`--surface-solid-fg` to opaque values, raises `--soft-bg-mix` so soft fills stay
|
|
214
|
+
light over glass, and adds the blur and an opaque busy veil as small rules.
|