@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/accordion.js
CHANGED
|
@@ -90,8 +90,13 @@ import {
|
|
|
90
90
|
useState,
|
|
91
91
|
} from "@uniflowed/react";
|
|
92
92
|
|
|
93
|
-
import type { Rest } from "./internal/merge-props.js";
|
|
94
|
-
import {
|
|
93
|
+
import type { PartEvent, RenderProp, Rest } from "./internal/merge-props.js";
|
|
94
|
+
import {
|
|
95
|
+
composeHandlers,
|
|
96
|
+
composeRefs,
|
|
97
|
+
withProps,
|
|
98
|
+
withoutComposed,
|
|
99
|
+
} from "./internal/merge-props.js";
|
|
95
100
|
import { moveOnKey } from "./internal/roving-focus.js";
|
|
96
101
|
import type { RovingSet } from "./internal/roving-focus.js";
|
|
97
102
|
import { useMeasuredHeight, usePresence, useUntilFound } from "./internal/disclosure.js";
|
|
@@ -176,6 +181,7 @@ export component AccordionRoot(
|
|
|
176
181
|
value?: $ReadOnlyArray<string>,
|
|
177
182
|
onValueChange?: (value: $ReadOnlyArray<string>) => void,
|
|
178
183
|
measure?: boolean = false,
|
|
184
|
+
render?: RenderProp,
|
|
179
185
|
...rest: Rest
|
|
180
186
|
) {
|
|
181
187
|
const [open, setOpen] = useControlled<$ReadOnlyArray<string>>(value, defaultValue, onValueChange);
|
|
@@ -203,22 +209,20 @@ export component AccordionRoot(
|
|
|
203
209
|
() => ({ open, toggle, closable: type === "multiple" || collapsible, type, measure }),
|
|
204
210
|
[open, toggle, type, collapsible, measure],
|
|
205
211
|
);
|
|
206
|
-
const
|
|
212
|
+
const props = withProps(withoutComposed(rest, ["onKeyDown"]), {
|
|
213
|
+
children,
|
|
214
|
+
// The name the arrow keys use to tell this accordion's headers from those
|
|
215
|
+
// of an accordion nested inside one of its panels.
|
|
216
|
+
"data-accordion": "",
|
|
217
|
+
onKeyDown: composeHandlers(rest.onKeyDown, (event: PartEvent) => {
|
|
218
|
+
const stack: $FlowFixMe = event.currentTarget;
|
|
219
|
+
moveOnKey(event, stack, HEADERS);
|
|
220
|
+
}),
|
|
221
|
+
});
|
|
207
222
|
|
|
208
223
|
return (
|
|
209
224
|
<AccordionContext.Provider value={state}>
|
|
210
|
-
<div
|
|
211
|
-
{...passed}
|
|
212
|
-
// The name the arrow keys use to tell this accordion's headers from
|
|
213
|
-
// those of an accordion nested inside one of its panels.
|
|
214
|
-
data-accordion=""
|
|
215
|
-
onKeyDown={composeHandlers(rest.onKeyDown, (event) => {
|
|
216
|
-
const stack: $FlowFixMe = event.currentTarget;
|
|
217
|
-
moveOnKey(event, stack, HEADERS);
|
|
218
|
-
})}
|
|
219
|
-
>
|
|
220
|
-
{children}
|
|
221
|
-
</div>
|
|
225
|
+
{render == null ? <div {...props} /> : render(props)}
|
|
222
226
|
</AccordionContext.Provider>
|
|
223
227
|
);
|
|
224
228
|
}
|
|
@@ -235,6 +239,7 @@ export component AccordionItem(
|
|
|
235
239
|
value: string,
|
|
236
240
|
children: renders* (AccordionHeader | AccordionContent),
|
|
237
241
|
disabled?: boolean = false,
|
|
242
|
+
render?: RenderProp,
|
|
238
243
|
...rest: Rest
|
|
239
244
|
) {
|
|
240
245
|
const accordion = useAccordion("Accordion.Item");
|
|
@@ -257,9 +262,11 @@ export component AccordionItem(
|
|
|
257
262
|
[base, open, toggle, value, accordion.closable, disabled, present],
|
|
258
263
|
);
|
|
259
264
|
|
|
265
|
+
const props = withProps(rest, { children });
|
|
266
|
+
|
|
260
267
|
return (
|
|
261
268
|
<AccordionItemContext.Provider value={state}>
|
|
262
|
-
<div {...
|
|
269
|
+
{render == null ? <div {...props} /> : render(props)}
|
|
263
270
|
</AccordionItemContext.Provider>
|
|
264
271
|
);
|
|
265
272
|
}
|
|
@@ -290,35 +297,34 @@ export component AccordionHeader(
|
|
|
290
297
|
* It carries no `tabIndex` of its own on purpose: every header stays in the
|
|
291
298
|
* page's tab order, which is what makes this an accordion and not a tab list.
|
|
292
299
|
*/
|
|
293
|
-
export component AccordionTrigger(children: React.Node, ...rest: Rest) {
|
|
300
|
+
export component AccordionTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
294
301
|
const item = useAccordionItem("Accordion.Trigger");
|
|
295
|
-
const passed = withoutComposed(rest, ["onClick"]);
|
|
296
302
|
// Locked and disabled are two different sentences a reader hears the same
|
|
297
303
|
// way, and both are `aria-disabled` rather than `disabled` so the header
|
|
298
304
|
// stays where they can find it: "this section will not close" and "this
|
|
299
305
|
// section is unavailable".
|
|
300
306
|
const inert = item.locked || item.disabled;
|
|
301
307
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
308
|
+
const props = withProps(withoutComposed(rest, ["onClick"]), {
|
|
309
|
+
"aria-controls": item.present ? item.contentId : undefined,
|
|
310
|
+
"aria-disabled": inert ? "true" : undefined,
|
|
311
|
+
"aria-expanded": item.open ? "true" : "false",
|
|
312
|
+
children,
|
|
313
|
+
// What the arrow keys look for. Not a role, because the accordion pattern
|
|
314
|
+
// has none to look for; see the module header.
|
|
315
|
+
"data-accordion-trigger": "",
|
|
316
|
+
id: item.triggerId,
|
|
317
|
+
onClick: composeHandlers(rest.onClick, () => {
|
|
318
|
+
if (!inert) {
|
|
319
|
+
item.toggle();
|
|
320
|
+
}
|
|
321
|
+
}),
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (render != null) {
|
|
325
|
+
return render(props);
|
|
326
|
+
}
|
|
327
|
+
return <button {...props} type="button" />;
|
|
322
328
|
}
|
|
323
329
|
|
|
324
330
|
/**
|
|
@@ -327,7 +333,7 @@ export component AccordionTrigger(children: React.Node, ...rest: Rest) {
|
|
|
327
333
|
* `internal/disclosure.js` explains what "stays in the document" is worth and
|
|
328
334
|
* what `hidden` is upgraded to for it.
|
|
329
335
|
*/
|
|
330
|
-
export component AccordionContent(children: React.Node, ...rest: Rest) {
|
|
336
|
+
export component AccordionContent(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
331
337
|
const accordion = useAccordion("Accordion.Content");
|
|
332
338
|
const item = useAccordionItem("Accordion.Content");
|
|
333
339
|
const contentRef = useRef<HTMLElement | null>(null);
|
|
@@ -335,19 +341,20 @@ export component AccordionContent(children: React.Node, ...rest: Rest) {
|
|
|
335
341
|
useUntilFound(contentRef, item.open);
|
|
336
342
|
useMeasuredHeight(contentRef, accordion.measure);
|
|
337
343
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
344
|
+
const props = withProps(withoutComposed(rest, ["ref"]), {
|
|
345
|
+
// The name a reader hears for this landmark is the header they pressed.
|
|
346
|
+
"aria-labelledby": item.triggerId,
|
|
347
|
+
children,
|
|
348
|
+
hidden: !item.open,
|
|
349
|
+
id: item.contentId,
|
|
350
|
+
ref: composeRefs(rest.ref, (element: HTMLElement | null) => {
|
|
351
|
+
contentRef.current = element;
|
|
352
|
+
}),
|
|
353
|
+
role: "region",
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (render != null) {
|
|
357
|
+
return render(props);
|
|
358
|
+
}
|
|
359
|
+
return <div {...props} />;
|
|
353
360
|
}
|
package/alert-dialog.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
import * as React from "@uniflowed/react";
|
|
47
47
|
import { createContext, useContext, useEffect, useMemo, useRef } from "@uniflowed/react";
|
|
48
48
|
|
|
49
|
-
import type { Rest } from "./internal/merge-props.js";
|
|
49
|
+
import type { RenderProp, Rest } from "./internal/merge-props.js";
|
|
50
50
|
import { composeRefs, forwarded, withoutComposed } from "./internal/merge-props.js";
|
|
51
51
|
import {
|
|
52
52
|
DialogBody,
|
|
@@ -118,13 +118,17 @@ export component AlertDialogRoot(
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/** What opens it, and what focus comes back to when it closes. */
|
|
121
|
-
export component AlertDialogTrigger(children: React.Node, ...rest: Rest) {
|
|
122
|
-
return
|
|
121
|
+
export component AlertDialogTrigger(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
122
|
+
return (
|
|
123
|
+
<DialogTrigger {...forwarded(rest)} render={render}>
|
|
124
|
+
{children}
|
|
125
|
+
</DialogTrigger>
|
|
126
|
+
);
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
/** The backdrop. See `Dialog.Overlay`: it is decoration and says so. */
|
|
126
|
-
export component AlertDialogOverlay(...rest: Rest) {
|
|
127
|
-
return <DialogOverlay {...forwarded(rest)} />;
|
|
130
|
+
export component AlertDialogOverlay(render?: RenderProp, ...rest: Rest) {
|
|
131
|
+
return <DialogOverlay {...forwarded(rest)} render={render} />;
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
/**
|
|
@@ -136,7 +140,7 @@ export component AlertDialogOverlay(...rest: Rest) {
|
|
|
136
140
|
* who set two of them would have an alert dialog that is wrong in the third
|
|
137
141
|
* without anything saying so.
|
|
138
142
|
*/
|
|
139
|
-
export component AlertDialogBody(children: React.Node, ...rest: Rest) {
|
|
143
|
+
export component AlertDialogBody(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
140
144
|
const alert = useAlertDialog("AlertDialog.Body");
|
|
141
145
|
|
|
142
146
|
return (
|
|
@@ -144,6 +148,7 @@ export component AlertDialogBody(children: React.Node, ...rest: Rest) {
|
|
|
144
148
|
{...forwarded(rest)}
|
|
145
149
|
dismissOnOutsidePress={false}
|
|
146
150
|
initialFocus={alert.cancelRef}
|
|
151
|
+
render={render}
|
|
147
152
|
role="alertdialog"
|
|
148
153
|
>
|
|
149
154
|
{children}
|
|
@@ -184,18 +189,30 @@ component RequireDescription() {
|
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
/** The top of the alert dialog. See `Dialog.Header` for why it is a `div`. */
|
|
187
|
-
export component AlertDialogHeader(children: React.Node, ...rest: Rest) {
|
|
188
|
-
return
|
|
192
|
+
export component AlertDialogHeader(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
193
|
+
return (
|
|
194
|
+
<DialogHeader {...forwarded(rest)} render={render}>
|
|
195
|
+
{children}
|
|
196
|
+
</DialogHeader>
|
|
197
|
+
);
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
/** The bottom, where `Action` and `Cancel` go. */
|
|
192
|
-
export component AlertDialogFooter(children: React.Node, ...rest: Rest) {
|
|
193
|
-
return
|
|
201
|
+
export component AlertDialogFooter(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
202
|
+
return (
|
|
203
|
+
<DialogFooter {...forwarded(rest)} render={render}>
|
|
204
|
+
{children}
|
|
205
|
+
</DialogFooter>
|
|
206
|
+
);
|
|
194
207
|
}
|
|
195
208
|
|
|
196
209
|
/** The question, which is the alert dialog's accessible name. */
|
|
197
|
-
export component AlertDialogTitle(children: React.Node, ...rest: Rest) {
|
|
198
|
-
return
|
|
210
|
+
export component AlertDialogTitle(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
211
|
+
return (
|
|
212
|
+
<DialogTitle {...forwarded(rest)} render={render}>
|
|
213
|
+
{children}
|
|
214
|
+
</DialogTitle>
|
|
215
|
+
);
|
|
199
216
|
}
|
|
200
217
|
|
|
201
218
|
/**
|
|
@@ -205,7 +222,7 @@ export component AlertDialogTitle(children: React.Node, ...rest: Rest) {
|
|
|
205
222
|
* exists to deliver, and the only moment the reader has to decide whether they
|
|
206
223
|
* care is before they have pressed anything.
|
|
207
224
|
*/
|
|
208
|
-
export component AlertDialogDescription(children: React.Node, ...rest: Rest) {
|
|
225
|
+
export component AlertDialogDescription(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
209
226
|
const alert = useAlertDialog("AlertDialog.Description");
|
|
210
227
|
const describedBy = alert.describedBy;
|
|
211
228
|
|
|
@@ -216,7 +233,11 @@ export component AlertDialogDescription(children: React.Node, ...rest: Rest) {
|
|
|
216
233
|
};
|
|
217
234
|
}, [describedBy]);
|
|
218
235
|
|
|
219
|
-
return
|
|
236
|
+
return (
|
|
237
|
+
<DialogDescription {...forwarded(rest)} render={render}>
|
|
238
|
+
{children}
|
|
239
|
+
</DialogDescription>
|
|
240
|
+
);
|
|
220
241
|
}
|
|
221
242
|
|
|
222
243
|
/**
|
|
@@ -227,8 +248,12 @@ export component AlertDialogDescription(children: React.Node, ...rest: Rest) {
|
|
|
227
248
|
* are made of, so the composition rule about a caller's `onClick` has one
|
|
228
249
|
* implementation rather than a second copy here.
|
|
229
250
|
*/
|
|
230
|
-
export component AlertDialogAction(children: React.Node, ...rest: Rest) {
|
|
231
|
-
return
|
|
251
|
+
export component AlertDialogAction(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
252
|
+
return (
|
|
253
|
+
<DialogClose {...forwarded(rest)} render={render}>
|
|
254
|
+
{children}
|
|
255
|
+
</DialogClose>
|
|
256
|
+
);
|
|
232
257
|
}
|
|
233
258
|
|
|
234
259
|
/**
|
|
@@ -238,7 +263,7 @@ export component AlertDialogAction(children: React.Node, ...rest: Rest) {
|
|
|
238
263
|
* without the caller wiring a ref: the least destructive action is a fact about
|
|
239
264
|
* which part this is, not a decision to be repeated at every call.
|
|
240
265
|
*/
|
|
241
|
-
export component AlertDialogCancel(children: React.Node, ...rest: Rest) {
|
|
266
|
+
export component AlertDialogCancel(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
242
267
|
const alert = useAlertDialog("AlertDialog.Cancel");
|
|
243
268
|
const cancelRef = alert.cancelRef;
|
|
244
269
|
const passed = withoutComposed(rest, ["ref"]);
|
|
@@ -249,6 +274,7 @@ export component AlertDialogCancel(children: React.Node, ...rest: Rest) {
|
|
|
249
274
|
ref={composeRefs(rest.ref, (element: HTMLElement | null) => {
|
|
250
275
|
cancelRef.current = element;
|
|
251
276
|
})}
|
|
277
|
+
render={render}
|
|
252
278
|
>
|
|
253
279
|
{children}
|
|
254
280
|
</DialogClose>
|
package/alert.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// A callout, and the live region it must not be by default.
|
|
4
|
+
//
|
|
5
|
+
// Of the twenty components in the catalogue that look like a class list, this
|
|
6
|
+
// is the one whose usual shape is arguably wrong to copy rather than merely
|
|
7
|
+
// empty. Every version of it renders `<div role="alert">`, always, and that one
|
|
8
|
+
// attribute is a decision about interrupting the reader that nobody made.
|
|
9
|
+
//
|
|
10
|
+
// # `role="alert"` is a live region, not a colour
|
|
11
|
+
//
|
|
12
|
+
// A live region announces *changes*. An element carrying one that is already in
|
|
13
|
+
// the document when the page loads has no change to report, so it is announced
|
|
14
|
+
// on insertion or it is not announced at all — and which of those you get is a
|
|
15
|
+
// property of the moment the element entered the document, not of the element.
|
|
16
|
+
//
|
|
17
|
+
// So a permanently rendered "your trial ends soon" box with `role="alert"` is
|
|
18
|
+
// one of two things, both bad:
|
|
19
|
+
//
|
|
20
|
+
// * an **interruption on every page load**, on the engines that treat the
|
|
21
|
+
// initial render as an insertion — the reader is pulled out of whatever
|
|
22
|
+
// they were doing to hear a sentence that was equally true yesterday;
|
|
23
|
+
// * or **silence**, on the engines that do not — in which case the role was
|
|
24
|
+
// decoration, and the box is read in its ordinary place in the page like
|
|
25
|
+
// the `<div>` it is.
|
|
26
|
+
//
|
|
27
|
+
// Neither is what the author wanted, and neither is visible in a screenshot.
|
|
28
|
+
// The two cases have to be told apart by the caller, because the caller is the
|
|
29
|
+
// only one who knows which one they have:
|
|
30
|
+
//
|
|
31
|
+
// * a **static callout** — a panel that is part of the page — is a container
|
|
32
|
+
// with a heading and no live semantics at all. It is read where a reader
|
|
33
|
+
// reaches it, and heading navigation finds it, which is what `Alert.Title`
|
|
34
|
+
// being a real heading is for.
|
|
35
|
+
// * an **alert** — something that appeared because something happened — is
|
|
36
|
+
// `live`, and is `role="alert"`.
|
|
37
|
+
//
|
|
38
|
+
// `field.js` already makes exactly this call for `Field.Error`, which is
|
|
39
|
+
// rendered only once the field is wrong and is `role="alert"` for that reason.
|
|
40
|
+
//
|
|
41
|
+
// # Why `live` is a boolean and there is no polite option
|
|
42
|
+
//
|
|
43
|
+
// Because a polite one cannot be built this way, and offering it would be
|
|
44
|
+
// offering silence. `combobox.js` states the rule: a live region added to the
|
|
45
|
+
// page in the same commit as the text it holds is usually not announced,
|
|
46
|
+
// because the technology watching it had nothing to watch until it was already
|
|
47
|
+
// too late. `role="status"` is polite, so it is subject to that rule in full —
|
|
48
|
+
// a polite region has to have been in the document *first*, empty, and a
|
|
49
|
+
// component you render at the moment the thing happens never was.
|
|
50
|
+
//
|
|
51
|
+
// `role="alert"` is assertive, and assertive regions are announced on insertion
|
|
52
|
+
// by every engine that implements them; that is what the role is for. So the
|
|
53
|
+
// one live shape this component can honestly offer is the assertive one.
|
|
54
|
+
//
|
|
55
|
+
// The polite, page-level shape is `Toast`, which is the component that exists
|
|
56
|
+
// to have been watching already — `toast.js` and ubugeeei-prod/uf#289. An
|
|
57
|
+
// application that wants "saved" said politely wants a toast, not an alert, and
|
|
58
|
+
// pointing at it is a better answer than a `live="polite"` that does nothing.
|
|
59
|
+
//
|
|
60
|
+
// # No `"use client"`
|
|
61
|
+
//
|
|
62
|
+
// It holds no state, listens to nothing and manages no focus. Which of the two
|
|
63
|
+
// alerts this is arrived as a prop, and the heading level did too. It renders
|
|
64
|
+
// on a server.
|
|
65
|
+
|
|
66
|
+
import * as React from "@uniflowed/react";
|
|
67
|
+
|
|
68
|
+
import type { RenderProp, Rest } from "./internal/merge-props.js";
|
|
69
|
+
import { withProps } from "./internal/merge-props.js";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A callout: a panel that is part of the page, or one that just appeared.
|
|
73
|
+
*
|
|
74
|
+
* `live` is the whole component. Without it there is no role, deliberately —
|
|
75
|
+
* a box a reader reaches in reading order needs no announcement, and giving it
|
|
76
|
+
* one costs an interruption on every page load or nothing at all. With it the
|
|
77
|
+
* container is `role="alert"`, which is assertive and therefore the one live
|
|
78
|
+
* shape that is announced when it is inserted with its text in it.
|
|
79
|
+
*
|
|
80
|
+
* {error != null && (
|
|
81
|
+
* <Alert.Root live>
|
|
82
|
+
* <Alert.Title>Could not save</Alert.Title>
|
|
83
|
+
* <Alert.Description>{error}</Alert.Description>
|
|
84
|
+
* </Alert.Root>
|
|
85
|
+
* )}
|
|
86
|
+
*
|
|
87
|
+
* Rendered unconditionally with `live` on it, this is the mistake the module
|
|
88
|
+
* header is about: the role is a promise about a change, and a box that was
|
|
89
|
+
* always there has no change to report.
|
|
90
|
+
*/
|
|
91
|
+
export component AlertRoot(
|
|
92
|
+
children: React.Node,
|
|
93
|
+
live?: boolean = false,
|
|
94
|
+
render?: RenderProp,
|
|
95
|
+
...rest: Rest
|
|
96
|
+
) {
|
|
97
|
+
const props = withProps(rest, { children, role: live ? "alert" : undefined });
|
|
98
|
+
if (render != null) {
|
|
99
|
+
return render(props);
|
|
100
|
+
}
|
|
101
|
+
return <div {...props} />;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The callout's heading.
|
|
106
|
+
*
|
|
107
|
+
* A real heading rather than a bold `<div>`, because a heading is how a screen
|
|
108
|
+
* reader user finds a region of a page without reading it — and a callout
|
|
109
|
+
* nobody can jump to is a callout that has to be walked into.
|
|
110
|
+
*
|
|
111
|
+
* `level` is the caller's for the reason `accordion.js` gives for the same
|
|
112
|
+
* prop: the level that keeps a document outline true depends on what the
|
|
113
|
+
* callout is inside, and a hard-coded one produces an outline nobody can
|
|
114
|
+
* navigate. The guess is stated rather than hidden — `3`, which is right for a
|
|
115
|
+
* callout inside a section that has a title of its own — and a level outside
|
|
116
|
+
* the six HTML has is clamped, because `<h7>` is not an element and is
|
|
117
|
+
* announced as nothing at all.
|
|
118
|
+
*/
|
|
119
|
+
export component AlertTitle(
|
|
120
|
+
children: React.Node,
|
|
121
|
+
level?: number = 3,
|
|
122
|
+
render?: RenderProp,
|
|
123
|
+
...rest: Rest
|
|
124
|
+
) {
|
|
125
|
+
const clamped = Math.min(6, Math.max(1, Math.trunc(level)));
|
|
126
|
+
const Heading = `h${String(clamped)}`;
|
|
127
|
+
const props = withProps(rest, { children });
|
|
128
|
+
|
|
129
|
+
if (render != null) {
|
|
130
|
+
return render(withProps(props, { "aria-level": clamped, role: "heading" }));
|
|
131
|
+
}
|
|
132
|
+
return <Heading {...props} />;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** What the callout says, under its heading. */
|
|
136
|
+
export component AlertDescription(children: React.Node, render?: RenderProp, ...rest: Rest) {
|
|
137
|
+
const props = withProps(rest, { children });
|
|
138
|
+
if (render != null) {
|
|
139
|
+
return render(props);
|
|
140
|
+
}
|
|
141
|
+
return <p {...props} />;
|
|
142
|
+
}
|