@uniflowed/ui 0.0.0-alpha.18 → 0.0.0-alpha.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accordion.js +61 -54
- package/alert-dialog.js +43 -17
- package/alert.js +142 -0
- package/avatar.js +276 -0
- package/breadcrumb.js +138 -0
- package/checkbox.js +45 -39
- package/collapsible.js +41 -36
- package/context-menu.js +32 -24
- package/dialog.js +143 -119
- package/drawer.js +121 -89
- package/field.js +71 -37
- package/hover-card.js +23 -27
- package/index.js +567 -31
- package/input-otp.js +8 -8
- package/interactions.js +2163 -0
- package/internal/merge-props.js +85 -1
- package/menu.js +236 -213
- package/menubar.js +91 -89
- package/package.json +8 -40
- package/pagination.js +29 -17
- package/popover.js +59 -55
- package/progress.js +21 -16
- package/radio-group.js +79 -75
- package/separator.js +97 -0
- package/sheet.js +41 -17
- package/sidebar.js +10 -10
- package/skeleton.js +159 -0
- package/slider.js +89 -89
- package/switch.js +37 -31
- package/table.js +90 -67
- package/tabs.js +100 -91
- package/toast.js +23 -55
- package/toggle-group.js +52 -49
- package/toggle.js +38 -25
- package/tooltip.js +26 -37
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
|
|
@@ -76,14 +175,33 @@
|
|
|
76
175
|
// are `Declined` with the preset functions that replace them named on each,
|
|
77
176
|
// and `cargo test -p uf_lib` fails if a name there stops existing.
|
|
78
177
|
//
|
|
79
|
-
// Five of that twenty are
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
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.
|
|
87
205
|
//
|
|
88
206
|
// # What these components promise React
|
|
89
207
|
//
|
|
@@ -103,20 +221,9 @@
|
|
|
103
221
|
//
|
|
104
222
|
// One component does use it, and it is the case the API is actually for.
|
|
105
223
|
// `toast("Saved")` is called from an event handler or a `catch`, so the queue
|
|
106
|
-
// of notifications lives outside React —
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// store; the DOM is not.
|
|
110
|
-
//
|
|
111
|
-
// That store should be an atom in `@uniflowed/state`, and was one. It is
|
|
112
|
-
// written out by hand in `toast.js` because `@uniflowed/ui` is published to
|
|
113
|
-
// npm and `@uniflowed/state` is not: its name has never been bound, and
|
|
114
|
-
// binding it takes a person with an `npm login` session and a 2FA prompt —
|
|
115
|
-
// ubugeeei-prod/uf#210. A published package whose dependency is missing
|
|
116
|
-
// installs as nothing, `ETARGET` on the first thing a user types, so this
|
|
117
|
-
// package cannot declare that dependency until the name exists. The queue
|
|
118
|
-
// moves back to `@uniflowed/state` when #210 binds it. The local store is the
|
|
119
|
-
// 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.
|
|
120
227
|
//
|
|
121
228
|
// # Server and client
|
|
122
229
|
//
|
|
@@ -184,11 +291,23 @@
|
|
|
184
291
|
// a dialog that is not modal, a tooltip describes its trigger and may never
|
|
185
292
|
// take focus, a hover card is neither and holds links — and because a flag
|
|
186
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.
|
|
187
306
|
//
|
|
188
|
-
// Every name below is exported from one of those,
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
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.
|
|
192
311
|
//
|
|
193
312
|
// `internal/` holds ten modules and nothing else, each a rule the primitives
|
|
194
313
|
// must apply identically and a consumer must not be able to apply differently:
|
|
@@ -217,6 +336,7 @@ import {
|
|
|
217
336
|
AccordionRoot,
|
|
218
337
|
AccordionTrigger,
|
|
219
338
|
} from "./accordion.js";
|
|
339
|
+
import { AlertDescription, AlertRoot, AlertTitle } from "./alert.js";
|
|
220
340
|
import {
|
|
221
341
|
AlertDialogAction,
|
|
222
342
|
AlertDialogBody,
|
|
@@ -229,6 +349,15 @@ import {
|
|
|
229
349
|
AlertDialogTitle,
|
|
230
350
|
AlertDialogTrigger,
|
|
231
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";
|
|
232
361
|
import {
|
|
233
362
|
CarouselContent,
|
|
234
363
|
CarouselItem,
|
|
@@ -287,7 +416,14 @@ import {
|
|
|
287
416
|
DrawerTitle,
|
|
288
417
|
DrawerTrigger,
|
|
289
418
|
} from "./drawer.js";
|
|
290
|
-
import {
|
|
419
|
+
import {
|
|
420
|
+
FieldControl,
|
|
421
|
+
FieldDescription,
|
|
422
|
+
FieldError,
|
|
423
|
+
FieldLabel,
|
|
424
|
+
FieldRoot,
|
|
425
|
+
FieldStatus,
|
|
426
|
+
} from "./field.js";
|
|
291
427
|
import { HoverCardBody, HoverCardRoot, HoverCardTrigger } from "./hover-card.js";
|
|
292
428
|
import { InputOtpGroup, InputOtpRoot, InputOtpSeparator, InputOtpSlot } from "./input-otp.js";
|
|
293
429
|
import {
|
|
@@ -336,6 +472,7 @@ import {
|
|
|
336
472
|
SelectTrigger,
|
|
337
473
|
SelectValue,
|
|
338
474
|
} from "./select.js";
|
|
475
|
+
import { Separator } from "./separator.js";
|
|
339
476
|
import {
|
|
340
477
|
SheetBody,
|
|
341
478
|
SheetClose,
|
|
@@ -355,6 +492,7 @@ import {
|
|
|
355
492
|
SidebarRoot,
|
|
356
493
|
SidebarTrigger,
|
|
357
494
|
} from "./sidebar.js";
|
|
495
|
+
import { SkeletonBox, SkeletonRoot } from "./skeleton.js";
|
|
358
496
|
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from "./slider.js";
|
|
359
497
|
import { Switch } from "./switch.js";
|
|
360
498
|
import {
|
|
@@ -387,6 +525,7 @@ import { ToggleGroupItem, ToggleGroupRoot } from "./toggle-group.js";
|
|
|
387
525
|
import { TooltipBody, TooltipProvider, TooltipRoot, TooltipTrigger } from "./tooltip.js";
|
|
388
526
|
|
|
389
527
|
export type { AccordionType } from "./accordion.js";
|
|
528
|
+
export type { AvatarStatus } from "./avatar.js";
|
|
390
529
|
// A date, however a caller had one to hand: a `PlainDate` from
|
|
391
530
|
// `@uniflowed/temporal`, or the ISO 8601 string a form field or a URL carries.
|
|
392
531
|
export type { DateValue } from "./calendar.js";
|
|
@@ -397,7 +536,11 @@ export type { DialogRole } from "./dialog.js";
|
|
|
397
536
|
// Which edge of the viewport a sheet or a drawer is attached to. `Sidebar` has
|
|
398
537
|
// its own two-member union, because a sidebar is never on the top or bottom.
|
|
399
538
|
export type { Edge } from "./sheet.js";
|
|
539
|
+
export type { FieldSource } from "./field.js";
|
|
400
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";
|
|
401
544
|
export type { SidebarSide } from "./sidebar.js";
|
|
402
545
|
// Where an anchored overlay opens, for a caller who holds one in a variable or
|
|
403
546
|
// a prop of their own. Unions rather than strings, so `side="botom"` is a type
|
|
@@ -410,7 +553,212 @@ export type { Sort } from "./table.js";
|
|
|
410
553
|
export type { Notification, ToastChanges, ToastOptions, Urgency } from "./toast.js";
|
|
411
554
|
export type { ToggleGroupType } from "./toggle-group.js";
|
|
412
555
|
|
|
413
|
-
|
|
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 };
|
|
414
762
|
|
|
415
763
|
/**
|
|
416
764
|
* Queueing a notification, from anywhere.
|
|
@@ -425,6 +773,72 @@ export { Checkbox, Progress, Switch, Toggle };
|
|
|
425
773
|
*/
|
|
426
774
|
export { dismissAllToasts, dismissToast, toast, updateToast };
|
|
427
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
|
+
useKeyboard,
|
|
803
|
+
useLongPress,
|
|
804
|
+
useMove,
|
|
805
|
+
usePress,
|
|
806
|
+
} from "./interactions.js";
|
|
807
|
+
export type {
|
|
808
|
+
FocusRingOptions,
|
|
809
|
+
FocusRingProps,
|
|
810
|
+
FocusRingResult,
|
|
811
|
+
FocusVisibleResult,
|
|
812
|
+
HoverEvent,
|
|
813
|
+
HoverOptions,
|
|
814
|
+
HoverProps,
|
|
815
|
+
HoverResult,
|
|
816
|
+
InteractionEvent,
|
|
817
|
+
InteractionProps,
|
|
818
|
+
KeyboardInteraction,
|
|
819
|
+
KeyboardOptions,
|
|
820
|
+
KeyboardProps,
|
|
821
|
+
KeyboardResult,
|
|
822
|
+
LongPressEvent,
|
|
823
|
+
LongPressOptions,
|
|
824
|
+
LongPressProps,
|
|
825
|
+
LongPressResult,
|
|
826
|
+
Modality,
|
|
827
|
+
MoveEndEvent,
|
|
828
|
+
MoveMoveEvent,
|
|
829
|
+
MoveOptions,
|
|
830
|
+
MovePointerType,
|
|
831
|
+
MoveProps,
|
|
832
|
+
MoveResult,
|
|
833
|
+
MoveStartEvent,
|
|
834
|
+
PhysicalPointer,
|
|
835
|
+
PointerType,
|
|
836
|
+
PressEvent,
|
|
837
|
+
PressOptions,
|
|
838
|
+
PressProps,
|
|
839
|
+
PressResult,
|
|
840
|
+
} from "./interactions.js";
|
|
841
|
+
|
|
428
842
|
/**
|
|
429
843
|
* An accessible form field.
|
|
430
844
|
*
|
|
@@ -432,14 +846,16 @@ export { dismissAllToasts, dismissToast, toast, updateToast };
|
|
|
432
846
|
* <Field.Label>Email</Field.Label>
|
|
433
847
|
* <Field.Control render={(props) => <input type="email" {...props} />} />
|
|
434
848
|
* <Field.Description>We will not share it.</Field.Description>
|
|
849
|
+
* <Field.Status>{saving ? "Saving…" : ""}</Field.Status>
|
|
435
850
|
* <Field.Error>{error}</Field.Error>
|
|
436
851
|
* </Field.Root>
|
|
437
852
|
*
|
|
438
853
|
* Inside a form, `field` replaces the hand-written `invalid`: the form says
|
|
439
854
|
* whether the field is wrong and what the message is, and the field composes
|
|
440
|
-
* every `aria-*` from that in one place.
|
|
441
|
-
*
|
|
442
|
-
*
|
|
855
|
+
* every `aria-*` from that in one place. It also marks the control busy during
|
|
856
|
+
* submit, validation, or async default loading. `@uniflowed/form`'s
|
|
857
|
+
* `useFieldSource` is what produces one, and `field.js`'s header says why the
|
|
858
|
+
* hook lives there rather than here.
|
|
443
859
|
*
|
|
444
860
|
* const email = useFieldSource(form, "email", { required: "We need one" });
|
|
445
861
|
* <Field.Root field={email}>…<Field.Error /></Field.Root>
|
|
@@ -454,6 +870,7 @@ export const Field = {
|
|
|
454
870
|
Label: FieldLabel,
|
|
455
871
|
Control: FieldControl,
|
|
456
872
|
Description: FieldDescription,
|
|
873
|
+
Status: FieldStatus,
|
|
457
874
|
Error: FieldError,
|
|
458
875
|
};
|
|
459
876
|
|
|
@@ -471,6 +888,11 @@ export const Field = {
|
|
|
471
888
|
* <Tabs.Panel value="one">…</Tabs.Panel>
|
|
472
889
|
* <Tabs.Panel value="two">…</Tabs.Panel>
|
|
473
890
|
* </Tabs.Root>
|
|
891
|
+
*
|
|
892
|
+
* Every part takes `render`, so a tab that is also a route — `<Tabs.Tab
|
|
893
|
+
* render={(props) => <a href="#billing" {...props} />}>` — is still a tab, with
|
|
894
|
+
* the roving tab stop and the `aria-controls` a tab has. `Tabs.List`'s
|
|
895
|
+
* `renders* Tabs.Tab` is unaffected, because it is the *part* it constrains.
|
|
474
896
|
*/
|
|
475
897
|
export const Tabs = {
|
|
476
898
|
Root: TabsRoot,
|
|
@@ -603,6 +1025,12 @@ export const ToggleGroup = {
|
|
|
603
1025
|
* </Dialog.Footer>
|
|
604
1026
|
* </Dialog.Body>
|
|
605
1027
|
* </Dialog.Root>
|
|
1028
|
+
*
|
|
1029
|
+
* Every part takes `render`. `Dialog.Title` is an `<h2>` by default and the
|
|
1030
|
+
* level is a fact about the page around it rather than about the dialog, so
|
|
1031
|
+
* `render={(props) => <h3 {...props} />}` is how a caller says which — without
|
|
1032
|
+
* losing the id `aria-labelledby` points at. `AlertDialog`, `Sheet` and
|
|
1033
|
+
* `Drawer` are made of these parts and pass `render` straight through.
|
|
606
1034
|
*/
|
|
607
1035
|
export const Dialog = {
|
|
608
1036
|
Root: DialogRoot,
|
|
@@ -828,6 +1256,17 @@ export const InputOtp = {
|
|
|
828
1256
|
* </Menu.Sub>
|
|
829
1257
|
* </Menu.Body>
|
|
830
1258
|
* </Menu.Root>
|
|
1259
|
+
*
|
|
1260
|
+
* Every part takes `render`, which is what makes a menu of links possible — and
|
|
1261
|
+
* a menu of links is the most ordinary menu there is:
|
|
1262
|
+
*
|
|
1263
|
+
* <Menu.Item render={(props) => <a href="/settings" {...props} />}>
|
|
1264
|
+
* Settings
|
|
1265
|
+
* </Menu.Item>
|
|
1266
|
+
*
|
|
1267
|
+
* The `<a>` keeps the middle click, the context menu and the status bar; the
|
|
1268
|
+
* item keeps the role, the id, the roving tab stop and the press that closes
|
|
1269
|
+
* the tree. See the module header for why that is the answer to "no copy step".
|
|
831
1270
|
*/
|
|
832
1271
|
export const Menu = {
|
|
833
1272
|
Root: MenuRoot,
|
|
@@ -1242,3 +1681,100 @@ export const Pagination = {
|
|
|
1242
1681
|
Previous: PaginationPrevious,
|
|
1243
1682
|
Next: PaginationNext,
|
|
1244
1683
|
};
|
|
1684
|
+
|
|
1685
|
+
/**
|
|
1686
|
+
* The trail above the page, read as places rather than as punctuation.
|
|
1687
|
+
*
|
|
1688
|
+
* <Breadcrumb.Root>
|
|
1689
|
+
* <Breadcrumb.List>
|
|
1690
|
+
* <Breadcrumb.Item>
|
|
1691
|
+
* <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
|
|
1692
|
+
* </Breadcrumb.Item>
|
|
1693
|
+
* <Breadcrumb.Separator>/</Breadcrumb.Separator>
|
|
1694
|
+
* <Breadcrumb.Item>
|
|
1695
|
+
* <Breadcrumb.Page>Billing</Breadcrumb.Page>
|
|
1696
|
+
* </Breadcrumb.Item>
|
|
1697
|
+
* </Breadcrumb.List>
|
|
1698
|
+
* </Breadcrumb.Root>
|
|
1699
|
+
*
|
|
1700
|
+
* `Pagination`'s shape one door along: a `<nav>` with a name, one
|
|
1701
|
+
* `aria-current="page"`, and the separators out of the accessibility tree so
|
|
1702
|
+
* the trail is not announced as "Home slash Settings slash Billing". The last
|
|
1703
|
+
* crumb is a `Breadcrumb.Page` and not a link, because it is where the reader
|
|
1704
|
+
* already is.
|
|
1705
|
+
*/
|
|
1706
|
+
export const Breadcrumb = {
|
|
1707
|
+
Root: BreadcrumbRoot,
|
|
1708
|
+
List: BreadcrumbList,
|
|
1709
|
+
Item: BreadcrumbItem,
|
|
1710
|
+
Link: BreadcrumbLink,
|
|
1711
|
+
Page: BreadcrumbPage,
|
|
1712
|
+
Separator: BreadcrumbSeparator,
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* A callout, and the `live` that decides whether anybody is interrupted by it.
|
|
1717
|
+
*
|
|
1718
|
+
* <Alert.Root>
|
|
1719
|
+
* <Alert.Title>Your trial ends on Friday</Alert.Title>
|
|
1720
|
+
* <Alert.Description>Add a card to keep your projects.</Alert.Description>
|
|
1721
|
+
* </Alert.Root>
|
|
1722
|
+
*
|
|
1723
|
+
* {error != null && (
|
|
1724
|
+
* <Alert.Root live>
|
|
1725
|
+
* <Alert.Title>Could not save</Alert.Title>
|
|
1726
|
+
* <Alert.Description>{error}</Alert.Description>
|
|
1727
|
+
* </Alert.Root>
|
|
1728
|
+
* )}
|
|
1729
|
+
*
|
|
1730
|
+
* The first has no role at all: it was there when the page loaded, so a live
|
|
1731
|
+
* region would announce it on every load or never, and neither is what anybody
|
|
1732
|
+
* wanted. The second appeared because something happened, which is what
|
|
1733
|
+
* `role="alert"` is for. `alert.js`'s header says why there is no polite
|
|
1734
|
+
* version of this and why `Toast` is that instead.
|
|
1735
|
+
*/
|
|
1736
|
+
export const Alert = {
|
|
1737
|
+
Root: AlertRoot,
|
|
1738
|
+
Title: AlertTitle,
|
|
1739
|
+
Description: AlertDescription,
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
/**
|
|
1743
|
+
* A picture of a person, and the two states it is not in yet.
|
|
1744
|
+
*
|
|
1745
|
+
* <Avatar.Root>
|
|
1746
|
+
* <Avatar.Image src={person.photo} />
|
|
1747
|
+
* <Avatar.Fallback>{initials(person.name)}</Avatar.Fallback>
|
|
1748
|
+
* </Avatar.Root>
|
|
1749
|
+
*
|
|
1750
|
+
* The fallback is absent while the image is loading and present once it has
|
|
1751
|
+
* failed, held back long enough that a cached image never flashes initials.
|
|
1752
|
+
* `alt` defaults to `""`, because an avatar beside the name it belongs to is
|
|
1753
|
+
* decorative and a component that helpfully puts the name there makes every
|
|
1754
|
+
* screen reader say it twice; pass `alt` where the picture is the only thing
|
|
1755
|
+
* identifying the person.
|
|
1756
|
+
*/
|
|
1757
|
+
export const Avatar = {
|
|
1758
|
+
Root: AvatarRoot,
|
|
1759
|
+
Image: AvatarImage,
|
|
1760
|
+
Fallback: AvatarFallback,
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* The grey boxes, and the sentence that stops them being an empty page.
|
|
1765
|
+
*
|
|
1766
|
+
* <Skeleton.Root busy={pending}>
|
|
1767
|
+
* {pending ? <Skeleton.Box /> : <Invoices rows={invoices} />}
|
|
1768
|
+
* </Skeleton.Root>
|
|
1769
|
+
*
|
|
1770
|
+
* The boxes are `aria-hidden`, the region is `aria-busy`, and a live region
|
|
1771
|
+
* that was mounted empty for a commit says "Loading" — a skeleton screen is
|
|
1772
|
+
* busy on its first render, so a region rendered with its message already in it
|
|
1773
|
+
* announces nothing at all. Keep the root mounted across the load and toggle
|
|
1774
|
+
* `busy`; unmounting it takes the region away before it can say the wait is
|
|
1775
|
+
* over.
|
|
1776
|
+
*/
|
|
1777
|
+
export const Skeleton = {
|
|
1778
|
+
Root: SkeletonRoot,
|
|
1779
|
+
Box: SkeletonBox,
|
|
1780
|
+
};
|