@antadesign/anta 0.3.31 → 0.3.32
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/dist/box-types.d.ts +12 -1
- package/dist/bundle.css +1 -1
- package/dist/bundle.js +125 -60
- package/dist/components/Box.d.ts +18 -6
- package/dist/components/Box.js +4 -0
- package/dist/components/Expander.d.ts +17 -9
- package/dist/components/Expander.js +14 -1
- package/dist/elements/a-box.css +1 -1
- package/dist/elements/a-box.d.ts +2 -0
- package/dist/elements/a-box.js +74 -32
- package/dist/elements/a-dialog.js +2 -0
- package/dist/elements/a-expander.css +1 -1
- package/dist/elements/a-expander.js +118 -39
- package/dist/elements/a-menu.js +40 -3
- package/dist/general_types.d.ts +18 -6
- package/dist/index.d.ts +1 -1
- package/docs/changelog.md +34 -0
- package/docs/components/box.md +81 -24
- package/docs/components/expander.md +88 -47
- package/docs/components/select-faceted.md +25 -11
- package/docs/packages/plot.md +234 -19
- package/package.json +1 -1
package/docs/components/box.md
CHANGED
|
@@ -10,6 +10,10 @@ pixels; strings accept CSS lengths. `padding` and `margin` also accept CSS
|
|
|
10
10
|
shorthand, such as `padding="8px 16px"` or `margin="0 auto"`. Omission adds no
|
|
11
11
|
spacing styles. A bare `round` fully rounds the corners.
|
|
12
12
|
|
|
13
|
+
Box uses `position: relative` by default. Absolutely positioned descendants
|
|
14
|
+
use it as their containing block. Set `position: static` on the Box if they
|
|
15
|
+
should use another positioned ancestor.
|
|
16
|
+
|
|
13
17
|
```tsx
|
|
14
18
|
<Box round={8} padding={10}><span /></Box>
|
|
15
19
|
<Box display="flex" round={8} gap={6} padding={10}><span /></Box>
|
|
@@ -44,8 +48,10 @@ edges hide content while scrolling. `fade` enables both automatically.
|
|
|
44
48
|
|
|
45
49
|
### Fading a clipped edge
|
|
46
50
|
|
|
47
|
-
`fade` masks edges with hidden content and
|
|
48
|
-
them.
|
|
51
|
+
`fade` masks edges with hidden content and eases the mask as scrolling reveals
|
|
52
|
+
them. The mask is removed after the last edge fades out, preserving shadows and
|
|
53
|
+
focus rings at rest. `fadeSize` sets its depth. It measures on connection, even
|
|
54
|
+
off screen.
|
|
49
55
|
|
|
50
56
|
```tsx
|
|
51
57
|
const TAGS = ['frontend', 'design-system', 'a11y', 'performance']
|
|
@@ -64,8 +70,9 @@ const TAGS = ['frontend', 'design-system', 'a11y', 'performance']
|
|
|
64
70
|
.fade-demo a-tag { flex: 0 0 auto; }
|
|
65
71
|
```
|
|
66
72
|
|
|
67
|
-
The
|
|
68
|
-
focus
|
|
73
|
+
The fade affects the padding box. The mask remains through its exit transition,
|
|
74
|
+
then disappears so shadows and focus rings are visible at rest. The easing is
|
|
75
|
+
disabled when reduced motion is requested.
|
|
69
76
|
|
|
70
77
|
To style hidden edges without `fade`, use `observe="edges"` and the
|
|
71
78
|
`hidden-start-x`, `hidden-end-x`, `hidden-start-y`, or `hidden-end-y` CSS states.
|
|
@@ -91,9 +98,14 @@ const TAGS = ['frontend', 'design-system', 'a11y', 'performance']
|
|
|
91
98
|
## Measurements
|
|
92
99
|
|
|
93
100
|
By default, `onMeasureChange` reports one frame after observation starts, then when
|
|
94
|
-
the border-box `width` or `height` changes. `changed` contains
|
|
95
|
-
since the last event; `current` is the full snapshot. Reporting pauses
|
|
96
|
-
and resumes with a fresh snapshot when Box returns.
|
|
101
|
+
the border-box `width` or `height` changes. `changed` contains fields
|
|
102
|
+
changed since the last event; `current` is the full snapshot. Reporting pauses
|
|
103
|
+
off screen and resumes with a fresh snapshot when Box returns.
|
|
104
|
+
|
|
105
|
+
Pass `observeOffscreen` when a virtualized layout needs measurement events or
|
|
106
|
+
clipping states while the Box is outside the viewport. It keeps the selected
|
|
107
|
+
observers active. Use `throttle` to limit reads when offscreen content changes
|
|
108
|
+
often.
|
|
97
109
|
|
|
98
110
|
`observe` accepts one of the eight selections below, or a typed array combining
|
|
99
111
|
them. `observe={['size', 'edges']}` and `observe={['edges', 'size']}` select the
|
|
@@ -134,19 +146,35 @@ When no measurement is selected, `onMeasureChange` adds `size`;
|
|
|
134
146
|
Passing both handlers observes size and context without enabling scroll events.
|
|
135
147
|
Without handlers or `fade`, omitting `observe` keeps Box idle.
|
|
136
148
|
|
|
137
|
-
Set `throttle` to a minimum interval in milliseconds. The
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
`
|
|
143
|
-
CSS states remain current.
|
|
149
|
+
Set `throttle` to a minimum interval in milliseconds. The initial measurement
|
|
150
|
+
is immediate; later activity gets a trailing read with the latest values. Omit
|
|
151
|
+
it or pass `0` for frame-based updates. Negative or non-finite values use `0`.
|
|
152
|
+
The interval limits measurement reads, CSS state updates, and
|
|
153
|
+
`onMeasureChange` events; context events are not throttled. The
|
|
154
|
+
`box.measurement` getter still reads fresh values on demand.
|
|
144
155
|
|
|
145
156
|
`Tooltip truncatedOnly` reads Box's clipping on demand. It works without
|
|
146
157
|
`observe` or `onMeasureChange` and does not enable continuous observation.
|
|
147
158
|
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
Pass `includeRectsFor` to add matching descendants to each measurement snapshot.
|
|
160
|
+
`current.rects` contains their border boxes in document order, with `top`,
|
|
161
|
+
`left`, `right`, and `bottom` measured from the Box's top-left border edge, plus
|
|
162
|
+
each match's `width` and `height`.
|
|
163
|
+
|
|
164
|
+
The selector does not start observation or add event triggers. Box reads the
|
|
165
|
+
rects when an existing `observe` selection reports a change. `current.rects`
|
|
166
|
+
always contains the latest array; `changed.rects` appears only when that array
|
|
167
|
+
differs from the previous report. For a fresh read at any time, use
|
|
168
|
+
`box.measurement.rects`.
|
|
169
|
+
|
|
170
|
+
With `observe="scroll"` and no throttle, Box can query, measure, and compare
|
|
171
|
+
every matching descendant on each reported frame. Keep the selector focused or
|
|
172
|
+
set `throttle` when many descendants match. A larger interval also delays the
|
|
173
|
+
fade as the reader reaches an edge.
|
|
174
|
+
|
|
175
|
+
This readout observes `['size', 'overflow', 'edges', 'scroll']` with `throttle={100}`
|
|
176
|
+
and `fade`. It includes the first content row in `rects`. Resize or scroll the
|
|
177
|
+
Box to update the values and edge mask.
|
|
150
178
|
|
|
151
179
|
```tsx title="measurechange"
|
|
152
180
|
const [measurement, setMeasurement] = useState<BoxMeasurement | null>(null)
|
|
@@ -155,19 +183,25 @@ const [measurement, setMeasurement] = useState<BoxMeasurement | null>(null)
|
|
|
155
183
|
round={8}
|
|
156
184
|
className="measure-probe-box"
|
|
157
185
|
observe={['size', 'overflow', 'edges', 'scroll']}
|
|
186
|
+
fade
|
|
187
|
+
includeRectsFor=".measure-probe-target"
|
|
158
188
|
throttle={100}
|
|
159
189
|
onMeasureChange={(_, { current }) => setMeasurement(current)}
|
|
160
190
|
>
|
|
161
191
|
<Text size="small" priority="tertiary">Resize or scroll this Box.</Text>
|
|
162
|
-
<div className="measure-probe-wide">wide content, so both axes overflow</div>
|
|
192
|
+
<div className="measure-probe-wide measure-probe-target">wide content, so both axes overflow</div>
|
|
163
193
|
<div className="measure-probe-wide">and a second line, so the vertical axis does too</div>
|
|
164
194
|
<div className="measure-probe-wide">and a third</div>
|
|
165
195
|
</Box>
|
|
166
196
|
|
|
167
197
|
<div className="measure-probe-readout">
|
|
168
|
-
{Object.entries(measurement ?? {}).map(([field, value]) => (
|
|
169
|
-
<
|
|
170
|
-
|
|
198
|
+
{Object.entries(measurement ?? {}).map(([field, value]) => field === 'rects' && Array.isArray(value)
|
|
199
|
+
? <div key={field} className="measure-probe-rects">
|
|
200
|
+
<Tag size="small" label="rects" value={`${value.length} match${value.length === 1 ? '' : 'es'}`} />
|
|
201
|
+
<pre><code>{JSON.stringify(value, null, 2)}</code></pre>
|
|
202
|
+
</div>
|
|
203
|
+
: <Tag key={field} size="small" label={field} value={String(value)} />
|
|
204
|
+
)}
|
|
171
205
|
</div>
|
|
172
206
|
```
|
|
173
207
|
|
|
@@ -184,6 +218,8 @@ const [measurement, setMeasurement] = useState<BoxMeasurement | null>(null)
|
|
|
184
218
|
}
|
|
185
219
|
.measure-probe-wide { inline-size: 520px; padding-block: 6px; }
|
|
186
220
|
.measure-probe-readout { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
221
|
+
.measure-probe-rects { flex-basis: 100%; min-width: 0; }
|
|
222
|
+
.measure-probe-rects pre { margin: 4px 0 0; padding: 8px; border: 1px solid var(--border-4); border-radius: 4px; background: var(--bg-2); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
187
223
|
```
|
|
188
224
|
|
|
189
225
|
See the [measurement fields](#boxmeasurement). Overflow fields map to kebab-case
|
|
@@ -284,13 +320,15 @@ const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
|
284
320
|
| `fade?` | boolean | — | Fades out every edge that currently hides clipped content, and drops the fade from an edge once the reader scrolls to it. |
|
|
285
321
|
| `fadeSize?` | number \| string | 24 | Depth of the `fade` gradient. A `number` is pixels; a string is any CSS length. |
|
|
286
322
|
| `gap?` | number \| string | — | Gap between children, matching the CSS `gap` property. A `number` is pixels; a string is any CSS length or two-value gap (`'1rem'`, `'8px 16px'`). Applies while the Box is a flex or grid container. |
|
|
323
|
+
| `includeRectsFor?` | string | — | CSS selector for descendants whose border boxes are included in `measurement.rects` when Box measures. Coordinates are relative to this Box's top-left border edge; matches are in document order. Box reads and compares matching rects for each measurement event. With `observe="scroll"`, use `throttle` when the selector matches many descendants; it also delays CSS state updates after the initial read. |
|
|
287
324
|
| `margin?` | number \| string | — | Outer spacing, matching CSS `margin`. Numbers are pixels; strings accept CSS shorthand, `auto`, negative lengths, and custom properties. Omission adds no style. |
|
|
288
325
|
| `observe?` | 'width' \| 'height' \| 'size' \| 'context' \| 'overflow' \| 'edges' \| 'scroll' \| 'all' \| readonly BoxObservation[] | — | One selection or an array of selections, in any order. `'size'` watches width and height; `'context'` watches rendering context; `'overflow'` watches content dimensions and clipping. `'edges'` reports which edges hide content; `'scroll'` reports offsets, potentially every frame; `'all'` selects everything. Selections are independent: use `['size', 'edges']` to combine them. A measurement handler implies `'size'` when no measurement is selected; a context handler adds `'context'`. Size skips content and scroll observers; overflow adds content observation; hidden edges and scroll add scroll reads. Without handlers or `fade`, omission stays idle. |
|
|
326
|
+
| `observeOffscreen?` | boolean | — | Keeps measurement events and CSS clipping states current outside the viewport. Has no effect unless `observe` or `fade` selects measurement. |
|
|
289
327
|
| `onContextChange?` | (event, detail) => void | — | Fired after Box's browser and local rendering context changes. `detail` contains the changed fields and a full current snapshot. |
|
|
290
|
-
| `onMeasureChange?` | (event, detail) => void | — | Fired when a selected measurement field changes. `detail` contains
|
|
328
|
+
| `onMeasureChange?` | (event, detail) => void | — | Fired when a selected measurement field changes. `detail.changed` contains fields changed since the previous event; `detail.current` includes the full snapshot with matching rects. Rect changes alone do not trigger an event. |
|
|
291
329
|
| `padding?` | number \| string | — | Inner spacing, matching CSS `padding`. Numbers are pixels; strings accept CSS shorthand, percentages, and custom properties. Omission adds no style. |
|
|
292
330
|
| `round?` | boolean \| number \| string | — | Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit for square corners. |
|
|
293
|
-
| `throttle?` | number | 0 | Minimum interval between
|
|
331
|
+
| `throttle?` | number | 0 | Minimum interval between measurements, in milliseconds. The initial measurement runs immediately; a trailing read updates CSS states and delivers the latest event. Descendant rects are read only for emitted events. The `measurement` getter always reads fresh values. |
|
|
294
332
|
|
|
295
333
|
### BoxMeasurement
|
|
296
334
|
|
|
@@ -307,6 +345,7 @@ const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
|
307
345
|
| `hiddenStartY` | boolean | — | |
|
|
308
346
|
| `overflowX` | boolean | — | Content exceeds the padding box on this axis, regardless of CSS overflow. |
|
|
309
347
|
| `overflowY` | boolean | — | |
|
|
348
|
+
| `rects` | BoxRect[] | — | Matches for `includeRectsFor`, in document order. Empty when omitted. |
|
|
310
349
|
| `scrollableX` | boolean | — | The exceeded content can be scrolled by the reader on this axis. |
|
|
311
350
|
| `scrollableY` | boolean | — | |
|
|
312
351
|
| `scrollHeight` | number | — | |
|
|
@@ -315,6 +354,19 @@ const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
|
315
354
|
| `scrollWidth` | number | — | Full scrollable-content dimensions, matching `scrollWidth` / `scrollHeight`. |
|
|
316
355
|
| `width` | number | — | Border-box width and height in CSS pixels. |
|
|
317
356
|
|
|
357
|
+
### BoxRect
|
|
358
|
+
|
|
359
|
+
Coordinates and dimensions of a matched descendant's border box.
|
|
360
|
+
|
|
361
|
+
| Field | Type | Default | Description |
|
|
362
|
+
|------|------|---------|-------------|
|
|
363
|
+
| `bottom` | number | — | |
|
|
364
|
+
| `height` | number | — | |
|
|
365
|
+
| `left` | number | — | |
|
|
366
|
+
| `right` | number | — | |
|
|
367
|
+
| `top` | number | — | |
|
|
368
|
+
| `width` | number | — | |
|
|
369
|
+
|
|
318
370
|
### BoxContext
|
|
319
371
|
|
|
320
372
|
| Field | Type | Default | Description |
|
|
@@ -381,7 +433,10 @@ Use `<a-box>` without JSX. Events are non-bubbling `CustomEvent`s with the same
|
|
|
381
433
|
observation. Combine the eight selections with spaces, such as
|
|
382
434
|
`observe="size edges"` or `observe="size scroll"`, in any order. Repeated tokens
|
|
383
435
|
have no effect; unknown tokens are ignored. A bare `observe` means `"all"`.
|
|
384
|
-
`throttle="100"` limits measurement
|
|
436
|
+
`throttle="100"` limits measurement reads, CSS state updates, and measurement
|
|
437
|
+
events to a 100 ms interval.
|
|
438
|
+
Add `observe-offscreen` to keep measurement observation active outside the
|
|
439
|
+
viewport.
|
|
385
440
|
|
|
386
441
|
```html title="a-box"
|
|
387
442
|
<a-box display="grid" gap="8px" round="12px" observe="all"
|
|
@@ -392,7 +447,9 @@ have no effect; unknown tokens are ignored. A bare `observe` means `"all"`.
|
|
|
392
447
|
```
|
|
393
448
|
|
|
394
449
|
Import `@antadesign/anta/elements/a-box` to register the element. Listen for
|
|
395
|
-
`measurechange` and `contextchange` with `addEventListener`.
|
|
450
|
+
`measurechange` and `contextchange` with `addEventListener`. Set
|
|
451
|
+
`include-rects-for=".marker"` to add matching descendant rects to each
|
|
452
|
+
measurement without changing the observation triggers.
|
|
396
453
|
`box.measurement`, `box.context`, and `box.isTruncated` read values synchronously.
|
|
397
454
|
|
|
398
455
|
## Styling
|
|
@@ -94,6 +94,43 @@ it's a no-op there. Use it where the expander sits in a content column
|
|
|
94
94
|
with room to its left (a prose measure); in a full-width / edge-to-edge
|
|
95
95
|
container the chevron would overhang the edge.
|
|
96
96
|
|
|
97
|
+
## Indicator
|
|
98
|
+
|
|
99
|
+
`indicatorPlacement="end"` puts the disclosure mark after the header actions.
|
|
100
|
+
The mark stays inside the summary button, so selecting it toggles the section.
|
|
101
|
+
Actions remain separate controls. The default `start` placement keeps the
|
|
102
|
+
right-pointing chevron that turns down when open. At the end, it points down
|
|
103
|
+
when closed and up when open.
|
|
104
|
+
|
|
105
|
+
Choose `indicator="triangle"` for a filled arrow or `indicator="plus"` for a
|
|
106
|
+
plus that becomes a minus. The default is `chevron`. The built-in motion follows
|
|
107
|
+
the reduced-motion preference. An unrecognized string or number leaves the
|
|
108
|
+
indicator empty; omit `indicator` to keep the default chevron.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<Expander title="Download report" indicatorPlacement="end"
|
|
112
|
+
actions={<Button size="small" label="Download" />}>
|
|
113
|
+
…
|
|
114
|
+
</Expander>
|
|
115
|
+
<Expander title="Plus and minus" indicator="plus" indicatorPlacement="end">…</Expander>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Pass a decorative node as `indicator` to replace the built-in mark. One node
|
|
119
|
+
rotates with the section. To switch between two visuals, pass `closed` and
|
|
120
|
+
`open` nodes. Neither form adds a focus stop or an accessible name: the title
|
|
121
|
+
names the summary button, and its `aria-expanded` reports the state. Put
|
|
122
|
+
interactive controls in `actions`.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<Expander title="Custom mark" indicatorPlacement="end"
|
|
126
|
+
indicator={<Icon shape="chevron-down" />}>…</Expander>
|
|
127
|
+
<Expander title="Two marks" indicatorPlacement="end"
|
|
128
|
+
indicator={{ closed: <Icon shape="plus" />, open: <Icon shape="minus" /> }}>…</Expander>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
On a tertiary end-placement expander, `outdent` still aligns the title and body
|
|
132
|
+
with surrounding content. The indicator stays inside the end edge.
|
|
133
|
+
|
|
97
134
|
## Title and level
|
|
98
135
|
|
|
99
136
|
`title` takes a string or any node. For a string, `level` (1–6) applies
|
|
@@ -168,10 +205,10 @@ toggle:
|
|
|
168
205
|
|
|
169
206
|
## Actions
|
|
170
207
|
|
|
171
|
-
Pass `actions` to render extra controls — buttons, tags —
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
208
|
+
Pass `actions` to render extra controls — buttons, tags — after the title.
|
|
209
|
+
They sit **outside** the toggle trigger as its header siblings. Clicking them
|
|
210
|
+
never toggles the expander; they're separately focusable and announced as
|
|
211
|
+
their own controls.
|
|
175
212
|
(Don't put interactive elements *inside* a custom `title` instead —
|
|
176
213
|
nesting controls in a button is an accessibility anti-pattern.)
|
|
177
214
|
|
|
@@ -309,13 +346,15 @@ function Accordion() {
|
|
|
309
346
|
| Prop | Type | Default | Description |
|
|
310
347
|
|------|------|---------|-------------|
|
|
311
348
|
| `title` | ReactNode | — | Summary (header) content. A string is rendered with the `level` type scale; pass a node (e.g. a `<Title>`) for full control or real heading semantics in the document outline. |
|
|
312
|
-
| `actions?` | ReactNode | — | Header actions
|
|
349
|
+
| `actions?` | ReactNode | — | Header actions rendered outside the toggle trigger. With an end indicator, actions sit between the title and indicator. |
|
|
313
350
|
| `defaultOpen?` | boolean | — | Initial open state for the uncontrolled case. |
|
|
314
351
|
| `disabled?` | boolean | — | Disables the header: not clickable or focusable, hover affordance off, text dimmed. The open state freezes as-is — disabling an open expander keeps it open. `actions` stay live; disable them separately if needed. |
|
|
352
|
+
| `indicator?` | ReactNode | 'chevron' | Built-in disclosure mark, a decorative node, or separate closed/open visuals. Other primitive values leave the indicator empty. Custom nodes are passive; use `actions` for controls. |
|
|
353
|
+
| `indicatorPlacement?` | 'start' \| 'end' | 'start' | Place the disclosure mark before the title or after the actions. |
|
|
315
354
|
| `level?` | 1 \| 2 \| 3 \| 4 \| 5 \| 6 | 5 | Heading type scale applied to a string `title` (mirrors `<Title>`'s levels). Visual only — for outline semantics, pass a `<Title>` as the title. |
|
|
316
355
|
| `onStateChange?` | (event, detail) => void | — | Fired before the open state changes. `event` is the cancelable `statechange` — call `event.preventDefault()` to veto an *uncontrolled* toggle (e.g. confirm before closing). `detail.next` is the requested open state, `detail.prev` the current one (booleans). In controlled mode, apply `detail.next` to `open` to accept, or do nothing to reject. |
|
|
317
356
|
| `open?` | boolean | — | Controlled open state. When provided, the consumer owns open/close. Selecting the summary requests a change through `onStateChange`; omit `open` for uncontrolled use. |
|
|
318
|
-
| `outdent?` | boolean | — |
|
|
357
|
+
| `outdent?` | boolean | — | Align the title and body with surrounding content on a tertiary expander. With the default start indicator, the mark hangs in the gutter; an end indicator stays inside its edge. |
|
|
319
358
|
| `priority?` | 'primary' \| 'secondary' \| 'tertiary' | 'secondary' | Surface emphasis. `secondary` (the default) is a subtle fill; `primary` is a more pronounced card; `tertiary` is transparent (the bare disclosure). |
|
|
320
359
|
| `round?` | boolean \| number \| string | — | Round corners sized to half the folded (header) height: a pill when folded, and — when expanded — the same corner radius rather than a bigger stadium. The element measures the header, so it tracks custom header content. Pass a `number` (px) or CSS length string for a *fixed* radius that overrides the measurement (and skips it) in both states. |
|
|
321
360
|
| `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for a one-off custom tone. Named tones re-point the text and (on filled priorities) the surface to the matching palette; a custom color keeps its hue while lightness/chroma are pinned. `'neutral'` (the default) is the same as omitting it. |
|
|
@@ -324,7 +363,8 @@ function Accordion() {
|
|
|
324
363
|
|
|
325
364
|
Use the web component directly when you are not using React or Preact and a native control does not fit.
|
|
326
365
|
|
|
327
|
-
Slots hold the title
|
|
366
|
+
Slots hold the title, actions, and an optional custom indicator. The details
|
|
367
|
+
element contains the collapsible body.
|
|
328
368
|
|
|
329
369
|
```html
|
|
330
370
|
<a-expander default-state="open">
|
|
@@ -336,6 +376,29 @@ Slots hold the title and actions. The details element contains the collapsible b
|
|
|
336
376
|
</a-expander>
|
|
337
377
|
```
|
|
338
378
|
|
|
379
|
+
Use the `indicator` and `indicator-placement` attributes for built-in marks.
|
|
380
|
+
For custom artwork, assign one element to `slot="indicator"`. Assign two
|
|
381
|
+
elements with `data-when="closed"` and `data-when="open"` when the artwork
|
|
382
|
+
changes between states. Indicator content is decorative and cannot receive focus
|
|
383
|
+
or pointer events.
|
|
384
|
+
|
|
385
|
+
```html
|
|
386
|
+
<a-expander indicator="triangle" indicator-placement="end">…</a-expander>
|
|
387
|
+
|
|
388
|
+
<a-expander indicator-placement="end">
|
|
389
|
+
<a-expander-summary slot="title">Custom mark</a-expander-summary>
|
|
390
|
+
<a-icon slot="indicator" shape="chevron-down" aria-hidden="true" inert></a-icon>
|
|
391
|
+
<a-expander-details>Content</a-expander-details>
|
|
392
|
+
</a-expander>
|
|
393
|
+
|
|
394
|
+
<a-expander indicator-placement="end">
|
|
395
|
+
<a-expander-summary slot="title">Two marks</a-expander-summary>
|
|
396
|
+
<a-icon slot="indicator" data-when="closed" shape="plus" aria-hidden="true" inert></a-icon>
|
|
397
|
+
<a-icon slot="indicator" data-when="open" shape="minus" aria-hidden="true" inert></a-icon>
|
|
398
|
+
<a-expander-details>Content</a-expander-details>
|
|
399
|
+
</a-expander>
|
|
400
|
+
```
|
|
401
|
+
|
|
339
402
|
### Native HTML details
|
|
340
403
|
|
|
341
404
|
For a simple disclosure, add `data-anta` to native `<details>`. It keeps the
|
|
@@ -358,18 +421,18 @@ Reach for the props first: **`tone`** sets the color (any CSS color for a custom
|
|
|
358
421
|
tone — it derives the surface in oklch), **`priority`** the fill, **`level`** the
|
|
359
422
|
heading. Two knobs are worth keeping: **`--expander-tone-source`** (the color a
|
|
360
423
|
custom `tone` derives from — set it to drive the palette from your own variable) and
|
|
361
|
-
**`--expander-gutter`** (the shared
|
|
362
|
-
|
|
424
|
+
**`--expander-gutter`** (the shared title and body inset; it also positions the
|
|
425
|
+
start indicator). It is 24px at the start, 12px at the end, and `0` with tertiary
|
|
426
|
+
`outdent`.
|
|
363
427
|
|
|
364
428
|
```tsx
|
|
365
429
|
<Expander tone="#e0457b" title="Custom" />
|
|
366
430
|
```
|
|
367
431
|
|
|
368
432
|
For everything else, `<a-expander>` exposes shadow **parts** — `::part(summary)` (the
|
|
369
|
-
header button), `::part(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
`::part(summary)`.
|
|
433
|
+
header button), `::part(indicator)` (the decorative mark), `::part(content)` (the
|
|
434
|
+
body), and `::part(actions)` (the actions row) — and the light-DOM
|
|
435
|
+
`<a-expander-summary>` / `<a-expander-details>` children, which you style directly.
|
|
373
436
|
|
|
374
437
|
Each example keeps its CSS in a `<style>` **inside** its own preview, targeting a demo
|
|
375
438
|
class on that preview's expander (e.g. `.hide-chevron`) — so the rule you read is
|
|
@@ -377,25 +440,19 @@ literally in the preview's DOM, next to the element it styles, never a page-wide
|
|
|
377
440
|
stylesheet. The folded recipe under each preview is that same CSS; in your app, swap the
|
|
378
441
|
demo class for your own selector.
|
|
379
442
|
|
|
380
|
-
**Hide the
|
|
381
|
-
|
|
382
|
-
remove it — then pull `--expander-gutter` in (it drives the title *and* body inset
|
|
383
|
-
together) to tighten the now-empty gutter, and drop your own marker into the title:
|
|
443
|
+
**Hide the indicator.** A disclosure needs a visible affordance. If the title
|
|
444
|
+
provides one, hide the indicator part and tighten the unused gutter:
|
|
384
445
|
|
|
385
446
|
```css
|
|
386
|
-
a-expander.hide-chevron::part(
|
|
447
|
+
a-expander.hide-chevron::part(indicator) { display: none; }
|
|
387
448
|
a-expander.hide-chevron { --expander-gutter: 12px; } /* tighten the inset (default 24px) */
|
|
388
449
|
```
|
|
389
450
|
|
|
390
|
-
**
|
|
391
|
-
|
|
451
|
+
**Change the indicator.** Use the built-in `triangle` or `plus` preset, or
|
|
452
|
+
provide a custom node through `indicator` / `slot="indicator"`:
|
|
392
453
|
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
solid triangle — it keeps its position and the open-state rotate */
|
|
396
|
-
a-expander.swap-chevron::part(summary)::before {
|
|
397
|
-
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 5l11 7-11 7z'/%3E%3C/svg%3E");
|
|
398
|
-
}
|
|
454
|
+
```tsx
|
|
455
|
+
<Expander title="Triangle indicator" indicator="triangle" priority="tertiary">…</Expander>
|
|
399
456
|
```
|
|
400
457
|
|
|
401
458
|
**Tint the whole header** on hover / press by styling the summary part (the header
|
|
@@ -473,29 +530,13 @@ export function SaveButton() {
|
|
|
473
530
|
`}</style>
|
|
474
531
|
```
|
|
475
532
|
|
|
476
|
-
*
|
|
477
|
-
|
|
478
|
-
indicator only reflects state, rotating via `:state(open)`. With the `<Expander>` JSX
|
|
479
|
-
wrapper, pass `actions={<Icon shape="chevron-down" />}` (the preview below uses the
|
|
480
|
-
equivalent element composition because these docs render statically):
|
|
533
|
+
*Style the indicator by state* with `:state(open)` and `::part(indicator)`. The
|
|
534
|
+
example changes its color without changing the built-in glyph or motion:
|
|
481
535
|
|
|
482
536
|
```tsx
|
|
483
|
-
|
|
484
|
-
chevron in the actions slot; the whole header toggles, the chevron flips */}
|
|
485
|
-
<Expander actions={<Icon shape="chevron-down" />} className="indicator" defaultOpen>
|
|
486
|
-
Build log
|
|
487
|
-
</Expander>
|
|
488
|
-
|
|
489
|
-
{/* the JSX wrapper boxes the icon in a layout-neutral span, so target the
|
|
490
|
-
[slot="actions"] element */}
|
|
537
|
+
<Expander title="Build log" indicatorPlacement="end" className="indicator" defaultOpen>…</Expander>
|
|
491
538
|
<style>{`
|
|
492
|
-
a-expander.indicator { --
|
|
493
|
-
a-expander.indicator::part(
|
|
494
|
-
a-expander.indicator [slot="actions"] {
|
|
495
|
-
pointer-events: none;
|
|
496
|
-
margin-inline-end: 6px; /* a little more right inset */
|
|
497
|
-
transition: transform 150ms ease;
|
|
498
|
-
}
|
|
499
|
-
a-expander.indicator:state(open) [slot="actions"] { transform: rotate(180deg); } /* points down → up */
|
|
539
|
+
a-expander.indicator::part(indicator) { color: var(--text-4); }
|
|
540
|
+
a-expander.indicator:state(open)::part(indicator) { color: var(--text-1); }
|
|
500
541
|
`}</style>
|
|
501
542
|
```
|
|
@@ -28,12 +28,21 @@ is limited. `offset` sets the gap in pixels between the trigger and the menu:
|
|
|
28
28
|
<SelectFaceted placement="top-end" offset={8} facets={FACETS} />
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Open **
|
|
31
|
+
Open **Recency** in the first demo, select **Custom range**, then pick **From**
|
|
32
|
+
and **To** from the fields side by side. Each field closes its own calendar after
|
|
33
|
+
a pick while the Recency editor stays open for the second date.
|
|
34
|
+
|
|
35
|
+
Open **Metadata** to choose key–value tags such as
|
|
32
36
|
**env production** or **region us-east**. Its `renderOption` callback reads
|
|
33
37
|
custom option fields and renders a `Tag` with `label` and `value`. Search
|
|
34
38
|
for **production** to see the same tag in global results.
|
|
35
39
|
|
|
36
40
|
```tsx
|
|
41
|
+
const recencySummary = (v) =>
|
|
42
|
+
'preset' in v
|
|
43
|
+
? { today: 'Today', yesterday: 'Yesterday', last14: 'Last 14 days', last30: 'Last 30 days' }[v.preset]
|
|
44
|
+
: v.from && v.to ? `${v.from} → ${v.to}` : 'Custom range'
|
|
45
|
+
|
|
37
46
|
const FACETS = [
|
|
38
47
|
{ key: 'assignee', label: 'Assignee', kind: 'multiple', filter: true, options: people },
|
|
39
48
|
{ key: 'owner', label: 'Owner', kind: 'single', filter: true, options: people },
|
|
@@ -56,6 +65,7 @@ const FACETS = [
|
|
|
56
65
|
},
|
|
57
66
|
{ key: 'title', label: 'Title contains', kind: 'text' },
|
|
58
67
|
{ key: 'duration', label: 'Min duration', kind: 'custom', summary: (v) => `≥ ${v.min}s`, render: /* … */ },
|
|
68
|
+
{ key: 'recency', label: 'Recency', kind: 'custom', summary: recencySummary, render: /* radio presets and two InputDate fields */ },
|
|
59
69
|
]
|
|
60
70
|
|
|
61
71
|
const isSet = (v) => !(v == null || v === '' || (Array.isArray(v) && v.length === 0))
|
|
@@ -83,8 +93,7 @@ function Demo() {
|
|
|
83
93
|
|
|
84
94
|
{active.length > 0 && (
|
|
85
95
|
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
|
|
86
|
-
{/* Show
|
|
87
|
-
the control's `leading` prefix. */}
|
|
96
|
+
{/* Show an editable control or summary for each active facet. */}
|
|
88
97
|
{active.map((facet) => {
|
|
89
98
|
// Options facets → an editable Select (single shows the trailing check;
|
|
90
99
|
// both are filterable + have a clearable footer).
|
|
@@ -106,7 +115,12 @@ function Demo() {
|
|
|
106
115
|
/>
|
|
107
116
|
)
|
|
108
117
|
|
|
109
|
-
|
|
118
|
+
if (facet.key === 'recency') {
|
|
119
|
+
const recency = value.recency
|
|
120
|
+
return <Tag key={facet.key} label="Recency" value={recencySummary(recency)} />
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Free-form text / duration → an editable Input.
|
|
110
124
|
const val = facet.kind === 'text' ? value[facet.key] : value[facet.key]?.min
|
|
111
125
|
const write = (raw) =>
|
|
112
126
|
setFacet(facet.key, facet.kind === 'text' ? raw || undefined : raw ? { min: raw } : undefined)
|
|
@@ -144,9 +158,9 @@ function Demo() {
|
|
|
144
158
|
}
|
|
145
159
|
```
|
|
146
160
|
|
|
147
|
-
The trigger opens the facet menu. The example
|
|
148
|
-
[Select](./select.md)
|
|
149
|
-
|
|
161
|
+
The trigger opens the facet menu. The example shows active values beside it.
|
|
162
|
+
Option facets use an editable [Select](./select.md) with the facet label in its
|
|
163
|
+
`leading` prefix. Recency uses a summary tag and is edited in the facet menu.
|
|
150
164
|
|
|
151
165
|
### Custom option content
|
|
152
166
|
|
|
@@ -300,7 +314,7 @@ A custom facet stores a `Date` directly. Unlike the value of a `single` or
|
|
|
300
314
|
#### A recency filter: presets plus a range
|
|
301
315
|
|
|
302
316
|
This editor combines a `RadioGroup` of presets with a **Custom range** option
|
|
303
|
-
that reveals two `InputDate` fields. Its value is either `{ preset }` or
|
|
317
|
+
that reveals two `InputDate` fields side by side. Its value is either `{ preset }` or
|
|
304
318
|
`{ from, to }`. The `resolveRange` helper converts either shape into the date
|
|
305
319
|
range used to filter the result set. Each InputDate calendar opens above the
|
|
306
320
|
facet menu without closing it.
|
|
@@ -332,7 +346,7 @@ const recency: SelectFacetCustom<Recency> = {
|
|
|
332
346
|
const mode = value == null ? '' : 'preset' in value ? value.preset : 'custom'
|
|
333
347
|
const range = value && 'from' in value ? value : { from: '', to: '' }
|
|
334
348
|
return (
|
|
335
|
-
<div style={{ display: 'grid', gap: 8 }}>
|
|
349
|
+
<div style={{ display: 'grid', gap: 8, minWidth: 360 }}>
|
|
336
350
|
<RadioGroup
|
|
337
351
|
size="small"
|
|
338
352
|
options={PRESETS} // Today / Yesterday / Last 14 / Last 30 / Custom range
|
|
@@ -342,12 +356,12 @@ const recency: SelectFacetCustom<Recency> = {
|
|
|
342
356
|
onStateChange={(_e, { next }) => onChange(next === 'custom' ? range : { preset: next })}
|
|
343
357
|
/>
|
|
344
358
|
{mode === 'custom' && (
|
|
345
|
-
|
|
359
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 8 }}>
|
|
346
360
|
<InputDate size="small" label="From" value={range.from}
|
|
347
361
|
onValueChange={(from) => onChange({ from, to: range.to })} />
|
|
348
362
|
<InputDate size="small" label="To" value={range.to} min={range.from || undefined}
|
|
349
363
|
onValueChange={(to) => onChange({ from: range.from, to })} />
|
|
350
|
-
|
|
364
|
+
</div>
|
|
351
365
|
)}
|
|
352
366
|
</div>
|
|
353
367
|
)
|