@antadesign/anta 0.3.31 → 0.3.33
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/avatar-core.d.ts +7 -2
- package/dist/box-types.d.ts +12 -1
- package/dist/bundle.css +1 -1
- package/dist/bundle.js +148 -73
- 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/components/Input.d.ts +9 -3
- package/dist/components/Input.js +2 -0
- package/dist/components/Slider.d.ts +22 -10
- package/dist/components/Slider.js +9 -5
- 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-button.js +2 -4
- 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-input-time.js +1 -2
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-input.js +11 -8
- package/dist/elements/a-menu.d.ts +1 -1
- package/dist/elements/a-menu.js +57 -7
- package/dist/elements/a-slider.d.ts +7 -5
- package/dist/elements/a-slider.js +256 -59
- package/dist/general_types.d.ts +27 -10
- package/dist/index.d.ts +2 -2
- package/dist/theme-antune.css +1 -1
- package/docs/changelog.md +59 -0
- package/docs/components/box.md +81 -24
- package/docs/components/button.md +9 -6
- package/docs/components/expander.md +88 -47
- package/docs/components/input.md +29 -9
- package/docs/components/panel.md +3 -2
- package/docs/components/select-faceted.md +40 -26
- package/docs/components/slider.md +139 -4
- package/docs/packages/plot.md +392 -38
- package/package.json +4 -2
|
@@ -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
|
```
|
package/docs/components/input.md
CHANGED
|
@@ -24,6 +24,22 @@ Anta's text scale, so a field lines up with same-size `Text` / `Button`.
|
|
|
24
24
|
| `medium` | 28px | 15 / 20 | 16px |
|
|
25
25
|
| `large` | 32px | 17 / 22 | 18px |
|
|
26
26
|
|
|
27
|
+
## Priority
|
|
28
|
+
|
|
29
|
+
`priority` changes the field surface without changing its size or behavior.
|
|
30
|
+
`primary` (the default) has a fill and border. `secondary` has a transparent
|
|
31
|
+
background and visible border. `tertiary` keeps both transparent at rest, then
|
|
32
|
+
uses secondary's border on hover or focus. Use it where the label or surrounding
|
|
33
|
+
content makes the field easy to find. All three show the same focus ring.
|
|
34
|
+
On an enabled field, a non-neutral `status` keeps its tinted fill and border
|
|
35
|
+
visible at rest.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<Input label="Primary" placeholder="Filled field" autoComplete="off" />
|
|
39
|
+
<Input priority="secondary" label="Secondary" placeholder="Outlined field" autoComplete="off" />
|
|
40
|
+
<Input priority="tertiary" label="Tertiary" placeholder="Field on hover or focus" autoComplete="off" />
|
|
41
|
+
```
|
|
42
|
+
|
|
27
43
|
## Label and hint
|
|
28
44
|
|
|
29
45
|
```tsx
|
|
@@ -470,6 +486,7 @@ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
|
|
|
470
486
|
| `onValueChange?` | (event, attrs) => void | — | Unified value-change handler — the easy path for state. Fires on `input` *and* `change` (and on clear), with the native `event` plus a convenience `attrs` snapshot (`value`, `name`, `empty`, `valid`, `validationMessage`) so you can do `setForm(s => ({ ...s, [attrs.name]: attrs.value }))` without digging into the event. Use `event.type` to tell a live edit (`input`) from a commit (`change`); read `id` / `type` / `className` off `event.target`. |
|
|
471
487
|
| `pattern?` | string | — | Validation pattern (single-line). |
|
|
472
488
|
| `placeholder?` | string | — | Placeholder shown when empty. |
|
|
489
|
+
| `priority?` | 'primary' \| 'secondary' \| 'tertiary' | primary | Field emphasis. `primary` has a fill and border, `secondary` keeps the border without a fill, and `tertiary` makes the border transparent until hover or focus. Status borders remain visible at rest. |
|
|
473
490
|
| `readOnly?` | boolean | — | Make the field read-only. |
|
|
474
491
|
| `required?` | boolean | — | Mark the field required (drives native validity). |
|
|
475
492
|
| `role?` | string | — | ARIA `role` for the field — e.g. `combobox` when the input drives a suggestion `listbox` (see `InputAutocomplete`). The custom element delegates it, together with standard `aria-*` props, to the focused native shadow control. Left unset by default. |
|
|
@@ -480,7 +497,7 @@ wraps to fewer columns as it narrows. Resize the preview to see it reflow.
|
|
|
480
497
|
| `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | — | Validation / feedback tone — colors the border + `hint` and prefixes a glyph. Only `critical` marks the field invalid (`aria-invalid`, blocks form submission, `:state(invalid)`); `success` / `warning` / `info` / `brand` are advisory and stay valid. Omit (or `neutral`) for a plain field. |
|
|
481
498
|
| `statusIcon?` | (string & {}) \| false \| IconShape | — | Glyph shown before the `hint` when `status` is set. Each status has a default (critical → `warning-diamond`, warning → `warning-triangle`, success → `circle-check`, info → `info`, brand → `circle-small-solid`); pass a shape to override, or `false` to drop it. `neutral` has no default glyph. |
|
|
482
499
|
| `step?` | number \| string | — | |
|
|
483
|
-
| `tone?` | string | — | Custom accent color — any literal CSS color tints the
|
|
500
|
+
| `tone?` | string | — | Custom accent color — any literal CSS color tints the border when shown (focus ring stays the global `--focus-ring`). For consistency with the other controls' custom-tone knob; a `status` still overrides for validation. |
|
|
484
501
|
| `trailing?` | ReactNode | — | Content pinned to the end of the field (e.g. icons, buttons), after the clear button when `clearable`. |
|
|
485
502
|
| `truncate?` | boolean | true | Ellipsize an overflowing single-line value. Read-only inputs already do this; pass `false` when an editable field should show the full value. |
|
|
486
503
|
| `type?` | 'text' \| 'search' \| 'email' \| 'password' \| 'tel' \| 'url' \| 'number' | text | Single-line input type. Ignored when `multiline`. `search` is a **wrapper-only** shorthand: it defaults a leading search icon and a clear button (both overridable — pass your own `leading`, or `clearable={false}`) and sets `inputmode="search"`, but the DOM input stays `type="text"`. The native `search` type never reaches the element, so the browser's own clear/search affordances never appear — Anta owns that chrome. |
|
|
@@ -534,9 +551,10 @@ their `size` attribute, so Anta does not reuse it. `round` and a custom-color
|
|
|
534
551
|
|
|
535
552
|
## Styling
|
|
536
553
|
|
|
537
|
-
Reach for the props first: **`
|
|
538
|
-
**`
|
|
539
|
-
|
|
554
|
+
Reach for the props first: **`priority`** sets the field surface,
|
|
555
|
+
**`status`** a validation tone (border + message), **`tone`** a custom accent
|
|
556
|
+
color for the border (any CSS color), and **`size`** the dimensions. The focus
|
|
557
|
+
ring is the global [`--focus-ring`](../colors.md#focus-ring).
|
|
540
558
|
|
|
541
559
|
```tsx
|
|
542
560
|
<Input tone="#e0457b" label="Custom accent" autoComplete="off" />
|
|
@@ -555,11 +573,13 @@ a-input::part(input) { font-variant-numeric: tabular-nums; }
|
|
|
555
573
|
a-input::part(label) { text-transform: uppercase; }
|
|
556
574
|
```
|
|
557
575
|
|
|
558
|
-
The border is drawn as an **inset `box-shadow`**,
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
576
|
+
The border is drawn as an **inset `box-shadow`**, so its width (`0.5px` at rest,
|
|
577
|
+
including tertiary's transparent border, and `1px` on hover, focus, or
|
|
578
|
+
non-neutral status)
|
|
579
|
+
never changes the box size. The field height stays aligned with `Button`.
|
|
580
|
+
Drop the leading `inset` to draw the border as an outset ring instead. You can
|
|
581
|
+
also re-point an `--input-*` color token on one instance
|
|
582
|
+
(`style={{ '--input-border': 'var(--border-1)' }}`)
|
|
563
583
|
or a wrapping selector; the resolver lives in `@layer anta`, so any un-layered rule
|
|
564
584
|
of yours wins.
|
|
565
585
|
|
package/docs/components/panel.md
CHANGED
|
@@ -92,8 +92,9 @@ through its controls. Restoring releases the trap and returns focus to the
|
|
|
92
92
|
previous external control, when available.
|
|
93
93
|
|
|
94
94
|
Provide a keyboard-accessible **Restore** control: Escape and outside clicks
|
|
95
|
-
do not restore the panel. Panel adds no dialog role
|
|
96
|
-
|
|
95
|
+
do not restore the panel. Panel adds no dialog role or background inertness.
|
|
96
|
+
Maximizing Panel does not lock page scrolling; the page's viewport scrollbar
|
|
97
|
+
may remain visible and usable. Use [Dialog](./dialog.md) for modal interactions.
|
|
97
98
|
|
|
98
99
|
Panel fills the browser viewport, without invoking the Fullscreen API.
|
|
99
100
|
Browsers without Popover API support keep it in normal layout.
|
|
@@ -28,12 +28,24 @@ 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. Both fields have
|
|
34
|
+
a clear button. Open **Min duration** to choose a comparison, then enter seconds.
|
|
35
|
+
The comparison alone does not apply the facet or add a result chip.
|
|
36
|
+
|
|
37
|
+
Open **Metadata** to choose key–value tags such as
|
|
32
38
|
**env production** or **region us-east**. Its `renderOption` callback reads
|
|
33
39
|
custom option fields and renders a `Tag` with `label` and `value`. Search
|
|
34
40
|
for **production** to see the same tag in global results.
|
|
35
41
|
|
|
36
42
|
```tsx
|
|
43
|
+
const recencySummary = (v) =>
|
|
44
|
+
'preset' in v
|
|
45
|
+
? { today: 'Today', yesterday: 'Yesterday', last14: 'Last 14 days', last30: 'Last 30 days' }[v.preset]
|
|
46
|
+
: v.from && v.to ? `${v.from} → ${v.to}` : 'Custom range'
|
|
47
|
+
const sign = (comparison) => ({ gt: '>', gte: '≥', eq: '=', lte: '≤', lt: '<' })[comparison] ?? '≥'
|
|
48
|
+
|
|
37
49
|
const FACETS = [
|
|
38
50
|
{ key: 'assignee', label: 'Assignee', kind: 'multiple', filter: true, options: people },
|
|
39
51
|
{ key: 'owner', label: 'Owner', kind: 'single', filter: true, options: people },
|
|
@@ -55,7 +67,8 @@ const FACETS = [
|
|
|
55
67
|
),
|
|
56
68
|
},
|
|
57
69
|
{ key: 'title', label: 'Title contains', kind: 'text' },
|
|
58
|
-
{ key: 'duration', label: 'Min duration', kind: 'custom', summary: (v) =>
|
|
70
|
+
{ key: 'duration', label: 'Min duration', kind: 'custom', summary: (v) => `${sign(v.comparison)} ${v.min}s`, render: /* DurationEditor with draft comparison and Input value */ },
|
|
71
|
+
{ key: 'recency', label: 'Recency', kind: 'custom', summary: recencySummary, render: /* radio presets and two InputDate fields */ },
|
|
59
72
|
]
|
|
60
73
|
|
|
61
74
|
const isSet = (v) => !(v == null || v === '' || (Array.isArray(v) && v.length === 0))
|
|
@@ -83,8 +96,7 @@ function Demo() {
|
|
|
83
96
|
|
|
84
97
|
{active.length > 0 && (
|
|
85
98
|
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
|
|
86
|
-
{/* Show
|
|
87
|
-
the control's `leading` prefix. */}
|
|
99
|
+
{/* Show an editable control or summary for each active facet. */}
|
|
88
100
|
{active.map((facet) => {
|
|
89
101
|
// Options facets → an editable Select (single shows the trailing check;
|
|
90
102
|
// both are filterable + have a clearable footer).
|
|
@@ -106,7 +118,12 @@ function Demo() {
|
|
|
106
118
|
/>
|
|
107
119
|
)
|
|
108
120
|
|
|
109
|
-
|
|
121
|
+
if (facet.key === 'recency') {
|
|
122
|
+
const recency = value.recency
|
|
123
|
+
return <Tag key={facet.key} label="Recency" value={recencySummary(recency)} />
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Free-form text / duration → an editable Input.
|
|
110
127
|
const val = facet.kind === 'text' ? value[facet.key] : value[facet.key]?.min
|
|
111
128
|
const write = (raw) =>
|
|
112
129
|
setFacet(facet.key, facet.kind === 'text' ? raw || undefined : raw ? { min: raw } : undefined)
|
|
@@ -144,9 +161,9 @@ function Demo() {
|
|
|
144
161
|
}
|
|
145
162
|
```
|
|
146
163
|
|
|
147
|
-
The trigger opens the facet menu. The example
|
|
148
|
-
[Select](./select.md)
|
|
149
|
-
|
|
164
|
+
The trigger opens the facet menu. The example shows active values beside it.
|
|
165
|
+
Option facets use an editable [Select](./select.md) with the facet label in its
|
|
166
|
+
`leading` prefix. Recency uses a summary tag and is edited in the facet menu.
|
|
150
167
|
|
|
151
168
|
### Custom option content
|
|
152
169
|
|
|
@@ -299,11 +316,11 @@ A custom facet stores a `Date` directly. Unlike the value of a `single` or
|
|
|
299
316
|
|
|
300
317
|
#### A recency filter: presets plus a range
|
|
301
318
|
|
|
302
|
-
This editor
|
|
303
|
-
|
|
304
|
-
`{ from, to }`. The `resolveRange` helper
|
|
305
|
-
range used to filter the result set. Each
|
|
306
|
-
facet menu without closing it.
|
|
319
|
+
This editor uses `MenuItem` rows with radio indicators for the presets.
|
|
320
|
+
Selecting **Custom range** reveals two `InputDate` fields side by side. The facet
|
|
321
|
+
value is either `{ preset }` or `{ from, to }`. The `resolveRange` helper
|
|
322
|
+
converts either shape into the date range used to filter the result set. Each
|
|
323
|
+
`InputDate` calendar opens above the facet menu without closing it.
|
|
307
324
|
|
|
308
325
|
```tsx
|
|
309
326
|
type Recency = { preset: 'today' | 'yesterday' | 'last14' | 'last30' } | { from: string; to: string }
|
|
@@ -332,22 +349,19 @@ const recency: SelectFacetCustom<Recency> = {
|
|
|
332
349
|
const mode = value == null ? '' : 'preset' in value ? value.preset : 'custom'
|
|
333
350
|
const range = value && 'from' in value ? value : { from: '', to: '' }
|
|
334
351
|
return (
|
|
335
|
-
<div style={{
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// only after `value` changes, so it can't drive a controlled RadioGroup).
|
|
342
|
-
onStateChange={(_e, { next }) => onChange(next === 'custom' ? range : { preset: next })}
|
|
343
|
-
/>
|
|
352
|
+
<div style={{ minWidth: 360 }}>
|
|
353
|
+
{PRESETS.map((preset) => (
|
|
354
|
+
<MenuItem key={preset.value} label={preset.label}
|
|
355
|
+
selectionIndicator="radio" selected={mode === preset.value} data-menu-open
|
|
356
|
+
onSelect={() => onChange(preset.value === 'custom' ? range : { preset: preset.value })} />
|
|
357
|
+
))}
|
|
344
358
|
{mode === 'custom' && (
|
|
345
|
-
|
|
346
|
-
<InputDate size="small" label="From" value={range.from}
|
|
359
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 8, padding: 8 }}>
|
|
360
|
+
<InputDate size="small" label="From" clearable value={range.from}
|
|
347
361
|
onValueChange={(from) => onChange({ from, to: range.to })} />
|
|
348
|
-
<InputDate size="small" label="To" value={range.to} min={range.from || undefined}
|
|
362
|
+
<InputDate size="small" label="To" clearable 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
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Slider
|
|
2
2
|
|
|
3
|
-
**`Slider`** selects one
|
|
3
|
+
**`Slider`** selects one number or an interval between two numbers. Pass a number
|
|
4
|
+
for one thumb or a two-number tuple for two thumbs. Its label sits above the
|
|
4
5
|
rail, and the value appears at the right side of the label row by default. Use
|
|
5
6
|
`value` with `onValueChange` to control it, or `defaultValue` for uncontrolled use.
|
|
6
7
|
|
|
@@ -23,6 +24,55 @@ Arrow keys change by `step`. Home and End set the minimum and maximum. PageUp an
|
|
|
23
24
|
<Slider label="Jump on press" defaultValue={35} trackClick="jump" />
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
## Range values
|
|
28
|
+
|
|
29
|
+
Pass two numbers to `value` or `defaultValue` to show two thumbs. Drag either
|
|
30
|
+
thumb across the other to move past it. The thumb you grabbed keeps moving, and
|
|
31
|
+
the value stays ordered from low to high. For example, dragging the thumb at 20
|
|
32
|
+
past 70 to 85 changes `[20, 70]` to `[70, 85]`. Both thumbs can share a value.
|
|
33
|
+
Each thumb has its own keyboard focus. Their tab order stays fixed when they
|
|
34
|
+
cross.
|
|
35
|
+
|
|
36
|
+
`onValueChange` receives a number for a single-value slider and an ordered
|
|
37
|
+
`[number, number]` tuple for a range slider. `onValueCommit` uses the same shape.
|
|
38
|
+
Without `defaultValue`, a form reset selects the full `[min, max]` range.
|
|
39
|
+
|
|
40
|
+
```tsx title="Controlled range"
|
|
41
|
+
import { useState } from 'preact/hooks'
|
|
42
|
+
import { Slider } from '@antadesign/anta'
|
|
43
|
+
|
|
44
|
+
function SliderRangeDemo() {
|
|
45
|
+
const [value, setValue] = useState<[number, number]>([20, 70])
|
|
46
|
+
const [lastChange, setLastChange] = useState<[number, number] | null>(null)
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<Slider
|
|
51
|
+
label="Selected interval"
|
|
52
|
+
value={value}
|
|
53
|
+
onValueChange={(_, { value: next }) => {
|
|
54
|
+
setValue(next)
|
|
55
|
+
setLastChange(next)
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
<p className="range-demo-output"><code>onValueChange</code>: <code>{lastChange ? JSON.stringify(lastChange) : '—'}</code></p>
|
|
59
|
+
</>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Inverted fill
|
|
65
|
+
|
|
66
|
+
Set `inverted` to fill the other part of the rail. For one thumb, the fill runs
|
|
67
|
+
from the value to the maximum. For two thumbs, the fill covers the rail outside
|
|
68
|
+
the selected interval. The minimum and maximum stay at their usual rail ends;
|
|
69
|
+
thumb movement and keyboard controls stay the same.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<Slider label="Inverted single value" defaultValue={40} inverted />
|
|
73
|
+
<Slider label="Inverted range" defaultValue={[20, 70]} inverted />
|
|
74
|
+
```
|
|
75
|
+
|
|
26
76
|
## Value placement
|
|
27
77
|
|
|
28
78
|
`valueDisplay="end"` is the default. Use `inline` for `Label: value`, `thumb` to keep a value above the thumb, or `none` to omit it. `valuePrefix` and `valueSuffix` add text to every live value display.
|
|
@@ -66,13 +116,14 @@ Arrow keys change by `step`. Home and End set the minimum and maximum. PageUp an
|
|
|
66
116
|
|
|
67
117
|
| Prop | Type | Default | Description |
|
|
68
118
|
|------|------|---------|-------------|
|
|
69
|
-
| `defaultValue?` | number | 0 | Initial uncontrolled value. |
|
|
119
|
+
| `defaultValue?` | number \| SliderRangeValue | 0 | Initial uncontrolled value. |
|
|
70
120
|
| `disabled?` | boolean | — | Disables pointer and keyboard interaction. |
|
|
121
|
+
| `inverted?` | boolean | — | Fill the rail outside the selected value or range. |
|
|
71
122
|
| `label?` | ReactNode | — | Visible field label, shown above the rail. A string supplies the slider's accessible name; give a rich label an explicit `aria-label`. |
|
|
72
123
|
| `markers?` | SliderMarker[] | — | Compact text labels positioned below the rail. They do not add dots or ticks to the rail. |
|
|
73
124
|
| `max?` | number | 100 | Highest permitted value. |
|
|
74
125
|
| `min?` | number | 0 | Lowest permitted value. |
|
|
75
|
-
| `name?` | string | — | Form field name.
|
|
126
|
+
| `name?` | string | — | Form field name. A range submits two values under this name. |
|
|
76
127
|
| `onValueChange?` | (event, attrs) => void | — | Fires on every keyboard or pointer value change. |
|
|
77
128
|
| `onValueCommit?` | (event, attrs) => void | — | Fires after a drag ends and after each keyboard value change. |
|
|
78
129
|
| `round?` | boolean \| number \| string | — | Fully round the rail and thumb. Pass a number (px) or CSS length string for a shared custom radius. |
|
|
@@ -84,7 +135,7 @@ Arrow keys change by `step`. Home and End set the minimum and maximum. PageUp an
|
|
|
84
135
|
| `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Color of the filled rail. Pass a named tone or a literal CSS color for a one-off custom tone. The unfilled rail stays neutral. |
|
|
85
136
|
| `trackClick?` | 'drag-only' \| 'jump' | 'drag-only' | Controls what happens when the rail is pressed. `drag-only` starts dragging from the current value. `jump` first moves to the pressed position. |
|
|
86
137
|
| `trackSize?` | number \| string | 2 | Thickness of both rail segments. Numbers use pixels; strings are CSS lengths. Keep it no larger than the thumb diameter. |
|
|
87
|
-
| `value?` | number | — | Controlled value. Update it from `onValueChange`. |
|
|
138
|
+
| `value?` | number \| SliderRangeValue | — | Controlled value. A pair of numbers selects a range. Update it from `onValueChange`. |
|
|
88
139
|
| `valueDisplay?` | 'end' \| 'inline' \| 'thumb' \| 'none' | 'end' | Where the live value appears. `end` puts it at the right edge of the label row. `inline` renders `Label: value`. `thumb` keeps it above the thumb. |
|
|
89
140
|
| `valuePrefix?` | string | — | Text inserted before the live numeric value, such as `$`. |
|
|
90
141
|
| `valueSuffix?` | string | — | Text inserted after the live numeric value, such as `%` or `°C`. |
|
|
@@ -93,6 +144,12 @@ Arrow keys change by `step`. Home and End set the minimum and maximum. PageUp an
|
|
|
93
144
|
|
|
94
145
|
Use `<a-slider>` when you are not using React or Preact. Add its `role` and accessible name yourself, then register it from `@antadesign/anta/elements`.
|
|
95
146
|
|
|
147
|
+
For a range, separate the two numbers with a space in `value` or `defaultvalue`.
|
|
148
|
+
Use `role="group"` and give the range an accessible name. Each thumb receives
|
|
149
|
+
its own slider role and keyboard focus. The element's `value` property and
|
|
150
|
+
change events expose a two-number array. If you set `name`, form submission
|
|
151
|
+
includes both values in low-to-high order. Read them with `FormData.getAll(name)`.
|
|
152
|
+
|
|
96
153
|
```html
|
|
97
154
|
<a-slider
|
|
98
155
|
role="slider"
|
|
@@ -109,6 +166,12 @@ Use `<a-slider>` when you are not using React or Preact. Add its `role` and acce
|
|
|
109
166
|
</a-slider>
|
|
110
167
|
```
|
|
111
168
|
|
|
169
|
+
```html
|
|
170
|
+
<a-slider role="group" aria-label="Selected interval" name="interval" defaultvalue="20 70" inverted>
|
|
171
|
+
<span slot="label">Selected interval</span>
|
|
172
|
+
</a-slider>
|
|
173
|
+
```
|
|
174
|
+
|
|
112
175
|
### Native HTML range input
|
|
113
176
|
|
|
114
177
|
Add `data-anta` to a native range input for Anta's rail, fill, thumb, and focus
|
|
@@ -140,3 +203,75 @@ as `Slider`. In raw HTML, `round` needs a CSS length such as `"4px"`; JSX
|
|
|
140
203
|
border-radius: 4px;
|
|
141
204
|
}
|
|
142
205
|
```
|
|
206
|
+
|
|
207
|
+
### Editable value beside the label
|
|
208
|
+
|
|
209
|
+
Compose `Input` and `Slider` as separate controls. The grid puts the label and
|
|
210
|
+
small input on one row and the slider underneath. Pass `valueDisplay="none"` so the
|
|
211
|
+
slider does not repeat its value. Keep the input's text as a draft; when the
|
|
212
|
+
field loses focus or you press Enter, commit a number within the slider's range.
|
|
213
|
+
Dragging or using the slider's keyboard controls updates both values.
|
|
214
|
+
|
|
215
|
+
```tsx title="Slider with an editable value"
|
|
216
|
+
import { useState } from 'preact/hooks'
|
|
217
|
+
import { Input, Slider } from '@antadesign/anta'
|
|
218
|
+
|
|
219
|
+
function SliderEditableValueDemo() {
|
|
220
|
+
const [value, setValue] = useState(55)
|
|
221
|
+
const [draft, setDraft] = useState('55')
|
|
222
|
+
|
|
223
|
+
const commit = () => {
|
|
224
|
+
const number = Number(draft)
|
|
225
|
+
const next = draft.trim() && Number.isFinite(number)
|
|
226
|
+
? Math.max(0, Math.min(100, Math.round(number)))
|
|
227
|
+
: value
|
|
228
|
+
setValue(next)
|
|
229
|
+
setDraft(String(next))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<div className="editable-slider">
|
|
234
|
+
<span className="editable-slider-label">Volume</span>
|
|
235
|
+
<Input
|
|
236
|
+
aria-label="Volume value"
|
|
237
|
+
size="small"
|
|
238
|
+
inputMode="numeric"
|
|
239
|
+
trailing="%"
|
|
240
|
+
value={draft}
|
|
241
|
+
onInput={(event) => setDraft(event.currentTarget.value)}
|
|
242
|
+
onBlur={commit}
|
|
243
|
+
onKeyDown={(event) => {
|
|
244
|
+
if (event.key === 'Enter') commit()
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
<Slider
|
|
248
|
+
aria-label="Volume"
|
|
249
|
+
value={value}
|
|
250
|
+
valueDisplay="none"
|
|
251
|
+
valueSuffix="%"
|
|
252
|
+
onValueChange={(_, { value: next }) => {
|
|
253
|
+
setValue(next)
|
|
254
|
+
setDraft(String(next))
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```css title="Editable Slider layout"
|
|
263
|
+
.editable-slider {
|
|
264
|
+
display: grid;
|
|
265
|
+
grid-template-columns: minmax(0, 1fr) 72px;
|
|
266
|
+
align-items: center;
|
|
267
|
+
gap: 0 12px;
|
|
268
|
+
width: min(100%, 360px);
|
|
269
|
+
margin-inline: auto;
|
|
270
|
+
}
|
|
271
|
+
.editable-slider-label {
|
|
272
|
+
color: var(--text-3);
|
|
273
|
+
font-size: 15px;
|
|
274
|
+
font-weight: 500;
|
|
275
|
+
}
|
|
276
|
+
.editable-slider > a-slider { grid-column: 1 / -1; }
|
|
277
|
+
```
|