@antadesign/anta 0.3.17 → 0.3.19
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/README.md +38 -242
- package/dist/components/Input.d.ts +4 -1
- package/dist/components/Input.js +2 -0
- package/dist/components/InputDate.css +1 -0
- package/dist/components/InputDate.d.ts +1 -0
- package/dist/components/InputDate.js +5 -5
- package/dist/components/Loader.d.ts +39 -0
- package/dist/components/Loader.js +33 -0
- package/dist/components/Menu.d.ts +5 -1
- package/dist/components/Menu.js +3 -2
- package/dist/components/Progress.d.ts +17 -8
- package/dist/components/Progress.js +12 -9
- package/dist/components/Select.d.ts +1 -0
- package/dist/components/Select.js +9 -13
- package/dist/components/SelectFaceted.d.ts +1 -0
- package/dist/components/SelectFaceted.js +29 -23
- package/dist/components/select-parts.css +1 -0
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-icon.d.ts +1 -0
- package/dist/elements/a-icon.js +1 -0
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-loader.css +1 -0
- package/dist/elements/a-loader.d.ts +12 -0
- package/dist/elements/a-loader.js +15 -0
- package/dist/elements/a-menu.js +12 -0
- package/dist/elements/a-progress.css +1 -1
- package/dist/elements/a-progress.js +32 -2
- package/dist/elements/a-slider.css +1 -1
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tabs.d.ts +3 -0
- package/dist/elements/a-tabs.js +40 -1
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +3 -0
- package/dist/general_types.d.ts +27 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- package/dist/jsx-runtime.d.ts +11 -1
- package/dist/reset.css +1 -1
- package/dist/theme-anta.css +1 -1
- package/docs/accessibility.md +34 -0
- package/docs/changelog.md +551 -0
- package/docs/colors.md +42 -0
- package/docs/comparison.md +28 -0
- package/docs/components/banner.md +267 -0
- package/docs/components/button.md +603 -0
- package/docs/components/card.md +322 -0
- package/docs/components/checkbox.md +297 -0
- package/docs/components/dialog.md +314 -0
- package/docs/components/expander.md +529 -0
- package/docs/components/icon.md +155 -0
- package/docs/components/input-autocomplete.md +101 -0
- package/docs/components/input-date.md +406 -0
- package/docs/components/input-time.md +174 -0
- package/docs/components/input.md +610 -0
- package/docs/components/loader.md +139 -0
- package/docs/components/menu.md +447 -0
- package/docs/components/progress.md +182 -0
- package/docs/components/radio.md +369 -0
- package/docs/components/select-faceted.md +456 -0
- package/docs/components/select.md +931 -0
- package/docs/components/slider.md +142 -0
- package/docs/components/switch.md +198 -0
- package/docs/components/tabs.md +471 -0
- package/docs/components/tag.md +185 -0
- package/docs/components/text.md +148 -0
- package/docs/components/title.md +146 -0
- package/docs/components/toaster.md +313 -0
- package/docs/components/tooltip.md +177 -0
- package/docs/credits.md +22 -0
- package/docs/index.md +52 -0
- package/docs/install-config.md +240 -0
- package/docs/normalization.md +105 -0
- package/docs/overview.md +10 -0
- package/docs/packages/stickers.md +187 -0
- package/docs/packages/table.md +153 -0
- package/docs/theming.md +13 -0
- package/package.json +10 -6
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Text
|
|
2
|
+
|
|
3
|
+
A block-level text container that scopes the color hierarchy of its
|
|
4
|
+
contents. `priority` sets the emphasis from `primary` (`--text-1`) to
|
|
5
|
+
`quaternary` (`--text-4`). It defaults to `secondary` (`--text-2`), so body
|
|
6
|
+
text reads a step softer than the strongest foreground; pass `primary`
|
|
7
|
+
for emphasis. `tone` adds a tint (`brand`, `success`, `critical`,
|
|
8
|
+
`warning`, `info`, or any literal CSS color for a one-off custom tone); the
|
|
9
|
+
default `neutral` leaves it untinted. `inline` renders it `inline-block` for use
|
|
10
|
+
mid-sentence.
|
|
11
|
+
|
|
12
|
+
Links nested inside `<Text>` take Anta's prose-link styling from the
|
|
13
|
+
design system, with no per-page overrides to maintain.
|
|
14
|
+
|
|
15
|
+
## Link behavior
|
|
16
|
+
|
|
17
|
+
Links inside `<Text>` follow a priority-aware hierarchy that keeps
|
|
18
|
+
emphasis consistent with the surrounding text:
|
|
19
|
+
|
|
20
|
+
| Priority | Link color (default) | Link color (hover) |
|
|
21
|
+
|---------------|-----------------------------------|-------------------------------|
|
|
22
|
+
| `primary` | `--link-color` (brand blue) | `--link-color-hover` |
|
|
23
|
+
| `secondary` | `--link-color` | `--link-color-hover` |
|
|
24
|
+
| `tertiary` | `currentColor` (= `--text-3`) | `--text-2` |
|
|
25
|
+
| `quaternary` | `currentColor` (= `--text-4`) | `--text-3` |
|
|
26
|
+
|
|
27
|
+
The underline is a 0.5px hairline at 75% alpha by default and bumps to
|
|
28
|
+
1px / 100% alpha on hover. On `:active` it returns to the 0.5px / 75%
|
|
29
|
+
resting state.
|
|
30
|
+
|
|
31
|
+
For tinted text (`tone="brand|success|critical|warning|info"`) links are
|
|
32
|
+
always muted regardless of priority: they take `currentColor` and step up
|
|
33
|
+
to the next-stronger level of the same tint on hover. At
|
|
34
|
+
`priority="primary"` there is no level above, so hovering only brings the
|
|
35
|
+
underline to full alpha.
|
|
36
|
+
|
|
37
|
+
## Demo
|
|
38
|
+
|
|
39
|
+
## Truncation & expansion
|
|
40
|
+
|
|
41
|
+
`truncate` takes `true` (or `1`) for a single-line ellipsis, any integer
|
|
42
|
+
≥ 2 for multi-line clamping, and `0` or a negative value for no
|
|
43
|
+
truncation. Both modes use `-webkit-line-clamp` inside
|
|
44
|
+
`display: -webkit-box`, supported in every major browser despite the
|
|
45
|
+
prefix (Firefox 68+, Chrome, Safari, Edge). The host gets `min-width: 0`
|
|
46
|
+
so truncation works inside flex and grid containers.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
{/* Single line */}
|
|
50
|
+
<Text truncate>{longSentence}</Text>
|
|
51
|
+
|
|
52
|
+
{/* Multi-line: pass the line count */}
|
|
53
|
+
<Text truncate={2}>{longSentence}</Text>
|
|
54
|
+
<Text truncate={3}>{longSentence}</Text>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pair `expandable` with `truncate` to let the reader reveal the full text.
|
|
58
|
+
The fade and chevron appear only when the clamped content overflows; text
|
|
59
|
+
that fits gets neither. Single-line truncation fades on the right edge,
|
|
60
|
+
multi-line over the last line. The chevron is a real `<button>` carrying
|
|
61
|
+
`aria-expanded`: click it, or focus it and press Enter, to drop the clamp.
|
|
62
|
+
`expandable` takes effect only with `truncate`.
|
|
63
|
+
|
|
64
|
+
Expanding is one-way by default, so the control is removed once the text
|
|
65
|
+
is revealed. Add `collapsible` for a two-way toggle: the chevron stays
|
|
66
|
+
visible while expanded (rotated up) and flips its label between "Show
|
|
67
|
+
more" and "Show less". `collapsible` takes effect only with `expandable`.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
{/* One-way: the chevron is removed once expanded */}
|
|
71
|
+
<Text truncate expandable>{longSentence}</Text>
|
|
72
|
+
<Text truncate={3} expandable>{longSentence}</Text>
|
|
73
|
+
|
|
74
|
+
{/* Two-way: the reader can collapse it back */}
|
|
75
|
+
<Text truncate={3} expandable collapsible>{longSentence}</Text>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Props
|
|
79
|
+
|
|
80
|
+
| Prop | Type | Default | Description |
|
|
81
|
+
|------|------|---------|-------------|
|
|
82
|
+
| `collapsible?` | boolean | — | Let the reader collapse back after expanding: the chevron becomes a
|
|
83
|
+
"Show more" / "Show less" toggle that stays visible while expanded.
|
|
84
|
+
Only takes effect together with `expandable`. |
|
|
85
|
+
| `expandable?` | boolean | — | Show a fade hint and chevron over the truncated text and let the user
|
|
86
|
+
expand it by clicking the chevron region or pressing Enter while the
|
|
87
|
+
chevron has keyboard focus. Only takes effect together with `truncate`.
|
|
88
|
+
On its own, expanding is **one-way** — the control is removed once
|
|
89
|
+
expanded; add `collapsible` for a two-way toggle. |
|
|
90
|
+
| `inline?` | boolean | — | Render as inline-block instead of the default block element. |
|
|
91
|
+
| `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | secondary | Visual priority. Maps to text-1..text-4 (`primary` = text-1, the
|
|
92
|
+
strongest). The default is `secondary` (text-2) — body text reads a
|
|
93
|
+
step softer than the strongest foreground; pass `primary` for emphasis. |
|
|
94
|
+
| `size?` | 'small' \| 'medium' \| 'large' | medium | Type scale. `small` = 13/16, `medium` = 15/20, `large` = 17/24. |
|
|
95
|
+
| `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Color tint. `neutral` (the default) is the untinted `--text-{N}` scale; a
|
|
96
|
+
named tone applies the matching `--text-{N}-{tone}` palette. Any literal CSS
|
|
97
|
+
color (`'#ff1493'`, `'rebeccapurple'`) is a one-off custom tone — its hue is
|
|
98
|
+
kept while lightness/chroma are pinned per priority in oklch. |
|
|
99
|
+
| `truncate?` | boolean \| number | — | Truncate with a trailing ellipsis. `true` (or `1`) clamps to a
|
|
100
|
+
single line; any integer ≥ 2 clamps to that many lines; `0` or a
|
|
101
|
+
negative value means no truncation. Uses the `-webkit-line-clamp`
|
|
102
|
+
technique, supported in all major browsers (Firefox 68+, Chrome,
|
|
103
|
+
Safari, Edge). |
|
|
104
|
+
|
|
105
|
+
Use this when you are not using the React or Preact wrapper and a native HTML control does not fit: construct the equivalent Anta web component from the elements below.
|
|
106
|
+
|
|
107
|
+
Use `<a-text>` directly for a block that carries the text priority and tone.
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<a-text priority="secondary">
|
|
111
|
+
Read the <a href="/colors/">color guidance</a>.
|
|
112
|
+
</a-text>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Colour comes from **`priority`** (the neutral text level) and **`tone`** (the tint);
|
|
116
|
+
`size` and the type props set the rest. For a named tone, `Text` consumes the
|
|
117
|
+
global **text role tokens** (`--text-1` … `--text-5`, the `-{tone}` tints,
|
|
118
|
+
`--link-color`), so retuning those once on `:root` / `.dark` shifts text colour
|
|
119
|
+
everywhere (see [Colors](../colors.md)). A custom `tone` (any CSS color) is derived in
|
|
120
|
+
oklch from **`--text-tone-source`** — the same one-off knob as `Button` / `Tag`.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<Text priority="secondary" tone="brand">Tinted secondary text</Text>
|
|
124
|
+
<Text tone="#0d9488">Custom-tone body copy</Text>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`<a-text>` is light-DOM, so one-offs are plain CSS on the element or its prose
|
|
128
|
+
links. The `.fancy` class names the demo example:
|
|
129
|
+
|
|
130
|
+
```css
|
|
131
|
+
/* teal body text via light-dark() (deep in light, bright in dark; Anta drives
|
|
132
|
+
color-scheme from its theme toggle), with wider tracking */
|
|
133
|
+
a-text.fancy {
|
|
134
|
+
color: light-dark(#0f6e5f, #5fd0bb);
|
|
135
|
+
font-size: 16px;
|
|
136
|
+
line-height: 1.7;
|
|
137
|
+
letter-spacing: 0.06em;
|
|
138
|
+
}
|
|
139
|
+
a-text.fancy a { text-decoration-thickness: 2px; text-underline-offset: 3px; }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Example
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { Text } from '@antadesign/anta'
|
|
146
|
+
|
|
147
|
+
<Text>The quick brown fox jumps over the lazy dog with a <a href="#">link inside</a> the sentence, followed by enough additional prose to push the paragraph well past a single line. Curabitur sodales ligula in libero, and Donec a tincidunt elit. Mauris vehicula, est nec porta cursus, tellus eros vestibulum lectus, vitae luctus turpis enim sed nibh.</Text>
|
|
148
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Title
|
|
2
|
+
|
|
3
|
+
A margin-free heading with six `level`s. `level` sets the type scale and the
|
|
4
|
+
`aria-level` exposed to assistive technology. `priority` runs from `primary`
|
|
5
|
+
through `quaternary` (`--text-1` through `--text-4`); `tone` accepts `neutral`
|
|
6
|
+
(the default), a named tone, or a literal CSS color.
|
|
7
|
+
|
|
8
|
+
Use a raw `<h1>`–`<h6>` for document-outline or SEO semantics. Anta's reset
|
|
9
|
+
gives those headings the same type scale and document margins. Use `<Title>` in
|
|
10
|
+
component layouts when you need tone, priority, or parent-controlled spacing.
|
|
11
|
+
|
|
12
|
+
> **`<Title>` has no margins.** Space it with the parent’s `gap` or padding. For
|
|
13
|
+
> a one-off, add `marginBlock` to the title itself.
|
|
14
|
+
|
|
15
|
+
## Levels
|
|
16
|
+
|
|
17
|
+
The parent supplies the `16px` gap between these titles.
|
|
18
|
+
|
|
19
|
+
Level 1: Workspace settings
|
|
20
|
+
Level 2: Recent activity
|
|
21
|
+
Level 3: Security alerts
|
|
22
|
+
Level 4: Sign-in policy
|
|
23
|
+
Level 5: Session timeout
|
|
24
|
+
Level 6: Updated five minutes ago
|
|
25
|
+
|
|
26
|
+
## Inline content
|
|
27
|
+
|
|
28
|
+
Pass icons and inline elements as children. Icons are optically aligned with
|
|
29
|
+
the title text.
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<Title level={3}>
|
|
33
|
+
<Icon shape="book-open" /> Saved items{' '}
|
|
34
|
+
<span style={{ color: 'var(--text-1-brand)' }}>(3)</span>
|
|
35
|
+
</Title>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use a span for a small inline accent. Don't nest `<Text>` or `<a-text>` inside a
|
|
39
|
+
title: they are block containers, while heading content should remain inline.
|
|
40
|
+
|
|
41
|
+
## Level reference
|
|
42
|
+
|
|
43
|
+
| level | font-size | line-height |
|
|
44
|
+
|------:|----------:|------------:|
|
|
45
|
+
| 1 | 28px | 32px |
|
|
46
|
+
| 2 | 24px | 28px |
|
|
47
|
+
| 3 | 20px | 24px |
|
|
48
|
+
| 4 | 17px | 20px |
|
|
49
|
+
| 5 | 15px | 20px |
|
|
50
|
+
| 6 | 13px | 16px |
|
|
51
|
+
|
|
52
|
+
## Tone × priority
|
|
53
|
+
|
|
54
|
+
The four priorities are shown across every named tone at `level={3}`. Omit
|
|
55
|
+
`tone` for the neutral scale. The grid scrolls horizontally on narrow screens.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Neutral primary
|
|
59
|
+
Secondary
|
|
60
|
+
Tertiary
|
|
61
|
+
Quaternary
|
|
62
|
+
|
|
63
|
+
Brand primary
|
|
64
|
+
Secondary
|
|
65
|
+
Tertiary
|
|
66
|
+
Quaternary
|
|
67
|
+
|
|
68
|
+
Success primary
|
|
69
|
+
Secondary
|
|
70
|
+
Tertiary
|
|
71
|
+
Quaternary
|
|
72
|
+
|
|
73
|
+
Critical primary
|
|
74
|
+
Secondary
|
|
75
|
+
Tertiary
|
|
76
|
+
Quaternary
|
|
77
|
+
|
|
78
|
+
Warning primary
|
|
79
|
+
Secondary
|
|
80
|
+
Tertiary
|
|
81
|
+
Quaternary
|
|
82
|
+
|
|
83
|
+
Info primary
|
|
84
|
+
Secondary
|
|
85
|
+
Tertiary
|
|
86
|
+
Quaternary
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Accessibility
|
|
90
|
+
|
|
91
|
+
`<Title>` renders an `<a-title>` tag with `role="heading"` and
|
|
92
|
+
`aria-level={level}`. It is not a real `<h*>`. Use a raw heading when the HTML
|
|
93
|
+
document outline or SEO needs heading elements; the reset applies the same type
|
|
94
|
+
scale.
|
|
95
|
+
|
|
96
|
+
### Props
|
|
97
|
+
|
|
98
|
+
| Prop | Type | Default | Description |
|
|
99
|
+
|------|------|---------|-------------|
|
|
100
|
+
| `level?` | 1 \| 2 \| 3 \| 4 \| 5 \| 6 | 2 | Heading level, 1-6. Drives font-size, line-height, and vertical
|
|
101
|
+
rhythm. Also surfaced to assistive tech via `aria-level`
|
|
102
|
+
(h1 is typically reserved for the page title). |
|
|
103
|
+
| `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | primary | Visual priority. Maps to text-1..text-4 (`primary` = text-1). |
|
|
104
|
+
| `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | neutral | Color tint. `neutral` (the default) is the untinted `--text-{N}` scale; a
|
|
105
|
+
named tone applies the matching `--text-{N}-{tone}` palette. Any literal CSS
|
|
106
|
+
color (`'#ff1493'`, `'rebeccapurple'`) is a one-off custom tone — its hue is
|
|
107
|
+
kept while lightness/chroma are pinned per priority in oklch. |
|
|
108
|
+
|
|
109
|
+
Use this when you are not using the React or Preact wrapper and a native HTML control does not fit: construct the equivalent Anta web component from the elements below.
|
|
110
|
+
|
|
111
|
+
Set the heading role and level when writing `<a-title>` directly.
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<a-title level="2" tone="brand" role="heading" aria-level="2">
|
|
115
|
+
Release notes
|
|
116
|
+
</a-title>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`priority` selects a neutral text level. Named `tone`s use the matching global
|
|
120
|
+
text tokens. A literal `tone` is stored in `--title-tone-source` and resolved
|
|
121
|
+
into the four priority levels.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<Title priority="secondary">Supporting heading</Title>
|
|
125
|
+
<Title tone="#0d9488">Custom-tone heading</Title>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`<a-title>` is light DOM, so local type treatment is ordinary CSS. The class is
|
|
129
|
+
only for this example.
|
|
130
|
+
|
|
131
|
+
```css
|
|
132
|
+
a-title.release-heading {
|
|
133
|
+
font-size: 18px;
|
|
134
|
+
line-height: 24px;
|
|
135
|
+
letter-spacing: 0.02em;
|
|
136
|
+
text-transform: uppercase;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Example
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { Title } from '@antadesign/anta'
|
|
144
|
+
|
|
145
|
+
<Title>Section heading</Title>
|
|
146
|
+
```
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Toaster
|
|
2
|
+
|
|
3
|
+
A viewport-anchored region that shows toasts — short, self-dismissing notices
|
|
4
|
+
stacked in a corner. It isn't a fixed "toast card": you toast **anything** — a
|
|
5
|
+
`Banner`, a `Card`, a `Sticker`, a string, a bespoke node — and the region handles
|
|
6
|
+
placement, stacking, and the auto-dismiss timer.
|
|
7
|
+
|
|
8
|
+
Two pieces work together. **`<Toaster>`** is the region; you mount one and keep it
|
|
9
|
+
mounted (a portal is fine). **`Toaster.manager`** is the store you drive from
|
|
10
|
+
anywhere: `Toaster.manager.add(render)` adds a toast, where `render` is a function
|
|
11
|
+
returning the content. The store holds no DOM — the mounted `<Toaster>` subscribes
|
|
12
|
+
to it and renders each toast through React/Preact, so there's nowhere for a toast
|
|
13
|
+
to go if no `<Toaster>` is mounted.
|
|
14
|
+
|
|
15
|
+
Configure one toast and fire it — the playground shows the render function plus the
|
|
16
|
+
options object you pass to `add`. Edit the `Banner` in the code (or the props panel)
|
|
17
|
+
and the `{ placement, duration }` it's toasted with.
|
|
18
|
+
|
|
19
|
+
## Mount the region, drive the store
|
|
20
|
+
|
|
21
|
+
Mount one `<Toaster>` at a stable spot in your tree and leave it there:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { Toaster } from '@antadesign/anta'
|
|
25
|
+
|
|
26
|
+
function App() {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<YourApp />
|
|
30
|
+
<Toaster /> {/* keep it mounted — toasts render here */}
|
|
31
|
+
</>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then, from anywhere, add a toast by passing a render function. The simplest case is
|
|
37
|
+
a string — it's wrapped in a dismissible `Banner` for you:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Toaster } from '@antadesign/anta'
|
|
41
|
+
|
|
42
|
+
Toaster.manager.add(() => 'Saved.', { placement: 'bottom-right' })
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`add` returns an `id`, and the render function receives it — so a toast can dismiss
|
|
46
|
+
itself. See [Timing and dismissal](#timing-and-dismissal) for how each kind of
|
|
47
|
+
content closes its toast.
|
|
48
|
+
|
|
49
|
+
## Toast anything
|
|
50
|
+
|
|
51
|
+
The render function can return a **string, JSX, or a live DOM node** — the same
|
|
52
|
+
`add` handles all three. A bare string / number is wrapped in a dismissible `Banner`;
|
|
53
|
+
other JSX renders through the reconciler as-is; a DOM node is slotted by the element
|
|
54
|
+
(return a *stable* node, created once, not a fresh one per call).
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// JSX — rendered as-is
|
|
58
|
+
Toaster.manager.add(() => <Card tone="info" size="small" header="Deployment ready">
|
|
59
|
+
Your build passed all checks.
|
|
60
|
+
</Card>, { placement: 'top-right' })
|
|
61
|
+
|
|
62
|
+
// a string — auto-wrapped in a dismissible Banner
|
|
63
|
+
Toaster.manager.add(() => 'Copied to clipboard')
|
|
64
|
+
|
|
65
|
+
// a DOM node (create it once, return the same reference)
|
|
66
|
+
const el = document.createElement('div')
|
|
67
|
+
el.textContent = '🎉'
|
|
68
|
+
Toaster.manager.add(() => el)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Returning a raw DOM node **into React errors** if you hand it to JSX directly —
|
|
72
|
+
`add` avoids that by routing a `Node` to the element instead of the reconciler.
|
|
73
|
+
(Toast a JSX component like `<Banner/>` by returning it; reach for a DOM node only
|
|
74
|
+
when you already have one, e.g. from a non-React widget.)
|
|
75
|
+
|
|
76
|
+
## Placement
|
|
77
|
+
|
|
78
|
+
`placement` picks the corner or edge; the default is `bottom-right`. Bottom zones
|
|
79
|
+
grow upward (newest nearest the edge), top zones grow downward. Six values:
|
|
80
|
+
|
|
81
|
+
`top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right`.
|
|
82
|
+
|
|
83
|
+
One `<Toaster>` serves every placement — each toast routes to its own zone, so a
|
|
84
|
+
single mounted region drops notices in any corner.
|
|
85
|
+
|
|
86
|
+
## Timing and dismissal
|
|
87
|
+
|
|
88
|
+
Each toast auto-dismisses after `duration` (ms), 5000 by default; the countdown
|
|
89
|
+
pauses while the pointer is over the toast **or** focus is inside it. `duration:
|
|
90
|
+
Infinity` keeps it until dismissed (empty, zero, and non-positive values fall back
|
|
91
|
+
to the default). On dismiss the toast animates out and then leaves.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
Toaster.manager.add(() => 'Still here until you close it', { duration: Infinity }) // sticky
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The toast itself carries **no ✕** — dismissal comes from the content. Three ways,
|
|
98
|
+
depending on what you toast:
|
|
99
|
+
|
|
100
|
+
- **A string** auto-wraps in a `Banner`, so its ✕ is there for free.
|
|
101
|
+
- **Any control carrying `data-toast-dismiss`** dismisses the toast on click. This is
|
|
102
|
+
the simplest path — no handler, no `id`, no `onDismiss`; the render function doesn't
|
|
103
|
+
even take an argument. Just add the attribute to a button, link, or menu item inside
|
|
104
|
+
the content (the same convention as `<a-banner>`'s `data-banner-dismiss` and
|
|
105
|
+
`<a-dialog>`'s `data-dialog-close`):
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
Toaster.manager.add(() => (
|
|
109
|
+
<Card header="Update ready">
|
|
110
|
+
A new version is available. <Button data-toast-dismiss label="Later" />
|
|
111
|
+
</Card>
|
|
112
|
+
))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **A `Banner` you toast yourself** dismisses through its own `onDismiss` — wire it
|
|
116
|
+
to `dismiss(id)`, and its ✕ (plus any `data-banner-dismiss` action inside it)
|
|
117
|
+
closes the toast:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
Toaster.manager.add((id) => (
|
|
121
|
+
<Banner tone="success" round message="Saved" onDismiss={() => Toaster.manager.dismiss(id)} />
|
|
122
|
+
))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Or drive dismissal from anywhere with the returned id:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
const id = Toaster.manager.add(() => 'Saving…', { duration: Infinity })
|
|
129
|
+
Toaster.manager.dismiss(id) // animate out, then remove
|
|
130
|
+
Toaster.manager.update(id, () => 'Saved') // swap content in place
|
|
131
|
+
Toaster.manager.clear() // dismiss all
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Pass your own `id` to **upsert** — a second `add` with the same id replaces the
|
|
135
|
+
live toast's content and restarts its timer, instead of stacking a duplicate. Good
|
|
136
|
+
for a step that reports progress, or to dedupe a repeated event:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
Toaster.manager.add(() => <Banner message="Saving…" />, { id: 'save' })
|
|
140
|
+
// …later…
|
|
141
|
+
Toaster.manager.add(() => <Banner tone="success" message="Saved" />, { id: 'save' }) // in place
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Show the time left
|
|
145
|
+
|
|
146
|
+
The toast exposes how much of its timer is left as **`--toast-remaining`** — a
|
|
147
|
+
number that animates from `1` (just shown) to `0` (about to dismiss), inherited by
|
|
148
|
+
the content. Draw a countdown with one CSS rule that reads it. No duration to pass,
|
|
149
|
+
no keyframes, no timer of your own — and it pauses with the toast (hover / focus)
|
|
150
|
+
because the variable itself pauses. Try **Toast with countdown** in the demo above
|
|
151
|
+
and hover it.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
Toaster.manager.add(
|
|
155
|
+
(id) => (
|
|
156
|
+
<div className="countdown-toast">
|
|
157
|
+
<Banner tone="info" round message="Auto-dismissing…" onDismiss={() => Toaster.manager.dismiss(id)} />
|
|
158
|
+
<span className="countdown-bar" />
|
|
159
|
+
</div>
|
|
160
|
+
),
|
|
161
|
+
{ duration: 6000 },
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```css
|
|
166
|
+
.countdown-toast { position: relative; }
|
|
167
|
+
.countdown-bar {
|
|
168
|
+
position: absolute; inset-inline: 10px; bottom: 5px; height: 3px;
|
|
169
|
+
border-radius: 999px; transform-origin: left;
|
|
170
|
+
background: color-mix(in oklch, currentColor 35%, transparent);
|
|
171
|
+
transform: scaleX(var(--toast-remaining)); /* 1 → 0 over the toast's life */
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Why a variable, not a prop?** You could instead pass `secondsLeft` into the render
|
|
176
|
+
function and tick it. But updating a prop re-runs the render function and re-diffs the
|
|
177
|
+
whole toast — the Banner, its ✕, everything — many times a second, which can disturb
|
|
178
|
+
focus, text selection, or an in-progress animation inside the content. `--toast-remaining`
|
|
179
|
+
updates a single inherited value the browser interpolates on the compositor: the
|
|
180
|
+
content renders **once**, nothing reconciles, and it's smooth at display refresh rate
|
|
181
|
+
instead of your tick interval. The toast already owns the timer and its pause, so
|
|
182
|
+
exposing the value keeps one source of truth.
|
|
183
|
+
|
|
184
|
+
`--toast-remaining` is display only — the real dismissal is the toast's own timer — so
|
|
185
|
+
where a browser can't animate a custom property it simply stays full. A **sticky**
|
|
186
|
+
(`duration: Infinity`) toast has no countdown; the value stays `1`.
|
|
187
|
+
|
|
188
|
+
The bar is motion — if you draw one, gate it under `prefers-reduced-motion` yourself
|
|
189
|
+
(e.g. `@media (prefers-reduced-motion: reduce) { .countdown-bar { display: none } }`).
|
|
190
|
+
`a-toast`'s own enter/exit fades are already suppressed there; `--toast-remaining`
|
|
191
|
+
keeps ticking so non-visual uses still work, and the toast still auto-dismisses.
|
|
192
|
+
|
|
193
|
+
## Multiple regions
|
|
194
|
+
|
|
195
|
+
`Toaster.manager` is the default store. For an isolated region, create your own and
|
|
196
|
+
bind it to a `<Toaster>` with `toaster`:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { createToaster, Toaster } from '@antadesign/anta'
|
|
200
|
+
|
|
201
|
+
export const alerts = createToaster()
|
|
202
|
+
|
|
203
|
+
// mount its region:
|
|
204
|
+
<Toaster toaster={alerts} />
|
|
205
|
+
|
|
206
|
+
// drive it:
|
|
207
|
+
alerts.add(() => <Banner tone="critical" message="Connection lost" />, { placement: 'top-center' })
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
One store drives one `<Toaster>`.
|
|
211
|
+
|
|
212
|
+
## Announcements
|
|
213
|
+
|
|
214
|
+
Announcement is opt-in **per toast**: pass `politeness` to give that toast an
|
|
215
|
+
`aria-live` region — `'polite'` waits for a pause, `'assertive'` interrupts. Omit
|
|
216
|
+
it for no announcement, which is right when the content carries its own live
|
|
217
|
+
semantics (a `Banner` defaults to `role="status"`).
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
Toaster.manager.add(() => <Banner tone="critical" message="Connection lost" />, {
|
|
221
|
+
politeness: 'assertive',
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Props
|
|
226
|
+
|
|
227
|
+
| Prop | Type | Default | Description |
|
|
228
|
+
|------|------|---------|-------------|
|
|
229
|
+
| `label?` | string | 'Notifications' | Accessible label for the region landmark. |
|
|
230
|
+
| `toaster?` | Toaster | — | The store this region renders. Omit to bind the default store driven by
|
|
231
|
+
`Toaster.manager`; pass a `createToaster()` for an isolated region. |
|
|
232
|
+
|
|
233
|
+
Use this when you are not using the React or Preact wrapper and a native HTML control does not fit: construct the equivalent Anta web component from the elements below.
|
|
234
|
+
|
|
235
|
+
Place each `<a-toast>` in a positional slot. This button clones the toast template
|
|
236
|
+
into the toaster. `data-toast-dismiss` requests removal; listen for `dismiss` and
|
|
237
|
+
remove the toast from the DOM.
|
|
238
|
+
|
|
239
|
+
```html
|
|
240
|
+
<div data-anta-composition="toaster">
|
|
241
|
+
<a-button data-toast-add role="button" tabindex="0"><a-button-label>Show update</a-button-label></a-button>
|
|
242
|
+
<a-toaster role="region" aria-label="Notifications">
|
|
243
|
+
</a-toaster>
|
|
244
|
+
<template data-toast-template>
|
|
245
|
+
<a-toast slot="bottom-center" aria-live="polite">
|
|
246
|
+
<a-banner tone="info" round>
|
|
247
|
+
<a-banner-message slot="message">A new version is available.</a-banner-message>
|
|
248
|
+
<a-button slot="actions" role="button" tabindex="0" data-toast-dismiss><a-button-label>Dismiss</a-button-label></a-button>
|
|
249
|
+
</a-banner>
|
|
250
|
+
</a-toast>
|
|
251
|
+
</template>
|
|
252
|
+
</div>
|
|
253
|
+
<script type="module">
|
|
254
|
+
import '@antadesign/anta/elements'
|
|
255
|
+
|
|
256
|
+
const root = document.querySelector('[data-anta-composition="toaster"]')
|
|
257
|
+
const trigger = root.querySelector('[data-toast-add]')
|
|
258
|
+
const template = root.querySelector('template[data-toast-template]')
|
|
259
|
+
const toaster = root.querySelector('a-toaster')
|
|
260
|
+
|
|
261
|
+
trigger.addEventListener('click', () => toaster.append(template.content.cloneNode(true)))
|
|
262
|
+
// A dismissed toast finishes its exit animation, but stays in the DOM until its owner removes it.
|
|
263
|
+
root.addEventListener('dismiss', (event) => {
|
|
264
|
+
if (event.target instanceof HTMLElement && event.target.matches('a-toast')) event.target.remove()
|
|
265
|
+
})
|
|
266
|
+
</script>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The region and item are plain custom elements — style them with ordinary CSS
|
|
270
|
+
(`@layer anta`, so an un-layered rule of yours wins without `!important`).
|
|
271
|
+
|
|
272
|
+
Region spacing lives on `a-toaster` as tokens: **`--toaster-inset`** (gap from the
|
|
273
|
+
viewport edge), **`--toaster-gap`** (space between stacked toasts), and
|
|
274
|
+
**`--toaster-width`** (column width). The item's enter/exit timing lives on
|
|
275
|
+
`a-toast`: **`--toast-dur`**. The item also publishes **`--toast-remaining`** (a
|
|
276
|
+
number, `1` → `0`, tracking the auto-dismiss timer) for content to read — see
|
|
277
|
+
[Show the time left](#show-the-time-left). The item is style-neutral — no chrome of
|
|
278
|
+
its own — so a toast looks like exactly the content you pass.
|
|
279
|
+
|
|
280
|
+
`<a-toast>` is the content box itself, so add elevation (or any style) with the
|
|
281
|
+
plain `a-toast` selector:
|
|
282
|
+
|
|
283
|
+
```css
|
|
284
|
+
a-toaster { --toaster-width: 420px; --toaster-gap: 10px; }
|
|
285
|
+
a-toast { filter: drop-shadow(0 6px 20px color-mix(in oklch, black 22%, transparent)); }
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The content keeps its own styling — a toned `Banner` brings its surface, a `Card`
|
|
289
|
+
its border.
|
|
290
|
+
|
|
291
|
+
## Example
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { Toaster, Button, Banner } from '@antadesign/anta'
|
|
295
|
+
|
|
296
|
+
<Toaster />
|
|
297
|
+
<Button
|
|
298
|
+
label="Toast it"
|
|
299
|
+
onClick={() =>
|
|
300
|
+
Toaster.manager.add(
|
|
301
|
+
(id) => (
|
|
302
|
+
<Banner
|
|
303
|
+
tone="success"
|
|
304
|
+
round
|
|
305
|
+
message="Your changes were saved."
|
|
306
|
+
onDismiss={() => Toaster.manager.dismiss(id)}
|
|
307
|
+
/>
|
|
308
|
+
),
|
|
309
|
+
{ placement: 'bottom-right', duration: 4000 },
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
/>
|
|
313
|
+
```
|