@antadesign/anta 0.3.18 → 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.
Files changed (59) hide show
  1. package/README.md +38 -242
  2. package/dist/components/Input.d.ts +4 -1
  3. package/dist/components/Input.js +2 -0
  4. package/dist/components/InputDate.css +1 -0
  5. package/dist/components/InputDate.d.ts +1 -0
  6. package/dist/components/InputDate.js +5 -5
  7. package/dist/components/Menu.d.ts +5 -1
  8. package/dist/components/Menu.js +3 -2
  9. package/dist/components/Select.d.ts +1 -0
  10. package/dist/components/Select.js +9 -13
  11. package/dist/components/SelectFaceted.d.ts +1 -0
  12. package/dist/components/SelectFaceted.js +29 -23
  13. package/dist/components/select-parts.css +1 -0
  14. package/dist/elements/a-input.css +1 -1
  15. package/dist/elements/a-menu.js +12 -0
  16. package/dist/elements/a-slider.css +1 -1
  17. package/dist/elements/a-tabs.css +1 -1
  18. package/dist/elements/a-tabs.d.ts +3 -0
  19. package/dist/elements/a-tabs.js +40 -1
  20. package/dist/general_types.d.ts +2 -0
  21. package/dist/jsx-runtime.d.ts +9 -0
  22. package/docs/accessibility.md +34 -0
  23. package/docs/changelog.md +551 -0
  24. package/docs/colors.md +42 -0
  25. package/docs/comparison.md +28 -0
  26. package/docs/components/banner.md +267 -0
  27. package/docs/components/button.md +603 -0
  28. package/docs/components/card.md +322 -0
  29. package/docs/components/checkbox.md +297 -0
  30. package/docs/components/dialog.md +314 -0
  31. package/docs/components/expander.md +529 -0
  32. package/docs/components/icon.md +155 -0
  33. package/docs/components/input-autocomplete.md +101 -0
  34. package/docs/components/input-date.md +406 -0
  35. package/docs/components/input-time.md +174 -0
  36. package/docs/components/input.md +610 -0
  37. package/docs/components/loader.md +139 -0
  38. package/docs/components/menu.md +447 -0
  39. package/docs/components/progress.md +182 -0
  40. package/docs/components/radio.md +369 -0
  41. package/docs/components/select-faceted.md +456 -0
  42. package/docs/components/select.md +931 -0
  43. package/docs/components/slider.md +142 -0
  44. package/docs/components/switch.md +198 -0
  45. package/docs/components/tabs.md +471 -0
  46. package/docs/components/tag.md +185 -0
  47. package/docs/components/text.md +148 -0
  48. package/docs/components/title.md +146 -0
  49. package/docs/components/toaster.md +313 -0
  50. package/docs/components/tooltip.md +177 -0
  51. package/docs/credits.md +22 -0
  52. package/docs/index.md +52 -0
  53. package/docs/install-config.md +240 -0
  54. package/docs/normalization.md +105 -0
  55. package/docs/overview.md +10 -0
  56. package/docs/packages/stickers.md +187 -0
  57. package/docs/packages/table.md +153 -0
  58. package/docs/theming.md +13 -0
  59. package/package.json +10 -6
@@ -0,0 +1,314 @@
1
+ # Dialog
2
+
3
+ A modal dialog: content that opens above a dimmed overlay and holds focus until
4
+ it's dismissed. The `Dialog` wrapper renders an `<a-dialog>` whose shadow DOM
5
+ wraps a native `<dialog>`, so the top layer (which escapes any `overflow` or
6
+ stacking context on the page), the focus trap, focus return on close, the
7
+ backdrop, and Esc all come from the platform.
8
+
9
+ It has three optional zones: a `header`, the body (`children`), and a `footer`,
10
+ plus a close button in the top-right corner. Set `position` to make it an edge
11
+ drawer instead.
12
+
13
+ ## Zones
14
+
15
+ `header`, `children` (the body), and `footer` are the three zones. The body
16
+ scrolls when it overflows the available height; the header and footer stay
17
+ pinned. Each zone is optional: drop `header` for a chromeless sheet, or `footer`
18
+ when there are no actions. The footer lays its content out in a right-aligned
19
+ row.
20
+
21
+ ```tsx
22
+ const [open, setOpen] = useState(false)
23
+
24
+ <Button onClick={() => setOpen(true)}>Open dialog</Button>
25
+ <Dialog
26
+ open={open}
27
+ onStateChange={(_e, { next }) => setOpen(next)}
28
+ header={<Title level={4}>Enable telemetry?</Title>}
29
+ footer={
30
+ <>
31
+ <Button onClick={() => setOpen(false)}>Not now</Button>
32
+ <Button priority="primary" onClick={() => setOpen(false)}>Enable</Button>
33
+ </>
34
+ }
35
+ >
36
+ <Text>Anonymous usage data helps us find the bugs that matter.</Text>
37
+ </Dialog>
38
+ ```
39
+
40
+ ## Position
41
+
42
+ `position` places the dialog. `center` (the default) is a modal centered on the
43
+ viewport, keeping a 32px gap from every edge. `left`, `right`, `top`, and
44
+ `bottom` turn it into an edge drawer: `left` / `right` run the full viewport
45
+ height and slide in from the side; `top` / `bottom` span the full width and slide
46
+ down / up. A drawer sits flush against its edge but keeps 32px of overlay on the
47
+ far side, so the backdrop stays visible however wide or tall you make it. Drawers
48
+ square their corners against the edges they touch. `fullscreen` fills the whole
49
+ viewport: no gap, no radius.
50
+
51
+ ```tsx
52
+ <Dialog position="left" open={open} onStateChange={…}>…</Dialog> {/* full-height left drawer */}
53
+ <Dialog position="right" open={open} onStateChange={…}>…</Dialog> {/* full-height right drawer */}
54
+ <Dialog position="top" open={open} onStateChange={…}>…</Dialog> {/* full-width top drawer */}
55
+ <Dialog position="bottom" open={open} onStateChange={…}>…</Dialog> {/* full-width bottom sheet */}
56
+ <Dialog position="fullscreen" open={open} onStateChange={…}>…</Dialog> {/* edge-to-edge, no gap */}
57
+ ```
58
+
59
+ A drawer's thickness is the axis it slides along: a `left` / `right` drawer's
60
+ width (380px by default), a `top` / `bottom` drawer's height (its content). The
61
+ cross axis fills the viewport, so the far-side space is `100vw`/`100dvh` minus
62
+ that thickness. Resize it with a position-scoped `::part(dialog)` rule (see
63
+ [Styling](#styling)).
64
+
65
+ ## Rounding corners
66
+
67
+ The centered modal is rounded by default (10px) and drawers are square. `round`
68
+ overrides that. A **side keyword** rounds only that edge's two corners, which
69
+ pairs with a drawer's exposed edge: a bottom sheet takes `round="top"`, a right
70
+ drawer `round="left"`.
71
+
72
+ ```tsx
73
+ <Dialog round={0}>…</Dialog> {/* unrounded centered modal */}
74
+ <Dialog position="bottom" round="top">…</Dialog> {/* round the sheet's top edge */}
75
+ <Dialog position="left" round="right">…</Dialog> {/* round a left drawer's inner edge */}
76
+ <Dialog round={24}>…</Dialog> {/* all corners, 24px */}
77
+ <Dialog round="12px 12px 0 0">…</Dialog> {/* any border-radius shorthand */}
78
+ ```
79
+
80
+ A `number` or CSS string sets the amount on all corners; the string may be a full
81
+ `border-radius` shorthand, so `round="12px 12px 0 0"` rounds just the top by 12px
82
+ (a side keyword with a custom amount). Bare `round` rounds all corners at the
83
+ default 10px, handy to round an otherwise-square drawer. Authoring the raw
84
+ `<a-dialog>`, a single length works through the typed `attr()` (`round="20px"`);
85
+ shorthands go through the wrapper's `round` prop.
86
+
87
+ ## Close button
88
+
89
+ `closable` controls one thing: whether the top-right ✕ is present (on by
90
+ default). The ✕ is just one way to close the dialog, alongside Esc, a backdrop
91
+ click, any `data-dialog-close` / footer action, and your own code. So
92
+ `closable={false}` only removes the ✕ (say, when the footer already carries a
93
+ Cancel button); the dialog stays closable through those other paths. The ✕
94
+ itself is a keyboard-focusable `Button` that requests a close like Esc and the
95
+ backdrop do.
96
+
97
+ ```tsx
98
+ <Dialog closable={false} header="Rename file" footer={…}>…</Dialog>
99
+ ```
100
+
101
+ ## Dismiss
102
+
103
+ "Dismissible" means light-dismiss: a backdrop click or Esc closes the dialog,
104
+ both on by default. `persistent` turns only that off. The dialog stays closable
105
+ through explicit controls (the ✕ when `closable`, a footer action, your own
106
+ code); `persistent` doesn't make it un-closable, it stops an accidental click or
107
+ stray Esc from dismissing it. Reach for it on an alert or destructive confirm
108
+ that should be answered deliberately.
109
+
110
+ ```tsx
111
+ <Dialog
112
+ persistent
113
+ header="Delete account?"
114
+ footer={
115
+ <>
116
+ <Button onClick={cancel}>Cancel</Button>
117
+ <Button priority="primary" tone="critical" onClick={confirm}>Delete</Button>
118
+ </>
119
+ }
120
+ >
121
+ <Text>This permanently removes your account and all its data.</Text>
122
+ </Dialog>
123
+ ```
124
+
125
+ A backdrop click dismisses only when the press **and** the release both land on
126
+ the backdrop. Selecting text and releasing outside the dialog won't close it,
127
+ and neither will clicking an option in a `Menu` or `Select` whose dropdown
128
+ overflows the dialog's box: that click lands on the popover. So a dialog can
129
+ hold interactive content that spills past its edges.
130
+
131
+ ## Triggers
132
+
133
+ Give the dialog a unique `name`, and any element with `data-dialog-open="{name}"`
134
+ opens it while `data-dialog-close="{name}"` closes it, with no state or handler.
135
+ This helps when the trigger sits far from the dialog in the markup. It's for
136
+ hand-authored or vanilla usage; controlled mode (below) ignores `name` and you
137
+ drive `open` yourself.
138
+
139
+ ```html
140
+ <a-button tabindex="0" data-dialog-open="settings">Settings</a-button>
141
+
142
+ <a-dialog name="settings">
143
+ <h2 slot="header">Settings</h2>
144
+ <p>…</p>
145
+ <a-button tabindex="0" slot="footer" data-dialog-close="settings">Done</a-button>
146
+ </a-dialog>
147
+ ```
148
+
149
+ ## Controlled vs uncontrolled
150
+
151
+ Open state follows Anta's shared **state contract**. Leave it *uncontrolled*
152
+ (seed with `defaultOpen`, or drive it with the `name` triggers) and the dialog
153
+ owns open/close. Pass `open` + `onStateChange` to *control* it: the dialog
154
+ follows `open` only, and every user dismiss (Esc, backdrop, close button)
155
+ *requests* a change that you apply to accept or ignore to reject. The wrapper
156
+ takes booleans and the element takes a `state` string enum; the wrapper maps
157
+ between them:
158
+
159
+ | | `<Dialog>` (JSX) | `<a-dialog>` (element) |
160
+ |---|---|---|
161
+ | **Uncontrolled** (element owns it) | `defaultOpen` / `name` triggers | `default-state="open" \| "closed"` / `name` |
162
+ | **Controlled** (you own it) | `open` + `onStateChange` | `state="open" \| "closed"` + `statechange` |
163
+ | **Change event** | `onStateChange(event, { next, prev })`, booleans | `statechange`, a `CustomEvent<{ next, prev }>` of `'open'` / `'closed'` |
164
+
165
+ `statechange` is **cancelable** and fires *before* the dialog applies the
166
+ change. Uncontrolled, call `event.preventDefault()` to veto a dismiss (e.g.
167
+ confirm before discarding unsaved edits). Controlled, the element never
168
+ self-applies; apply `detail.next` to `open` to accept, or do nothing to reject.
169
+
170
+ In controlled mode the element keeps the dialog in sync with your `open` value,
171
+ even against a native close it didn't route (a `<form method="dialog">` submit,
172
+ or calling `close()` on the underlying `<dialog>`). Such a close re-asserts your
173
+ state: the dialog reopens if `open` is still true. Drive closing through your
174
+ state, setting `open` to `false` in `onStateChange`.
175
+
176
+ ```tsx
177
+ // controlled
178
+ const [open, setOpen] = useState(false)
179
+ <Button onClick={() => setOpen(true)}>Open</Button>
180
+ <Dialog open={open} onStateChange={(_e, { next }) => setOpen(next)}>…</Dialog>
181
+
182
+ // uncontrolled, veto the close while there are unsaved changes
183
+ <Dialog defaultOpen onStateChange={(e, { next }) => {
184
+ if (!next && unsaved) e.preventDefault()
185
+ }}>…</Dialog>
186
+ ```
187
+
188
+ ## Focus
189
+
190
+ On open, focus goes to the **dialog itself** rather than a control inside it. So
191
+ the user Tabs to reach the first field, focus never lands on the ✕ or a
192
+ destructive button, and it works the same with or without a close button or
193
+ focusable content. A screen reader announces the dialog by its `header` text
194
+ (mirrored to the dialog's accessible name), and the ✕ is the last tab stop.
195
+
196
+ To focus a specific control on open instead (a search field, say), put
197
+ `autofocus` on it; the dialog honors that and skips the default. On close, focus
198
+ returns to whatever opened the dialog, from the native `<dialog>`.
199
+
200
+ ```tsx
201
+ <Dialog header="Rename" footer={…}>
202
+ <Input label="Name" autofocus /> {/* focus the field on open */}
203
+ </Dialog>
204
+ ```
205
+
206
+ ### Props
207
+
208
+ | Prop | Type | Default | Description |
209
+ |------|------|---------|-------------|
210
+ | `children?` | ReactNode | — | The dialog body. Scrolls when it overflows the available height. |
211
+ | `closable?` | boolean | true | Whether the top-right ✕ button is present. It's one way to close the dialog,
212
+ alongside Esc, the backdrop, a `data-dialog-close` / footer action, and your
213
+ own code; `false` just removes the ✕, it doesn't make the dialog un-closable. |
214
+ | `defaultOpen?` | boolean | — | Initial open state for the uncontrolled case (read once on mount). |
215
+ | `footer?` | ReactNode | — | Footer content, usually the action buttons. Rendered in the bottom zone as
216
+ a right-aligned row (wraps under pressure). Omit for none. |
217
+ | `header?` | ReactNode | — | Header content: a title, or any node. Rendered in the top zone. Omit for a
218
+ chromeless dialog with only a body. |
219
+ | `name?` | string | — | Uncontrolled trigger name. Any element with `data-dialog-open="{name}"`
220
+ opens this dialog, `data-dialog-close="{name}"` closes it. A convenience for
221
+ triggers rendered elsewhere; ignored in controlled mode. |
222
+ | `onStateChange?` | (event, detail) => void | — | Fired before the open state changes, on every user open or dismiss.
223
+ `event` is the cancelable `statechange`: call `event.preventDefault()` to
224
+ veto an *uncontrolled* transition. `detail.next` is the requested open
225
+ state, `detail.prev` the current one (booleans). In controlled mode, apply
226
+ `detail.next` to `open` to accept, or do nothing to reject. |
227
+ | `open?` | boolean | — | Controlled open state. When provided, the consumer owns open/close: the
228
+ dialog only follows this prop, and every user dismiss (Esc, backdrop, close
229
+ button) *requests* a change via `onStateChange` (reject by not updating).
230
+ Leave undefined for uncontrolled. |
231
+ | `persistent?` | boolean | — | Turn off light-dismiss: a backdrop click and Esc no longer close the dialog.
232
+ It stays closable through explicit controls (the ✕ when `closable`, a footer
233
+ action, your own code); `persistent` isn't "un-closable", it stops an
234
+ accidental click or stray Esc from dismissing. For an alert / confirm that
235
+ should be answered deliberately. Omit for the default dismissible behavior. |
236
+ | `position?` | 'center' \| 'left' \| 'right' \| 'top' \| 'bottom' \| 'fullscreen' | 'center' | Placement. `center` (the default) is a centered modal; `left` / `right` /
237
+ `top` / `bottom` turn it into an edge drawer (full height for left/right,
238
+ full width for top/bottom); `fullscreen` fills the whole viewport with no
239
+ edge gap or corner radius. |
240
+ | `round?` | boolean \| number \| 'top' \| 'right' \| 'bottom' \| 'left' \| (string & {}) | — | Corner radius. A side keyword — `'top'` / `'right'` / `'bottom'` / `'left'` —
241
+ rounds only that edge's two corners (at `--dialog-radius`), which pairs with
242
+ a drawer's exposed edge (a bottom sheet → `'top'`, a right drawer → `'left'`).
243
+ A `number` (px) or CSS string rounds all corners at that value; the string
244
+ may be a full `border-radius` shorthand (`'12px 12px 0 0'`). `true` rounds all
245
+ corners at `--dialog-radius`. Omit for the position default: `center` is
246
+ rounded, drawers and `fullscreen` are square. |
247
+
248
+ 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.
249
+
250
+ Slots hold the header, footer, and close control. Add `tabindex="0"` to every
251
+ raw `<a-button>`.
252
+
253
+ ```html
254
+ <a-button role="button" tabindex="0" data-dialog-open="invite"><a-button-label>Invite teammates</a-button-label></a-button>
255
+ <a-dialog name="invite">
256
+ <a-title slot="header" level="4" role="heading" aria-level="4">Invite teammates</a-title>
257
+ <a-text>Send an invite link to your team.</a-text>
258
+ <a-button slot="footer" role="button" tabindex="0" data-dialog-close="invite"><a-button-label>Cancel</a-button-label></a-button>
259
+ <a-button slot="footer" role="button" tabindex="0" priority="primary" data-dialog-close="invite"><a-button-label>Send</a-button-label></a-button>
260
+ <a-button slot="close" role="button" tabindex="0" priority="tertiary" size="large"
261
+ aria-label="Close" data-custom-event="closerequest"><a-icon shape="x" aria-hidden="true"></a-icon></a-button>
262
+ </a-dialog>
263
+ ```
264
+
265
+ Reach for the props first: **`position`** (modal, drawer edge, or fullscreen),
266
+ **`closable`** (the ✕), **`persistent`** (light-dismiss off), **`round`** (corner
267
+ radius). For everything else, style the shadow **parts** in plain CSS.
268
+
269
+ `<a-dialog>` exposes `::part(dialog)` (the surface), `::part(header)`,
270
+ `::part(body)`, `::part(footer)`, `::part(close)`, and `::part(dialog)::backdrop`
271
+ (the overlay). A `::part` rule wins over the element's internal styles with no
272
+ `!important`, so the surface, size, radius, shadow, overlay, and zone padding are
273
+ all reachable.
274
+
275
+ **Scope the rule to a position (or a class).** A bare `a-dialog::part(dialog)`
276
+ hits every dialog, drawers included, where a `width` override would collapse a
277
+ bottom sheet off its full-bleed edge. Target the one you mean: a class for a
278
+ specific dialog, `[position="right"]` for a drawer edge, `:not([position])` for
279
+ the centered modal (`center` carries no `position` attribute).
280
+
281
+ ```css
282
+ /* one dialog, via a class — nothing else is touched */
283
+ a-dialog.styled-dialog::part(dialog) {
284
+ width: 640px;
285
+ border-radius: 20px;
286
+ background: var(--bg-2);
287
+ }
288
+ a-dialog.styled-dialog::part(dialog)::backdrop {
289
+ background: color-mix(in oklch, black 60%, transparent);
290
+ }
291
+ ```
292
+
293
+ More recipes. The centered modal's max size is the viewport minus a 32px edge
294
+ gap; set `max-width` / `max-height` on the surface to change it. A drawer's
295
+ thickness is the axis it slides along, so scope it to that edge and the cross
296
+ axis stays full-bleed.
297
+
298
+ ```css
299
+ /* centered modal: size + the viewport edge gap (max-*) */
300
+ a-dialog:not([position])::part(dialog) { width: 720px; max-width: 90vw; max-height: 85dvh; }
301
+
302
+ /* a drawer's thickness — scoped to its edge */
303
+ a-dialog[position="right"]::part(dialog) { width: 480px; }
304
+ a-dialog[position="bottom"]::part(dialog) { height: 60dvh; }
305
+
306
+ /* zone padding, shadow, overlay (unscoped → every dialog) */
307
+ a-dialog::part(header) { padding: 24px 24px 12px; font-size: 20px; }
308
+ a-dialog::part(footer) { justify-content: space-between; }
309
+ a-dialog::part(dialog) { box-shadow: 0 20px 60px color-mix(in oklch, black 30%, transparent); }
310
+ a-dialog::part(dialog)::backdrop { background: color-mix(in oklch, black 50%, transparent); }
311
+ ```
312
+
313
+ If you override the surface, shadow, or overlay, add a `.dark` rule to match —
314
+ the built-in defaults track light / dark, a fixed override won't.