@uniflowed/ui 0.0.0-alpha.8 → 0.1.0

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 (64) hide show
  1. package/accordion.js +84 -57
  2. package/alert-dialog.js +284 -0
  3. package/alert.js +142 -0
  4. package/avatar.js +280 -0
  5. package/breadcrumb.js +138 -0
  6. package/calendar.js +560 -0
  7. package/carousel.js +410 -0
  8. package/checkbox.js +215 -31
  9. package/collapsible.js +72 -48
  10. package/color-picker.js +172 -0
  11. package/combobox.js +216 -39
  12. package/context-menu.js +215 -0
  13. package/date-field.js +9 -0
  14. package/date-picker.js +357 -0
  15. package/date-range-picker.js +120 -0
  16. package/dialog.js +235 -198
  17. package/drag-drop.js +125 -0
  18. package/drawer.js +504 -0
  19. package/field.js +260 -43
  20. package/grid-list.js +8 -0
  21. package/hover-card.js +334 -0
  22. package/i18n-provider.js +89 -0
  23. package/index.js +1254 -32
  24. package/input-otp.js +218 -0
  25. package/interactions.js +2327 -0
  26. package/internal/anchor.js +565 -0
  27. package/internal/collection.js +395 -0
  28. package/internal/date-grid.js +260 -0
  29. package/internal/date-range.js +26 -0
  30. package/internal/disclosure.js +201 -0
  31. package/internal/focus.js +64 -0
  32. package/internal/hover-intent.js +259 -0
  33. package/internal/menu-tree.js +228 -0
  34. package/internal/merge-props.js +117 -1
  35. package/internal/roving-focus.js +15 -4
  36. package/internal/segmented-field.js +316 -0
  37. package/list-box.js +13 -0
  38. package/menu.js +553 -361
  39. package/menubar.js +295 -0
  40. package/number-field.js +263 -0
  41. package/package.json +8 -25
  42. package/pagination.js +34 -22
  43. package/popover.js +367 -0
  44. package/progress.js +21 -16
  45. package/radio-group.js +81 -75
  46. package/range-calendar.js +78 -0
  47. package/resizable.js +155 -9
  48. package/scroll-area.js +283 -0
  49. package/select.js +83 -37
  50. package/separator.js +97 -0
  51. package/sheet.js +189 -0
  52. package/sidebar.js +320 -0
  53. package/skeleton.js +163 -0
  54. package/slider.js +95 -89
  55. package/switch.js +42 -34
  56. package/table.js +112 -71
  57. package/tabs.js +100 -91
  58. package/tag-group.js +8 -0
  59. package/time-field.js +8 -0
  60. package/toast.js +36 -66
  61. package/toggle-group.js +53 -49
  62. package/toggle.js +41 -27
  63. package/tooltip.js +404 -0
  64. package/tree.js +8 -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
@@ -57,20 +221,9 @@
57
221
  //
58
222
  // One component does use it, and it is the case the API is actually for.
59
223
  // `toast("Saved")` is called from an event handler or a `catch`, so the queue
60
- // of notifications lives outside React — a store at module scope in
61
- // `toast.js`, read through `useSyncExternalStore` with the cached immutable
62
- // snapshots and the consistent server snapshot that requires. A queue is a
63
- // store; the DOM is not.
64
- //
65
- // That store should be an atom in `@uniflowed/state`, and was one. It is
66
- // written out by hand in `toast.js` because `@uniflowed/ui` is published to
67
- // npm and `@uniflowed/state` is not: its name has never been bound, and
68
- // binding it takes a person with an `npm login` session and a 2FA prompt —
69
- // ubugeeei-prod/uf#210. A published package whose dependency is missing
70
- // installs as nothing, `ETARGET` on the first thing a user types, so this
71
- // package cannot declare that dependency until the name exists. The queue
72
- // moves back to `@uniflowed/state` when #210 binds it. The local store is the
73
- // shippable design, not the better one.
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.
74
227
  //
75
228
  // # Server and client
76
229
  //
@@ -85,10 +238,29 @@
85
238
  // One root module per primitive, each with its own `exports` subpath, each
86
239
  // named after the thing it implements:
87
240
  //
88
- // - `dialog.js` — the focus trap, focus restore, scroll lock and inert page.
89
- // - `menu.js` — the arrow keys, typeahead, submenus and `Escape` stacking.
90
- // - `combobox.js` — `aria-activedescendant` over a filtered list, and the
91
- // 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.
92
264
  // - `select.js` — the other half of the combobox pattern: the select-only one,
93
265
  // with typeahead, option groups and a value a form can submit.
94
266
  // - `tabs.js` — the roving `tabindex`, and automatic versus manual activation.
@@ -112,24 +284,50 @@
112
284
  // role, which is why it is beside one rather than with the layout.
113
285
  // - `table.js` and `pagination.js` — the sort that is announced, the selection
114
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.
115
306
  //
116
- // Every name below is exported from one of those, so a consumer may import
117
- // `@uniflowed/ui` or `@uniflowed/ui/dialog` and get the same thing. The split
118
- // is by primitive because that is the unit a reader looks for, the unit a
119
- // 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.
120
311
  //
121
- // `internal/` holds six modules and nothing else, each a rule the primitives
312
+ // `internal/` holds ten modules and nothing else, each a rule the primitives
122
313
  // must apply identically and a consumer must not be able to apply differently:
123
314
  // `merge-props.js` (the caller's props go on first, the component's semantics
124
315
  // last), `controlled-state.js` (what "controlled" means here),
125
316
  // `roving-focus.js` (how a set of items is found and moved between),
126
317
  // `disclosure.js` (how a button says whether a region is showing, and how a
127
318
  // closed region stays findable), `form-value.js` (what a `<form>` submits for a
128
- // control the browser has never heard of), and `range.js` (the arithmetic that
319
+ // control the browser has never heard of), `range.js` (the arithmetic that
129
320
  // keeps `aria-valuemin`, `aria-valuemax` and `aria-valuenow` true about each
130
- // other). Each says in its own header why it is unreachable rather than
131
- // exported. There is no `internal/props.js`-shaped bag of helpers: a module
132
- // that cannot say what it is about does not belong in this package.
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.
133
331
 
134
332
  import {
135
333
  AccordionContent,
@@ -138,10 +336,50 @@ import {
138
336
  AccordionRoot,
139
337
  AccordionTrigger,
140
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";
141
376
  import { Checkbox } from "./checkbox.js";
377
+ import { ContextMenuRoot, ContextMenuTrigger } from "./context-menu.js";
142
378
  import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "./collapsible.js";
143
379
  import {
144
380
  ComboboxEmpty,
381
+ ComboboxGroup,
382
+ ComboboxGroupLabel,
145
383
  ComboboxInput,
146
384
  ComboboxLabel,
147
385
  ComboboxList,
@@ -149,6 +387,12 @@ import {
149
387
  ComboboxRoot,
150
388
  ComboboxStatus,
151
389
  } from "./combobox.js";
390
+ import {
391
+ DatePickerCalendar,
392
+ DatePickerInput,
393
+ DatePickerRoot,
394
+ DatePickerTrigger,
395
+ } from "./date-picker.js";
152
396
  import {
153
397
  DialogBody,
154
398
  DialogClose,
@@ -160,18 +404,43 @@ import {
160
404
  DialogTitle,
161
405
  DialogTrigger,
162
406
  } from "./dialog.js";
163
- 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";
164
429
  import {
165
430
  MenuBody,
431
+ MenuCheckboxItem,
166
432
  MenuGroup,
167
433
  MenuItem,
168
434
  MenuLabel,
435
+ MenuRadioGroup,
436
+ MenuRadioItem,
169
437
  MenuRoot,
170
438
  MenuSeparator,
171
439
  MenuSub,
172
440
  MenuSubTrigger,
173
441
  MenuTrigger,
174
442
  } from "./menu.js";
443
+ import { MenubarMenu, MenubarRoot, MenubarTrigger } from "./menubar.js";
175
444
  import {
176
445
  NavigationMenuBody,
177
446
  NavigationMenuItem,
@@ -187,9 +456,11 @@ import {
187
456
  PaginationPrevious,
188
457
  PaginationRoot,
189
458
  } from "./pagination.js";
459
+ import { PopoverBody, PopoverRoot, PopoverTrigger } from "./popover.js";
190
460
  import { Progress } from "./progress.js";
191
461
  import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from "./radio-group.js";
192
462
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./resizable.js";
463
+ import { ScrollAreaRoot, ScrollAreaScrollbar, ScrollAreaViewport } from "./scroll-area.js";
193
464
  import {
194
465
  SelectGroup,
195
466
  SelectGroupLabel,
@@ -201,6 +472,27 @@ import {
201
472
  SelectTrigger,
202
473
  SelectValue,
203
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";
204
496
  import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from "./slider.js";
205
497
  import { Switch } from "./switch.js";
206
498
  import {
@@ -230,14 +522,243 @@ import {
230
522
  } from "./toast.js";
231
523
  import { Toggle } from "./toggle.js";
232
524
  import { ToggleGroupItem, ToggleGroupRoot } from "./toggle-group.js";
525
+ import { TooltipBody, TooltipProvider, TooltipRoot, TooltipTrigger } from "./tooltip.js";
233
526
 
234
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";
235
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";
236
552
  export type { Sort } from "./table.js";
237
553
  export type { Notification, ToastChanges, ToastOptions, Urgency } from "./toast.js";
238
554
  export type { ToggleGroupType } from "./toggle-group.js";
239
555
 
240
- export { Checkbox, Progress, Switch, Toggle };
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 };
241
762
 
242
763
  /**
243
764
  * Queueing a notification, from anywhere.
@@ -252,6 +773,75 @@ export { Checkbox, Progress, Switch, Toggle };
252
773
  */
253
774
  export { dismissAllToasts, dismissToast, toast, updateToast };
254
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";
844
+
255
845
  /**
256
846
  * An accessible form field.
257
847
  *
@@ -259,14 +849,31 @@ export { dismissAllToasts, dismissToast, toast, updateToast };
259
849
  * <Field.Label>Email</Field.Label>
260
850
  * <Field.Control render={(props) => <input type="email" {...props} />} />
261
851
  * <Field.Description>We will not share it.</Field.Description>
852
+ * <Field.Status>{saving ? "Saving…" : ""}</Field.Status>
262
853
  * <Field.Error>{error}</Field.Error>
263
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.
264
870
  */
265
871
  export const Field = {
266
872
  Root: FieldRoot,
267
873
  Label: FieldLabel,
268
874
  Control: FieldControl,
269
875
  Description: FieldDescription,
876
+ Status: FieldStatus,
270
877
  Error: FieldError,
271
878
  };
272
879
 
@@ -284,6 +891,11 @@ export const Field = {
284
891
  * <Tabs.Panel value="one">…</Tabs.Panel>
285
892
  * <Tabs.Panel value="two">…</Tabs.Panel>
286
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.
287
899
  */
288
900
  export const Tabs = {
289
901
  Root: TabsRoot,
@@ -416,6 +1028,12 @@ export const ToggleGroup = {
416
1028
  * </Dialog.Footer>
417
1029
  * </Dialog.Body>
418
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.
419
1037
  */
420
1038
  export const Dialog = {
421
1039
  Root: DialogRoot,
@@ -429,6 +1047,199 @@ export const Dialog = {
429
1047
  Close: DialogClose,
430
1048
  };
431
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
+
432
1243
  /**
433
1244
  * A menu, with the keyboard map every native menu has had for thirty years.
434
1245
  *
@@ -448,12 +1259,94 @@ export const Dialog = {
448
1259
  * </Menu.Sub>
449
1260
  * </Menu.Body>
450
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".
451
1273
  */
452
1274
  export const Menu = {
453
1275
  Root: MenuRoot,
454
1276
  Trigger: MenuTrigger,
455
1277
  Body: MenuBody,
456
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,
457
1350
  Separator: MenuSeparator,
458
1351
  Group: MenuGroup,
459
1352
  Label: MenuLabel,
@@ -470,13 +1363,19 @@ export const Menu = {
470
1363
  * <Combobox.Label>Country</Combobox.Label>
471
1364
  * <Combobox.Input />
472
1365
  * <Combobox.List>
473
- * {matches.map((each) => (
474
- * <Combobox.Option key={each} value={each}>{each}</Combobox.Option>
475
- * ))}
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>
476
1372
  * </Combobox.List>
477
1373
  * <Combobox.Empty>No matches.</Combobox.Empty>
478
1374
  * <Combobox.Status />
479
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.
480
1379
  */
481
1380
  export const Combobox = {
482
1381
  Root: ComboboxRoot,
@@ -484,6 +1383,8 @@ export const Combobox = {
484
1383
  Input: ComboboxInput,
485
1384
  List: ComboboxList,
486
1385
  Option: ComboboxOption,
1386
+ Group: ComboboxGroup,
1387
+ GroupLabel: ComboboxGroupLabel,
487
1388
  Empty: ComboboxEmpty,
488
1389
  Status: ComboboxStatus,
489
1390
  };
@@ -526,6 +1427,139 @@ export const Select = {
526
1427
  Separator: SelectSeparator,
527
1428
  };
528
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
+
529
1563
  /**
530
1564
  * Notifications, in a live region that was watching before them.
531
1565
  *
@@ -650,3 +1684,191 @@ export const Pagination = {
650
1684
  Previous: PaginationPrevious,
651
1685
  Next: PaginationNext,
652
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
+ };
1784
+
1785
+ export { I18nProvider, useLocale, useCollator, useFilter } from "./i18n-provider.js";
1786
+ import {
1787
+ NumberFieldRoot,
1788
+ NumberFieldInput,
1789
+ NumberFieldIncrement,
1790
+ NumberFieldDecrement,
1791
+ } from "./number-field.js";
1792
+ export const NumberField = {
1793
+ Root: NumberFieldRoot,
1794
+ Input: NumberFieldInput,
1795
+ Increment: NumberFieldIncrement,
1796
+ Decrement: NumberFieldDecrement,
1797
+ };
1798
+
1799
+ export { ListBox } from "./list-box.js";
1800
+ export { GridList } from "./grid-list.js";
1801
+ export { Tree } from "./tree.js";
1802
+ export { TagGroup } from "./tag-group.js";
1803
+ export type { CollectionItem, CollectionItemState, CollectionProps } from "./list-box.js";
1804
+
1805
+ export type { Drop, DragAndDrop } from "./drag-drop.js";
1806
+ export { useDragAndDrop } from "./drag-drop.js";
1807
+ import {
1808
+ ColorPickerRoot,
1809
+ ColorPickerInput,
1810
+ ColorPickerField,
1811
+ ColorPickerChannel,
1812
+ ColorPickerSwatch,
1813
+ } from "./color-picker.js";
1814
+ export const ColorPicker = {
1815
+ Root: ColorPickerRoot,
1816
+ Input: ColorPickerInput,
1817
+ Field: ColorPickerField,
1818
+ Channel: ColorPickerChannel,
1819
+ Swatch: ColorPickerSwatch,
1820
+ };
1821
+
1822
+ export { DateField } from "./date-field.js";
1823
+ export { TimeField } from "./time-field.js";
1824
+ export type { DateFieldProps } from "./date-field.js";
1825
+ import { RangeCalendarRoot } from "./range-calendar.js";
1826
+ import {
1827
+ DateRangePickerRoot,
1828
+ DateRangePickerStartField,
1829
+ DateRangePickerEndField,
1830
+ DateRangePickerTrigger,
1831
+ DateRangePickerCalendar,
1832
+ } from "./date-range-picker.js";
1833
+ export type { DateRange } from "./range-calendar.js";
1834
+ export const RangeCalendar = {
1835
+ Root: RangeCalendarRoot,
1836
+ Month: CalendarMonth,
1837
+ Day: CalendarDay,
1838
+ Previous: CalendarPrevious,
1839
+ Next: CalendarNext,
1840
+ };
1841
+ export const DateRangePicker = {
1842
+ Root: DateRangePickerRoot,
1843
+ StartField: DateRangePickerStartField,
1844
+ EndField: DateRangePickerEndField,
1845
+ Trigger: DateRangePickerTrigger,
1846
+ Calendar: DateRangePickerCalendar,
1847
+ };
1848
+
1849
+ export {
1850
+ NumberFieldRoot,
1851
+ NumberFieldInput,
1852
+ NumberFieldIncrement,
1853
+ NumberFieldDecrement,
1854
+ parseNumber,
1855
+ } from "./number-field.js";
1856
+ export type { NumberFormatOptions } from "./number-field.js";
1857
+ export {
1858
+ ColorPickerRoot,
1859
+ ColorPickerInput,
1860
+ ColorPickerField,
1861
+ ColorPickerChannel,
1862
+ ColorPickerSwatch,
1863
+ parseColor,
1864
+ } from "./color-picker.js";
1865
+ export { RangeCalendarRoot } from "./range-calendar.js";
1866
+ export {
1867
+ DateRangePickerRoot,
1868
+ DateRangePickerStartField,
1869
+ DateRangePickerEndField,
1870
+ DateRangePickerTrigger,
1871
+ DateRangePickerCalendar,
1872
+ } from "./date-range-picker.js";
1873
+ export type { Locale } from "./i18n-provider.js";
1874
+ export { startsWithLocale } from "./i18n-provider.js";