@uniflowed/ui 0.0.0-alpha.4 → 0.0.0-alpha.40

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 (52) hide show
  1. package/accordion.js +360 -0
  2. package/alert-dialog.js +282 -0
  3. package/alert.js +142 -0
  4. package/avatar.js +276 -0
  5. package/breadcrumb.js +138 -0
  6. package/calendar.js +547 -0
  7. package/carousel.js +410 -0
  8. package/checkbox.js +216 -31
  9. package/collapsible.js +169 -0
  10. package/combobox.js +209 -40
  11. package/context-menu.js +206 -0
  12. package/date-picker.js +346 -0
  13. package/dialog.js +229 -197
  14. package/drawer.js +490 -0
  15. package/field.js +257 -42
  16. package/hover-card.js +330 -0
  17. package/index.js +1548 -24
  18. package/input-otp.js +218 -0
  19. package/interactions.js +2323 -0
  20. package/internal/anchor.js +565 -0
  21. package/internal/date-grid.js +260 -0
  22. package/internal/disclosure.js +298 -0
  23. package/internal/focus.js +64 -0
  24. package/internal/form-value.js +83 -0
  25. package/internal/hover-intent.js +259 -0
  26. package/internal/menu-tree.js +228 -0
  27. package/internal/merge-props.js +206 -7
  28. package/internal/range.js +147 -0
  29. package/internal/roving-focus.js +205 -11
  30. package/menu.js +521 -336
  31. package/menubar.js +288 -0
  32. package/navigation-menu.js +251 -0
  33. package/package.json +8 -12
  34. package/pagination.js +209 -0
  35. package/popover.js +344 -0
  36. package/progress.js +91 -0
  37. package/radio-group.js +302 -0
  38. package/resizable.js +447 -0
  39. package/scroll-area.js +283 -0
  40. package/select.js +888 -0
  41. package/separator.js +97 -0
  42. package/sheet.js +189 -0
  43. package/sidebar.js +313 -0
  44. package/skeleton.js +159 -0
  45. package/slider.js +405 -0
  46. package/switch.js +43 -34
  47. package/table.js +520 -0
  48. package/tabs.js +99 -96
  49. package/toast.js +592 -0
  50. package/toggle-group.js +282 -0
  51. package/toggle.js +105 -0
  52. package/tooltip.js +400 -0
package/index.js CHANGED
@@ -32,6 +32,105 @@
32
32
  // A library written in TypeScript can document those constraints; it cannot
33
33
  // state them.
34
34
  //
35
+ // # The copy step copies the look, never the behaviour
36
+ //
37
+ // shadcn's product is not a component. It is `npx shadcn add dialog`, which
38
+ // writes the source into your repository so that you own it and change it —
39
+ // and around that sit `init`, `view`, `search`, `build`, `migrate`, `eject`, an
40
+ // MCP server, a `components.json` and a registry format anybody can publish to.
41
+ // The roadmap first answered that with "typed imports, preset styles, and
42
+ // **no copy step**", and ubugeeei-prod/uf#303 is where the answer was argued.
43
+ // ubugeeei-prod/uf#947 kept half of it and reversed the other half, and the
44
+ // half it kept is this package.
45
+ //
46
+ // **Why this package is still never copied.** A copy is a fork with no
47
+ // upstream, and four things follow. A focus trap fixed here reaches everyone
48
+ // who upgrades and reaches nobody who copied. The composition constraints above
49
+ // are checked *across the boundary*: `Tabs.List` declaring `renders* Tabs.Tab`
50
+ // means something while the library is imported and means nothing once the
51
+ // source has been pasted into an application, because then it is the
52
+ // application's own component and Flow has nothing left to hold it to.
53
+ // `sideEffects: false` and one module per primitive already give a bundler
54
+ // everything a copy would. And the accessibility work stays in one place with
55
+ // one suite over it, rather than in every consumer's repository at the version
56
+ // they took it at.
57
+ //
58
+ // **What `uf ui add` copies instead.** The styled layer. `uf ui add dialog`
59
+ // writes `app/components/ui/dialog.js`, which imports its parts from
60
+ // `@uniflowed/ui` and owns the scrim, the spacing and the tone of the
61
+ // trigger — what a person means when they say "our dialog". None of the four
62
+ // arguments above is reopened by it, because none of them is about the look:
63
+ // the focus trap still arrives by upgrade, and the copied `TabsList` still takes
64
+ // `renders* TabsTab` over a `TabsTab` that renders this package's tab, so the
65
+ // constraint holds in the application's own file. `crates/uf_ui`'s header is
66
+ // the decision in full.
67
+ //
68
+ // **What a caller gets without copying anything.** The reason people copy
69
+ // is to change the markup, so that has to be answered or this is a worse
70
+ // library for the same use. The answer is `render`, which every part that
71
+ // renders an element of its own is growing:
72
+ //
73
+ // <Menu.Item render={(props) => <a href="/settings" {...props} />}>
74
+ // Settings
75
+ // </Menu.Item>
76
+ //
77
+ // The part computes every attribute, every id, every composed handler and every
78
+ // ref exactly as it would have, and hands them to the caller to put on their
79
+ // own element; `children` is among them, so a caller who spreads and
80
+ // self-closes still gets what was written between the tags. What it does *not*
81
+ // hand over is anything true of the element rather than of the part —
82
+ // `type="button"` stays on the `<button>` branch.
83
+ //
84
+ // It is deliberately not Radix's `asChild`. Cloning a child hides which props
85
+ // arrived and in what order; a function is handed the object, so a caller can
86
+ // read it, order it themselves, and drop one on purpose. And the part is still
87
+ // the part: `Menu.Body`'s `renders*` still rejects a `<div>` where a
88
+ // `Menu.Item` belongs, because a `Menu.Item` rendered as an `<a>` is a
89
+ // `Menu.Item`. That is the half a copied source cannot keep, and it is what
90
+ // makes leaving this package uncopied a trade rather than a loss.
91
+ //
92
+ // **Where it is, today.** Every fixed-element part has it in `Accordion`,
93
+ // `Alert`, `AlertDialog`, `Avatar`, `Breadcrumb`, `Collapsible`, `Dialog`,
94
+ // `Drawer`, `Menu`, `ContextMenu`, `Menubar`, `Pagination`, `Popover`, `Sheet`,
95
+ // `Skeleton`, `Slider`, `Table`, `Tabs` and `ToggleGroup`; so do the
96
+ // single-part `Checkbox`, `Progress`, `Separator`, `Switch` and `Toggle`.
97
+ // `Field.Root`, `Field.Label`, `Field.Control`, `Field.Description`,
98
+ // `Field.Status` and `Field.Error` take it too, as do `RadioGroup`,
99
+ // `InputOtp.Separator`, `Tooltip.Trigger`, `Tooltip.Body`,
100
+ // `HoverCard.Trigger`, `HoverCard.Body` and `Sidebar.Item`. A documented
101
+ // escape hatch that is not there is worse than an undocumented one that is, so
102
+ // the state of every part is a table in `packages/ui/ui.test.js` rather than a
103
+ // claim in this paragraph: it names each part as implemented, no-element, or
104
+ // fixed remainder, and the suite holds the lists to the exported files. A part
105
+ // added to this barrel is in none of them and the suite says so.
106
+ //
107
+ // **Are the `internal/` modules ever public?** No, and the consequence is
108
+ // worth stating rather than leaving as an omission. `merge-props.js`,
109
+ // `roving-focus.js`, `controlled-state.js` and the rest each explain in their
110
+ // own header why exporting them would publish a weaker promise than the
111
+ // components make — a consumer who could reach `merge-props.js` could build a
112
+ // part that spreads `rest` last, which is the failure it exists to prevent. So
113
+ // there is no third-party primitive that participates the way these do, and no
114
+ // registry of them: the uf-shaped equivalent of publishing a component is a
115
+ // pull request against this package, where its keyboard map gets the same suite
116
+ // as everything else. That is a real cost of the decision and the right side of
117
+ // it for a library whose value is that the hard parts are correct. What a third
118
+ // party *can* build on is the escape hatch itself, which is the whole public
119
+ // surface it needs: a component of theirs given to `render` receives the props
120
+ // this package would have used, and their own composition sits inside a part
121
+ // that is still checked.
122
+ //
123
+ // **What the CLI adds.** `uf ui add`, `uf ui list` and `uf ui diff`, over a
124
+ // registry of styled components in `registry/ui/` that is embedded into the
125
+ // binary — each one built on parts exported here, and none of them a copy of
126
+ // one. There is still no registry of *primitives*, for the reason the previous
127
+ // paragraph gives, and there is no `components.json`: `uf.config.js` is the one
128
+ // configuration surface by design, so a UI option belongs there rather than in
129
+ // a second file. The affordance `shadcn view` has — "tell me what this
130
+ // component is made of" — is still `uf inspect --json` for the parts, out of
131
+ // `crates/uf_lib/src/ui.rs`, which `cargo test -p uf_lib` holds to this barrel
132
+ // in both directions.
133
+ //
35
134
  // # Styling is a default, not a dependency
36
135
  //
37
136
  // Nothing here imports StyleX, and nothing here has a StyleX-shaped type. A
@@ -39,6 +138,71 @@
39
138
  // the same components with exactly the same behaviour; the design-system layer
40
139
  // that adds uf's default styles is built *on* these, not into them.
41
140
  //
141
+ // # What is not here, and where it went instead
142
+ //
143
+ // About twenty of the catalogue this package is measured against have no
144
+ // behaviour at all. Badge, Card, Button, Input, Textarea, Label and Aspect
145
+ // Ratio are, between them, a class list and a `<div>`. For a library whose
146
+ // product *is* the styles that is coherent — you copy them in and you own
147
+ // them. It is not coherent here: a `Badge` with no styles is a `<span>`, a
148
+ // `Card` with no styles is a `<div>`, and shipping them from a package that
149
+ // ships no styles would make this a library of empty elements. Shipping them
150
+ // from `@uniflowed/stylex` would make *that* a component library, which its
151
+ // own header forbids. So for a long time the answer on record was both and
152
+ // neither, which is ubugeeei-prod/uf#298.
153
+ //
154
+ // The line that holds is not "styled versus headless". It is **whether the
155
+ // thing has a decision in it**:
156
+ //
157
+ // - An ARIA decision, a state machine or a keyboard requirement makes it a
158
+ // component here, even when it renders a single element. `Progress` is one
159
+ // `<div>`, and it belongs, because the conditional that omits
160
+ // `aria-valuenow` when the amount is unknown — rather than sending
161
+ // `aria-valuenow={0}`, which says "nothing has happened" — is the whole
162
+ // component. `Toggle` and `Checkbox` are the same shape for the same reason.
163
+ // - Anything that is only a class list belongs in `@uniflowed/stylex/preset`,
164
+ // which already has the right form: `buttonStyles({ tone, size })`,
165
+ // `cardStyles()`, `textStyles({ size, tone })` and `fieldStyles()` return
166
+ // `{ className }` for a caller to spread onto their own element. That is a
167
+ // better answer than a `<Badge>`, not a lesser one — a component wrapping a
168
+ // `<button>` takes away `type="submit"`, `formAction`, the ref and every
169
+ // attribute nobody thought to forward, and gives back a class name the
170
+ // caller could have written.
171
+ //
172
+ // This is stated rather than left as an omission, because an omission reads as
173
+ // an oversight and the next contributor closes it with a `<Badge>`.
174
+ // `crates/uf_lib/src/ui.rs` carries the same decision as data: those entries
175
+ // are `Declined` with the preset functions that replace them named on each,
176
+ // and `cargo test -p uf_lib` fails if a name there stops existing.
177
+ //
178
+ // Five of that twenty are on the other side of the line and now ship — Alert,
179
+ // Avatar, Breadcrumb, Separator and Skeleton — each for one specific reason,
180
+ // and each of them one or two elements:
181
+ //
182
+ // - **Alert** is the one whose usual shape is arguably wrong to copy.
183
+ // `role="alert"` is a live region, an element already in the document when
184
+ // the page loads announces on insertion or not at all, and a permanently
185
+ // rendered "your trial ends soon" box carrying that role is either an
186
+ // interruption on every page load or silence. So the role is behind `live`,
187
+ // and a static callout does not get one. `field.js` already makes that call
188
+ // correctly for `Field.Error`.
189
+ // - **Avatar** is a three-state machine — loading, loaded, failed — with the
190
+ // fallback held back so a cached image does not flash somebody's initials,
191
+ // and with `alt=""` by default, because an avatar beside a name that puts
192
+ // the name in `alt` makes every screen reader say it twice.
193
+ // - **Breadcrumb** is `Pagination`'s shape one door along: a named `<nav>`, one
194
+ // `aria-current="page"`, and separators hidden so the trail is not read as
195
+ // "Home slash Settings slash Billing".
196
+ // - **Separator** is two lines with one decision in them, and it is the
197
+ // decision `Progress` is: `role="separator"` with an `aria-orientation` for a
198
+ // boundary a reader should be told about, `aria-hidden` for a rule that is
199
+ // only a rule.
200
+ // - **Skeleton** is the one that silently makes a page worse. A screen of
201
+ // skeletons is a screen of empty boxes, so the boxes are `aria-hidden`, the
202
+ // region they stand in is `aria-busy`, and a live region that was empty for
203
+ // one commit says "Loading" — which is #289's rule met at the moment it bites
204
+ // hardest, because a skeleton screen is busy on its very first render.
205
+ //
42
206
  // # What these components promise React
43
207
  //
44
208
  // Nothing here mutates during a render, reads a ref during a render, or depends
@@ -50,9 +214,16 @@
50
214
  // Where a component has to learn something the DOM knows — how many options a
51
215
  // caller filtered down to, which item the arrow key should move to — it reads
52
216
  // the document in an effect or an event handler and, if a render depends on
53
- // the answer, puts it in state. It is deliberately *not* `useSyncExternalStore`:
54
- // that is for a store whose value a render reads, and reading layout during a
55
- // render is the thing it exists to prevent.
217
+ // the answer, puts it in state. That is deliberately *not*
218
+ // `useSyncExternalStore`: the DOM is not a store whose value a render may
219
+ // read, and reading layout during a render is the thing that API exists to
220
+ // prevent.
221
+ //
222
+ // One component does use it, and it is the case the API is actually for.
223
+ // `toast("Saved")` is called from an event handler or a `catch`, so the queue
224
+ // of notifications lives outside React — an atom in `@uniflowed/state`, read
225
+ // through `useSyncExternalStore` with the cached immutable snapshots and the
226
+ // consistent server snapshot that requires. A queue is a store; the DOM is not.
56
227
  //
57
228
  // # Server and client
58
229
  //
@@ -67,32 +238,148 @@
67
238
  // One root module per primitive, each with its own `exports` subpath, each
68
239
  // named after the thing it implements:
69
240
  //
70
- // - `dialog.js` — the focus trap, focus restore, scroll lock and inert page.
71
- // - `menu.js` — the arrow keys, typeahead, submenus and `Escape` stacking.
72
- // - `combobox.js` — `aria-activedescendant` over a filtered list, and the
73
- // count a screen reader is told.
241
+ // - `dialog.js` — the focus trap, focus restore, scroll lock and inert page,
242
+ // and the three props the four components below it differ from it by.
243
+ // - `alert-dialog.js`, `sheet.js`, `drawer.js` and `sidebar.js` — the four
244
+ // built on that one. An alert dialog is modal and cannot be dismissed by
245
+ // pressing beside it; a sheet is a dialog with an edge; a drawer is a sheet
246
+ // with a gesture, and therefore with WCAG 2.5.7's keyboard equivalent of it;
247
+ // a sidebar is most often neither modal nor a dialog, and becomes both on a
248
+ // narrow viewport.
249
+ // - `carousel.js`, `scroll-area.js` and `input-otp.js` — the three that replace
250
+ // something the browser already does, and so the three that have to be better
251
+ // than what they replaced. Each module's header says what it gives that the
252
+ // plain element does not; if it ever stops being true, the component should
253
+ // be deleted rather than fixed.
254
+ // - `menu.js`, `context-menu.js` and `menubar.js` — the arrow keys, typeahead,
255
+ // submenus and `Escape` stacking, and the two components that are that
256
+ // behaviour opened differently. A context menu is opened by the right button,
257
+ // by `Shift+F10` and by a long press, and opens at a *point*; a menubar is a
258
+ // row of them with one tab stop and arrows that walk between the open menus.
259
+ // shadcn's fourth menu, Dropdown Menu, is `menu.js` under another name and is
260
+ // deliberately not a second export.
261
+ // - `combobox.js` — `aria-activedescendant` over a filtered list, the count a
262
+ // screen reader is told, and the option groups that make a command palette a
263
+ // composition rather than a seventh module.
264
+ // - `select.js` — the other half of the combobox pattern: the select-only one,
265
+ // with typeahead, option groups and a value a form can submit.
74
266
  // - `tabs.js` — the roving `tabindex`, and automatic versus manual activation.
267
+ // - `toast.js` — the live region that was watching before there was anything
268
+ // to announce, and the countdown that stops.
75
269
  // - `field.js` — the label, description, error and `aria-invalid` wiring.
76
- // - `switch.js` and `checkbox.js` — the two two-state controls, apart because
77
- // the third state and the `Enter` key genuinely differ between them.
270
+ // - `switch.js`, `checkbox.js` and `toggle.js` — the three two-state controls,
271
+ // apart because a reader is told something different by each, and because the
272
+ // third state and the `Enter` key genuinely differ between them.
273
+ // - `radio-group.js` — one answer out of several, and the tab stop an
274
+ // unanswered group would otherwise not have.
275
+ // - `toggle-group.js` — a row of toggle buttons as one control, whose `single`
276
+ // mode is a radio group and is rendered by `radio-group.js` rather than
277
+ // written a second time.
278
+ // - `collapsible.js`, `accordion.js` and `navigation-menu.js` — the disclosure
279
+ // pattern on its own, stacked, and applied to a site's navigation. The third
280
+ // of those exists as much to prevent `role="menu"` from being used for a list
281
+ // of links as to provide anything.
282
+ // - `slider.js`, `resizable.js` and `progress.js` — the three that report a
283
+ // number in a range. A window splitter is a slider wearing a separator's
284
+ // role, which is why it is beside one rather than with the layout.
285
+ // - `table.js` and `pagination.js` — the sort that is announced, the selection
286
+ // that can be mixed, and the rows a page is not showing.
287
+ // - `popover.js`, `tooltip.js` and `hover-card.js` — the three anchored
288
+ // overlays, which are one component seen from three distances: one you
289
+ // click, one you hover, and one you hover and then read. They are three
290
+ // modules because what a reader is told differs in every one — a popover is
291
+ // a dialog that is not modal, a tooltip describes its trigger and may never
292
+ // take focus, a hover card is neither and holds links — and because a flag
293
+ // selecting between them would be one flag every behaviour had to read.
294
+ // - `alert.js`, `avatar.js`, `breadcrumb.js`, `separator.js` and `skeleton.js`
295
+ // — the five above that look like a class list and are not. One or two
296
+ // elements each, and one conditional each: whether a callout announces
297
+ // itself, which of three states an image is in, whether the last crumb is a
298
+ // link, whether a rule is in the accessibility tree, and whether anybody is
299
+ // told the page is loading.
300
+ // - `interactions.js` — the press, the hover, the focus ring, the long press,
301
+ // the drag and the key, and the one module here that exports hooks rather
302
+ // than parts. A press that is released outside does not press, a hover is
303
+ // never a finger's, a ring is drawn for the keyboard and not for the pointer,
304
+ // and a screen reader's click is a press; its header says what each rule
305
+ // prevents and where it differs from React Aria's.
78
306
  //
79
- // Every name below is exported from one of those, so a consumer may import
80
- // `@uniflowed/ui` or `@uniflowed/ui/dialog` and get the same thing. The split
81
- // is by primitive because that is the unit a reader looks for, the unit a
82
- // bundler drops, and the unit the WAI-ARIA practices are written in.
307
+ // Every name below is exported from one of those, and this file is the only way
308
+ // to import any of them: `package.json` exports it alone. The split is by
309
+ // primitive because that is the unit a reader looks for, the unit a bundler
310
+ // drops, and the unit the WAI-ARIA practices are written in.
83
311
  //
84
- // `internal/` holds three modules and nothing else, each a rule the primitives
312
+ // `internal/` holds ten modules and nothing else, each a rule the primitives
85
313
  // must apply identically and a consumer must not be able to apply differently:
86
314
  // `merge-props.js` (the caller's props go on first, the component's semantics
87
- // last), `controlled-state.js` (what "controlled" means here), and
88
- // `roving-focus.js` (how a set of items is found and moved between). Each says
89
- // in its own header why it is unreachable rather than exported. There is no
90
- // `internal/props.js`-shaped bag of helpers: a module that cannot say what it
91
- // is about does not belong in this package.
315
+ // last), `controlled-state.js` (what "controlled" means here),
316
+ // `roving-focus.js` (how a set of items is found and moved between),
317
+ // `disclosure.js` (how a button says whether a region is showing, and how a
318
+ // closed region stays findable), `form-value.js` (what a `<form>` submits for a
319
+ // control the browser has never heard of), `range.js` (the arithmetic that
320
+ // keeps `aria-valuemin`, `aria-valuemax` and `aria-valuenow` true about each
321
+ // other), `anchor.js` (where an overlay goes, and what it does when it does not
322
+ // fit where it was asked to go), `focus.js` (which elements a reader can reach,
323
+ // which a focus trap and a popover want opposite things from), and
324
+ // `hover-intent.js` (what WCAG requires of content shown on hover or focus,
325
+ // which is three clauses and one mechanism), and `menu-tree.js` (the chain of
326
+ // open menus the three menu components share, and what `Escape` and choosing an
327
+ // item are defined in terms of). Each says in its own header why it is
328
+ // unreachable rather than exported. There is no `internal/props.js`-shaped
329
+ // bag of helpers: a module that cannot say what it is about does not belong in
330
+ // this package.
92
331
 
332
+ import {
333
+ AccordionContent,
334
+ AccordionHeader,
335
+ AccordionItem,
336
+ AccordionRoot,
337
+ AccordionTrigger,
338
+ } from "./accordion.js";
339
+ import { AlertDescription, AlertRoot, AlertTitle } from "./alert.js";
340
+ import {
341
+ AlertDialogAction,
342
+ AlertDialogBody,
343
+ AlertDialogCancel,
344
+ AlertDialogDescription,
345
+ AlertDialogFooter,
346
+ AlertDialogHeader,
347
+ AlertDialogOverlay,
348
+ AlertDialogRoot,
349
+ AlertDialogTitle,
350
+ AlertDialogTrigger,
351
+ } from "./alert-dialog.js";
352
+ import { AvatarFallback, AvatarImage, AvatarRoot } from "./avatar.js";
353
+ import {
354
+ BreadcrumbItem,
355
+ BreadcrumbLink,
356
+ BreadcrumbList,
357
+ BreadcrumbPage,
358
+ BreadcrumbRoot,
359
+ BreadcrumbSeparator,
360
+ } from "./breadcrumb.js";
361
+ import {
362
+ CarouselContent,
363
+ CarouselItem,
364
+ CarouselNext,
365
+ CarouselPause,
366
+ CarouselPrevious,
367
+ CarouselRoot,
368
+ } from "./carousel.js";
369
+ import {
370
+ CalendarDay,
371
+ CalendarMonth,
372
+ CalendarNext,
373
+ CalendarPrevious,
374
+ CalendarRoot,
375
+ } from "./calendar.js";
93
376
  import { Checkbox } from "./checkbox.js";
377
+ import { ContextMenuRoot, ContextMenuTrigger } from "./context-menu.js";
378
+ import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "./collapsible.js";
94
379
  import {
95
380
  ComboboxEmpty,
381
+ ComboboxGroup,
382
+ ComboboxGroupLabel,
96
383
  ComboboxInput,
97
384
  ComboboxLabel,
98
385
  ComboboxList,
@@ -100,6 +387,12 @@ import {
100
387
  ComboboxRoot,
101
388
  ComboboxStatus,
102
389
  } from "./combobox.js";
390
+ import {
391
+ DatePickerCalendar,
392
+ DatePickerInput,
393
+ DatePickerRoot,
394
+ DatePickerTrigger,
395
+ } from "./date-picker.js";
103
396
  import {
104
397
  DialogBody,
105
398
  DialogClose,
@@ -111,24 +404,443 @@ import {
111
404
  DialogTitle,
112
405
  DialogTrigger,
113
406
  } from "./dialog.js";
114
- import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from "./field.js";
407
+ import {
408
+ DrawerBody,
409
+ DrawerClose,
410
+ DrawerDescription,
411
+ DrawerFooter,
412
+ DrawerHandle,
413
+ DrawerHeader,
414
+ DrawerOverlay,
415
+ DrawerRoot,
416
+ DrawerTitle,
417
+ DrawerTrigger,
418
+ } from "./drawer.js";
419
+ import {
420
+ FieldControl,
421
+ FieldDescription,
422
+ FieldError,
423
+ FieldLabel,
424
+ FieldRoot,
425
+ FieldStatus,
426
+ } from "./field.js";
427
+ import { HoverCardBody, HoverCardRoot, HoverCardTrigger } from "./hover-card.js";
428
+ import { InputOtpGroup, InputOtpRoot, InputOtpSeparator, InputOtpSlot } from "./input-otp.js";
115
429
  import {
116
430
  MenuBody,
431
+ MenuCheckboxItem,
117
432
  MenuGroup,
118
433
  MenuItem,
119
434
  MenuLabel,
435
+ MenuRadioGroup,
436
+ MenuRadioItem,
120
437
  MenuRoot,
121
438
  MenuSeparator,
122
439
  MenuSub,
123
440
  MenuSubTrigger,
124
441
  MenuTrigger,
125
442
  } from "./menu.js";
443
+ import { MenubarMenu, MenubarRoot, MenubarTrigger } from "./menubar.js";
444
+ import {
445
+ NavigationMenuBody,
446
+ NavigationMenuItem,
447
+ NavigationMenuLink,
448
+ NavigationMenuList,
449
+ NavigationMenuRoot,
450
+ NavigationMenuTrigger,
451
+ } from "./navigation-menu.js";
452
+ import {
453
+ PaginationContent,
454
+ PaginationItem,
455
+ PaginationNext,
456
+ PaginationPrevious,
457
+ PaginationRoot,
458
+ } from "./pagination.js";
459
+ import { PopoverBody, PopoverRoot, PopoverTrigger } from "./popover.js";
460
+ import { Progress } from "./progress.js";
461
+ import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from "./radio-group.js";
462
+ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./resizable.js";
463
+ import { ScrollAreaRoot, ScrollAreaScrollbar, ScrollAreaViewport } from "./scroll-area.js";
464
+ import {
465
+ SelectGroup,
466
+ SelectGroupLabel,
467
+ SelectLabel,
468
+ SelectList,
469
+ SelectOption,
470
+ SelectRoot,
471
+ SelectSeparator,
472
+ SelectTrigger,
473
+ SelectValue,
474
+ } from "./select.js";
475
+ import { Separator } from "./separator.js";
476
+ import {
477
+ SheetBody,
478
+ SheetClose,
479
+ SheetDescription,
480
+ SheetFooter,
481
+ SheetHeader,
482
+ SheetOverlay,
483
+ SheetRoot,
484
+ SheetTitle,
485
+ SheetTrigger,
486
+ } from "./sheet.js";
487
+ import {
488
+ SidebarBody,
489
+ SidebarFooter,
490
+ SidebarHeader,
491
+ SidebarItem,
492
+ SidebarRoot,
493
+ SidebarTrigger,
494
+ } from "./sidebar.js";
495
+ import { SkeletonBox, SkeletonRoot } from "./skeleton.js";
496
+ import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from "./slider.js";
126
497
  import { Switch } from "./switch.js";
498
+ import {
499
+ TableBody,
500
+ TableCaption,
501
+ TableCell,
502
+ TableHead,
503
+ TableHeader,
504
+ TableRoot,
505
+ TableRow,
506
+ TableRowHeader,
507
+ TableRowSelect,
508
+ TableSelectAll,
509
+ } from "./table.js";
127
510
  import { TabsList, TabsPanel, TabsRoot, TabsTab } from "./tabs.js";
511
+ import {
512
+ ToastAction,
513
+ ToastClose,
514
+ ToastDescription,
515
+ ToastRegion,
516
+ ToastRoot,
517
+ ToastTitle,
518
+ dismissAllToasts,
519
+ dismissToast,
520
+ toast,
521
+ updateToast,
522
+ } from "./toast.js";
523
+ import { Toggle } from "./toggle.js";
524
+ import { ToggleGroupItem, ToggleGroupRoot } from "./toggle-group.js";
525
+ import { TooltipBody, TooltipProvider, TooltipRoot, TooltipTrigger } from "./tooltip.js";
128
526
 
527
+ export type { AccordionType } from "./accordion.js";
528
+ export type { AvatarStatus } from "./avatar.js";
529
+ // A date, however a caller had one to hand: a `PlainDate` from
530
+ // `@uniflowed/temporal`, or the ISO 8601 string a form field or a URL carries.
531
+ export type { DateValue } from "./calendar.js";
129
532
  export type { ActivationMode } from "./tabs.js";
533
+ // What a modal announces itself as, for a caller who holds one in a variable.
534
+ // Two members, not a string: see `dialog.js`.
535
+ export type { DialogRole } from "./dialog.js";
536
+ // Which edge of the viewport a sheet or a drawer is attached to. `Sidebar` has
537
+ // its own two-member union, because a sidebar is never on the top or bottom.
538
+ export type { Edge } from "./sheet.js";
539
+ export type { FieldSource } from "./field.js";
540
+ export type { InputOtpKind } from "./input-otp.js";
541
+ export type { MenuSelect } from "./menu.js";
542
+ // Which way a carousel, a scroll area or a separator runs.
543
+ export type { Orientation } from "./separator.js";
544
+ export type { SidebarSide } from "./sidebar.js";
545
+ // Where an anchored overlay opens, for a caller who holds one in a variable or
546
+ // a prop of their own. Unions rather than strings, so `side="botom"` is a type
547
+ // error at the call rather than an overlay that quietly opens somewhere else.
548
+ // `LogicalSide` is the same four plus `inline-start` and `inline-end`, which
549
+ // are the ones that mean "the way the reader reads" - what a submenu opens
550
+ // onto, and the left of the page in Arabic.
551
+ export type { Align, LogicalSide, Side } from "./popover.js";
552
+ export type { Sort } from "./table.js";
553
+ export type { Notification, ToastChanges, ToastOptions, Urgency } from "./toast.js";
554
+ export type { ToggleGroupType } from "./toggle-group.js";
130
555
 
131
- export { Checkbox, Switch };
556
+ /**
557
+ * Every part, under the name its module gives it.
558
+ *
559
+ * This file is the only entry point the package has — `package.json` exports
560
+ * `.` and nothing else — so a name a module exports and this list leaves out is
561
+ * a name nobody can import. The namespaces below are the same parts spelled for
562
+ * composing a page (`Dialog.Root`); these are for the wrapper, the preset and
563
+ * the test that want one part (`import { DialogRoot } from "@uniflowed/ui"`).
564
+ * `sideEffects: false` lets a bundler keep the module a name comes from and drop
565
+ * the rest, and `uf_rsc` names that module, not the package, as the client
566
+ * boundary of a Server Component that imports the name.
567
+ */
568
+ export {
569
+ AccordionContent,
570
+ AccordionHeader,
571
+ AccordionItem,
572
+ AccordionRoot,
573
+ AccordionTrigger,
574
+ AlertDescription,
575
+ AlertDialogAction,
576
+ AlertDialogBody,
577
+ AlertDialogCancel,
578
+ AlertDialogDescription,
579
+ AlertDialogFooter,
580
+ AlertDialogHeader,
581
+ AlertDialogOverlay,
582
+ AlertDialogRoot,
583
+ AlertDialogTitle,
584
+ AlertDialogTrigger,
585
+ AlertRoot,
586
+ AlertTitle,
587
+ AvatarFallback,
588
+ AvatarImage,
589
+ AvatarRoot,
590
+ BreadcrumbItem,
591
+ BreadcrumbLink,
592
+ BreadcrumbList,
593
+ BreadcrumbPage,
594
+ BreadcrumbRoot,
595
+ BreadcrumbSeparator,
596
+ CalendarDay,
597
+ CalendarMonth,
598
+ CalendarNext,
599
+ CalendarPrevious,
600
+ CalendarRoot,
601
+ CarouselContent,
602
+ CarouselItem,
603
+ CarouselNext,
604
+ CarouselPause,
605
+ CarouselPrevious,
606
+ CarouselRoot,
607
+ CollapsibleContent,
608
+ CollapsibleRoot,
609
+ CollapsibleTrigger,
610
+ ComboboxEmpty,
611
+ ComboboxGroup,
612
+ ComboboxGroupLabel,
613
+ ComboboxInput,
614
+ ComboboxLabel,
615
+ ComboboxList,
616
+ ComboboxOption,
617
+ ComboboxRoot,
618
+ ComboboxStatus,
619
+ ContextMenuRoot,
620
+ ContextMenuTrigger,
621
+ DatePickerCalendar,
622
+ DatePickerInput,
623
+ DatePickerRoot,
624
+ DatePickerTrigger,
625
+ DialogBody,
626
+ DialogClose,
627
+ DialogDescription,
628
+ DialogFooter,
629
+ DialogHeader,
630
+ DialogOverlay,
631
+ DialogRoot,
632
+ DialogTitle,
633
+ DialogTrigger,
634
+ DrawerBody,
635
+ DrawerClose,
636
+ DrawerDescription,
637
+ DrawerFooter,
638
+ DrawerHandle,
639
+ DrawerHeader,
640
+ DrawerOverlay,
641
+ DrawerRoot,
642
+ DrawerTitle,
643
+ DrawerTrigger,
644
+ FieldControl,
645
+ FieldDescription,
646
+ FieldError,
647
+ FieldLabel,
648
+ FieldRoot,
649
+ FieldStatus,
650
+ HoverCardBody,
651
+ HoverCardRoot,
652
+ HoverCardTrigger,
653
+ InputOtpGroup,
654
+ InputOtpRoot,
655
+ InputOtpSeparator,
656
+ InputOtpSlot,
657
+ MenuBody,
658
+ MenuCheckboxItem,
659
+ MenuGroup,
660
+ MenuItem,
661
+ MenuLabel,
662
+ MenuRadioGroup,
663
+ MenuRadioItem,
664
+ MenuRoot,
665
+ MenuSeparator,
666
+ MenuSub,
667
+ MenuSubTrigger,
668
+ MenuTrigger,
669
+ MenubarMenu,
670
+ MenubarRoot,
671
+ MenubarTrigger,
672
+ NavigationMenuBody,
673
+ NavigationMenuItem,
674
+ NavigationMenuLink,
675
+ NavigationMenuList,
676
+ NavigationMenuRoot,
677
+ NavigationMenuTrigger,
678
+ PaginationContent,
679
+ PaginationItem,
680
+ PaginationNext,
681
+ PaginationPrevious,
682
+ PaginationRoot,
683
+ PopoverBody,
684
+ PopoverRoot,
685
+ PopoverTrigger,
686
+ RadioGroupIndicator,
687
+ RadioGroupItem,
688
+ RadioGroupRoot,
689
+ ResizableHandle,
690
+ ResizablePanel,
691
+ ResizablePanelGroup,
692
+ ScrollAreaRoot,
693
+ ScrollAreaScrollbar,
694
+ ScrollAreaViewport,
695
+ SelectGroup,
696
+ SelectGroupLabel,
697
+ SelectLabel,
698
+ SelectList,
699
+ SelectOption,
700
+ SelectRoot,
701
+ SelectSeparator,
702
+ SelectTrigger,
703
+ SelectValue,
704
+ SheetBody,
705
+ SheetClose,
706
+ SheetDescription,
707
+ SheetFooter,
708
+ SheetHeader,
709
+ SheetOverlay,
710
+ SheetRoot,
711
+ SheetTitle,
712
+ SheetTrigger,
713
+ SidebarBody,
714
+ SidebarFooter,
715
+ SidebarHeader,
716
+ SidebarItem,
717
+ SidebarRoot,
718
+ SidebarTrigger,
719
+ SkeletonBox,
720
+ SkeletonRoot,
721
+ SliderRange,
722
+ SliderRoot,
723
+ SliderThumb,
724
+ SliderTrack,
725
+ TableBody,
726
+ TableCaption,
727
+ TableCell,
728
+ TableHead,
729
+ TableHeader,
730
+ TableRoot,
731
+ TableRow,
732
+ TableRowHeader,
733
+ TableRowSelect,
734
+ TableSelectAll,
735
+ TabsList,
736
+ TabsPanel,
737
+ TabsRoot,
738
+ TabsTab,
739
+ ToastAction,
740
+ ToastClose,
741
+ ToastDescription,
742
+ ToastRegion,
743
+ ToastRoot,
744
+ ToastTitle,
745
+ ToggleGroupItem,
746
+ ToggleGroupRoot,
747
+ TooltipBody,
748
+ TooltipProvider,
749
+ TooltipRoot,
750
+ TooltipTrigger,
751
+ };
752
+
753
+ /**
754
+ * The five that are one component rather than a namespace of parts.
755
+ *
756
+ * Each takes `render`, so the control or line a design system already has — a
757
+ * `<div>` with a knob drawn in it, somebody's `<Pressable>`, a presentational
758
+ * meter shell — keeps the role, state, keys and attributes while being their
759
+ * element. See the module headers and the table in `packages/ui/ui.test.js`.
760
+ */
761
+ export { Checkbox, Progress, Separator, Switch, Toggle };
762
+
763
+ /**
764
+ * Queueing a notification, from anywhere.
765
+ *
766
+ * Functions rather than a hook, because the places a notification comes from —
767
+ * an event handler, a `catch`, a Server Action's error path — do not have a
768
+ * component to hold state in. `Toast.Region` is what displays them.
769
+ *
770
+ * const id = toast("Uploading…", { duration: null });
771
+ * updateToast(id, { content: "Uploaded", duration: 4000 });
772
+ * toast("Could not save", { urgency: "assertive" });
773
+ */
774
+ export { dismissAllToasts, dismissToast, toast, updateToast };
775
+
776
+ /**
777
+ * The interactions every part here is made of, for a control of the caller's own.
778
+ *
779
+ * `usePress` is a press from a pointer, a key or assistive technology, with one
780
+ * set of events; `useHover` is a mouse's and a pen's and never a finger's;
781
+ * `useFocusRing` draws a ring for keyboard focus and not for pointer focus;
782
+ * `useLongPress`, `useMove` and `useKeyboard` are the rest; `mergeProps` puts
783
+ * several of them on one element. `interactions.js` says what each one gets
784
+ * right, and where it differs from React Aria's, at length.
785
+ *
786
+ * const { isPressed, pressProps } = usePress({ onPress: save });
787
+ * const { focusProps, isFocusVisible } = useFocusRing();
788
+ * <div {...mergeProps(pressProps, focusProps)} role="button" tabIndex={0} />
789
+ *
790
+ * Hooks rather than parts, and the one module here that exports them: its rules
791
+ * are about input devices rather than about markup this package builds, so a
792
+ * control written with them is the same control a part is rather than a weaker
793
+ * copy of one.
794
+ */
795
+ export {
796
+ getInteractionModality,
797
+ mergeProps,
798
+ useFocusRing,
799
+ useFocusVisible,
800
+ useHover,
801
+ useInteractionModality,
802
+ useInteractOutside,
803
+ useKeyboard,
804
+ useLongPress,
805
+ useMove,
806
+ usePress,
807
+ } from "./interactions.js";
808
+ export type {
809
+ FocusRingOptions,
810
+ FocusRingProps,
811
+ FocusRingResult,
812
+ FocusVisibleResult,
813
+ HoverEvent,
814
+ HoverOptions,
815
+ HoverProps,
816
+ HoverResult,
817
+ InteractionEvent,
818
+ InteractionProps,
819
+ InteractOutsideOptions,
820
+ InteractOutsideRef,
821
+ KeyboardInteraction,
822
+ KeyboardOptions,
823
+ KeyboardProps,
824
+ KeyboardResult,
825
+ LongPressEvent,
826
+ LongPressOptions,
827
+ LongPressProps,
828
+ LongPressResult,
829
+ Modality,
830
+ MoveEndEvent,
831
+ MoveMoveEvent,
832
+ MoveOptions,
833
+ MovePointerType,
834
+ MoveProps,
835
+ MoveResult,
836
+ MoveStartEvent,
837
+ PhysicalPointer,
838
+ PointerType,
839
+ PressEvent,
840
+ PressOptions,
841
+ PressProps,
842
+ PressResult,
843
+ } from "./interactions.js";
132
844
 
133
845
  /**
134
846
  * An accessible form field.
@@ -137,14 +849,31 @@ export { Checkbox, Switch };
137
849
  * <Field.Label>Email</Field.Label>
138
850
  * <Field.Control render={(props) => <input type="email" {...props} />} />
139
851
  * <Field.Description>We will not share it.</Field.Description>
852
+ * <Field.Status>{saving ? "Saving…" : ""}</Field.Status>
140
853
  * <Field.Error>{error}</Field.Error>
141
854
  * </Field.Root>
855
+ *
856
+ * Inside a form, `field` replaces the hand-written `invalid`: the form says
857
+ * whether the field is wrong and what the message is, and the field composes
858
+ * every `aria-*` from that in one place. It also marks the control busy during
859
+ * submit, validation, or async default loading. `@uniflowed/form`'s
860
+ * `useFieldSource` is what produces one, and `field.js`'s header says why the
861
+ * hook lives there rather than here.
862
+ *
863
+ * const email = useFieldSource(form, "email", { required: "We need one" });
864
+ * <Field.Root field={email}>…<Field.Error /></Field.Root>
865
+ *
866
+ * `group` is for a set with no single control to point a `<label for>` at — a
867
+ * radio group, a checkbox group, three selects making a date. The root becomes
868
+ * `role="group"` named by the label, and the description and the error describe
869
+ * the set.
142
870
  */
143
871
  export const Field = {
144
872
  Root: FieldRoot,
145
873
  Label: FieldLabel,
146
874
  Control: FieldControl,
147
875
  Description: FieldDescription,
876
+ Status: FieldStatus,
148
877
  Error: FieldError,
149
878
  };
150
879
 
@@ -162,6 +891,11 @@ export const Field = {
162
891
  * <Tabs.Panel value="one">…</Tabs.Panel>
163
892
  * <Tabs.Panel value="two">…</Tabs.Panel>
164
893
  * </Tabs.Root>
894
+ *
895
+ * Every part takes `render`, so a tab that is also a route — `<Tabs.Tab
896
+ * render={(props) => <a href="#billing" {...props} />}>` — is still a tab, with
897
+ * the roving tab stop and the `aria-controls` a tab has. `Tabs.List`'s
898
+ * `renders* Tabs.Tab` is unaffected, because it is the *part* it constrains.
165
899
  */
166
900
  export const Tabs = {
167
901
  Root: TabsRoot,
@@ -170,6 +904,114 @@ export const Tabs = {
170
904
  Panel: TabsPanel,
171
905
  };
172
906
 
907
+ /**
908
+ * A button and the region it shows, with the three attributes that say so.
909
+ *
910
+ * The content stays in the document while it is closed, so the browser's
911
+ * find-in-page can still reach the text in it.
912
+ *
913
+ * <Collapsible.Root>
914
+ * <Collapsible.Trigger>Details</Collapsible.Trigger>
915
+ * <Collapsible.Content>…</Collapsible.Content>
916
+ * </Collapsible.Root>
917
+ */
918
+ export const Collapsible = {
919
+ Root: CollapsibleRoot,
920
+ Trigger: CollapsibleTrigger,
921
+ Content: CollapsibleContent,
922
+ };
923
+
924
+ /**
925
+ * A stack of disclosures that know about each other.
926
+ *
927
+ * `Accordion.Header` takes the heading `level`, because which heading an
928
+ * accordion's sections are depends on where the accordion sits. Each panel is a
929
+ * region named after the header that opens it.
930
+ *
931
+ * <Accordion.Root type="single">
932
+ * <Accordion.Item value="shipping">
933
+ * <Accordion.Header level={3}>
934
+ * <Accordion.Trigger>Shipping</Accordion.Trigger>
935
+ * </Accordion.Header>
936
+ * <Accordion.Content>…</Accordion.Content>
937
+ * </Accordion.Item>
938
+ * </Accordion.Root>
939
+ */
940
+ export const Accordion = {
941
+ Root: AccordionRoot,
942
+ Item: AccordionItem,
943
+ Header: AccordionHeader,
944
+ Trigger: AccordionTrigger,
945
+ Content: AccordionContent,
946
+ };
947
+
948
+ /**
949
+ * Site navigation: a list of links behind buttons, and not a `menu`.
950
+ *
951
+ * <NavigationMenu.Root aria-label="Main">
952
+ * <NavigationMenu.List>
953
+ * <NavigationMenu.Item value="docs">
954
+ * <NavigationMenu.Trigger>Docs</NavigationMenu.Trigger>
955
+ * <NavigationMenu.Body>
956
+ * <NavigationMenu.Link href="/guide">Guide</NavigationMenu.Link>
957
+ * </NavigationMenu.Body>
958
+ * </NavigationMenu.Item>
959
+ * </NavigationMenu.List>
960
+ * </NavigationMenu.Root>
961
+ */
962
+ export const NavigationMenu = {
963
+ Root: NavigationMenuRoot,
964
+ List: NavigationMenuList,
965
+ Item: NavigationMenuItem,
966
+ Trigger: NavigationMenuTrigger,
967
+ Body: NavigationMenuBody,
968
+ Link: NavigationMenuLink,
969
+ };
970
+
971
+ /**
972
+ * One answer out of several, with the arrow keys that check as they move.
973
+ *
974
+ * `Tab` reaches the chosen answer, or the first one while there is none, and
975
+ * leaves the whole group in one press. `name` puts the answer where a form can
976
+ * submit it.
977
+ *
978
+ * <Field.Root>
979
+ * <Field.Label>Plan</Field.Label>
980
+ * <Field.Control
981
+ * render={(props) => (
982
+ * <RadioGroup.Root {...props} defaultValue="free" name="plan">
983
+ * <RadioGroup.Item value="free">
984
+ * Free <RadioGroup.Indicator>●</RadioGroup.Indicator>
985
+ * </RadioGroup.Item>
986
+ * <RadioGroup.Item value="pro">Pro</RadioGroup.Item>
987
+ * </RadioGroup.Root>
988
+ * )}
989
+ * />
990
+ * </Field.Root>
991
+ */
992
+ export const RadioGroup = {
993
+ Root: RadioGroupRoot,
994
+ Item: RadioGroupItem,
995
+ Indicator: RadioGroupIndicator,
996
+ };
997
+
998
+ /**
999
+ * A row of toggle buttons that behaves as one control.
1000
+ *
1001
+ * `type="multiple"` is a group of toggle buttons, any number of them pressed.
1002
+ * `type="single"` is a radio group drawn as segments, and is rendered by
1003
+ * `RadioGroup` rather than written a second time.
1004
+ *
1005
+ * <ToggleGroup.Root aria-label="Formatting" type="multiple">
1006
+ * <ToggleGroup.Item value="bold">B</ToggleGroup.Item>
1007
+ * <ToggleGroup.Item value="italic">I</ToggleGroup.Item>
1008
+ * </ToggleGroup.Root>
1009
+ */
1010
+ export const ToggleGroup = {
1011
+ Root: ToggleGroupRoot,
1012
+ Item: ToggleGroupItem,
1013
+ };
1014
+
173
1015
  /**
174
1016
  * A modal dialog: focus moved in, kept in, and given back.
175
1017
  *
@@ -186,6 +1028,12 @@ export const Tabs = {
186
1028
  * </Dialog.Footer>
187
1029
  * </Dialog.Body>
188
1030
  * </Dialog.Root>
1031
+ *
1032
+ * Every part takes `render`. `Dialog.Title` is an `<h2>` by default and the
1033
+ * level is a fact about the page around it rather than about the dialog, so
1034
+ * `render={(props) => <h3 {...props} />}` is how a caller says which — without
1035
+ * losing the id `aria-labelledby` points at. `AlertDialog`, `Sheet` and
1036
+ * `Drawer` are made of these parts and pass `render` straight through.
189
1037
  */
190
1038
  export const Dialog = {
191
1039
  Root: DialogRoot,
@@ -199,6 +1047,199 @@ export const Dialog = {
199
1047
  Close: DialogClose,
200
1048
  };
201
1049
 
1050
+ /**
1051
+ * The confirmation: modal, announced as an alert, and not dismissible by a
1052
+ * press beside it.
1053
+ *
1054
+ * Focus lands on `Cancel` rather than on the first thing in the dialog, and the
1055
+ * description is required — `role="alertdialog"` exists to announce one, so an
1056
+ * alert dialog without it interrupts the reader to say nothing.
1057
+ *
1058
+ * <AlertDialog.Root>
1059
+ * <AlertDialog.Trigger>Delete</AlertDialog.Trigger>
1060
+ * <AlertDialog.Overlay />
1061
+ * <AlertDialog.Body>
1062
+ * <AlertDialog.Header>
1063
+ * <AlertDialog.Title>Delete this project?</AlertDialog.Title>
1064
+ * <AlertDialog.Description>This cannot be undone.</AlertDialog.Description>
1065
+ * </AlertDialog.Header>
1066
+ * <AlertDialog.Footer>
1067
+ * <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
1068
+ * <AlertDialog.Action onClick={remove}>Delete</AlertDialog.Action>
1069
+ * </AlertDialog.Footer>
1070
+ * </AlertDialog.Body>
1071
+ * </AlertDialog.Root>
1072
+ */
1073
+ export const AlertDialog = {
1074
+ Root: AlertDialogRoot,
1075
+ Trigger: AlertDialogTrigger,
1076
+ Overlay: AlertDialogOverlay,
1077
+ Body: AlertDialogBody,
1078
+ Header: AlertDialogHeader,
1079
+ Footer: AlertDialogFooter,
1080
+ Title: AlertDialogTitle,
1081
+ Description: AlertDialogDescription,
1082
+ Action: AlertDialogAction,
1083
+ Cancel: AlertDialogCancel,
1084
+ };
1085
+
1086
+ /**
1087
+ * A modal dialog attached to an edge of the viewport.
1088
+ *
1089
+ * `side` is a union rather than a class name, and every part reports it as
1090
+ * `data-side` — the same attribute `Popover.Body` writes, so one stylesheet
1091
+ * rule covers every overlay in this package.
1092
+ *
1093
+ * <Sheet.Root side="left">
1094
+ * <Sheet.Trigger>Filters</Sheet.Trigger>
1095
+ * <Sheet.Overlay />
1096
+ * <Sheet.Body>
1097
+ * <Sheet.Title>Filters</Sheet.Title>
1098
+ * <Sheet.Close>Done</Sheet.Close>
1099
+ * </Sheet.Body>
1100
+ * </Sheet.Root>
1101
+ */
1102
+ export const Sheet = {
1103
+ Root: SheetRoot,
1104
+ Trigger: SheetTrigger,
1105
+ Overlay: SheetOverlay,
1106
+ Body: SheetBody,
1107
+ Header: SheetHeader,
1108
+ Footer: SheetFooter,
1109
+ Title: SheetTitle,
1110
+ Description: SheetDescription,
1111
+ Close: SheetClose,
1112
+ };
1113
+
1114
+ /**
1115
+ * The sheet you can drag away, with the keyboard that can do everything the
1116
+ * drag can.
1117
+ *
1118
+ * `Drawer.Handle` is a `role="slider"` over the snap points: the arrow keys
1119
+ * move between them, `Home` and `End` go to the ends, and the closing key at
1120
+ * the smallest snap point closes it. WCAG 2.5.7 also wants a single-pointer
1121
+ * alternative, so a drawer with a handle and no `Drawer.Close` raises.
1122
+ *
1123
+ * <Drawer.Root side="bottom" snapPoints={[0.4, 1]}>
1124
+ * <Drawer.Trigger>Details</Drawer.Trigger>
1125
+ * <Drawer.Overlay />
1126
+ * <Drawer.Body>
1127
+ * <Drawer.Handle label="Resize the details" />
1128
+ * <Drawer.Title>Details</Drawer.Title>
1129
+ * <Drawer.Close>Close</Drawer.Close>
1130
+ * </Drawer.Body>
1131
+ * </Drawer.Root>
1132
+ */
1133
+ export const Drawer = {
1134
+ Root: DrawerRoot,
1135
+ Trigger: DrawerTrigger,
1136
+ Overlay: DrawerOverlay,
1137
+ Body: DrawerBody,
1138
+ Handle: DrawerHandle,
1139
+ Header: DrawerHeader,
1140
+ Footer: DrawerFooter,
1141
+ Title: DrawerTitle,
1142
+ Description: DrawerDescription,
1143
+ Close: DrawerClose,
1144
+ };
1145
+
1146
+ /**
1147
+ * Navigation beside the page, which becomes a modal sheet on a narrow one.
1148
+ *
1149
+ * `Sidebar.Item` takes a `label` and keeps it as the button's accessible name
1150
+ * the moment the sidebar collapses to icons — which is the whole reason a
1151
+ * collapsing sidebar is a component rather than a class.
1152
+ *
1153
+ * <Sidebar.Root defaultOpen={fromCookie}>
1154
+ * <Sidebar.Trigger>Menu</Sidebar.Trigger>
1155
+ * <Sidebar.Body label="Main">
1156
+ * <Sidebar.Item label="Settings">
1157
+ * <Gear /> Settings
1158
+ * </Sidebar.Item>
1159
+ * </Sidebar.Body>
1160
+ * </Sidebar.Root>
1161
+ */
1162
+ export const Sidebar = {
1163
+ Root: SidebarRoot,
1164
+ Trigger: SidebarTrigger,
1165
+ Header: SidebarHeader,
1166
+ Body: SidebarBody,
1167
+ Footer: SidebarFooter,
1168
+ Item: SidebarItem,
1169
+ };
1170
+
1171
+ /**
1172
+ * Slides, one at a time, that a reader can stop and cannot fall into.
1173
+ *
1174
+ * `Carousel.Pause` is WCAG 2.2.2's mechanism and must be the first focusable
1175
+ * thing inside the carousel; the slides that are not showing are `inert`, so
1176
+ * `Tab` cannot reach a link nobody can see.
1177
+ *
1178
+ * <Carousel.Root autoplay={5000} count={3} label="Featured">
1179
+ * <Carousel.Pause />
1180
+ * <Carousel.Content>
1181
+ * <Carousel.Item index={0}>…</Carousel.Item>
1182
+ * <Carousel.Item index={1}>…</Carousel.Item>
1183
+ * <Carousel.Item index={2}>…</Carousel.Item>
1184
+ * </Carousel.Content>
1185
+ * <Carousel.Previous />
1186
+ * <Carousel.Next />
1187
+ * </Carousel.Root>
1188
+ */
1189
+ export const Carousel = {
1190
+ Root: CarouselRoot,
1191
+ Content: CarouselContent,
1192
+ Item: CarouselItem,
1193
+ Pause: CarouselPause,
1194
+ Previous: CarouselPrevious,
1195
+ Next: CarouselNext,
1196
+ };
1197
+
1198
+ /**
1199
+ * An overflow container a keyboard can actually scroll.
1200
+ *
1201
+ * `role="region"`, a name and `tabindex="0"`, because a scroll container is not
1202
+ * focusable in every browser and one that is not is one a keyboard reader can
1203
+ * see the top of and nothing else.
1204
+ *
1205
+ * <ScrollArea.Root label="Release notes">
1206
+ * <ScrollArea.Viewport>…</ScrollArea.Viewport>
1207
+ * <ScrollArea.Scrollbar orientation="vertical" />
1208
+ * </ScrollArea.Root>
1209
+ */
1210
+ export const ScrollArea = {
1211
+ Root: ScrollAreaRoot,
1212
+ Viewport: ScrollAreaViewport,
1213
+ Scrollbar: ScrollAreaScrollbar,
1214
+ };
1215
+
1216
+ /**
1217
+ * A one-time code: six boxes drawn over one real `<input>`.
1218
+ *
1219
+ * One input, so `autocomplete="one-time-code"` works, a paste fills every box,
1220
+ * and a form submits one value under one name.
1221
+ *
1222
+ * <InputOtp.Root label="One-time code" length={6} name="code">
1223
+ * <InputOtp.Group>
1224
+ * <InputOtp.Slot index={0} />
1225
+ * <InputOtp.Slot index={1} />
1226
+ * <InputOtp.Slot index={2} />
1227
+ * </InputOtp.Group>
1228
+ * <InputOtp.Separator>-</InputOtp.Separator>
1229
+ * <InputOtp.Group>
1230
+ * <InputOtp.Slot index={3} />
1231
+ * <InputOtp.Slot index={4} />
1232
+ * <InputOtp.Slot index={5} />
1233
+ * </InputOtp.Group>
1234
+ * </InputOtp.Root>
1235
+ */
1236
+ export const InputOtp = {
1237
+ Root: InputOtpRoot,
1238
+ Group: InputOtpGroup,
1239
+ Slot: InputOtpSlot,
1240
+ Separator: InputOtpSeparator,
1241
+ };
1242
+
202
1243
  /**
203
1244
  * A menu, with the keyboard map every native menu has had for thirty years.
204
1245
  *
@@ -218,12 +1259,94 @@ export const Dialog = {
218
1259
  * </Menu.Sub>
219
1260
  * </Menu.Body>
220
1261
  * </Menu.Root>
1262
+ *
1263
+ * Every part takes `render`, which is what makes a menu of links possible — and
1264
+ * a menu of links is the most ordinary menu there is:
1265
+ *
1266
+ * <Menu.Item render={(props) => <a href="/settings" {...props} />}>
1267
+ * Settings
1268
+ * </Menu.Item>
1269
+ *
1270
+ * The `<a>` keeps the middle click, the context menu and the status bar; the
1271
+ * item keeps the role, the id, the roving tab stop and the press that closes
1272
+ * the tree. See the module header for why that is the answer to "no copy step".
221
1273
  */
222
1274
  export const Menu = {
223
1275
  Root: MenuRoot,
224
1276
  Trigger: MenuTrigger,
225
1277
  Body: MenuBody,
226
1278
  Item: MenuItem,
1279
+ CheckboxItem: MenuCheckboxItem,
1280
+ RadioGroup: MenuRadioGroup,
1281
+ RadioItem: MenuRadioItem,
1282
+ Separator: MenuSeparator,
1283
+ Group: MenuGroup,
1284
+ Label: MenuLabel,
1285
+ Sub: MenuSub,
1286
+ SubTrigger: MenuSubTrigger,
1287
+ };
1288
+
1289
+ /**
1290
+ * The same menu, opened by the right button — and by the keyboard.
1291
+ *
1292
+ * `Shift+F10`, the `ContextMenu` key and a long press all open it, because a
1293
+ * command reachable only by right-click is reachable only by a pointer, which
1294
+ * is a WCAG 2.1.1 failure. `context-menu.js` says why the trigger is in the tab
1295
+ * order and when to take it out again.
1296
+ *
1297
+ * The body needs an `aria-label`: its trigger is a table row or a canvas rather
1298
+ * than a short name, so unlike `Menu.Body` it cannot name itself after one.
1299
+ *
1300
+ * <ContextMenu.Root>
1301
+ * <ContextMenu.Trigger>{row}</ContextMenu.Trigger>
1302
+ * <ContextMenu.Body aria-label="Row actions">
1303
+ * <ContextMenu.Item onSelect={rename}>Rename…</ContextMenu.Item>
1304
+ * <ContextMenu.CheckboxItem defaultChecked>Show hidden</ContextMenu.CheckboxItem>
1305
+ * </ContextMenu.Body>
1306
+ * </ContextMenu.Root>
1307
+ */
1308
+ export const ContextMenu = {
1309
+ Root: ContextMenuRoot,
1310
+ Trigger: ContextMenuTrigger,
1311
+ Body: MenuBody,
1312
+ Item: MenuItem,
1313
+ CheckboxItem: MenuCheckboxItem,
1314
+ RadioGroup: MenuRadioGroup,
1315
+ RadioItem: MenuRadioItem,
1316
+ Separator: MenuSeparator,
1317
+ Group: MenuGroup,
1318
+ Label: MenuLabel,
1319
+ Sub: MenuSub,
1320
+ SubTrigger: MenuSubTrigger,
1321
+ };
1322
+
1323
+ /**
1324
+ * A row of menus that behaves as one control: File, Edit, View.
1325
+ *
1326
+ * One tab stop for the whole bar, arrows between the menus, and — the part that
1327
+ * is always missing — arrows *while a menu is open* that close it and open the
1328
+ * next one, so a reader walks File → Edit → View without pressing Escape.
1329
+ *
1330
+ * <Menubar.Root aria-label="Main">
1331
+ * <Menubar.Menu value="file">
1332
+ * <Menubar.Trigger>File</Menubar.Trigger>
1333
+ * <Menubar.Body>
1334
+ * <Menubar.Item onSelect={open}>Open…</Menubar.Item>
1335
+ * </Menubar.Body>
1336
+ * </Menubar.Menu>
1337
+ * </Menubar.Root>
1338
+ */
1339
+ export const Menubar = {
1340
+ Root: MenubarRoot,
1341
+ Menu: MenubarMenu,
1342
+ Trigger: MenubarTrigger,
1343
+ // `Menu.Body` itself: a bar's menu is a root menu, and `menubar.js`'s header
1344
+ // says why a wrapper with the same defaults would be a second place to drift.
1345
+ Body: MenuBody,
1346
+ Item: MenuItem,
1347
+ CheckboxItem: MenuCheckboxItem,
1348
+ RadioGroup: MenuRadioGroup,
1349
+ RadioItem: MenuRadioItem,
227
1350
  Separator: MenuSeparator,
228
1351
  Group: MenuGroup,
229
1352
  Label: MenuLabel,
@@ -240,13 +1363,19 @@ export const Menu = {
240
1363
  * <Combobox.Label>Country</Combobox.Label>
241
1364
  * <Combobox.Input />
242
1365
  * <Combobox.List>
243
- * {matches.map((each) => (
244
- * <Combobox.Option key={each} value={each}>{each}</Combobox.Option>
245
- * ))}
1366
+ * <Combobox.Group>
1367
+ * <Combobox.GroupLabel>Europe</Combobox.GroupLabel>
1368
+ * {european.map((each) => (
1369
+ * <Combobox.Option key={each} value={each}>{each}</Combobox.Option>
1370
+ * ))}
1371
+ * </Combobox.Group>
246
1372
  * </Combobox.List>
247
1373
  * <Combobox.Empty>No matches.</Combobox.Empty>
248
1374
  * <Combobox.Status />
249
1375
  * </Combobox.Root>
1376
+ *
1377
+ * `Combobox.Label` names the field and `Combobox.GroupLabel` names a group of
1378
+ * options, which is why there are two of them.
250
1379
  */
251
1380
  export const Combobox = {
252
1381
  Root: ComboboxRoot,
@@ -254,6 +1383,401 @@ export const Combobox = {
254
1383
  Input: ComboboxInput,
255
1384
  List: ComboboxList,
256
1385
  Option: ComboboxOption,
1386
+ Group: ComboboxGroup,
1387
+ GroupLabel: ComboboxGroupLabel,
257
1388
  Empty: ComboboxEmpty,
258
1389
  Status: ComboboxStatus,
259
1390
  };
1391
+
1392
+ /**
1393
+ * The other half of the combobox pattern: a button, a list, and no typing.
1394
+ *
1395
+ * Use a native `<select>` when a native `<select>` will do — `select.js` says
1396
+ * so first and means it. This is for the popup a `<select>` cannot draw.
1397
+ *
1398
+ * <Select.Root defaultValue="GB" name="country">
1399
+ * <Select.Label>Country</Select.Label>
1400
+ * <Select.Trigger>
1401
+ * <Select.Value placeholder="Choose one" />
1402
+ * </Select.Trigger>
1403
+ * <Select.List>
1404
+ * <Select.Group>
1405
+ * <Select.GroupLabel>Europe</Select.GroupLabel>
1406
+ * <Select.Option value="GB">United Kingdom</Select.Option>
1407
+ * <Select.Option value="FR">France</Select.Option>
1408
+ * </Select.Group>
1409
+ * <Select.Separator />
1410
+ * <Select.Option value="JP">Japan</Select.Option>
1411
+ * </Select.List>
1412
+ * </Select.Root>
1413
+ *
1414
+ * `Select.Label` names the field and `Select.GroupLabel` names a group of
1415
+ * options. shadcn has one `SelectLabel` and it is the second of those; a select
1416
+ * needs both, so they are two parts here.
1417
+ */
1418
+ export const Select = {
1419
+ Root: SelectRoot,
1420
+ Label: SelectLabel,
1421
+ Trigger: SelectTrigger,
1422
+ Value: SelectValue,
1423
+ List: SelectList,
1424
+ Option: SelectOption,
1425
+ Group: SelectGroup,
1426
+ GroupLabel: SelectGroupLabel,
1427
+ Separator: SelectSeparator,
1428
+ };
1429
+
1430
+ /**
1431
+ * A dialog that is not modal, anchored to the button that opened it.
1432
+ *
1433
+ * Focus moves in, `Escape` closes it and gives focus back, and `Tab` *leaves* —
1434
+ * the page behind a popover is still there, still scrollable and still
1435
+ * tabbable, which is every way in which it is not a `Dialog`.
1436
+ *
1437
+ * <Popover.Root>
1438
+ * <Popover.Trigger>Filters</Popover.Trigger>
1439
+ * <Popover.Body align="start" side="bottom" sideOffset={8}>
1440
+ * <label>
1441
+ * Only mine <input type="checkbox" />
1442
+ * </label>
1443
+ * </Popover.Body>
1444
+ * </Popover.Root>
1445
+ *
1446
+ * `Popover.Body` reports where it ended up as `data-side` and `data-align`, and
1447
+ * writes the trigger's width and the room it had as custom properties, so a
1448
+ * stylesheet can point an arrow and cap a height without measuring anything.
1449
+ */
1450
+ export const Popover = {
1451
+ Root: PopoverRoot,
1452
+ Trigger: PopoverTrigger,
1453
+ Body: PopoverBody,
1454
+ };
1455
+
1456
+ /**
1457
+ * A month of dates, as one stop in the page's tab order.
1458
+ *
1459
+ * The grid is `role="grid"`, the arrow keys move by a day and by a week, and
1460
+ * `PageUp` and `PageDown` change the month - with `Shift`, the year. Running off
1461
+ * the end of a month shows the next one and lands on its first day, and the
1462
+ * month is announced in a live region when it changes.
1463
+ *
1464
+ * <Calendar.Root defaultValue="2026-10-14" onValueChange={setWhen}>
1465
+ * <Calendar.Previous>Previous month</Calendar.Previous>
1466
+ * <Calendar.Next>Next month</Calendar.Next>
1467
+ * <Calendar.Month />
1468
+ * </Calendar.Root>
1469
+ *
1470
+ * `Calendar.Month` takes a function child when a day needs more than its number
1471
+ * in it - a dot for an appointment, a price for a night - and it is handed the
1472
+ * date and returns a `Calendar.Day`.
1473
+ *
1474
+ * Dates are `@uniflowed/temporal`'s `PlainDate`, or the ISO strings it reads.
1475
+ * `isDateDisabled` marks a day unavailable *without* making it unreachable: it
1476
+ * is `aria-disabled` and the arrow keys still land on it, which is the opposite
1477
+ * of what a disabled menu item does and the only way a reader can find out which
1478
+ * days are unavailable.
1479
+ */
1480
+ export const Calendar = {
1481
+ Root: CalendarRoot,
1482
+ Previous: CalendarPrevious,
1483
+ Next: CalendarNext,
1484
+ Month: CalendarMonth,
1485
+ Day: CalendarDay,
1486
+ };
1487
+
1488
+ /**
1489
+ * A field somebody types a date into, and a calendar for the times they would
1490
+ * rather point at one.
1491
+ *
1492
+ * <DatePicker.Root onValueChange={setWhen} value={when}>
1493
+ * <DatePicker.Input aria-label="Arrive on" />
1494
+ * <DatePicker.Trigger>Choose a date</DatePicker.Trigger>
1495
+ * <DatePicker.Calendar>
1496
+ * <Calendar.Previous>Previous month</Calendar.Previous>
1497
+ * <Calendar.Next>Next month</Calendar.Next>
1498
+ * <Calendar.Month />
1499
+ * </DatePicker.Calendar>
1500
+ * </DatePicker.Root>
1501
+ *
1502
+ * The field is the control and the grid is the second way in: `Escape` and a
1503
+ * chosen date both put focus back on the field. `format` and `parse` are ISO
1504
+ * 8601 both ways unless a caller passes their own - `date-picker.js` says why a
1505
+ * locale format is not this package's to guess.
1506
+ */
1507
+ export const DatePicker = {
1508
+ Root: DatePickerRoot,
1509
+ Input: DatePickerInput,
1510
+ Trigger: DatePickerTrigger,
1511
+ Calendar: DatePickerCalendar,
1512
+ };
1513
+
1514
+ /**
1515
+ * A phrase about a control, on hover and on focus, that WCAG would accept.
1516
+ *
1517
+ * Dismissible with `Escape`, hoverable — the pointer can travel onto it — and
1518
+ * never focusable. It does not open on touch, deliberately, so the trigger must
1519
+ * carry its own name for a reader holding a phone.
1520
+ *
1521
+ * <Tooltip.Provider delayDuration={700} skipDelayDuration={300}>
1522
+ * <Tooltip.Root>
1523
+ * <Tooltip.Trigger aria-label="Bold">B</Tooltip.Trigger>
1524
+ * <Tooltip.Body>Bold (⌘B)</Tooltip.Body>
1525
+ * </Tooltip.Root>
1526
+ * <Tooltip.Root>
1527
+ * <Tooltip.Trigger aria-label="Italic">I</Tooltip.Trigger>
1528
+ * <Tooltip.Body>Italic (⌘I)</Tooltip.Body>
1529
+ * </Tooltip.Root>
1530
+ * </Tooltip.Provider>
1531
+ *
1532
+ * `Tooltip.Provider` is what makes the second icon in that toolbar answer at
1533
+ * once instead of making the reader wait the delay again. A tooltip outside one
1534
+ * is a complete tooltip with a delay of its own.
1535
+ */
1536
+ export const Tooltip = {
1537
+ Provider: TooltipProvider,
1538
+ Root: TooltipRoot,
1539
+ Trigger: TooltipTrigger,
1540
+ Body: TooltipBody,
1541
+ };
1542
+
1543
+ /**
1544
+ * The preview a name expands into: hovered, focused, and full of links.
1545
+ *
1546
+ * Not a tooltip — its contents are reachable, by pointer and by `Tab` — and not
1547
+ * a dialog, because nothing about it is modal.
1548
+ *
1549
+ * <HoverCard.Root>
1550
+ * <HoverCard.Trigger render={(props) => <a href="/ada" {...props}>@ada</a>} />
1551
+ * <HoverCard.Body>
1552
+ * <p>Ada Lovelace</p>
1553
+ * <a href="/ada/notes">Notes</a>
1554
+ * </HoverCard.Body>
1555
+ * </HoverCard.Root>
1556
+ */
1557
+ export const HoverCard = {
1558
+ Root: HoverCardRoot,
1559
+ Trigger: HoverCardTrigger,
1560
+ Body: HoverCardBody,
1561
+ };
1562
+
1563
+ /**
1564
+ * Notifications, in a live region that was watching before them.
1565
+ *
1566
+ * Render `Toast.Region` once, in the layout; `toast()` from anywhere.
1567
+ *
1568
+ * <Toast.Region>
1569
+ * {(each) => (
1570
+ * <Toast.Root>
1571
+ * <Toast.Title>{each.content}</Toast.Title>
1572
+ * <Toast.Action onClick={undo}>Undo</Toast.Action>
1573
+ * <Toast.Close />
1574
+ * </Toast.Root>
1575
+ * )}
1576
+ * </Toast.Region>
1577
+ */
1578
+ export const Toast = {
1579
+ Region: ToastRegion,
1580
+ Root: ToastRoot,
1581
+ Title: ToastTitle,
1582
+ Description: ToastDescription,
1583
+ Action: ToastAction,
1584
+ Close: ToastClose,
1585
+ };
1586
+
1587
+ /**
1588
+ * A value in a range, with `role="slider"` on the thumb where it belongs.
1589
+ *
1590
+ * One thumb or two; a range is the same component with a second one, each
1591
+ * bounded by its neighbour and each needing its own name.
1592
+ *
1593
+ * <Slider.Root defaultValue={[20, 60]} valueText={(each) => `£${each}`}>
1594
+ * <Slider.Track>
1595
+ * <Slider.Range />
1596
+ * </Slider.Track>
1597
+ * <Slider.Thumb aria-label="Minimum" index={0} />
1598
+ * <Slider.Thumb aria-label="Maximum" index={1} />
1599
+ * </Slider.Root>
1600
+ */
1601
+ export const Slider = {
1602
+ Root: SliderRoot,
1603
+ Track: SliderTrack,
1604
+ Range: SliderRange,
1605
+ Thumb: SliderThumb,
1606
+ };
1607
+
1608
+ /**
1609
+ * Two panes and the splitter between them, operable from the keyboard.
1610
+ *
1611
+ * <Resizable.PanelGroup defaultValue={30}>
1612
+ * <Resizable.Panel primary>Files</Resizable.Panel>
1613
+ * <Resizable.Handle label="Resize the file list" />
1614
+ * <Resizable.Panel>Editor</Resizable.Panel>
1615
+ * </Resizable.PanelGroup>
1616
+ */
1617
+ export const Resizable = {
1618
+ PanelGroup: ResizablePanelGroup,
1619
+ Panel: ResizablePanel,
1620
+ Handle: ResizableHandle,
1621
+ };
1622
+
1623
+ /**
1624
+ * A table, with the four things about one nobody gets right by hand.
1625
+ *
1626
+ * A real `<table>`, deliberately not a `role="grid"` — `table.js` says why —
1627
+ * and its own live region, so a re-sort is something a reader is told about
1628
+ * rather than something that happens silently behind them.
1629
+ *
1630
+ * <Table.Root onSortChange={setSort} rowCount={500} rowOffset={90} sort={sort}>
1631
+ * <Table.Caption>People</Table.Caption>
1632
+ * <Table.Header>
1633
+ * <Table.Row>
1634
+ * <Table.Head>
1635
+ * <Table.SelectAll checked={all} onCheckedChange={setAll} />
1636
+ * </Table.Head>
1637
+ * <Table.Head column="name">Name</Table.Head>
1638
+ * </Table.Row>
1639
+ * </Table.Header>
1640
+ * <Table.Body>
1641
+ * {page.map((person, at) => (
1642
+ * <Table.Row index={at} key={person.id}>
1643
+ * <Table.Cell>
1644
+ * <Table.RowSelect
1645
+ * checked={chosen.has(person.id)}
1646
+ * label={`Select ${person.name}`}
1647
+ * onCheckedChange={(on) => choose(person.id, on)}
1648
+ * />
1649
+ * </Table.Cell>
1650
+ * <Table.RowHeader>{person.name}</Table.RowHeader>
1651
+ * </Table.Row>
1652
+ * ))}
1653
+ * </Table.Body>
1654
+ * </Table.Root>
1655
+ */
1656
+ export const Table = {
1657
+ Root: TableRoot,
1658
+ Caption: TableCaption,
1659
+ Header: TableHeader,
1660
+ Body: TableBody,
1661
+ Row: TableRow,
1662
+ Head: TableHead,
1663
+ RowHeader: TableRowHeader,
1664
+ Cell: TableCell,
1665
+ SelectAll: TableSelectAll,
1666
+ RowSelect: TableRowSelect,
1667
+ };
1668
+
1669
+ /**
1670
+ * The navigation a paginated table needs, and the sentence that says it moved.
1671
+ *
1672
+ * <Pagination.Root page={4} pageCount={25}>
1673
+ * <Pagination.Content>
1674
+ * <Pagination.Previous disabled={page === 1} href={hrefFor(page - 1)}>‹</Pagination.Previous>
1675
+ * <Pagination.Item current href={hrefFor(4)}>4</Pagination.Item>
1676
+ * <Pagination.Next href={hrefFor(page + 1)}>›</Pagination.Next>
1677
+ * </Pagination.Content>
1678
+ * </Pagination.Root>
1679
+ */
1680
+ export const Pagination = {
1681
+ Root: PaginationRoot,
1682
+ Content: PaginationContent,
1683
+ Item: PaginationItem,
1684
+ Previous: PaginationPrevious,
1685
+ Next: PaginationNext,
1686
+ };
1687
+
1688
+ /**
1689
+ * The trail above the page, read as places rather than as punctuation.
1690
+ *
1691
+ * <Breadcrumb.Root>
1692
+ * <Breadcrumb.List>
1693
+ * <Breadcrumb.Item>
1694
+ * <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
1695
+ * </Breadcrumb.Item>
1696
+ * <Breadcrumb.Separator>/</Breadcrumb.Separator>
1697
+ * <Breadcrumb.Item>
1698
+ * <Breadcrumb.Page>Billing</Breadcrumb.Page>
1699
+ * </Breadcrumb.Item>
1700
+ * </Breadcrumb.List>
1701
+ * </Breadcrumb.Root>
1702
+ *
1703
+ * `Pagination`'s shape one door along: a `<nav>` with a name, one
1704
+ * `aria-current="page"`, and the separators out of the accessibility tree so
1705
+ * the trail is not announced as "Home slash Settings slash Billing". The last
1706
+ * crumb is a `Breadcrumb.Page` and not a link, because it is where the reader
1707
+ * already is.
1708
+ */
1709
+ export const Breadcrumb = {
1710
+ Root: BreadcrumbRoot,
1711
+ List: BreadcrumbList,
1712
+ Item: BreadcrumbItem,
1713
+ Link: BreadcrumbLink,
1714
+ Page: BreadcrumbPage,
1715
+ Separator: BreadcrumbSeparator,
1716
+ };
1717
+
1718
+ /**
1719
+ * A callout, and the `live` that decides whether anybody is interrupted by it.
1720
+ *
1721
+ * <Alert.Root>
1722
+ * <Alert.Title>Your trial ends on Friday</Alert.Title>
1723
+ * <Alert.Description>Add a card to keep your projects.</Alert.Description>
1724
+ * </Alert.Root>
1725
+ *
1726
+ * {error != null && (
1727
+ * <Alert.Root live>
1728
+ * <Alert.Title>Could not save</Alert.Title>
1729
+ * <Alert.Description>{error}</Alert.Description>
1730
+ * </Alert.Root>
1731
+ * )}
1732
+ *
1733
+ * The first has no role at all: it was there when the page loaded, so a live
1734
+ * region would announce it on every load or never, and neither is what anybody
1735
+ * wanted. The second appeared because something happened, which is what
1736
+ * `role="alert"` is for. `alert.js`'s header says why there is no polite
1737
+ * version of this and why `Toast` is that instead.
1738
+ */
1739
+ export const Alert = {
1740
+ Root: AlertRoot,
1741
+ Title: AlertTitle,
1742
+ Description: AlertDescription,
1743
+ };
1744
+
1745
+ /**
1746
+ * A picture of a person, and the two states it is not in yet.
1747
+ *
1748
+ * <Avatar.Root>
1749
+ * <Avatar.Image src={person.photo} />
1750
+ * <Avatar.Fallback>{initials(person.name)}</Avatar.Fallback>
1751
+ * </Avatar.Root>
1752
+ *
1753
+ * The fallback is absent while the image is loading and present once it has
1754
+ * failed, held back long enough that a cached image never flashes initials.
1755
+ * `alt` defaults to `""`, because an avatar beside the name it belongs to is
1756
+ * decorative and a component that helpfully puts the name there makes every
1757
+ * screen reader say it twice; pass `alt` where the picture is the only thing
1758
+ * identifying the person.
1759
+ */
1760
+ export const Avatar = {
1761
+ Root: AvatarRoot,
1762
+ Image: AvatarImage,
1763
+ Fallback: AvatarFallback,
1764
+ };
1765
+
1766
+ /**
1767
+ * The grey boxes, and the sentence that stops them being an empty page.
1768
+ *
1769
+ * <Skeleton.Root busy={pending}>
1770
+ * {pending ? <Skeleton.Box /> : <Invoices rows={invoices} />}
1771
+ * </Skeleton.Root>
1772
+ *
1773
+ * The boxes are `aria-hidden`, the region is `aria-busy`, and a live region
1774
+ * that was mounted empty for a commit says "Loading" — a skeleton screen is
1775
+ * busy on its first render, so a region rendered with its message already in it
1776
+ * announces nothing at all. Keep the root mounted across the load and toggle
1777
+ * `busy`; unmounting it takes the region away before it can say the wait is
1778
+ * over.
1779
+ */
1780
+ export const Skeleton = {
1781
+ Root: SkeletonRoot,
1782
+ Box: SkeletonBox,
1783
+ };