actual-css 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -1
- package/README.md +2 -2
- package/components.json +2 -1
- package/dist/actual.full.css +314 -106
- 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 +2 -0
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +10 -0
- package/docs/pages/components/button.md +1 -1
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/dialog.md +5 -2
- 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/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/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 +5 -1
- package/reserved-classes.json +1 -0
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/flyout.css +4 -1
- package/src/css/components/meter.css +1 -1
- 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/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
|
@@ -57,10 +57,11 @@ than the registration root, without crossing shadow boundaries.
|
|
|
57
57
|
|
|
58
58
|
> `data-enhance` is a generic opt-in for when HTML does not already provide an unambiguous one.
|
|
59
59
|
|
|
60
|
-
- **A token** is for a
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
- **A token** is for a named behavior whose opt-in carries no configuration,
|
|
61
|
+
whether it owns a subtree or enhances one leaf (tabs, flyout, range,
|
|
62
|
+
scrollspy, validation).
|
|
63
|
+
- **A self-describing `data-*`** is for a leaf whose attribute is simultaneously
|
|
64
|
+
the opt-in and the configuration (`data-tooltip`, `data-mask`, `data-filter`,
|
|
64
65
|
`data-context-menu`).
|
|
65
66
|
- **Elements that are their own opt-in** need nothing (`<dialog>`).
|
|
66
67
|
|
|
@@ -72,6 +73,13 @@ than the registration root, without crossing shadow boundaries.
|
|
|
72
73
|
on the same root throws; `disconnect()` releases the name; re-registration
|
|
73
74
|
after disconnect is allowed; the same token on a different root is independent.
|
|
74
75
|
|
|
76
|
+
Named registrations share one `[data-enhance]` discovery gate per root: however
|
|
77
|
+
many behaviors are registered, the observer queries that single selector and
|
|
78
|
+
each registration matches its own `[data-enhance~="name"]` against what it finds.
|
|
79
|
+
`enhance()` records share the same observer and lifecycle with their arbitrary
|
|
80
|
+
selectors. Behaviors on one element start in no guaranteed order and must not
|
|
81
|
+
depend on each other.
|
|
82
|
+
|
|
75
83
|
`enhance()` stays generic and unguarded — registering the same selector twice
|
|
76
84
|
creates two independent records that both run.
|
|
77
85
|
|
|
@@ -95,8 +103,8 @@ those registrations explicitly.
|
|
|
95
103
|
|
|
96
104
|
- Use a command for an immediate trigger-to-current-target action. Commands
|
|
97
105
|
resolve the DOM at event time and do not own element lifecycle.
|
|
98
|
-
- Use `registerEnhancement()` for
|
|
99
|
-
state for a subtree.
|
|
106
|
+
- Use `registerEnhancement()` for a named, opted-in behavior that owns listeners
|
|
107
|
+
or state for a subtree or leaf.
|
|
100
108
|
- Use `enhance()` for a self-describing leaf behavior that needs connection and
|
|
101
109
|
cleanup lifecycle without a named token.
|
|
102
110
|
- Prefer delegation when no persistent per-element state is required.
|
|
@@ -145,7 +153,8 @@ of the contract itself.
|
|
|
145
153
|
| ------------ | ------------------------------------------------ | ------------------- |
|
|
146
154
|
| `tabs` | Tab panel switching, arrow-key navigation | `.tabs` |
|
|
147
155
|
| `flyout` | Trigger → panel positioning, open/close | `.flyout` |
|
|
156
|
+
| `range` | Fill share and named-value synchronization | `.range` |
|
|
148
157
|
| `scrollspy` | Scroll-driven nav highlighting | `.scrollspy` |
|
|
149
158
|
| `validation` | Form validation, focus management, server errors | `.needs-validation` |
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
Five tokens. Everything else stays self-describing.
|
|
@@ -386,12 +386,16 @@ Prefer native controls and progressive styling.
|
|
|
386
386
|
|
|
387
387
|
The concrete signal to watch is `appearance: base-slider` with `::slider-track`, `::slider-fill`, and `::slider-thumb`. Checked on Chrome 152: `CSS.supports('appearance', 'base-slider')` and `CSS.supports('selector(::slider-fill)')` are both false, so there is nothing to enhance against yet.
|
|
388
388
|
|
|
389
|
-
Until then, `.range` intentionally omits
|
|
389
|
+
Until then, `.range` intentionally omits tick marks, and its filled track comes
|
|
390
|
+
from the `range` enhancement (one `--range-progress` share driving a
|
|
391
|
+
track gradient) rather than engine-specific pseudo-elements:
|
|
390
392
|
|
|
391
|
-
- The Chromium filled-track trick needs `overflow: hidden` on the runnable track plus a large `box-shadow` on the thumb. It collides twice with `range.css`: the thumb is wider than the track and would be clipped, and the
|
|
392
|
-
- CSS-only tick marks need a step count duplicated out of `min`/`max`/`step` into a custom property, so markup carries the same contract twice and the two can diverge. `datalist` cannot carry it either: its native ticks are part of the appearance that `appearance: none` removes, and no engine renders `<option label>` for a range.
|
|
393
|
+
- The Chromium filled-track trick needs `overflow: hidden` on the runnable track plus a large `box-shadow` on the thumb. It collides twice with `range.css`: the thumb is wider than the track and would be clipped, and the thumb's `box-shadow` is already owned by the focus ring.
|
|
394
|
+
- CSS-only tick marks need a step count duplicated out of `min`/`max`/`step` into a custom property, so markup carries the same contract twice and the two can diverge. `datalist` cannot carry it either: its native ticks are part of the appearance that `appearance: none` removes, and no engine renders `<option label>` for a range — Actual reads the datalist as data only.
|
|
393
395
|
|
|
394
|
-
When the base-slider parts become interoperable,
|
|
396
|
+
When the base-slider parts become interoperable, the `--range-progress`
|
|
397
|
+
calculation becomes a `@supports` enhancement in the shape of
|
|
398
|
+
`custom-select.css` — less engine-specific CSS, not more.
|
|
395
399
|
|
|
396
400
|
### Navigation
|
|
397
401
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Rich text: three levels and why a package demos level 2
|
|
2
|
+
|
|
3
|
+
Editing text and editing a document are different problems with different
|
|
4
|
+
costs. Actual draws the line at the document boundary, in three levels:
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
1. TEXTAREA (native)
|
|
8
|
+
text, autosize, actions row, plain-text caret helpers
|
|
9
|
+
→ the composer pattern: demo/templates/composer.html
|
|
10
|
+
|
|
11
|
+
2. HOSTED RICH-TEXT PACKAGE (integration)
|
|
12
|
+
bold / italic / lists / links / quote, undo, paste cleanup,
|
|
13
|
+
structured @mentions, / snippets
|
|
14
|
+
→ the rich-text recipe: demo/templates/rich-text.html + @lekoala/rich-text
|
|
15
|
+
|
|
16
|
+
3. STRUCTURED DOCUMENT ENGINE (integration, heavier)
|
|
17
|
+
custom document nodes, annotations, collaborative state
|
|
18
|
+
→ no demo; the bridge contract from level 2 applies unchanged
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Level 1 stays a real `<textarea>`: form value, IME, undo, mobile,
|
|
22
|
+
accessibility, validation. `contenteditable="plaintext-only"` buys nothing over
|
|
23
|
+
it and loses the native form control, so the framework does not use it. The
|
|
24
|
+
Markdown round-trip some engines offer is still beta upstream and is not the
|
|
25
|
+
storage contract.
|
|
26
|
+
|
|
27
|
+
## Why a package for the level-2 demo
|
|
28
|
+
|
|
29
|
+
The demo exists to prove the bridge (tokens, pressed states, focus, invalid,
|
|
30
|
+
composer chrome), not the engine. Owning the toolbar, sanitizer, roving focus
|
|
31
|
+
and lifecycle inside the demo meant Actual did involuntarily much of the
|
|
32
|
+
editor's job. Extracting that into `@lekoala/rich-text` restored the boundary:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Actual CSS presentation, tokens, composer chrome, dialogs, validation
|
|
36
|
+
@lekoala/rich-text textarea enhancement, Squire, sanitization, toolbar
|
|
37
|
+
semantics + keyboard, links, mentions, suggestions,
|
|
38
|
+
form/reset/readonly/disabled, lifecycle
|
|
39
|
+
application people lookup, templates, attachments, dictation, send
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`@lekoala/rich-text` owns the editor UI semantics; Actual owns its visual
|
|
43
|
+
language. Squire is an implementation detail of the integration package: it
|
|
44
|
+
owns selection, editing, paste normalisation and undo/redo, while the package
|
|
45
|
+
owns the product-facing contract around it (progressive enhancement of a real
|
|
46
|
+
`<textarea>`, mandatory sanitization, accessible generated UI, lifecycle and
|
|
47
|
+
cleanup). HTML stays the source of truth, which keeps the storage decoupled
|
|
48
|
+
and the engine choice reversible.
|
|
49
|
+
|
|
50
|
+
## What was deliberately not demoed
|
|
51
|
+
|
|
52
|
+
- A second engine demo would prove nothing more about the bridge, and one
|
|
53
|
+
candidate changes fundamental choices between minor versions — a pinned demo
|
|
54
|
+
would rot. Spikes belong in `tmp/`, not `demo/`.
|
|
55
|
+
- Plain-text caret helpers stay a textarea-level improvement at level 1.
|
|
56
|
+
Structured `@mentions` live at level 2 because they are sanitized, atomic
|
|
57
|
+
entities inserted as HTML — the demo's Snippet/Mention buttons reuse the
|
|
58
|
+
same suggestion seam as `/` and `@`, so one capability keeps two surfaces
|
|
59
|
+
without duplicating its logic.
|
|
60
|
+
- Syntax highlighting inside inputs is a search-DSL concern, not a composer
|
|
61
|
+
concern; layering it onto a textarea rebuilds a fake rich editor.
|
|
@@ -630,8 +630,8 @@ so an author who lowers `--step-min` below `--step-size` narrows the scroll
|
|
|
630
630
|
budget without collapsing items behind their own markers.
|
|
631
631
|
|
|
632
632
|
The cost of scrolling is that clipping happens at the padding edge, and a focus
|
|
633
|
-
ring is drawn outside its element's border box — `--focus-outline` is
|
|
634
|
-
|
|
633
|
+
ring is drawn outside its element's border box — `--focus-outline` is
|
|
634
|
+
`--focus-ring-width` wide at `--focus-outline-offset`, so 4px past a label that sits
|
|
635
635
|
flush with the bottom of a stacked row. A navigable row therefore reserves
|
|
636
636
|
exactly that much `padding-block`, gated on `.steps-horizontal:has(a[href])`. An
|
|
637
637
|
informational stepper — the common case, with no ring to protect — keeps its
|
package/docs/navigation.json
CHANGED
|
@@ -84,22 +84,22 @@ The frame color comes from an intent such as `.primary`, `.success`, or
|
|
|
84
84
|
a neutral intent produces a neutral aura. Use a chromatic intent when you want
|
|
85
85
|
a visibly luminous effect.
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
of the glow.
|
|
87
|
+
The default is a thin lit line with a faint halo. Three hooks tune how loud it
|
|
88
|
+
is: `--aura-width` sets the line, `--aura-intensity` scales both halos at once,
|
|
89
|
+
resting and breathing alike, and `--aura-duration` sets one full rotation of
|
|
90
|
+
the accent, or one breath of the glow.
|
|
91
91
|
|
|
92
92
|
```html demo
|
|
93
93
|
<div class="cluster" style="gap: 2rem">
|
|
94
|
-
<span class="aura aura-glow primary" style="--aura-intensity: 0
|
|
95
|
-
<span class="card" style="inline-size: 8rem">
|
|
94
|
+
<span class="aura aura-glow primary" style="--aura-intensity: 0">
|
|
95
|
+
<span class="card" style="inline-size: 8rem">Line only</span>
|
|
96
96
|
</span>
|
|
97
97
|
|
|
98
98
|
<span class="aura aura-glow primary">
|
|
99
99
|
<span class="card" style="inline-size: 8rem">Default</span>
|
|
100
100
|
</span>
|
|
101
101
|
|
|
102
|
-
<span class="aura aura-glow primary" style="--aura-intensity:
|
|
102
|
+
<span class="aura aura-glow primary" style="--aura-width: 3px; --aura-intensity: 1">
|
|
103
103
|
<span class="card" style="inline-size: 8rem">Loud</span>
|
|
104
104
|
</span>
|
|
105
105
|
|
|
@@ -113,11 +113,10 @@ Keep exactly one direct child and set `--aura-radius` when its shape differs
|
|
|
113
113
|
from the common button, card, alert, and badge shapes detected as a visual
|
|
114
114
|
enhancement.
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
wrapper around it.
|
|
116
|
+
A transparent or translucent child — an `.outline` alert, a `.ghost` button, a
|
|
117
|
+
glass surface — shows `--surface-opaque` beneath it, the theme's opaque
|
|
118
|
+
neutral, rather than the frame color. On a differently painted band, give the
|
|
119
|
+
child its own surface so it matches the band.
|
|
121
120
|
|
|
122
121
|
## CSS hooks
|
|
123
122
|
|
|
@@ -125,7 +124,7 @@ wrapper around it.
|
|
|
125
124
|
- `--aura-width` — frame thickness.
|
|
126
125
|
- `--aura-blur` — near halo blur; the far halo derives its own blur and its
|
|
127
126
|
spread beyond the frame from this value, so one hook scales both.
|
|
128
|
-
- `--aura-intensity` — multiplies both halo
|
|
127
|
+
- `--aura-intensity` — multiplies both halo strengths, resting and breathing.
|
|
129
128
|
It does not touch the frame itself, which stays a crisp line at any setting.
|
|
130
129
|
- `--aura-radius` — child corner radius used to derive the outer radius.
|
|
131
130
|
- `--aura-duration` — one rotation of the accent, or one breath of the glow.
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
> Compact label for counts, status, or category tags, with shared intents and variants.
|
|
4
4
|
|
|
5
|
+
A badge is **information, never a control**: counts, status, categories. It is
|
|
6
|
+
non-interactive by nature — for a selectable option use
|
|
7
|
+
[`.choice-card.chip`](../forms/choice-cards.md#chips); for a value produced by
|
|
8
|
+
a widget that can be removed (combobox multi-value), the widget owns the
|
|
9
|
+
removable tag (see [combobox](../forms/combobox.md)).
|
|
10
|
+
|
|
5
11
|
- Supports intent colors.
|
|
6
12
|
- Soft by default. Use `.solid` for counters and notification badges, or `.outline` for quieter emphasis.
|
|
7
13
|
- Empty badges are solid status dots: visual variants such as `.soft` and `.outline` do not change their fill.
|
|
@@ -180,6 +186,10 @@ Override the existing hooks when the three-step scale does not fit the content.
|
|
|
180
186
|
Use `.badge soft` for tag visuals. Add a direct [`.close`](close.md) button only when the tag can actually be removed. There is no separate chip component.
|
|
181
187
|
For a compact action or filter, use `.btn.sm` instead; badges describe content
|
|
182
188
|
and must not be turned into toggle controls merely to obtain a compact shape.
|
|
189
|
+
Never put a `.close` inside a `.choice-card.chip` label: the label is already
|
|
190
|
+
an interactive target driving its checkbox/radio, and a nested button would mix
|
|
191
|
+
two interactive targets. A removable tag next to a selectable chip is a sibling
|
|
192
|
+
(a widget tag or a `.badge` + `.close`), never a child of the label.
|
|
183
193
|
|
|
184
194
|
Inside a badge the close follows the pill's size, keeps a 24px pointer target,
|
|
185
195
|
and tucks into the padding.
|
|
@@ -34,6 +34,11 @@ For a live conversation, put an appropriate live-region or `role="log"` on the
|
|
|
34
34
|
conversation container according to the product's announcement needs. Do not
|
|
35
35
|
put `role="log"` on individual messages.
|
|
36
36
|
|
|
37
|
+
For the message input itself, keep a native `<textarea>` inside a surface with
|
|
38
|
+
an actions row — see the [composer template](../../demo/templates/composer.html).
|
|
39
|
+
Formatting needs a hosted editor instead — see the
|
|
40
|
+
[rich-text template](../../demo/templates/rich-text.html).
|
|
41
|
+
|
|
37
42
|
## Class reference
|
|
38
43
|
|
|
39
44
|
| Class | Description |
|
|
@@ -22,7 +22,8 @@ layout.
|
|
|
22
22
|
|
|
23
23
|
Modals use the platform-native `<dialog class="modal">` element with `commandfor` and `command` buttons.
|
|
24
24
|
|
|
25
|
-
Use `command="show-modal"` to open a modal dialog
|
|
25
|
+
Use `command="show-modal"` to open a modal dialog, or `command="--show"` to open
|
|
26
|
+
it non-modally.
|
|
26
27
|
|
|
27
28
|
Add `aria-haspopup="dialog"` and `aria-controls="<id>"` to opening buttons so
|
|
28
29
|
their semantics are present before either native or framework JavaScript runs.
|
|
@@ -297,7 +298,9 @@ Available options:
|
|
|
297
298
|
light dismiss and closes the dialog (or rewrites `closedby="any"` to
|
|
298
299
|
`closedby="closerequest"` so the native dialog does not double-handle).
|
|
299
300
|
It never affects Escape or explicit close requests.
|
|
300
|
-
* `data-dialog-modal="false"`
|
|
301
|
+
* `data-dialog-modal="false"` makes direct `openDialog()` calls use `show()`
|
|
302
|
+
instead of `showModal()`. The `show-modal` and `--show` commands override
|
|
303
|
+
this default.
|
|
301
304
|
* `data-dialog-view-transition` enables a view transition that morphs the dialog to/from its trigger. Only active when the browser supports `document.startViewTransition` and the user allows motion.
|
|
302
305
|
* `closedby` keeps its native meaning: `"any"` closes on backdrop click and
|
|
303
306
|
Escape, `"closerequest"` closes on Escape only, `"none"` disables both. The
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
- Use `aria-current="page"` for the current page.
|
|
8
8
|
- Put `.sm` or `.lg` on `.pagination` to scale its controls.
|
|
9
9
|
- Numeric links may use `aria-label="Page N"` for clearer screen reader output.
|
|
10
|
-
- Prefer text labels for Previous and Next. Icon-only controls need an accessible name.
|
|
11
|
-
-
|
|
10
|
+
- Prefer text labels for Previous and Next; a chevron icon may accompany them. Icon-only controls need an accessible name.
|
|
11
|
+
- Compose page links with `.btn.ghost`; `aria-current="page"` fills the current one, so it is the only boxed item.
|
|
12
12
|
- Arrows, page numbers, and truncation ellipses share one geometric system: every item gets the same minimum footprint, and wider content (100, 1000, text labels) grows past it naturally.
|
|
13
13
|
- Truncated ranges are plain text with `.muted` and `aria-hidden="true"`, not `.btn` — they are decorative, not actionable.
|
|
14
14
|
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
```html demo
|
|
25
25
|
<nav aria-label="Pagination">
|
|
26
26
|
<ol class="pagination sm">
|
|
27
|
-
<li><a href="?page=2" class="btn
|
|
28
|
-
<li><a href="?page=1" class="btn
|
|
29
|
-
<li><a href="?page=2" class="btn
|
|
30
|
-
<li><a href="?page=3" class="btn" aria-current="page" aria-label="Page 3">3</a></li>
|
|
31
|
-
<li><a href="?page=4" class="btn
|
|
32
|
-
<li><a href="?page=5" class="btn
|
|
27
|
+
<li><a href="?page=2" class="btn ghost" rel="prev"><i class="ti ti-chevron-left" aria-hidden="true"></i> Previous</a></li>
|
|
28
|
+
<li><a href="?page=1" class="btn ghost" aria-label="Page 1">1</a></li>
|
|
29
|
+
<li><a href="?page=2" class="btn ghost" aria-label="Page 2">2</a></li>
|
|
30
|
+
<li><a href="?page=3" class="btn ghost" aria-current="page" aria-label="Page 3">3</a></li>
|
|
31
|
+
<li><a href="?page=4" class="btn ghost" aria-label="Page 4">4</a></li>
|
|
32
|
+
<li><a href="?page=5" class="btn ghost" rel="next">Next <i class="ti ti-chevron-right" aria-hidden="true"></i></a></li>
|
|
33
33
|
</ol>
|
|
34
34
|
</nav>
|
|
35
35
|
```
|
|
@@ -42,15 +42,15 @@ buttons — they are decorative, not actionable.
|
|
|
42
42
|
```html demo
|
|
43
43
|
<nav aria-label="Pagination">
|
|
44
44
|
<ol class="pagination sm">
|
|
45
|
-
<li><a href="?page=
|
|
46
|
-
<li><a href="?page=1" class="btn
|
|
45
|
+
<li><a href="?page=7" class="btn ghost" rel="prev"><i class="ti ti-chevron-left" aria-hidden="true"></i> Previous</a></li>
|
|
46
|
+
<li><a href="?page=1" class="btn ghost" aria-label="Page 1">1</a></li>
|
|
47
47
|
<li><span class="muted" aria-hidden="true">…</span></li>
|
|
48
|
-
<li><a href="?page=7" class="btn
|
|
49
|
-
<li><a href="?page=8" class="btn" aria-current="page" aria-label="Page 8">8</a></li>
|
|
50
|
-
<li><a href="?page=9" class="btn
|
|
48
|
+
<li><a href="?page=7" class="btn ghost" aria-label="Page 7">7</a></li>
|
|
49
|
+
<li><a href="?page=8" class="btn ghost" aria-current="page" aria-label="Page 8">8</a></li>
|
|
50
|
+
<li><a href="?page=9" class="btn ghost" aria-label="Page 9">9</a></li>
|
|
51
51
|
<li><span class="muted" aria-hidden="true">…</span></li>
|
|
52
|
-
<li><a href="?page=20" class="btn
|
|
53
|
-
<li><a href="?page=9" class="btn
|
|
52
|
+
<li><a href="?page=20" class="btn ghost" aria-label="Page 20">20</a></li>
|
|
53
|
+
<li><a href="?page=9" class="btn ghost" rel="next">Next <i class="ti ti-chevron-right" aria-hidden="true"></i></a></li>
|
|
54
54
|
</ol>
|
|
55
55
|
</nav>
|
|
56
56
|
```
|
|
@@ -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
|
|
@@ -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 —
|