@antadesign/anta 0.3.24 → 0.3.26

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.
Files changed (93) hide show
  1. package/dist/anta_helpers.js +3 -3
  2. package/dist/box-types.d.ts +161 -0
  3. package/dist/box-types.js +0 -0
  4. package/dist/bundle.css +1 -1
  5. package/dist/bundle.js +26 -25
  6. package/dist/components/Banner.d.ts +10 -10
  7. package/dist/components/Banner.js +3 -1
  8. package/dist/components/Box.d.ts +37 -0
  9. package/dist/components/Box.js +63 -0
  10. package/dist/components/Breadcrumbs.css +1 -1
  11. package/dist/components/ButtonCopy.d.ts +2 -2
  12. package/dist/components/InputDate.css +1 -1
  13. package/dist/components/MenuItem.d.ts +5 -1
  14. package/dist/components/MenuItem.js +72 -2
  15. package/dist/components/MenuItemCopy.d.ts +2 -2
  16. package/dist/components/Steps.css +1 -1
  17. package/dist/components/copy-props.d.ts +3 -4
  18. package/dist/components/select-parts.css +1 -1
  19. package/dist/elements/a-avatar.css +1 -1
  20. package/dist/elements/a-banner.css +1 -1
  21. package/dist/elements/a-banner.js +16 -4
  22. package/dist/elements/a-box.css +1 -0
  23. package/dist/elements/a-box.d.ts +41 -0
  24. package/dist/elements/a-box.js +576 -0
  25. package/dist/elements/a-button.css +1 -1
  26. package/dist/elements/a-calendar.css +1 -1
  27. package/dist/elements/a-calendar.js +2 -2
  28. package/dist/elements/a-card.css +1 -1
  29. package/dist/elements/a-checkbox.css +1 -1
  30. package/dist/elements/a-copy.css +1 -1
  31. package/dist/elements/a-dialog.css +1 -1
  32. package/dist/elements/a-expander.css +1 -1
  33. package/dist/elements/a-expander.js +2 -2
  34. package/dist/elements/a-icon.css +1 -1
  35. package/dist/elements/a-icon.shapes.css +1 -1
  36. package/dist/elements/a-input-time.css +1 -1
  37. package/dist/elements/a-input-time.js +3 -3
  38. package/dist/elements/a-input.css +1 -1
  39. package/dist/elements/a-input.js +14 -6
  40. package/dist/elements/a-loader.css +1 -1
  41. package/dist/elements/a-menu-group.css +1 -1
  42. package/dist/elements/a-menu-item.css +1 -1
  43. package/dist/elements/a-menu-item.js +3 -3
  44. package/dist/elements/a-menu-separator.css +1 -1
  45. package/dist/elements/a-menu.css +1 -1
  46. package/dist/elements/a-menu.d.ts +4 -4
  47. package/dist/elements/a-menu.js +8 -6
  48. package/dist/elements/a-progress.css +1 -1
  49. package/dist/elements/a-radio-group.css +1 -1
  50. package/dist/elements/a-radio-group.js +3 -3
  51. package/dist/elements/a-radio.css +1 -1
  52. package/dist/elements/a-slider.css +1 -1
  53. package/dist/elements/a-switch.css +1 -1
  54. package/dist/elements/a-switch.js +2 -2
  55. package/dist/elements/a-tab.css +1 -1
  56. package/dist/elements/a-tabpanel.css +1 -1
  57. package/dist/elements/a-tabpanel.js +2 -2
  58. package/dist/elements/a-tabs.css +1 -1
  59. package/dist/elements/a-tabs.js +6 -6
  60. package/dist/elements/a-tag.css +1 -1
  61. package/dist/elements/a-text.css +1 -1
  62. package/dist/elements/a-title.css +1 -1
  63. package/dist/elements/a-toast.css +1 -1
  64. package/dist/elements/a-toast.js +2 -2
  65. package/dist/elements/a-toaster.css +1 -1
  66. package/dist/elements/a-tooltip.css +1 -1
  67. package/dist/elements/a-tooltip.d.ts +4 -3
  68. package/dist/elements/a-tooltip.js +9 -5
  69. package/dist/elements/copy-behavior.d.ts +13 -15
  70. package/dist/elements/index.d.ts +1 -0
  71. package/dist/elements/index.js +3 -0
  72. package/dist/general_types.d.ts +42 -4
  73. package/dist/generate-icons.mjs +3 -3
  74. package/dist/index.d.ts +3 -0
  75. package/dist/index.js +2 -0
  76. package/dist/jsx-runtime.d.ts +2 -1
  77. package/dist/reset.css +1 -1
  78. package/dist/theme-anta.css +1 -1
  79. package/dist/tokens.css +1 -1
  80. package/docs/changelog.md +42 -1
  81. package/docs/components/avatar.md +368 -0
  82. package/docs/components/banner.md +26 -15
  83. package/docs/components/box.md +282 -0
  84. package/docs/components/breadcrumbs.md +2 -2
  85. package/docs/components/button.md +26 -15
  86. package/docs/components/input.md +5 -0
  87. package/docs/components/menu.md +14 -11
  88. package/docs/components/tag.md +47 -0
  89. package/docs/index.md +4 -3
  90. package/docs/install-config.md +58 -13
  91. package/docs/normalization.md +37 -28
  92. package/docs/theming.md +4 -1
  93. package/package.json +2 -2
@@ -1,15 +1,12 @@
1
1
  # Banner
2
2
 
3
- A full-width message strip for a page- or section-level notice: an announcement, a
4
- status change, a promotion. The `Banner` JSX wrapper renders an `<a-banner>` whose
5
- content is a single centered row — `message`, optional `children`, then `actions` —
6
- with a dismiss ✕ on the right edge. It's **closable** by default.
3
+ A full-width notice for an announcement, status change, or promotion. `Banner`
4
+ renders an `<a-banner>` with `message`, optional `children`, `actions`, and a dismiss
5
+ control. Content starts at the inline edge; pass `align="center"` to center it
6
+ ([Align](#align)). `closable` defaults to `true`.
7
7
 
8
- Its surface reuses `Expander`'s primary vocabulary — a toned box that tracks light
9
- and dark — but laid out as a short horizontal strip sitting edge to edge, about
10
- twice the resting height of `Progress`. It's **borderless by default**; add a rule
11
- under it or an outline around it with `border-bottom-width` / `border-width`, the
12
- same opt-in as `Progress` ([Styling](#styling)).
8
+ `Banner` is borderless. Set `border-bottom-width` for a rule or `border-width` for
9
+ an outline ([Styling](#styling)).
13
10
 
14
11
  ## Tone
15
12
 
@@ -49,11 +46,24 @@ pass a `number` (px) or a CSS length string for a custom radius.
49
46
  <Banner round={12} tone="success" message="A custom 12px radius." />
50
47
  ```
51
48
 
49
+ ## Align
50
+
51
+ `align` places the content row. `start` (the default) aligns it to the inline edge
52
+ and reserves space for the dismiss control. `center` centers it in the bar. Wrapped
53
+ message text follows the selected alignment.
54
+
55
+ ```tsx
56
+ <Banner tone="info" message="Start-aligned content." /> {/* start is the default */}
57
+ <Banner align="center" tone="info" message="Centered content." />
58
+ ```
59
+
60
+ The layout follows `direction: rtl`.
61
+
52
62
  ## Message and content
53
63
 
54
- `message` is the leading content — a string (rendered at the banner type scale) or
55
- any node. `children` sit between the message and the actions, for a chip, a link,
56
- or a secondary line. Both are centered as one row.
64
+ `message` is the leading content: a string at the banner type scale or any node.
65
+ `children` sit between the message and actions, for example a chip, link, or
66
+ secondary line.
57
67
 
58
68
  ```tsx
59
69
  <Banner tone="info" message="Deployment complete.">
@@ -192,6 +202,8 @@ through `dismissed` instead.
192
202
  |------|------|---------|-------------|
193
203
  | `actions?` | ReactNode | — | Trailing controls (buttons, links) rendered as a compact row after the
194
204
  message and `children`. |
205
+ | `align?` | 'start' \| 'center' | 'start' | Positions the content row. `'start'` aligns it to the inline edge; `'center'`
206
+ centers it. Wrapped message text follows the selected alignment. |
195
207
  | `children?` | ReactNode | — | Free content placed BETWEEN the message and the actions. |
196
208
  | `closable?` | boolean | true | Whether the trailing ✕ dismiss button is present. `false` removes it — drive
197
209
  dismissal yourself (a controlled `dismissed`, or your own control) instead.
@@ -202,8 +214,7 @@ through `dismissed` instead.
202
214
  not updating). **Requires `onDismiss`** — controlled mode never self-hides, so
203
215
  without a handler to set `dismissed` the ✕ can't close the banner. Leave
204
216
  undefined for uncontrolled. |
205
- | `message?` | ReactNode | — | Leading message — a string (rendered at the banner type scale) or any node.
206
- Centered in the bar, followed by `children` and then `actions`. |
217
+ | `message?` | ReactNode | — | Leading message: a string at the banner type scale or any node. |
207
218
  | `onDismiss?` | () => void | — | Fired when the user dismisses the banner (clicks ✕). Uncontrolled, the banner
208
219
  hides itself and this notifies. Controlled, use it to accept the
209
220
  request — set `dismissed` to `true` (or ignore to reject); pair it with
@@ -257,7 +268,7 @@ banner's border matches its tone with no extra work.
257
268
  <Banner round tone="success" message="A full outline on a round banner." style={{ borderWidth: 1 }} />
258
269
  ```
259
270
 
260
- The host `a-banner` **is** the centered bar — its surface, text, border, and layout
271
+ The host `a-banner` **is** the bar — its surface, text, border, and layout
261
272
  are all plain CSS on the host (`@layer anta`, so an un-layered rule of yours wins
262
273
  without `!important`). For the individual regions, `<a-banner>` exposes
263
274
  `::part(message)` / `::part(content)` / `::part(actions)` / `::part(close)`.
@@ -0,0 +1,282 @@
1
+ # Box
2
+
3
+ `Box` is a DOM container that adds layout props, overflow states, and browser
4
+ context events.
5
+
6
+ ## Display
7
+
8
+ `display` and `gap` set the matching CSS properties. A numeric `gap` uses
9
+ pixels. A string accepts any CSS length.
10
+
11
+ ```tsx
12
+ <Box round={8}><span /></Box>
13
+ <Box display="flex" round={8} gap={6}><span /></Box>
14
+ <Box display="grid" round={8} gap="0.5rem" style={{ gridTemplateColumns: '1fr 1fr' }}><span /></Box>
15
+ ```
16
+
17
+ `round` accepts the same values. Pass `round` for fully rounded corners. Omit
18
+ it for square corners.
19
+
20
+ `display`, `gap`, and `round` use the `anta.components` layer. Your
21
+ `className` and `style` override them.
22
+
23
+ ## Overflow
24
+
25
+ Box reports when content exceeds its bounds (`overflowX` and `overflowY`), when
26
+ CSS clips it (`clippedX` and `clippedY`), and when it can scroll (`scrollableX`
27
+ and `scrollableY`). It exposes each measurement as a CSS state, so CSS can react
28
+ without JSX state or a measurement ref.
29
+
30
+ A Box measures for `fade`, `onMeasureChange`, or `observe="size"`. Use
31
+ `observe="size"` when CSS reads its states. A Box with none of these attaches no
32
+ observers. Measurement pauses off screen, except `fade`, which measures on
33
+ connection so it can paint its mask.
34
+
35
+ `observe` accepts `"size"`, `"context"`, or `"all"`. A bare `observe` means
36
+ `"all"`. The JSX `Box` derives the needed value from its handlers. When you use
37
+ `a-box` directly, set it yourself; `addEventListener` does not start reporting:
38
+
39
+ ```html
40
+ <a-box observe="size">Content</a-box>
41
+ <a-box observe="all">Content</a-box>
42
+ ```
43
+
44
+ The attribute works with listeners added before upgrade, removed with `once` or
45
+ `AbortSignal`, and replaced during React renders.
46
+
47
+ `.edge` is a demo class name. Use your own selector.
48
+
49
+ ```tsx
50
+ <Box observe="size" round={8} className="edge" style={{ width: 150 }}>Content that fits.</Box>
51
+
52
+ <Box observe="size" round={8} className="edge" style={{ width: 150, overflow: 'hidden', whiteSpace: 'nowrap' }}>
53
+ A label too long for this box.
54
+ </Box>
55
+
56
+ <Box observe="size" round={8} className="edge" style={{ width: 150, height: 56, overflowY: 'auto' }}>
57
+ One. Two. Three. Four. Five. Six. Seven. Eight. Nine. Ten.
58
+ </Box>
59
+ ```
60
+
61
+ ```css
62
+ .edge:state(clipped-x) { border-color: var(--border-3-warning); }
63
+ .edge:state(scrollable-y) { border-color: var(--border-3-info); }
64
+ ```
65
+
66
+ ### Fading a clipped edge
67
+
68
+ `fade` masks each edge that hides content. Scrolling moves the mask to the edge
69
+ that still hides content and removes it at the end. `fadeSize` sets its depth.
70
+
71
+ ```tsx
72
+ <Box fade fadeSize={32} round display="flex" gap={6} style={{ overflowX: 'auto' }}>
73
+ {TAGS.map((t) => <Tag key={t} size="small" label={t} />)}
74
+ </Box>
75
+ ```
76
+
77
+ The mask clips to the padding box. It preserves the Box border, shadows, and
78
+ focus ring when no edge is hidden.
79
+
80
+ The four edge states are `hidden-start-x`, `hidden-end-x`, `hidden-start-y`, and
81
+ `hidden-end-y`. Use `hiddenStartX` and its siblings in JSX. To style the states
82
+ without `fade`, add `observe="size"`.
83
+
84
+ ```css
85
+ /* <Box observe="size" className="my-box"> */
86
+ .my-box:state(hidden-end-x) {
87
+ mask-image: linear-gradient(to right, black calc(100% - 2rem), transparent);
88
+ }
89
+ ```
90
+
91
+ ### Tooltip on clipped content
92
+
93
+ `Tooltip truncatedOnly` uses Box's `isTruncated` getter. It shows when the Box
94
+ clips on either axis, including wrapped children.
95
+
96
+ ```tsx
97
+ const TAGS = ['frontend', 'design-system', 'a11y', 'performance']
98
+
99
+ <Box display="flex" round={8} gap={6} className="tag-box" style={{ height: 34 }}>
100
+ {TAGS.map((t) => <Tag key={t} size="small" label={t} />)}
101
+ <Tooltip truncatedOnly>{TAGS.join(' · ')}</Tooltip>
102
+ </Box>
103
+ ```
104
+
105
+ ```css
106
+ .tag-box { flex-wrap: wrap; padding: 6px; overflow: hidden; }
107
+ ```
108
+
109
+ ## Measurements
110
+
111
+ `measurechange` fires one frame after observation starts and after the Box or its
112
+ content changes. `changed` contains the moved fields, and `current` is the full
113
+ snapshot. The event pauses off screen and reports again when the Box returns.
114
+
115
+ ```tsx title="measurechange"
116
+ <Box
117
+ style={{ maxWidth: 210, overflow: 'hidden', whiteSpace: 'nowrap' }}
118
+ onMeasureChange={(_, { changed, current }) => {
119
+ // changed → { clippedX: true, scrollWidth: 412 }
120
+ if (changed.clippedX) setShowOverflowHint(current.clippedX)
121
+ }}
122
+ >
123
+ Long account name
124
+ </Box>
125
+ ```
126
+
127
+ The preview updates as you resize or scroll the Box. Tags mark `true` values.
128
+
129
+ [`BoxMeasurement`](#boxmeasurement) lists every field. Box maps each overflow
130
+ field to a kebab-case CSS state, such as `overflow-x`, `clipped-y`,
131
+ `scrollable-x`, and `hidden-end-y`. These states are not host attributes. They
132
+ remain current only while `fade`, `observe="size"`, or `onMeasureChange` enables
133
+ measurement.
134
+
135
+ ## Context
136
+
137
+ `contextchange` uses the same `{ changed, current }` shape. Boxes share one
138
+ context observer per window, which stops when the last context reader
139
+ disconnects.
140
+
141
+ [`BoxContext`](#boxcontext) lists every field. `contextchange` sets no CSS
142
+ states. Use `:focus-within`, mode classes, and media queries when CSS is the
143
+ reader.
144
+
145
+ Every value in the following preview is live. Switch the site theme, resize the
146
+ window, zoom the page, or use a touch device. Treat `osVersion` and
147
+ `browserVersion` as hints because browsers freeze parts of these values. Use
148
+ `pointer`, `hover`, or a feature test to gate behavior. The second Box is in a
149
+ `.light` scope, so its `mode` remains `light` on a dark page while `globalMode`
150
+ follows the document.
151
+
152
+ `context.font` is the Box's resolved text style, `context.inset` measures from
153
+ the border edge to the content edge, and `devicePixelRatio` provides the canvas
154
+ scale. Use them to align canvas text with the DOM.
155
+
156
+ `measurement` cannot derive the inset: `width` is the border box, and
157
+ `clientWidth` is the padding box.
158
+
159
+ Set `ctx.font` from `font.shorthand` first. Chromium and Firefox reset
160
+ `fontStretch`, `fontVariantCaps`, `fontKerning`, and `textRendering` when
161
+ `ctx.font` changes. `stretch` and `variantCaps` stay out of the shorthand because
162
+ a percentage `font-stretch` invalidates it.
163
+
164
+ ```tsx title="canvas"
165
+ <Box
166
+ onContextChange={(_, { current }) => {
167
+ const canvas = canvasRef.current
168
+ const ctx = canvas?.getContext('2d')
169
+ if (!ctx) return
170
+
171
+ const { font, devicePixelRatio: dpr } = current
172
+ canvas.width = 300 * dpr
173
+ canvas.height = 80 * dpr
174
+ ctx.scale(dpr, dpr)
175
+
176
+ ctx.font = font.shorthand
177
+ ctx.fontStretch = font.stretch
178
+ ctx.fontVariantCaps = font.variantCaps
179
+ ctx.fontKerning = font.kerning
180
+ ctx.textRendering = font.textRendering
181
+ ctx.letterSpacing = font.letterSpacing
182
+ ctx.wordSpacing = font.wordSpacing
183
+ ctx.direction = font.direction
184
+ ctx.fillStyle = font.color
185
+
186
+ const x = current.inset.borderLeft + current.inset.paddingLeft
187
+ const y = current.inset.borderTop + current.inset.paddingTop
188
+ ctx.fillText('Matches the DOM', x, y + (font.lineHeight ?? font.size))
189
+ }}
190
+ >
191
+ <canvas ref={canvasRef} />
192
+ </Box>
193
+ ```
194
+
195
+ `getComputedStyle(element).font` is empty in Chromium, Firefox, and WebKit, so
196
+ Box assembles the shorthand.
197
+
198
+ ```tsx title="contextchange"
199
+ <Box onContextChange={(_, { changed, current }) => {
200
+ if (changed.mode) updatePreviewTheme(current.mode)
201
+ if (changed.focusWithin) announceFocus(current.focusWithin)
202
+ }}>
203
+ <span>Preview content</span>
204
+ </Box>
205
+ ```
206
+
207
+ ### Props
208
+
209
+ | Prop | Type | Default | Description |
210
+ |------|------|---------|-------------|
211
+ | `display?` | BoxDisplay | block | Layout model for the host. All other layout, sizing, mask, and shadow
212
+ properties stay ordinary `className` / `style` CSS on the Box itself. |
213
+ | `fade?` | boolean | — | Fades out every edge that currently hides clipped content, and drops the
214
+ fade from an edge once the reader scrolls to it. |
215
+ | `fadeSize?` | number \| string | 24 | Depth of the `fade` gradient. A `number` is pixels; a string is any CSS
216
+ length. |
217
+ | `gap?` | number \| string | — | Gap between children, matching the CSS `gap` property. A `number` is
218
+ pixels; a string is any CSS length or two-value gap (`'1rem'`,
219
+ `'8px 16px'`). Applies while the Box is a flex or grid container. |
220
+ | `observe?` | 'size' \| 'context' \| 'all' | — | What the Box watches, when a handler is not what turns it on. `'size'`
221
+ keeps the overflow CSS states (`:state(clipped-x)`, `:state(scrollable-y)`,
222
+ …) current — reach for it when your own CSS is the only reader. `'context'`
223
+ and `'all'` are there for symmetry; passing `onMeasureChange` or
224
+ `onContextChange` already turns the matching half on. |
225
+ | `onContextChange?` | (event, detail) => void | — | Fired after Box's browser and local rendering context changes. `detail`
226
+ contains the changed fields and a full current snapshot. |
227
+ | `onMeasureChange?` | (event, detail) => void | — | Fired after Box geometry or its content-overflow state changes. `detail`
228
+ contains the changed fields and a full current snapshot. |
229
+ | `round?` | boolean \| number \| string | — | Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a
230
+ `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit
231
+ for square corners. |
232
+
233
+ ### BoxMeasurement
234
+
235
+ The `measurechange` payload, as `changed` (only what moved) and `current` (the
236
+ whole snapshot).
237
+
238
+ ### BoxContext
239
+
240
+ The `contextchange` payload, in the same two shapes.
241
+
242
+ ### BoxFont
243
+
244
+ `context.font`, the resolved text style.
245
+
246
+ ### BoxInset
247
+
248
+ `context.inset`, the distance from the border edge to the content edge.
249
+
250
+ Use `<a-box>` when you assemble DOM without a JSX wrapper. Both events are
251
+ ordinary, non-bubbling `CustomEvent`s carrying the same detail object.
252
+
253
+ ```html title="a-box"
254
+ <a-box display="grid" gap="8px" round="12px" observe="all" id="summary"
255
+ style="overflow: auto; max-height: 16rem">
256
+ <p>Summary content</p>
257
+ </a-box>
258
+
259
+ <script type="module">
260
+ import '@antadesign/anta/elements/a-box'
261
+
262
+ const box = document.querySelector('#summary')
263
+ box.addEventListener('measurechange', ({ detail }) => {
264
+ console.log(detail.changed, detail.current.scrollableY)
265
+ })
266
+ box.addEventListener('contextchange', ({ detail }) => {
267
+ console.log(detail.changed, detail.current.mode)
268
+ })
269
+ </script>
270
+ ```
271
+
272
+ `gap`, `round`, and `fade-size` read their length through typed `attr()`, which
273
+ today only Chrome supports. The matching custom properties in the host's inline
274
+ style work everywhere and take precedence, so that is what the JSX wrapper sets:
275
+
276
+ ```html
277
+ <a-box gap round fade
278
+ style="--box-gap: 8px; --box-round: 12px; --box-fade-size: 32px">Content</a-box>
279
+ ```
280
+
281
+ `box.measurement`, `box.context`, and `box.isTruncated` read the same values
282
+ synchronously.
@@ -108,8 +108,8 @@ copying final item `current` when it represents the current page.
108
108
  ```
109
109
 
110
110
  Use `copyUrl` to copy `location.href`, `copyNode` to copy a marked rendered
111
- region, or `copy` for a known string. `onCopyRequest` refreshes a reactive string
112
- before copying, and `onCopied` receives the result.
111
+ region, or `copy` for a known string. For dynamic text, pass a controlled `copy`
112
+ string and set it from `onCopyRequest`. `onCopied` receives the result.
113
113
 
114
114
  ### Props
115
115
 
@@ -288,11 +288,11 @@ carries a link back to where it came from.
288
288
  <ButtonCopy copy={snippet} copyWithUrl label="Copy snippet" /> {/* snippet + source URL */}
289
289
  ```
290
290
 
291
- ### Lazy content
291
+ ### Copy dynamic text
292
292
 
293
- When the content is expensive to compute or changes over time, keep `copy`
294
- reactive and refresh it in **`onCopyRequest`**, which fires on **pointerdown** —
295
- the click that follows copies the latest value.
293
+ `copy` is a controlled string. Start it at `''`, then set the new text in
294
+ `onCopyRequest`. The next render updates `copy`, and activation writes that value.
295
+ `onCopyRequest` does not return text. It does not add a polling loop.
296
296
 
297
297
  ```tsx
298
298
  const [report, setReport] = useState('')
@@ -300,16 +300,28 @@ const [report, setReport] = useState('')
300
300
  <ButtonCopy
301
301
  copy={report}
302
302
  label="Copy report"
303
- onCopyRequest={() => setReport(generateReport())} // recomputed on pointerdown
303
+ onCopyRequest={() => setReport(generateReport())}
304
304
  />
305
305
  ```
306
306
 
307
- The value is recomputed on pointerdown and read on the click a moment later, so
308
- the pointerdown→click gap absorbs the work — including an update that arrives from
309
- another thread. Only the serializable `copy` string crosses, through the normal
310
- re-render, so this holds in off-UI-thread runtimes (a worker-rendered app) where a
311
- callback couldn't. The clipboard API's user-gesture requirement is preserved
312
- because the write happens on the click.
307
+ `onCopyRequest` fires on pointerdown or Enter/Space keydown, before the click or
308
+ menu selection that writes to the clipboard.
309
+
310
+ #### Why `copy` is controlled
311
+
312
+ In a usual React or Preact app, an `onClick` handler can calculate a string and
313
+ call the Clipboard API itself. `ButtonCopy` also supports applications rendered
314
+ from a worker. The DOM copy control and Clipboard API are on the browser UI
315
+ thread, but the application's JSX code can be elsewhere. A function cannot cross
316
+ that boundary as a callable reference.
317
+
318
+ The request tells the application to calculate the text. Its state update makes
319
+ the renderer send the string as the `copy` attribute. The control then writes that
320
+ attribute during activation, when the browser permits clipboard access. A
321
+ `lazyCopy: () => string` prop could only hide this same state update inside the
322
+ wrapper. A generic return value would also need a defined clipboard format and
323
+ serialization; Anta supports text (`copy`), a DOM region (`copyNode`), and the
324
+ current URL (`copyUrl`).
313
325
 
314
326
  ### Props
315
327
 
@@ -342,10 +354,9 @@ because the write happens on the click.
342
354
  activation, and removes the button from the tab order while active. |
343
355
  | `onClick?` | (e) => void | — | Click handler. |
344
356
  | `onCopied?` | (ok) => void | — | Fires after the copy attempt with whether it succeeded. |
345
- | `onCopyRequest?` | () => void | — | Compute the copy content lazily. Fires on pointerdown / keydown; update
346
- `copy` (a state change) here and the activation copies the latest value.
347
- The gap lets the update land even off the UI thread — only the
348
- serializable `copy` string crosses. |
357
+ | `onCopyRequest?` | () => void | — | Refresh a dynamic `copy` value before activation. Set the new string in
358
+ application state so the next render updates `copy`. Return values are
359
+ ignored. Fires on pointerdown and Enter/Space keydown. |
349
360
  | `paddingless?` | boolean | — | Drops outer padding to zero. |
350
361
  | `ping?` | string | — | Space-separated URLs the browser pings on navigation. |
351
362
  | `priority?` | 'primary' \| 'secondary' \| 'tertiary' \| 'quaternary' | secondary | Visual emphasis. |
@@ -545,6 +545,10 @@ file name, with an Anta-styled chooser button. `submit` and `reset` inputs use t
545
545
  matching Anta button chrome; set `priority="primary"` on a submit control when it
546
546
  is the form's main action.
547
547
 
548
+ `<textarea data-anta>` uses the same field chrome. Set `rows` for its height; one
549
+ field-height is the minimum, and it resizes vertically. Native textareas do not
550
+ autogrow; use Input's [`multiline`](#multiline) instead.
551
+
548
552
  Use `data-anta-size="small"` or `"large"` for field sizing. Native inputs own
549
553
  their `size` attribute, so Anta does not reuse it. `round` and a custom-color
550
554
  `tone` work the same way as Input.
@@ -557,6 +561,7 @@ their `size` attribute, so Anta does not reuse it. `round` and a custom-color
557
561
  <input data-anta round type="url" name="site" placeholder="Rounded URL">
558
562
  <input data-anta type="password" name="password" value="password">
559
563
  <input data-anta tone="rebeccapurple" type="number" name="seats" value="3" min="1">
564
+ <textarea data-anta rows="3" name="notes" placeholder="Notes"></textarea>
560
565
  <input data-anta type="file" name="attachment" accept=".pdf,.docx">
561
566
 
562
567
  <input data-anta type="submit" value="Submit" priority="primary">
@@ -366,12 +366,12 @@ chevron stay centered on the row, so it composes with a trailing counter.
366
366
  </Menu>
367
367
  ```
368
368
 
369
- `MenuItemCopy` is a row that copies to the clipboard when chosen — `copy` for a
370
- literal string, `copyNode` for a DOM region. It composes a `<MenuItem>` with a
371
- slotted `<a-copy>` element that performs the write, and keeps the menu open on
372
- select so the check / retone feedback shows. `onCopied(ok)` fires after each
373
- attempt. `iconPlacement="trailing"` moves the feedback glyph to the end of the
374
- row; `"none"` shows a cursor-near confirmation instead.
369
+ `MenuItemCopy` is a row that copies to the clipboard when chosen: `copy` for a
370
+ string, `copyNode` for a DOM region, or `copyUrl` for the current URL. It composes
371
+ a `<MenuItem>` with a slotted `<a-copy>` element that performs the write, and
372
+ keeps the menu open on select so the check / retone feedback shows. `onCopied(ok)`
373
+ fires after each attempt. `iconPlacement="trailing"` moves the feedback glyph to
374
+ the end of the row; `"none"` shows a cursor-near confirmation instead.
375
375
 
376
376
  Copy sits alongside normal items, so a "Share" menu can mix a copy-link row with
377
377
  navigating links.
@@ -421,15 +421,18 @@ navigating links.
421
421
  carries `role` + `aria-checked`; only the drawn mark changes. Suppresses the
422
422
  built-in checkbox / radio and the trailing `check` glyph. The node is made
423
423
  passive (aria-hidden, no pointer events) so the row receives the click. |
424
- | `kbd?` | string | — | A trailing keyboard-shortcut hint, e.g. `"⌘E"`. |
424
+ | `kbd?` | string | — | Trailing shortcut hint, e.g. `"⌘E"`. Anta translates it to
425
+ `aria-keyshortcuts` and hides the visual glyphs from assistive technology.
426
+ Use `+` within a chord and spaces between shortcuts (`"⌘K ⌘S"` becomes
427
+ `"Meta+K Meta+S"`). Pass `aria-keyshortcuts` to override the translation.
428
+ This does not bind the shortcut. |
425
429
  | `label?` | ReactNode | — | The item's text. Usually a string, but any node is accepted — e.g. a filtered
426
430
  `Select` bolds the matched substring. Omit and pass `children` for richer
427
431
  content. |
428
432
  | `onCopied?` | (ok) => void | — | Fires after the copy attempt with whether it succeeded. |
429
- | `onCopyRequest?` | () => void | — | Compute the copy content lazily. Fires on pointerdown / keydown; update
430
- `copy` (a state change) here and the activation copies the latest value.
431
- The gap lets the update land even off the UI thread — only the
432
- serializable `copy` string crosses. |
433
+ | `onCopyRequest?` | () => void | — | Refresh a dynamic `copy` value before activation. Set the new string in
434
+ application state so the next render updates `copy`. Return values are
435
+ ignored. Fires on pointerdown and Enter/Space keydown. |
433
436
  | `onMouseDown?` | (event) => void | — | Raw `mousedown` on the row. Mainly to `preventDefault()` so the row doesn't
434
437
  take focus on a mouse press — e.g. a combobox option keeping focus in its
435
438
  input field while the click still selects. |
@@ -183,3 +183,50 @@ a-tag.badge a-tag-label { font-weight: 600; letter-spacing: 0.15ch; }
183
183
 
184
184
  For a *solid* recolor, prefer `tone` / `priority` — don't reach for the resolved
185
185
  `--tag-bg` / `--tag-border` / `--tag-text` (they're recomputed per priority and tone).
186
+
187
+ ### Interactive tags
188
+
189
+ `Tag` is presentational. Wrap it in an anchor for navigation or a native button
190
+ for an action. The outer control receives focus and keyboard activation.
191
+
192
+ ```tsx
193
+ <a className="tag-link" href="/builds/42">
194
+ <Tag tone="info" icon="external-link" label="Build" value="#42" />
195
+ </a>
196
+ ```
197
+
198
+ ```css
199
+ a.tag-link {
200
+ display: inline-flex;
201
+ text-decoration: none;
202
+ cursor: pointer;
203
+ }
204
+ ```
205
+
206
+ A removable tag has one interactive child: its remove `Button`. Keep the label
207
+ as tag content, give the button an accessible name, and let the segment divider
208
+ separate it. Remove the tag's block padding so the small button keeps its target
209
+ size. The negative focus offset keeps its focus ring inside the tag's clipped edge.
210
+
211
+ ```tsx
212
+ <Tag className="removable-tag" tone="info">
213
+ <span>Frontend</span>
214
+ <Button priority="quaternary" size="small" icon="x" aria-label="Remove Frontend" />
215
+ </Tag>
216
+ ```
217
+
218
+ ```css
219
+ a-tag.removable-tag {
220
+ padding-block: 0;
221
+ }
222
+ a-tag.removable-tag > a-button {
223
+ align-self: stretch;
224
+ }
225
+ a-tag.removable-tag > a-button:focus-visible {
226
+ outline-offset: -2px;
227
+ }
228
+ ```
229
+
230
+ When a tag both opens something and removes it, render two sibling controls in a
231
+ compound layout. Do not place a remove button inside an anchor or button that
232
+ wraps the tag.
package/docs/index.md CHANGED
@@ -21,6 +21,7 @@ Anta is designed for a clean DOM. Prefer the props and attributes exposed by eac
21
21
  - [Tag](./components/tag.md)
22
22
  - [Tooltip](./components/tooltip.md)
23
23
  - [Icon](./components/icon.md)
24
+ - [Avatar](./components/avatar.md)
24
25
  - [Loader](./components/loader.md)
25
26
  - [Progress](./components/progress.md)
26
27
 
@@ -33,9 +34,6 @@ Anta is designed for a clean DOM. Prefer the props and attributes exposed by eac
33
34
  - [Tabs](./components/tabs.md)
34
35
  - [Steps](./components/steps.md)
35
36
 
36
- - [Expander](./components/expander.md)
37
- - [Menu](./components/menu.md)
38
-
39
37
  - [Input](./components/input.md)
40
38
  - [InputAutocomplete](./components/input-autocomplete.md)
41
39
  - [InputDate](./components/input-date.md)
@@ -43,10 +41,13 @@ Anta is designed for a clean DOM. Prefer the props and attributes exposed by eac
43
41
  - [Select](./components/select.md)
44
42
  - [SelectFaceted](./components/select-faceted.md)
45
43
 
44
+ - [Box](./components/box.md)
46
45
  - [Banner](./components/banner.md)
47
46
  - [Card](./components/card.md)
48
47
  - [Dialog](./components/dialog.md)
49
48
  - [Toaster](./components/toaster.md)
49
+ - [Expander](./components/expander.md)
50
+ - [Menu](./components/menu.md)
50
51
 
51
52
  ## Packages
52
53