@uniflowed/ui 0.0.0-alpha.10
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 +335 -0
- package/alert-dialog.js +256 -0
- package/carousel.js +410 -0
- package/checkbox.js +80 -0
- package/collapsible.js +147 -0
- package/combobox.js +557 -0
- package/dialog.js +499 -0
- package/drawer.js +458 -0
- package/field.js +170 -0
- package/hover-card.js +334 -0
- package/index.js +1012 -0
- package/input-otp.js +218 -0
- package/internal/anchor.js +500 -0
- package/internal/controlled-state.js +65 -0
- package/internal/disclosure.js +97 -0
- package/internal/focus.js +64 -0
- package/internal/form-value.js +83 -0
- package/internal/hover-intent.js +259 -0
- package/internal/merge-props.js +201 -0
- package/internal/range.js +147 -0
- package/internal/roving-focus.js +430 -0
- package/menu.js +654 -0
- package/navigation-menu.js +251 -0
- package/package.json +57 -0
- package/pagination.js +197 -0
- package/popover.js +326 -0
- package/progress.js +86 -0
- package/radio-group.js +298 -0
- package/resizable.js +307 -0
- package/scroll-area.js +283 -0
- package/select.js +855 -0
- package/sheet.js +165 -0
- package/sidebar.js +300 -0
- package/slider.js +405 -0
- package/switch.js +73 -0
- package/table.js +479 -0
- package/tabs.js +280 -0
- package/toast.js +624 -0
- package/toggle-group.js +280 -0
- package/toggle.js +91 -0
- package/tooltip.js +411 -0
package/index.js
ADDED
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// `@uniflowed/ui`: components you own, with the behaviour you would get wrong.
|
|
4
|
+
//
|
|
5
|
+
// The premise is the one shadcn established and it is the right one: a
|
|
6
|
+
// component library that ships styles is a library you fight, so these ship
|
|
7
|
+
// none. Every part takes `className` and every other DOM prop and passes it
|
|
8
|
+
// through; what they contribute is the part that is genuinely hard and
|
|
9
|
+
// genuinely invisible when it is missing.
|
|
10
|
+
//
|
|
11
|
+
// That part is behaviour, and specifically keyboard and screen-reader
|
|
12
|
+
// behaviour: a roving `tabindex` so a twelve-tab list does not take twelve Tab
|
|
13
|
+
// presses to get past, a focus trap that actually cannot be escaped, focus
|
|
14
|
+
// restored to whatever opened a dialog, typeahead in a menu, an
|
|
15
|
+
// `aria-activedescendant` that names an option still in the document. None of
|
|
16
|
+
// it is visible in a screenshot and all of it is what separates a component
|
|
17
|
+
// from a `div` that looks like one.
|
|
18
|
+
//
|
|
19
|
+
// Each primitive implements the WAI-ARIA authoring practices pattern for it —
|
|
20
|
+
// the roles, the `aria-*` wiring, the focus management and the whole keyboard
|
|
21
|
+
// map — and each module's header says which interaction it exists to get right
|
|
22
|
+
// and what a naive version breaks.
|
|
23
|
+
//
|
|
24
|
+
// # Composition is type-checked
|
|
25
|
+
//
|
|
26
|
+
// This is where Flow says something no other type system can. `Tabs.List`
|
|
27
|
+
// declares `renders* Tabs.Tab`, so a `<button>` in a tab list is a *type
|
|
28
|
+
// error* — not a review comment, not a runtime warning, not a screen reader
|
|
29
|
+
// announcing "button" where the reader expected "tab, 2 of 5". `Menu.Body` and
|
|
30
|
+
// `Combobox.List` state the same constraint about what may appear inside a
|
|
31
|
+
// menu and a listbox, which ARIA also requires and which nothing else checks.
|
|
32
|
+
// A library written in TypeScript can document those constraints; it cannot
|
|
33
|
+
// state them.
|
|
34
|
+
//
|
|
35
|
+
// # Styling is a default, not a dependency
|
|
36
|
+
//
|
|
37
|
+
// Nothing here imports StyleX, and nothing here has a StyleX-shaped type. A
|
|
38
|
+
// consumer styling with plain CSS, CSS Modules or anything else gets exactly
|
|
39
|
+
// the same components with exactly the same behaviour; the design-system layer
|
|
40
|
+
// that adds uf's default styles is built *on* these, not into them.
|
|
41
|
+
//
|
|
42
|
+
// # What these components promise React
|
|
43
|
+
//
|
|
44
|
+
// Nothing here mutates during a render, reads a ref during a render, or depends
|
|
45
|
+
// on a render happening exactly once — so React Compiler's memoization and
|
|
46
|
+
// ordinary `memo` are both safe, and none of it needs an escape hatch. The
|
|
47
|
+
// refs that exist (`triggerRef`, `pendingFocus`, the typeahead buffer) are
|
|
48
|
+
// written only from event handlers and effects, and nothing renders them.
|
|
49
|
+
//
|
|
50
|
+
// Where a component has to learn something the DOM knows — how many options a
|
|
51
|
+
// caller filtered down to, which item the arrow key should move to — it reads
|
|
52
|
+
// the document in an effect or an event handler and, if a render depends on
|
|
53
|
+
// the answer, puts it in state. That is deliberately *not*
|
|
54
|
+
// `useSyncExternalStore`: the DOM is not a store whose value a render may
|
|
55
|
+
// read, and reading layout during a render is the thing that API exists to
|
|
56
|
+
// prevent.
|
|
57
|
+
//
|
|
58
|
+
// One component does use it, and it is the case the API is actually for.
|
|
59
|
+
// `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.
|
|
74
|
+
//
|
|
75
|
+
// # Server and client
|
|
76
|
+
//
|
|
77
|
+
// Every module that manages focus, listens to the document or holds state
|
|
78
|
+
// declares `"use client"`, because each of those needs a browser. That is a
|
|
79
|
+
// property of the components, not of the application: an RSC page may import
|
|
80
|
+
// this package from a Server Component, and only the parts that need the client
|
|
81
|
+
// join the client bundle.
|
|
82
|
+
//
|
|
83
|
+
// # How the package is laid out
|
|
84
|
+
//
|
|
85
|
+
// One root module per primitive, each with its own `exports` subpath, each
|
|
86
|
+
// named after the thing it implements:
|
|
87
|
+
//
|
|
88
|
+
// - `dialog.js` — the focus trap, focus restore, scroll lock and inert page,
|
|
89
|
+
// and the three props the four components below it differ from it by.
|
|
90
|
+
// - `alert-dialog.js`, `sheet.js`, `drawer.js` and `sidebar.js` — the four
|
|
91
|
+
// built on that one. An alert dialog is modal and cannot be dismissed by
|
|
92
|
+
// pressing beside it; a sheet is a dialog with an edge; a drawer is a sheet
|
|
93
|
+
// with a gesture, and therefore with WCAG 2.5.7's keyboard equivalent of it;
|
|
94
|
+
// a sidebar is most often neither modal nor a dialog, and becomes both on a
|
|
95
|
+
// narrow viewport.
|
|
96
|
+
// - `carousel.js`, `scroll-area.js` and `input-otp.js` — the three that replace
|
|
97
|
+
// something the browser already does, and so the three that have to be better
|
|
98
|
+
// than what they replaced. Each module's header says what it gives that the
|
|
99
|
+
// plain element does not; if it ever stops being true, the component should
|
|
100
|
+
// be deleted rather than fixed.
|
|
101
|
+
// - `menu.js` — the arrow keys, typeahead, submenus and `Escape` stacking.
|
|
102
|
+
// - `combobox.js` — `aria-activedescendant` over a filtered list, and the
|
|
103
|
+
// count a screen reader is told.
|
|
104
|
+
// - `select.js` — the other half of the combobox pattern: the select-only one,
|
|
105
|
+
// with typeahead, option groups and a value a form can submit.
|
|
106
|
+
// - `tabs.js` — the roving `tabindex`, and automatic versus manual activation.
|
|
107
|
+
// - `toast.js` — the live region that was watching before there was anything
|
|
108
|
+
// to announce, and the countdown that stops.
|
|
109
|
+
// - `field.js` — the label, description, error and `aria-invalid` wiring.
|
|
110
|
+
// - `switch.js`, `checkbox.js` and `toggle.js` — the three two-state controls,
|
|
111
|
+
// apart because a reader is told something different by each, and because the
|
|
112
|
+
// third state and the `Enter` key genuinely differ between them.
|
|
113
|
+
// - `radio-group.js` — one answer out of several, and the tab stop an
|
|
114
|
+
// unanswered group would otherwise not have.
|
|
115
|
+
// - `toggle-group.js` — a row of toggle buttons as one control, whose `single`
|
|
116
|
+
// mode is a radio group and is rendered by `radio-group.js` rather than
|
|
117
|
+
// written a second time.
|
|
118
|
+
// - `collapsible.js`, `accordion.js` and `navigation-menu.js` — the disclosure
|
|
119
|
+
// pattern on its own, stacked, and applied to a site's navigation. The third
|
|
120
|
+
// of those exists as much to prevent `role="menu"` from being used for a list
|
|
121
|
+
// of links as to provide anything.
|
|
122
|
+
// - `slider.js`, `resizable.js` and `progress.js` — the three that report a
|
|
123
|
+
// number in a range. A window splitter is a slider wearing a separator's
|
|
124
|
+
// role, which is why it is beside one rather than with the layout.
|
|
125
|
+
// - `table.js` and `pagination.js` — the sort that is announced, the selection
|
|
126
|
+
// that can be mixed, and the rows a page is not showing.
|
|
127
|
+
// - `popover.js`, `tooltip.js` and `hover-card.js` — the three anchored
|
|
128
|
+
// overlays, which are one component seen from three distances: one you
|
|
129
|
+
// click, one you hover, and one you hover and then read. They are three
|
|
130
|
+
// modules because what a reader is told differs in every one — a popover is
|
|
131
|
+
// a dialog that is not modal, a tooltip describes its trigger and may never
|
|
132
|
+
// take focus, a hover card is neither and holds links — and because a flag
|
|
133
|
+
// selecting between them would be one flag every behaviour had to read.
|
|
134
|
+
//
|
|
135
|
+
// Every name below is exported from one of those, so a consumer may import
|
|
136
|
+
// `@uniflowed/ui` or `@uniflowed/ui/dialog` and get the same thing. The split
|
|
137
|
+
// is by primitive because that is the unit a reader looks for, the unit a
|
|
138
|
+
// bundler drops, and the unit the WAI-ARIA practices are written in.
|
|
139
|
+
//
|
|
140
|
+
// `internal/` holds nine modules and nothing else, each a rule the primitives
|
|
141
|
+
// must apply identically and a consumer must not be able to apply differently:
|
|
142
|
+
// `merge-props.js` (the caller's props go on first, the component's semantics
|
|
143
|
+
// last), `controlled-state.js` (what "controlled" means here),
|
|
144
|
+
// `roving-focus.js` (how a set of items is found and moved between),
|
|
145
|
+
// `disclosure.js` (how a button says whether a region is showing, and how a
|
|
146
|
+
// closed region stays findable), `form-value.js` (what a `<form>` submits for a
|
|
147
|
+
// control the browser has never heard of), `range.js` (the arithmetic that
|
|
148
|
+
// keeps `aria-valuemin`, `aria-valuemax` and `aria-valuenow` true about each
|
|
149
|
+
// other), `anchor.js` (where an overlay goes, and what it does when it does not
|
|
150
|
+
// fit where it was asked to go), `focus.js` (which elements a reader can reach,
|
|
151
|
+
// which a focus trap and a popover want opposite things from), and
|
|
152
|
+
// `hover-intent.js` (what WCAG requires of content shown on hover or focus,
|
|
153
|
+
// which is three clauses and one mechanism). Each says in its own header why it
|
|
154
|
+
// is unreachable rather than exported. There is no `internal/props.js`-shaped
|
|
155
|
+
// bag of helpers: a module that cannot say what it is about does not belong in
|
|
156
|
+
// this package.
|
|
157
|
+
|
|
158
|
+
import {
|
|
159
|
+
AccordionContent,
|
|
160
|
+
AccordionHeader,
|
|
161
|
+
AccordionItem,
|
|
162
|
+
AccordionRoot,
|
|
163
|
+
AccordionTrigger,
|
|
164
|
+
} from "./accordion.js";
|
|
165
|
+
import {
|
|
166
|
+
AlertDialogAction,
|
|
167
|
+
AlertDialogBody,
|
|
168
|
+
AlertDialogCancel,
|
|
169
|
+
AlertDialogDescription,
|
|
170
|
+
AlertDialogFooter,
|
|
171
|
+
AlertDialogHeader,
|
|
172
|
+
AlertDialogOverlay,
|
|
173
|
+
AlertDialogRoot,
|
|
174
|
+
AlertDialogTitle,
|
|
175
|
+
AlertDialogTrigger,
|
|
176
|
+
} from "./alert-dialog.js";
|
|
177
|
+
import {
|
|
178
|
+
CarouselContent,
|
|
179
|
+
CarouselItem,
|
|
180
|
+
CarouselNext,
|
|
181
|
+
CarouselPause,
|
|
182
|
+
CarouselPrevious,
|
|
183
|
+
CarouselRoot,
|
|
184
|
+
} from "./carousel.js";
|
|
185
|
+
import { Checkbox } from "./checkbox.js";
|
|
186
|
+
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "./collapsible.js";
|
|
187
|
+
import {
|
|
188
|
+
ComboboxEmpty,
|
|
189
|
+
ComboboxInput,
|
|
190
|
+
ComboboxLabel,
|
|
191
|
+
ComboboxList,
|
|
192
|
+
ComboboxOption,
|
|
193
|
+
ComboboxRoot,
|
|
194
|
+
ComboboxStatus,
|
|
195
|
+
} from "./combobox.js";
|
|
196
|
+
import {
|
|
197
|
+
DialogBody,
|
|
198
|
+
DialogClose,
|
|
199
|
+
DialogDescription,
|
|
200
|
+
DialogFooter,
|
|
201
|
+
DialogHeader,
|
|
202
|
+
DialogOverlay,
|
|
203
|
+
DialogRoot,
|
|
204
|
+
DialogTitle,
|
|
205
|
+
DialogTrigger,
|
|
206
|
+
} from "./dialog.js";
|
|
207
|
+
import {
|
|
208
|
+
DrawerBody,
|
|
209
|
+
DrawerClose,
|
|
210
|
+
DrawerDescription,
|
|
211
|
+
DrawerFooter,
|
|
212
|
+
DrawerHandle,
|
|
213
|
+
DrawerHeader,
|
|
214
|
+
DrawerOverlay,
|
|
215
|
+
DrawerRoot,
|
|
216
|
+
DrawerTitle,
|
|
217
|
+
DrawerTrigger,
|
|
218
|
+
} from "./drawer.js";
|
|
219
|
+
import { FieldControl, FieldDescription, FieldError, FieldLabel, FieldRoot } from "./field.js";
|
|
220
|
+
import { HoverCardBody, HoverCardRoot, HoverCardTrigger } from "./hover-card.js";
|
|
221
|
+
import { InputOtpGroup, InputOtpRoot, InputOtpSeparator, InputOtpSlot } from "./input-otp.js";
|
|
222
|
+
import {
|
|
223
|
+
MenuBody,
|
|
224
|
+
MenuGroup,
|
|
225
|
+
MenuItem,
|
|
226
|
+
MenuLabel,
|
|
227
|
+
MenuRoot,
|
|
228
|
+
MenuSeparator,
|
|
229
|
+
MenuSub,
|
|
230
|
+
MenuSubTrigger,
|
|
231
|
+
MenuTrigger,
|
|
232
|
+
} from "./menu.js";
|
|
233
|
+
import {
|
|
234
|
+
NavigationMenuBody,
|
|
235
|
+
NavigationMenuItem,
|
|
236
|
+
NavigationMenuLink,
|
|
237
|
+
NavigationMenuList,
|
|
238
|
+
NavigationMenuRoot,
|
|
239
|
+
NavigationMenuTrigger,
|
|
240
|
+
} from "./navigation-menu.js";
|
|
241
|
+
import {
|
|
242
|
+
PaginationContent,
|
|
243
|
+
PaginationItem,
|
|
244
|
+
PaginationNext,
|
|
245
|
+
PaginationPrevious,
|
|
246
|
+
PaginationRoot,
|
|
247
|
+
} from "./pagination.js";
|
|
248
|
+
import { PopoverBody, PopoverRoot, PopoverTrigger } from "./popover.js";
|
|
249
|
+
import { Progress } from "./progress.js";
|
|
250
|
+
import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from "./radio-group.js";
|
|
251
|
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./resizable.js";
|
|
252
|
+
import { ScrollAreaRoot, ScrollAreaScrollbar, ScrollAreaViewport } from "./scroll-area.js";
|
|
253
|
+
import {
|
|
254
|
+
SelectGroup,
|
|
255
|
+
SelectGroupLabel,
|
|
256
|
+
SelectLabel,
|
|
257
|
+
SelectList,
|
|
258
|
+
SelectOption,
|
|
259
|
+
SelectRoot,
|
|
260
|
+
SelectSeparator,
|
|
261
|
+
SelectTrigger,
|
|
262
|
+
SelectValue,
|
|
263
|
+
} from "./select.js";
|
|
264
|
+
import {
|
|
265
|
+
SheetBody,
|
|
266
|
+
SheetClose,
|
|
267
|
+
SheetDescription,
|
|
268
|
+
SheetFooter,
|
|
269
|
+
SheetHeader,
|
|
270
|
+
SheetOverlay,
|
|
271
|
+
SheetRoot,
|
|
272
|
+
SheetTitle,
|
|
273
|
+
SheetTrigger,
|
|
274
|
+
} from "./sheet.js";
|
|
275
|
+
import {
|
|
276
|
+
SidebarBody,
|
|
277
|
+
SidebarFooter,
|
|
278
|
+
SidebarHeader,
|
|
279
|
+
SidebarItem,
|
|
280
|
+
SidebarRoot,
|
|
281
|
+
SidebarTrigger,
|
|
282
|
+
} from "./sidebar.js";
|
|
283
|
+
import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from "./slider.js";
|
|
284
|
+
import { Switch } from "./switch.js";
|
|
285
|
+
import {
|
|
286
|
+
TableBody,
|
|
287
|
+
TableCaption,
|
|
288
|
+
TableCell,
|
|
289
|
+
TableHead,
|
|
290
|
+
TableHeader,
|
|
291
|
+
TableRoot,
|
|
292
|
+
TableRow,
|
|
293
|
+
TableRowHeader,
|
|
294
|
+
TableRowSelect,
|
|
295
|
+
TableSelectAll,
|
|
296
|
+
} from "./table.js";
|
|
297
|
+
import { TabsList, TabsPanel, TabsRoot, TabsTab } from "./tabs.js";
|
|
298
|
+
import {
|
|
299
|
+
ToastAction,
|
|
300
|
+
ToastClose,
|
|
301
|
+
ToastDescription,
|
|
302
|
+
ToastRegion,
|
|
303
|
+
ToastRoot,
|
|
304
|
+
ToastTitle,
|
|
305
|
+
dismissAllToasts,
|
|
306
|
+
dismissToast,
|
|
307
|
+
toast,
|
|
308
|
+
updateToast,
|
|
309
|
+
} from "./toast.js";
|
|
310
|
+
import { Toggle } from "./toggle.js";
|
|
311
|
+
import { ToggleGroupItem, ToggleGroupRoot } from "./toggle-group.js";
|
|
312
|
+
import { TooltipBody, TooltipProvider, TooltipRoot, TooltipTrigger } from "./tooltip.js";
|
|
313
|
+
|
|
314
|
+
export type { AccordionType } from "./accordion.js";
|
|
315
|
+
export type { ActivationMode } from "./tabs.js";
|
|
316
|
+
// What a modal announces itself as, for a caller who holds one in a variable.
|
|
317
|
+
// Two members, not a string: see `dialog.js`.
|
|
318
|
+
export type { DialogRole } from "./dialog.js";
|
|
319
|
+
// Which edge of the viewport a sheet or a drawer is attached to. `Sidebar` has
|
|
320
|
+
// its own two-member union, because a sidebar is never on the top or bottom.
|
|
321
|
+
export type { Edge } from "./sheet.js";
|
|
322
|
+
export type { InputOtpKind } from "./input-otp.js";
|
|
323
|
+
export type { SidebarSide } from "./sidebar.js";
|
|
324
|
+
// Where an anchored overlay opens, for a caller who holds one in a variable or
|
|
325
|
+
// a prop of their own. Unions rather than strings, so `side="botom"` is a type
|
|
326
|
+
// error at the call rather than an overlay that quietly opens somewhere else.
|
|
327
|
+
export type { Align, Side } from "./popover.js";
|
|
328
|
+
export type { Sort } from "./table.js";
|
|
329
|
+
export type { Notification, ToastChanges, ToastOptions, Urgency } from "./toast.js";
|
|
330
|
+
export type { ToggleGroupType } from "./toggle-group.js";
|
|
331
|
+
|
|
332
|
+
export { Checkbox, Progress, Switch, Toggle };
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Queueing a notification, from anywhere.
|
|
336
|
+
*
|
|
337
|
+
* Functions rather than a hook, because the places a notification comes from —
|
|
338
|
+
* an event handler, a `catch`, a Server Action's error path — do not have a
|
|
339
|
+
* component to hold state in. `Toast.Region` is what displays them.
|
|
340
|
+
*
|
|
341
|
+
* const id = toast("Uploading…", { duration: null });
|
|
342
|
+
* updateToast(id, { content: "Uploaded", duration: 4000 });
|
|
343
|
+
* toast("Could not save", { urgency: "assertive" });
|
|
344
|
+
*/
|
|
345
|
+
export { dismissAllToasts, dismissToast, toast, updateToast };
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* An accessible form field.
|
|
349
|
+
*
|
|
350
|
+
* <Field.Root invalid={error != null}>
|
|
351
|
+
* <Field.Label>Email</Field.Label>
|
|
352
|
+
* <Field.Control render={(props) => <input type="email" {...props} />} />
|
|
353
|
+
* <Field.Description>We will not share it.</Field.Description>
|
|
354
|
+
* <Field.Error>{error}</Field.Error>
|
|
355
|
+
* </Field.Root>
|
|
356
|
+
*/
|
|
357
|
+
export const Field = {
|
|
358
|
+
Root: FieldRoot,
|
|
359
|
+
Label: FieldLabel,
|
|
360
|
+
Control: FieldControl,
|
|
361
|
+
Description: FieldDescription,
|
|
362
|
+
Error: FieldError,
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Tabs, with the arrow-key behaviour the pattern requires.
|
|
367
|
+
*
|
|
368
|
+
* `activationMode="manual"` moves focus without selecting, for panels that cost
|
|
369
|
+
* something to show.
|
|
370
|
+
*
|
|
371
|
+
* <Tabs.Root defaultValue="one">
|
|
372
|
+
* <Tabs.List aria-label="Sections">
|
|
373
|
+
* <Tabs.Tab value="one">One</Tabs.Tab>
|
|
374
|
+
* <Tabs.Tab value="two">Two</Tabs.Tab>
|
|
375
|
+
* </Tabs.List>
|
|
376
|
+
* <Tabs.Panel value="one">…</Tabs.Panel>
|
|
377
|
+
* <Tabs.Panel value="two">…</Tabs.Panel>
|
|
378
|
+
* </Tabs.Root>
|
|
379
|
+
*/
|
|
380
|
+
export const Tabs = {
|
|
381
|
+
Root: TabsRoot,
|
|
382
|
+
List: TabsList,
|
|
383
|
+
Tab: TabsTab,
|
|
384
|
+
Panel: TabsPanel,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* A button and the region it shows, with the three attributes that say so.
|
|
389
|
+
*
|
|
390
|
+
* The content stays in the document while it is closed, so the browser's
|
|
391
|
+
* find-in-page can still reach the text in it.
|
|
392
|
+
*
|
|
393
|
+
* <Collapsible.Root>
|
|
394
|
+
* <Collapsible.Trigger>Details</Collapsible.Trigger>
|
|
395
|
+
* <Collapsible.Content>…</Collapsible.Content>
|
|
396
|
+
* </Collapsible.Root>
|
|
397
|
+
*/
|
|
398
|
+
export const Collapsible = {
|
|
399
|
+
Root: CollapsibleRoot,
|
|
400
|
+
Trigger: CollapsibleTrigger,
|
|
401
|
+
Content: CollapsibleContent,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* A stack of disclosures that know about each other.
|
|
406
|
+
*
|
|
407
|
+
* `Accordion.Header` takes the heading `level`, because which heading an
|
|
408
|
+
* accordion's sections are depends on where the accordion sits. Each panel is a
|
|
409
|
+
* region named after the header that opens it.
|
|
410
|
+
*
|
|
411
|
+
* <Accordion.Root type="single">
|
|
412
|
+
* <Accordion.Item value="shipping">
|
|
413
|
+
* <Accordion.Header level={3}>
|
|
414
|
+
* <Accordion.Trigger>Shipping</Accordion.Trigger>
|
|
415
|
+
* </Accordion.Header>
|
|
416
|
+
* <Accordion.Content>…</Accordion.Content>
|
|
417
|
+
* </Accordion.Item>
|
|
418
|
+
* </Accordion.Root>
|
|
419
|
+
*/
|
|
420
|
+
export const Accordion = {
|
|
421
|
+
Root: AccordionRoot,
|
|
422
|
+
Item: AccordionItem,
|
|
423
|
+
Header: AccordionHeader,
|
|
424
|
+
Trigger: AccordionTrigger,
|
|
425
|
+
Content: AccordionContent,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Site navigation: a list of links behind buttons, and not a `menu`.
|
|
430
|
+
*
|
|
431
|
+
* <NavigationMenu.Root aria-label="Main">
|
|
432
|
+
* <NavigationMenu.List>
|
|
433
|
+
* <NavigationMenu.Item value="docs">
|
|
434
|
+
* <NavigationMenu.Trigger>Docs</NavigationMenu.Trigger>
|
|
435
|
+
* <NavigationMenu.Body>
|
|
436
|
+
* <NavigationMenu.Link href="/guide">Guide</NavigationMenu.Link>
|
|
437
|
+
* </NavigationMenu.Body>
|
|
438
|
+
* </NavigationMenu.Item>
|
|
439
|
+
* </NavigationMenu.List>
|
|
440
|
+
* </NavigationMenu.Root>
|
|
441
|
+
*/
|
|
442
|
+
export const NavigationMenu = {
|
|
443
|
+
Root: NavigationMenuRoot,
|
|
444
|
+
List: NavigationMenuList,
|
|
445
|
+
Item: NavigationMenuItem,
|
|
446
|
+
Trigger: NavigationMenuTrigger,
|
|
447
|
+
Body: NavigationMenuBody,
|
|
448
|
+
Link: NavigationMenuLink,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* One answer out of several, with the arrow keys that check as they move.
|
|
453
|
+
*
|
|
454
|
+
* `Tab` reaches the chosen answer, or the first one while there is none, and
|
|
455
|
+
* leaves the whole group in one press. `name` puts the answer where a form can
|
|
456
|
+
* submit it.
|
|
457
|
+
*
|
|
458
|
+
* <Field.Root>
|
|
459
|
+
* <Field.Label>Plan</Field.Label>
|
|
460
|
+
* <Field.Control
|
|
461
|
+
* render={(props) => (
|
|
462
|
+
* <RadioGroup.Root {...props} defaultValue="free" name="plan">
|
|
463
|
+
* <RadioGroup.Item value="free">
|
|
464
|
+
* Free <RadioGroup.Indicator>●</RadioGroup.Indicator>
|
|
465
|
+
* </RadioGroup.Item>
|
|
466
|
+
* <RadioGroup.Item value="pro">Pro</RadioGroup.Item>
|
|
467
|
+
* </RadioGroup.Root>
|
|
468
|
+
* )}
|
|
469
|
+
* />
|
|
470
|
+
* </Field.Root>
|
|
471
|
+
*/
|
|
472
|
+
export const RadioGroup = {
|
|
473
|
+
Root: RadioGroupRoot,
|
|
474
|
+
Item: RadioGroupItem,
|
|
475
|
+
Indicator: RadioGroupIndicator,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* A row of toggle buttons that behaves as one control.
|
|
480
|
+
*
|
|
481
|
+
* `type="multiple"` is a group of toggle buttons, any number of them pressed.
|
|
482
|
+
* `type="single"` is a radio group drawn as segments, and is rendered by
|
|
483
|
+
* `RadioGroup` rather than written a second time.
|
|
484
|
+
*
|
|
485
|
+
* <ToggleGroup.Root aria-label="Formatting" type="multiple">
|
|
486
|
+
* <ToggleGroup.Item value="bold">B</ToggleGroup.Item>
|
|
487
|
+
* <ToggleGroup.Item value="italic">I</ToggleGroup.Item>
|
|
488
|
+
* </ToggleGroup.Root>
|
|
489
|
+
*/
|
|
490
|
+
export const ToggleGroup = {
|
|
491
|
+
Root: ToggleGroupRoot,
|
|
492
|
+
Item: ToggleGroupItem,
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* A modal dialog: focus moved in, kept in, and given back.
|
|
497
|
+
*
|
|
498
|
+
* <Dialog.Root>
|
|
499
|
+
* <Dialog.Trigger>Delete</Dialog.Trigger>
|
|
500
|
+
* <Dialog.Overlay />
|
|
501
|
+
* <Dialog.Body>
|
|
502
|
+
* <Dialog.Header>
|
|
503
|
+
* <Dialog.Title>Delete this project?</Dialog.Title>
|
|
504
|
+
* <Dialog.Description>This cannot be undone.</Dialog.Description>
|
|
505
|
+
* </Dialog.Header>
|
|
506
|
+
* <Dialog.Footer>
|
|
507
|
+
* <Dialog.Close>Cancel</Dialog.Close>
|
|
508
|
+
* </Dialog.Footer>
|
|
509
|
+
* </Dialog.Body>
|
|
510
|
+
* </Dialog.Root>
|
|
511
|
+
*/
|
|
512
|
+
export const Dialog = {
|
|
513
|
+
Root: DialogRoot,
|
|
514
|
+
Trigger: DialogTrigger,
|
|
515
|
+
Overlay: DialogOverlay,
|
|
516
|
+
Body: DialogBody,
|
|
517
|
+
Header: DialogHeader,
|
|
518
|
+
Footer: DialogFooter,
|
|
519
|
+
Title: DialogTitle,
|
|
520
|
+
Description: DialogDescription,
|
|
521
|
+
Close: DialogClose,
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* The confirmation: modal, announced as an alert, and not dismissible by a
|
|
526
|
+
* press beside it.
|
|
527
|
+
*
|
|
528
|
+
* Focus lands on `Cancel` rather than on the first thing in the dialog, and the
|
|
529
|
+
* description is required — `role="alertdialog"` exists to announce one, so an
|
|
530
|
+
* alert dialog without it interrupts the reader to say nothing.
|
|
531
|
+
*
|
|
532
|
+
* <AlertDialog.Root>
|
|
533
|
+
* <AlertDialog.Trigger>Delete</AlertDialog.Trigger>
|
|
534
|
+
* <AlertDialog.Overlay />
|
|
535
|
+
* <AlertDialog.Body>
|
|
536
|
+
* <AlertDialog.Header>
|
|
537
|
+
* <AlertDialog.Title>Delete this project?</AlertDialog.Title>
|
|
538
|
+
* <AlertDialog.Description>This cannot be undone.</AlertDialog.Description>
|
|
539
|
+
* </AlertDialog.Header>
|
|
540
|
+
* <AlertDialog.Footer>
|
|
541
|
+
* <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
|
542
|
+
* <AlertDialog.Action onClick={remove}>Delete</AlertDialog.Action>
|
|
543
|
+
* </AlertDialog.Footer>
|
|
544
|
+
* </AlertDialog.Body>
|
|
545
|
+
* </AlertDialog.Root>
|
|
546
|
+
*/
|
|
547
|
+
export const AlertDialog = {
|
|
548
|
+
Root: AlertDialogRoot,
|
|
549
|
+
Trigger: AlertDialogTrigger,
|
|
550
|
+
Overlay: AlertDialogOverlay,
|
|
551
|
+
Body: AlertDialogBody,
|
|
552
|
+
Header: AlertDialogHeader,
|
|
553
|
+
Footer: AlertDialogFooter,
|
|
554
|
+
Title: AlertDialogTitle,
|
|
555
|
+
Description: AlertDialogDescription,
|
|
556
|
+
Action: AlertDialogAction,
|
|
557
|
+
Cancel: AlertDialogCancel,
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* A modal dialog attached to an edge of the viewport.
|
|
562
|
+
*
|
|
563
|
+
* `side` is a union rather than a class name, and every part reports it as
|
|
564
|
+
* `data-side` — the same attribute `Popover.Body` writes, so one stylesheet
|
|
565
|
+
* rule covers every overlay in this package.
|
|
566
|
+
*
|
|
567
|
+
* <Sheet.Root side="left">
|
|
568
|
+
* <Sheet.Trigger>Filters</Sheet.Trigger>
|
|
569
|
+
* <Sheet.Overlay />
|
|
570
|
+
* <Sheet.Body>
|
|
571
|
+
* <Sheet.Title>Filters</Sheet.Title>
|
|
572
|
+
* <Sheet.Close>Done</Sheet.Close>
|
|
573
|
+
* </Sheet.Body>
|
|
574
|
+
* </Sheet.Root>
|
|
575
|
+
*/
|
|
576
|
+
export const Sheet = {
|
|
577
|
+
Root: SheetRoot,
|
|
578
|
+
Trigger: SheetTrigger,
|
|
579
|
+
Overlay: SheetOverlay,
|
|
580
|
+
Body: SheetBody,
|
|
581
|
+
Header: SheetHeader,
|
|
582
|
+
Footer: SheetFooter,
|
|
583
|
+
Title: SheetTitle,
|
|
584
|
+
Description: SheetDescription,
|
|
585
|
+
Close: SheetClose,
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* The sheet you can drag away, with the keyboard that can do everything the
|
|
590
|
+
* drag can.
|
|
591
|
+
*
|
|
592
|
+
* `Drawer.Handle` is a `role="slider"` over the snap points: the arrow keys
|
|
593
|
+
* move between them, `Home` and `End` go to the ends, and the closing key at
|
|
594
|
+
* the smallest snap point closes it. WCAG 2.5.7 also wants a single-pointer
|
|
595
|
+
* alternative, so a drawer with a handle and no `Drawer.Close` raises.
|
|
596
|
+
*
|
|
597
|
+
* <Drawer.Root side="bottom" snapPoints={[0.4, 1]}>
|
|
598
|
+
* <Drawer.Trigger>Details</Drawer.Trigger>
|
|
599
|
+
* <Drawer.Overlay />
|
|
600
|
+
* <Drawer.Body>
|
|
601
|
+
* <Drawer.Handle label="Resize the details" />
|
|
602
|
+
* <Drawer.Title>Details</Drawer.Title>
|
|
603
|
+
* <Drawer.Close>Close</Drawer.Close>
|
|
604
|
+
* </Drawer.Body>
|
|
605
|
+
* </Drawer.Root>
|
|
606
|
+
*/
|
|
607
|
+
export const Drawer = {
|
|
608
|
+
Root: DrawerRoot,
|
|
609
|
+
Trigger: DrawerTrigger,
|
|
610
|
+
Overlay: DrawerOverlay,
|
|
611
|
+
Body: DrawerBody,
|
|
612
|
+
Handle: DrawerHandle,
|
|
613
|
+
Header: DrawerHeader,
|
|
614
|
+
Footer: DrawerFooter,
|
|
615
|
+
Title: DrawerTitle,
|
|
616
|
+
Description: DrawerDescription,
|
|
617
|
+
Close: DrawerClose,
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Navigation beside the page, which becomes a modal sheet on a narrow one.
|
|
622
|
+
*
|
|
623
|
+
* `Sidebar.Item` takes a `label` and keeps it as the button's accessible name
|
|
624
|
+
* the moment the sidebar collapses to icons — which is the whole reason a
|
|
625
|
+
* collapsing sidebar is a component rather than a class.
|
|
626
|
+
*
|
|
627
|
+
* <Sidebar.Root defaultOpen={fromCookie}>
|
|
628
|
+
* <Sidebar.Trigger>Menu</Sidebar.Trigger>
|
|
629
|
+
* <Sidebar.Body label="Main">
|
|
630
|
+
* <Sidebar.Item label="Settings">
|
|
631
|
+
* <Gear /> Settings
|
|
632
|
+
* </Sidebar.Item>
|
|
633
|
+
* </Sidebar.Body>
|
|
634
|
+
* </Sidebar.Root>
|
|
635
|
+
*/
|
|
636
|
+
export const Sidebar = {
|
|
637
|
+
Root: SidebarRoot,
|
|
638
|
+
Trigger: SidebarTrigger,
|
|
639
|
+
Header: SidebarHeader,
|
|
640
|
+
Body: SidebarBody,
|
|
641
|
+
Footer: SidebarFooter,
|
|
642
|
+
Item: SidebarItem,
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Slides, one at a time, that a reader can stop and cannot fall into.
|
|
647
|
+
*
|
|
648
|
+
* `Carousel.Pause` is WCAG 2.2.2's mechanism and must be the first focusable
|
|
649
|
+
* thing inside the carousel; the slides that are not showing are `inert`, so
|
|
650
|
+
* `Tab` cannot reach a link nobody can see.
|
|
651
|
+
*
|
|
652
|
+
* <Carousel.Root autoplay={5000} count={3} label="Featured">
|
|
653
|
+
* <Carousel.Pause />
|
|
654
|
+
* <Carousel.Content>
|
|
655
|
+
* <Carousel.Item index={0}>…</Carousel.Item>
|
|
656
|
+
* <Carousel.Item index={1}>…</Carousel.Item>
|
|
657
|
+
* <Carousel.Item index={2}>…</Carousel.Item>
|
|
658
|
+
* </Carousel.Content>
|
|
659
|
+
* <Carousel.Previous />
|
|
660
|
+
* <Carousel.Next />
|
|
661
|
+
* </Carousel.Root>
|
|
662
|
+
*/
|
|
663
|
+
export const Carousel = {
|
|
664
|
+
Root: CarouselRoot,
|
|
665
|
+
Content: CarouselContent,
|
|
666
|
+
Item: CarouselItem,
|
|
667
|
+
Pause: CarouselPause,
|
|
668
|
+
Previous: CarouselPrevious,
|
|
669
|
+
Next: CarouselNext,
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* An overflow container a keyboard can actually scroll.
|
|
674
|
+
*
|
|
675
|
+
* `role="region"`, a name and `tabindex="0"`, because a scroll container is not
|
|
676
|
+
* focusable in every browser and one that is not is one a keyboard reader can
|
|
677
|
+
* see the top of and nothing else.
|
|
678
|
+
*
|
|
679
|
+
* <ScrollArea.Root label="Release notes">
|
|
680
|
+
* <ScrollArea.Viewport>…</ScrollArea.Viewport>
|
|
681
|
+
* <ScrollArea.Scrollbar orientation="vertical" />
|
|
682
|
+
* </ScrollArea.Root>
|
|
683
|
+
*/
|
|
684
|
+
export const ScrollArea = {
|
|
685
|
+
Root: ScrollAreaRoot,
|
|
686
|
+
Viewport: ScrollAreaViewport,
|
|
687
|
+
Scrollbar: ScrollAreaScrollbar,
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* A one-time code: six boxes drawn over one real `<input>`.
|
|
692
|
+
*
|
|
693
|
+
* One input, so `autocomplete="one-time-code"` works, a paste fills every box,
|
|
694
|
+
* and a form submits one value under one name.
|
|
695
|
+
*
|
|
696
|
+
* <InputOtp.Root label="One-time code" length={6} name="code">
|
|
697
|
+
* <InputOtp.Group>
|
|
698
|
+
* <InputOtp.Slot index={0} />
|
|
699
|
+
* <InputOtp.Slot index={1} />
|
|
700
|
+
* <InputOtp.Slot index={2} />
|
|
701
|
+
* </InputOtp.Group>
|
|
702
|
+
* <InputOtp.Separator>-</InputOtp.Separator>
|
|
703
|
+
* <InputOtp.Group>
|
|
704
|
+
* <InputOtp.Slot index={3} />
|
|
705
|
+
* <InputOtp.Slot index={4} />
|
|
706
|
+
* <InputOtp.Slot index={5} />
|
|
707
|
+
* </InputOtp.Group>
|
|
708
|
+
* </InputOtp.Root>
|
|
709
|
+
*/
|
|
710
|
+
export const InputOtp = {
|
|
711
|
+
Root: InputOtpRoot,
|
|
712
|
+
Group: InputOtpGroup,
|
|
713
|
+
Slot: InputOtpSlot,
|
|
714
|
+
Separator: InputOtpSeparator,
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* A menu, with the keyboard map every native menu has had for thirty years.
|
|
719
|
+
*
|
|
720
|
+
* <Menu.Root>
|
|
721
|
+
* <Menu.Trigger>File</Menu.Trigger>
|
|
722
|
+
* <Menu.Body>
|
|
723
|
+
* <Menu.Group>
|
|
724
|
+
* <Menu.Label>Recent</Menu.Label>
|
|
725
|
+
* <Menu.Item onSelect={open}>Open…</Menu.Item>
|
|
726
|
+
* </Menu.Group>
|
|
727
|
+
* <Menu.Separator />
|
|
728
|
+
* <Menu.Sub>
|
|
729
|
+
* <Menu.SubTrigger>Export</Menu.SubTrigger>
|
|
730
|
+
* <Menu.Body>
|
|
731
|
+
* <Menu.Item onSelect={png}>PNG</Menu.Item>
|
|
732
|
+
* </Menu.Body>
|
|
733
|
+
* </Menu.Sub>
|
|
734
|
+
* </Menu.Body>
|
|
735
|
+
* </Menu.Root>
|
|
736
|
+
*/
|
|
737
|
+
export const Menu = {
|
|
738
|
+
Root: MenuRoot,
|
|
739
|
+
Trigger: MenuTrigger,
|
|
740
|
+
Body: MenuBody,
|
|
741
|
+
Item: MenuItem,
|
|
742
|
+
Separator: MenuSeparator,
|
|
743
|
+
Group: MenuGroup,
|
|
744
|
+
Label: MenuLabel,
|
|
745
|
+
Sub: MenuSub,
|
|
746
|
+
SubTrigger: MenuSubTrigger,
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* A text field with a list of options, navigated without leaving the field.
|
|
751
|
+
*
|
|
752
|
+
* The caller filters; the component keeps the ARIA wiring true while they do.
|
|
753
|
+
*
|
|
754
|
+
* <Combobox.Root inputValue={query} onInputValueChange={setQuery}>
|
|
755
|
+
* <Combobox.Label>Country</Combobox.Label>
|
|
756
|
+
* <Combobox.Input />
|
|
757
|
+
* <Combobox.List>
|
|
758
|
+
* {matches.map((each) => (
|
|
759
|
+
* <Combobox.Option key={each} value={each}>{each}</Combobox.Option>
|
|
760
|
+
* ))}
|
|
761
|
+
* </Combobox.List>
|
|
762
|
+
* <Combobox.Empty>No matches.</Combobox.Empty>
|
|
763
|
+
* <Combobox.Status />
|
|
764
|
+
* </Combobox.Root>
|
|
765
|
+
*/
|
|
766
|
+
export const Combobox = {
|
|
767
|
+
Root: ComboboxRoot,
|
|
768
|
+
Label: ComboboxLabel,
|
|
769
|
+
Input: ComboboxInput,
|
|
770
|
+
List: ComboboxList,
|
|
771
|
+
Option: ComboboxOption,
|
|
772
|
+
Empty: ComboboxEmpty,
|
|
773
|
+
Status: ComboboxStatus,
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* The other half of the combobox pattern: a button, a list, and no typing.
|
|
778
|
+
*
|
|
779
|
+
* Use a native `<select>` when a native `<select>` will do — `select.js` says
|
|
780
|
+
* so first and means it. This is for the popup a `<select>` cannot draw.
|
|
781
|
+
*
|
|
782
|
+
* <Select.Root defaultValue="GB" name="country">
|
|
783
|
+
* <Select.Label>Country</Select.Label>
|
|
784
|
+
* <Select.Trigger>
|
|
785
|
+
* <Select.Value placeholder="Choose one" />
|
|
786
|
+
* </Select.Trigger>
|
|
787
|
+
* <Select.List>
|
|
788
|
+
* <Select.Group>
|
|
789
|
+
* <Select.GroupLabel>Europe</Select.GroupLabel>
|
|
790
|
+
* <Select.Option value="GB">United Kingdom</Select.Option>
|
|
791
|
+
* <Select.Option value="FR">France</Select.Option>
|
|
792
|
+
* </Select.Group>
|
|
793
|
+
* <Select.Separator />
|
|
794
|
+
* <Select.Option value="JP">Japan</Select.Option>
|
|
795
|
+
* </Select.List>
|
|
796
|
+
* </Select.Root>
|
|
797
|
+
*
|
|
798
|
+
* `Select.Label` names the field and `Select.GroupLabel` names a group of
|
|
799
|
+
* options. shadcn has one `SelectLabel` and it is the second of those; a select
|
|
800
|
+
* needs both, so they are two parts here.
|
|
801
|
+
*/
|
|
802
|
+
export const Select = {
|
|
803
|
+
Root: SelectRoot,
|
|
804
|
+
Label: SelectLabel,
|
|
805
|
+
Trigger: SelectTrigger,
|
|
806
|
+
Value: SelectValue,
|
|
807
|
+
List: SelectList,
|
|
808
|
+
Option: SelectOption,
|
|
809
|
+
Group: SelectGroup,
|
|
810
|
+
GroupLabel: SelectGroupLabel,
|
|
811
|
+
Separator: SelectSeparator,
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* A dialog that is not modal, anchored to the button that opened it.
|
|
816
|
+
*
|
|
817
|
+
* Focus moves in, `Escape` closes it and gives focus back, and `Tab` *leaves* —
|
|
818
|
+
* the page behind a popover is still there, still scrollable and still
|
|
819
|
+
* tabbable, which is every way in which it is not a `Dialog`.
|
|
820
|
+
*
|
|
821
|
+
* <Popover.Root>
|
|
822
|
+
* <Popover.Trigger>Filters</Popover.Trigger>
|
|
823
|
+
* <Popover.Body align="start" side="bottom" sideOffset={8}>
|
|
824
|
+
* <label>
|
|
825
|
+
* Only mine <input type="checkbox" />
|
|
826
|
+
* </label>
|
|
827
|
+
* </Popover.Body>
|
|
828
|
+
* </Popover.Root>
|
|
829
|
+
*
|
|
830
|
+
* `Popover.Body` reports where it ended up as `data-side` and `data-align`, and
|
|
831
|
+
* writes the trigger's width and the room it had as custom properties, so a
|
|
832
|
+
* stylesheet can point an arrow and cap a height without measuring anything.
|
|
833
|
+
*/
|
|
834
|
+
export const Popover = {
|
|
835
|
+
Root: PopoverRoot,
|
|
836
|
+
Trigger: PopoverTrigger,
|
|
837
|
+
Body: PopoverBody,
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* A phrase about a control, on hover and on focus, that WCAG would accept.
|
|
842
|
+
*
|
|
843
|
+
* Dismissible with `Escape`, hoverable — the pointer can travel onto it — and
|
|
844
|
+
* never focusable. It does not open on touch, deliberately, so the trigger must
|
|
845
|
+
* carry its own name for a reader holding a phone.
|
|
846
|
+
*
|
|
847
|
+
* <Tooltip.Provider delayDuration={700} skipDelayDuration={300}>
|
|
848
|
+
* <Tooltip.Root>
|
|
849
|
+
* <Tooltip.Trigger aria-label="Bold">B</Tooltip.Trigger>
|
|
850
|
+
* <Tooltip.Body>Bold (⌘B)</Tooltip.Body>
|
|
851
|
+
* </Tooltip.Root>
|
|
852
|
+
* <Tooltip.Root>
|
|
853
|
+
* <Tooltip.Trigger aria-label="Italic">I</Tooltip.Trigger>
|
|
854
|
+
* <Tooltip.Body>Italic (⌘I)</Tooltip.Body>
|
|
855
|
+
* </Tooltip.Root>
|
|
856
|
+
* </Tooltip.Provider>
|
|
857
|
+
*
|
|
858
|
+
* `Tooltip.Provider` is what makes the second icon in that toolbar answer at
|
|
859
|
+
* once instead of making the reader wait the delay again. A tooltip outside one
|
|
860
|
+
* is a complete tooltip with a delay of its own.
|
|
861
|
+
*/
|
|
862
|
+
export const Tooltip = {
|
|
863
|
+
Provider: TooltipProvider,
|
|
864
|
+
Root: TooltipRoot,
|
|
865
|
+
Trigger: TooltipTrigger,
|
|
866
|
+
Body: TooltipBody,
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* The preview a name expands into: hovered, focused, and full of links.
|
|
871
|
+
*
|
|
872
|
+
* Not a tooltip — its contents are reachable, by pointer and by `Tab` — and not
|
|
873
|
+
* a dialog, because nothing about it is modal.
|
|
874
|
+
*
|
|
875
|
+
* <HoverCard.Root>
|
|
876
|
+
* <HoverCard.Trigger render={(props) => <a href="/ada" {...props}>@ada</a>} />
|
|
877
|
+
* <HoverCard.Body>
|
|
878
|
+
* <p>Ada Lovelace</p>
|
|
879
|
+
* <a href="/ada/notes">Notes</a>
|
|
880
|
+
* </HoverCard.Body>
|
|
881
|
+
* </HoverCard.Root>
|
|
882
|
+
*/
|
|
883
|
+
export const HoverCard = {
|
|
884
|
+
Root: HoverCardRoot,
|
|
885
|
+
Trigger: HoverCardTrigger,
|
|
886
|
+
Body: HoverCardBody,
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Notifications, in a live region that was watching before them.
|
|
891
|
+
*
|
|
892
|
+
* Render `Toast.Region` once, in the layout; `toast()` from anywhere.
|
|
893
|
+
*
|
|
894
|
+
* <Toast.Region>
|
|
895
|
+
* {(each) => (
|
|
896
|
+
* <Toast.Root>
|
|
897
|
+
* <Toast.Title>{each.content}</Toast.Title>
|
|
898
|
+
* <Toast.Action onClick={undo}>Undo</Toast.Action>
|
|
899
|
+
* <Toast.Close />
|
|
900
|
+
* </Toast.Root>
|
|
901
|
+
* )}
|
|
902
|
+
* </Toast.Region>
|
|
903
|
+
*/
|
|
904
|
+
export const Toast = {
|
|
905
|
+
Region: ToastRegion,
|
|
906
|
+
Root: ToastRoot,
|
|
907
|
+
Title: ToastTitle,
|
|
908
|
+
Description: ToastDescription,
|
|
909
|
+
Action: ToastAction,
|
|
910
|
+
Close: ToastClose,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* A value in a range, with `role="slider"` on the thumb where it belongs.
|
|
915
|
+
*
|
|
916
|
+
* One thumb or two; a range is the same component with a second one, each
|
|
917
|
+
* bounded by its neighbour and each needing its own name.
|
|
918
|
+
*
|
|
919
|
+
* <Slider.Root defaultValue={[20, 60]} valueText={(each) => `£${each}`}>
|
|
920
|
+
* <Slider.Track>
|
|
921
|
+
* <Slider.Range />
|
|
922
|
+
* </Slider.Track>
|
|
923
|
+
* <Slider.Thumb aria-label="Minimum" index={0} />
|
|
924
|
+
* <Slider.Thumb aria-label="Maximum" index={1} />
|
|
925
|
+
* </Slider.Root>
|
|
926
|
+
*/
|
|
927
|
+
export const Slider = {
|
|
928
|
+
Root: SliderRoot,
|
|
929
|
+
Track: SliderTrack,
|
|
930
|
+
Range: SliderRange,
|
|
931
|
+
Thumb: SliderThumb,
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Two panes and the splitter between them, operable from the keyboard.
|
|
936
|
+
*
|
|
937
|
+
* <Resizable.PanelGroup defaultValue={30}>
|
|
938
|
+
* <Resizable.Panel primary>Files</Resizable.Panel>
|
|
939
|
+
* <Resizable.Handle label="Resize the file list" />
|
|
940
|
+
* <Resizable.Panel>Editor</Resizable.Panel>
|
|
941
|
+
* </Resizable.PanelGroup>
|
|
942
|
+
*/
|
|
943
|
+
export const Resizable = {
|
|
944
|
+
PanelGroup: ResizablePanelGroup,
|
|
945
|
+
Panel: ResizablePanel,
|
|
946
|
+
Handle: ResizableHandle,
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* A table, with the four things about one nobody gets right by hand.
|
|
951
|
+
*
|
|
952
|
+
* A real `<table>`, deliberately not a `role="grid"` — `table.js` says why —
|
|
953
|
+
* and its own live region, so a re-sort is something a reader is told about
|
|
954
|
+
* rather than something that happens silently behind them.
|
|
955
|
+
*
|
|
956
|
+
* <Table.Root onSortChange={setSort} rowCount={500} rowOffset={90} sort={sort}>
|
|
957
|
+
* <Table.Caption>People</Table.Caption>
|
|
958
|
+
* <Table.Header>
|
|
959
|
+
* <Table.Row>
|
|
960
|
+
* <Table.Head>
|
|
961
|
+
* <Table.SelectAll checked={all} onCheckedChange={setAll} />
|
|
962
|
+
* </Table.Head>
|
|
963
|
+
* <Table.Head column="name">Name</Table.Head>
|
|
964
|
+
* </Table.Row>
|
|
965
|
+
* </Table.Header>
|
|
966
|
+
* <Table.Body>
|
|
967
|
+
* {page.map((person, at) => (
|
|
968
|
+
* <Table.Row index={at} key={person.id}>
|
|
969
|
+
* <Table.Cell>
|
|
970
|
+
* <Table.RowSelect
|
|
971
|
+
* checked={chosen.has(person.id)}
|
|
972
|
+
* label={`Select ${person.name}`}
|
|
973
|
+
* onCheckedChange={(on) => choose(person.id, on)}
|
|
974
|
+
* />
|
|
975
|
+
* </Table.Cell>
|
|
976
|
+
* <Table.RowHeader>{person.name}</Table.RowHeader>
|
|
977
|
+
* </Table.Row>
|
|
978
|
+
* ))}
|
|
979
|
+
* </Table.Body>
|
|
980
|
+
* </Table.Root>
|
|
981
|
+
*/
|
|
982
|
+
export const Table = {
|
|
983
|
+
Root: TableRoot,
|
|
984
|
+
Caption: TableCaption,
|
|
985
|
+
Header: TableHeader,
|
|
986
|
+
Body: TableBody,
|
|
987
|
+
Row: TableRow,
|
|
988
|
+
Head: TableHead,
|
|
989
|
+
RowHeader: TableRowHeader,
|
|
990
|
+
Cell: TableCell,
|
|
991
|
+
SelectAll: TableSelectAll,
|
|
992
|
+
RowSelect: TableRowSelect,
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* The navigation a paginated table needs, and the sentence that says it moved.
|
|
997
|
+
*
|
|
998
|
+
* <Pagination.Root page={4} pageCount={25}>
|
|
999
|
+
* <Pagination.Content>
|
|
1000
|
+
* <Pagination.Previous disabled={page === 1} href={hrefFor(page - 1)}>‹</Pagination.Previous>
|
|
1001
|
+
* <Pagination.Item current href={hrefFor(4)}>4</Pagination.Item>
|
|
1002
|
+
* <Pagination.Next href={hrefFor(page + 1)}>›</Pagination.Next>
|
|
1003
|
+
* </Pagination.Content>
|
|
1004
|
+
* </Pagination.Root>
|
|
1005
|
+
*/
|
|
1006
|
+
export const Pagination = {
|
|
1007
|
+
Root: PaginationRoot,
|
|
1008
|
+
Content: PaginationContent,
|
|
1009
|
+
Item: PaginationItem,
|
|
1010
|
+
Previous: PaginationPrevious,
|
|
1011
|
+
Next: PaginationNext,
|
|
1012
|
+
};
|