@seekalfred/primitives 0.7.3 → 0.7.4
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/README.md +60 -1
- package/dist/alert/alert.d.ts +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +14 -0
- package/dist/alert-dialog/alert-dialog.js +51 -26
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/announcement-bar/announcement-bar.d.ts +15 -4
- package/dist/announcement-bar/announcement-bar.js +31 -6
- package/dist/announcement-bar/announcement-bar.js.map +1 -1
- package/dist/avatar/avatar.d.ts +7 -0
- package/dist/avatar/avatar.js +33 -12
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart-series.d.ts +75 -0
- package/dist/bar-chart/bar-chart-series.js +401 -0
- package/dist/bar-chart/bar-chart-series.js.map +1 -0
- package/dist/bar-chart/bar-chart.d.ts +25 -1
- package/dist/bar-chart/bar-chart.js +26 -1
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/index.d.ts +2 -1
- package/dist/button/button.d.ts +2 -2
- package/dist/chip-group/chip-group.d.ts +11 -0
- package/dist/chip-group/chip-group.js +50 -34
- package/dist/chip-group/chip-group.js.map +1 -1
- package/dist/chip-group/index.d.ts +2 -2
- package/dist/copy-button/copy-button.d.ts +19 -3
- package/dist/copy-button/copy-button.js +7 -4
- package/dist/copy-button/copy-button.js.map +1 -1
- package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
- package/dist/file-drop-zone/file-drop-zone.js +26 -8
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/gauge/gauge.d.ts +18 -1
- package/dist/gauge/gauge.js +35 -21
- package/dist/gauge/gauge.js.map +1 -1
- package/dist/gauge/index.d.ts +2 -2
- package/dist/index.d.ts +8 -7
- package/dist/index.js +4 -4
- package/dist/input/input.d.ts +7 -0
- package/dist/input/input.js +3 -1
- package/dist/input/input.js.map +1 -1
- package/dist/lib/field-shell.js +2 -6
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/required-marker.js +20 -0
- package/dist/lib/required-marker.js.map +1 -0
- package/dist/line-chart/index.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts +105 -3
- package/dist/line-chart/line-chart.js +183 -44
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -2
- package/dist/money-input/money-input.d.ts +14 -0
- package/dist/money-input/money-input.js +17 -7
- package/dist/money-input/money-input.js.map +1 -1
- package/dist/morph-select/morph-select.d.ts +8 -1
- package/dist/morph-select/morph-select.js +8 -5
- package/dist/morph-select/morph-select.js.map +1 -1
- package/dist/multi-select/multi-select.d.ts +31 -0
- package/dist/multi-select/multi-select.js +122 -5
- package/dist/multi-select/multi-select.js.map +1 -1
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/index.js +2 -2
- package/dist/progress/progress.d.ts +53 -2
- package/dist/progress/progress.js +146 -36
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.d.ts +1 -1
- package/dist/scrub-input/scrub-input.d.ts +11 -1
- package/dist/scrub-input/scrub-input.js +22 -10
- package/dist/scrub-input/scrub-input.js.map +1 -1
- package/dist/select/select.d.ts +9 -0
- package/dist/select/select.js +3 -2
- package/dist/select/select.js.map +1 -1
- package/dist/sparkline/sparkline.d.ts +27 -2
- package/dist/sparkline/sparkline.js +115 -18
- package/dist/sparkline/sparkline.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +32 -1
- package/dist/stat-card/stat-card.js +52 -9
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +114 -4
- package/dist/table/table.js +374 -94
- package/dist/table/table.js.map +1 -1
- package/dist/text-shimmer/text-shimmer.d.ts +18 -2
- package/dist/text-shimmer/text-shimmer.js +5 -2
- package/dist/text-shimmer/text-shimmer.js.map +1 -1
- package/dist/textarea/textarea.d.ts +7 -0
- package/dist/textarea/textarea.js +3 -1
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -968,6 +968,18 @@ The wiring, done for you:
|
|
|
968
968
|
- The error row has `role="alert"`.
|
|
969
969
|
- Screen readers get the plain copy. The animated words are `aria-hidden`.
|
|
970
970
|
|
|
971
|
+
**`isRequired`** makes the control `required` (announced by screen readers and
|
|
972
|
+
checked by native form validation) and shows the required marker after the
|
|
973
|
+
label: the same red asterisk as `PasswordField` and `Select`, `aria-hidden` so
|
|
974
|
+
the name stays the label's text. Off by default. Without a `label` the control
|
|
975
|
+
is still `required`, with no label to mark. A native `required` on its own still
|
|
976
|
+
only sets the attribute and draws no marker, as before.
|
|
977
|
+
|
|
978
|
+
```tsx
|
|
979
|
+
<Input label="Email" isRequired />
|
|
980
|
+
<Textarea label="Message" isRequired />
|
|
981
|
+
```
|
|
982
|
+
|
|
971
983
|
Limits worth knowing:
|
|
972
984
|
|
|
973
985
|
- **Do not pass `aria-label` together with `label`.** `aria-label` wins, so the
|
|
@@ -1218,6 +1230,27 @@ With `labelHidden` and no `showValue` nothing is printed above the track, so the
|
|
|
1218
1230
|
its tone and no check lands. Use it for a timer-driven estimate that can reach
|
|
1219
1231
|
100% before the work finishes.
|
|
1220
1232
|
|
|
1233
|
+
`target` draws an upright tick where the bar should be by now, on the same
|
|
1234
|
+
scale as `value` (clamped to `minValue`..`maxValue`), standing proud of the
|
|
1235
|
+
track with a 1px halo so it reads over the fill too. The tick is decorative:
|
|
1236
|
+
`targetLabel` (default "Target") and the formatted target ("Expected by now:
|
|
1237
|
+
40%", formatted like the bar's own value) become the bar's accessible
|
|
1238
|
+
description, kept alongside any `aria-describedby` of yours. `showTargetLabel`
|
|
1239
|
+
also prints that text under the tick, centred on it and kept inside the bar
|
|
1240
|
+
near either end. No `target`, no tick and the same DOM as before.
|
|
1241
|
+
|
|
1242
|
+
`surface="inverse"` is for a bar on the inverted, foreground-coloured surface
|
|
1243
|
+
(a dark strip in the light theme): the track is the inverse text colour mixed
|
|
1244
|
+
into that surface, and the default fill, the tick and the copy take the inverse
|
|
1245
|
+
text colour. Explicit tones keep their colours.
|
|
1246
|
+
|
|
1247
|
+
```tsx
|
|
1248
|
+
<Progress label="Organic sessions" value={340} maxValue={1000} target={450} targetLabel="Expected by now" labelHidden />
|
|
1249
|
+
<div className="bg-foreground p-4">
|
|
1250
|
+
<Progress label="Plan reaches" value={72} target={100} surface="inverse" labelHidden />
|
|
1251
|
+
</div>
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1221
1254
|
### Motion (Progress)
|
|
1222
1255
|
|
|
1223
1256
|
The fill follows the value on the smooth spring (it was a 200ms ease) and the printed percentage counts to the new number on the same spring, so the number always matches the bar. When a default-toned bar completes it eases to the success colour (after 200ms) and a check lands beside the count; an explicit `tone` is never overridden, `showCompletion={false}` turns both off, and a bar that is full at mount shows its check with no motion. An indeterminate segment sweeps the track every 1.6s; under reduced motion it does not travel, and the fill spans the track and pulses slowly instead. A value that is not a percentage (a price) is shown as it is. A changed string label swaps as a whole.
|
|
@@ -1382,6 +1415,15 @@ only — WCAG 1.4.1. The message is wired into `aria-describedby` for you.
|
|
|
1382
1415
|
<Select label="Country" isInvalid errorMessage="Pick a country to continue." />
|
|
1383
1416
|
```
|
|
1384
1417
|
|
|
1418
|
+
**`isRequired`** is React Aria's prop: the hidden native select is `required`
|
|
1419
|
+
and the root gets `data-required`. It also shows the required marker after the
|
|
1420
|
+
label, the same asterisk as `Input`, `Textarea` and `PasswordField`, hidden from
|
|
1421
|
+
the trigger's accessible name.
|
|
1422
|
+
|
|
1423
|
+
```tsx
|
|
1424
|
+
<Select label="Country" isRequired>…</Select>
|
|
1425
|
+
```
|
|
1426
|
+
|
|
1385
1427
|
## Separator
|
|
1386
1428
|
|
|
1387
1429
|
```tsx
|
|
@@ -1530,6 +1572,17 @@ link, `onPress` a button, and `isSelected` with `onPress` a filter toggle with
|
|
|
1530
1572
|
use `Badge`, whose `tone` already covers success, warning, danger, info,
|
|
1531
1573
|
primary and neutral.
|
|
1532
1574
|
|
|
1575
|
+
`isLoading` keeps the card's box while its figure loads: the label stays (an
|
|
1576
|
+
empty `label` pulses in its line), the value and the context line (pass `sub`
|
|
1577
|
+
to keep it) become placeholders one line tall, the change pill pulses at its
|
|
1578
|
+
own size, the content is `aria-busy` and the root carries `data-loading`.
|
|
1579
|
+
Screen readers hear `loadingLabel` ("Loading") in place of the figure. A row of
|
|
1580
|
+
loading cards is exactly as tall as the row that replaces it.
|
|
1581
|
+
|
|
1582
|
+
```tsx
|
|
1583
|
+
<StatCard label={metric?.name ?? ""} value={metric?.value ?? ""} sub="vs previous period" isLoading={!metric} />
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1533
1586
|
## Switch
|
|
1534
1587
|
|
|
1535
1588
|
```tsx
|
|
@@ -1642,6 +1695,12 @@ depends on row *data*, use `rowClassName`.
|
|
|
1642
1695
|
| `striped` | zebra-stripes alternate rows |
|
|
1643
1696
|
| `totalRow` | `(column) => ReactNode`; renders a real `<tfoot>` |
|
|
1644
1697
|
| `Column.numeric` | tabular numerals, end-aligned by default |
|
|
1698
|
+
| `getSubRows` | `(row) => T[]`; rows that open to show the rows under them, with a disclosure button in the first cell (`aria-expanded`, named by the cell) |
|
|
1699
|
+
| `expandedKeys` / `defaultExpandedKeys` / `onExpandedChange` | the open rows, controlled or not; nothing is open by default |
|
|
1700
|
+
| `isGroupRow` | `(row) => boolean`; a group header row whose first cell spans the row. It only heads its rows: `onRowAction` does not fire for it and it shows no pointer |
|
|
1701
|
+
| `density` | `default` · `compact` — `py-2` rows in small type, the product's tight report tables |
|
|
1702
|
+
| `headerHidden` | hides the header row from sight and keeps it for assistive tech (key/value tables); widths still apply, sorting is off |
|
|
1703
|
+
| `keyboardModel` | `grid` (default) · `table` — a plain `role="table"` where Tab walks the links and buttons in the cells; no selection or `onRowAction`; `getSubRows` and `isGroupRow` work in it, with the disclosure button in the Tab order |
|
|
1645
1704
|
|
|
1646
1705
|
### Using it in alfred-ui
|
|
1647
1706
|
|
|
@@ -1684,7 +1743,7 @@ not worth a palette entry. If you want it exactly, override
|
|
|
1684
1743
|
| `rowClassName` | unchanged | |
|
|
1685
1744
|
| `rowIdField="keywordId"` | `getRowId={(r) => r.keywordId}` | |
|
|
1686
1745
|
|
|
1687
|
-
|
|
1746
|
+
Sticky headers, loading skeletons, pinned columns, the total
|
|
1688
1747
|
row, filtering, column visibility and CSV export are **not ported** — each had
|
|
1689
1748
|
one consumer or none across the six legacy tables, and they belong to a future
|
|
1690
1749
|
`DataGrid`.
|
package/dist/alert/alert.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { AriaRole, ComponentPropsWithoutRef, ReactNode, SVGProps } from "react";
|
|
|
24
24
|
* problems rather than porting them.
|
|
25
25
|
*/
|
|
26
26
|
export declare const alertVariants: (props?: ({
|
|
27
|
-
variant?: "
|
|
27
|
+
variant?: "warning" | "destructive" | "default" | "success" | "info" | null | undefined;
|
|
28
28
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
29
29
|
/**
|
|
30
30
|
* The icon's colour: the state's own hue on every variant. Inside an Alert the
|
|
@@ -35,7 +35,7 @@ export declare const alertVariants: (props?: ({
|
|
|
35
35
|
* banner of the app's own) can show a tone the same way the alert does.
|
|
36
36
|
*/
|
|
37
37
|
export declare const alertIconVariants: (props?: ({
|
|
38
|
-
variant?: "
|
|
38
|
+
variant?: "warning" | "destructive" | "default" | "success" | "info" | null | undefined;
|
|
39
39
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
40
40
|
/**
|
|
41
41
|
* The built-in glyph of each variant, drawn with currentColor so the icon
|
|
@@ -66,6 +66,20 @@ export interface AlertDialogProps extends Omit<DialogProps, "role" | "closeButto
|
|
|
66
66
|
* `false`, `true` and `""` (as from `cond && <X />`) render no body.
|
|
67
67
|
*/
|
|
68
68
|
body?: ReactNode;
|
|
69
|
+
/**
|
|
70
|
+
* A decorative icon at the start of the header, in a round tile beside the
|
|
71
|
+
* title: a warning triangle before a wide-reaching switch, a trash can before
|
|
72
|
+
* a delete. Hidden from assistive technology, so the title must still say
|
|
73
|
+
* what is at stake. Leave it out and the header is as before.
|
|
74
|
+
*/
|
|
75
|
+
icon?: ReactNode;
|
|
76
|
+
/**
|
|
77
|
+
* The colour of the icon's tile, from the status tokens: `neutral` (the
|
|
78
|
+
* default), `warning`, `destructive`, `info` or `success`. It paints the tile
|
|
79
|
+
* only, never the actions. Without an `icon` it does nothing. Mirrored to
|
|
80
|
+
* `data-tone` on the tile.
|
|
81
|
+
*/
|
|
82
|
+
tone?: "neutral" | "warning" | "destructive" | "info" | "success";
|
|
69
83
|
/** The actions. Give the cancel button `slot="close"`. */
|
|
70
84
|
children?: ReactNode;
|
|
71
85
|
}
|
|
@@ -51,6 +51,21 @@ const AlertDialogAction = forwardRef(function AlertDialogAction({ className, ...
|
|
|
51
51
|
});
|
|
52
52
|
const alertDialogHeaderVariants = cva("flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6");
|
|
53
53
|
/**
|
|
54
|
+
* The header icon's tile: the close button's size, so a one-line title centres
|
|
55
|
+
* on both. Each tone is a subtle tint with the ink the token contrast contract
|
|
56
|
+
* grades on it.
|
|
57
|
+
*/
|
|
58
|
+
const alertDialogIconVariants = cva("inline-flex size-9 shrink-0 items-center justify-center rounded-circle [&_svg]:pointer-events-none [&_svg]:size-5", {
|
|
59
|
+
variants: { tone: {
|
|
60
|
+
neutral: "bg-muted text-foreground",
|
|
61
|
+
warning: "bg-warning-subtle text-warning-foreground",
|
|
62
|
+
destructive: "bg-destructive-subtle text-destructive-subtle-foreground",
|
|
63
|
+
info: "bg-info-subtle text-info-foreground",
|
|
64
|
+
success: "bg-success-subtle text-success-foreground"
|
|
65
|
+
} },
|
|
66
|
+
defaultVariants: { tone: "neutral" }
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
54
69
|
* The title column. With no subtitle it is at least the close button's height
|
|
55
70
|
* and centres the title in it, so a one-line title sits level with the close;
|
|
56
71
|
* a longer title grows the column from the top as before. With a subtitle the
|
|
@@ -87,7 +102,7 @@ function Swappable({ value }) {
|
|
|
87
102
|
function isEmpty(node) {
|
|
88
103
|
return node == null || typeof node === "boolean" || node === "";
|
|
89
104
|
}
|
|
90
|
-
const AlertDialog = forwardRef(function AlertDialog({ title, subtitle, beforeTitle, description, body, children, className, closeLabel = "Close", overlayClassName, entrance = "rise", ...props }, ref) {
|
|
105
|
+
const AlertDialog = forwardRef(function AlertDialog({ title, subtitle, beforeTitle, description, body, icon, tone = "neutral", children, className, closeLabel = "Close", overlayClassName, entrance = "rise", ...props }, ref) {
|
|
91
106
|
return /* @__PURE__ */ jsx(ModalOverlay, {
|
|
92
107
|
...props,
|
|
93
108
|
isDismissable: false,
|
|
@@ -105,33 +120,43 @@ const AlertDialog = forwardRef(function AlertDialog({ title, subtitle, beforeTit
|
|
|
105
120
|
/* @__PURE__ */ jsxs("div", {
|
|
106
121
|
"data-slot": "alert-dialog-header",
|
|
107
122
|
className: alertDialogHeaderVariants(),
|
|
108
|
-
children: [
|
|
109
|
-
|
|
110
|
-
children: [/* @__PURE__ */ jsx(Heading, {
|
|
111
|
-
slot: "title",
|
|
112
|
-
"data-slot": "dialog-title",
|
|
113
|
-
className: alertDialogTitleVariants(),
|
|
114
|
-
children: /* @__PURE__ */ jsx(Swappable, { value: title })
|
|
115
|
-
}), subtitle == null ? null : /* @__PURE__ */ jsx("p", {
|
|
116
|
-
"data-slot": "alert-dialog-subtitle",
|
|
117
|
-
className: "m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary",
|
|
118
|
-
children: /* @__PURE__ */ jsx(Swappable, { value: subtitle })
|
|
119
|
-
})]
|
|
120
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
121
|
-
slot: "close",
|
|
122
|
-
"aria-label": closeLabel,
|
|
123
|
-
"data-slot": "alert-dialog-close",
|
|
124
|
-
className: alertDialogCloseVariants(),
|
|
125
|
-
children: /* @__PURE__ */ jsx("svg", {
|
|
123
|
+
children: [
|
|
124
|
+
isEmpty(icon) ? null : /* @__PURE__ */ jsx("span", {
|
|
126
125
|
"aria-hidden": "true",
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
"data-slot": "alert-dialog-icon",
|
|
127
|
+
"data-tone": tone,
|
|
128
|
+
className: alertDialogIconVariants({ tone }),
|
|
129
|
+
children: icon
|
|
130
|
+
}),
|
|
131
|
+
/* @__PURE__ */ jsxs("div", {
|
|
132
|
+
className: alertDialogTitleBlockVariants({ subtitle: subtitle != null }),
|
|
133
|
+
children: [/* @__PURE__ */ jsx(Heading, {
|
|
134
|
+
slot: "title",
|
|
135
|
+
"data-slot": "dialog-title",
|
|
136
|
+
className: alertDialogTitleVariants(),
|
|
137
|
+
children: /* @__PURE__ */ jsx(Swappable, { value: title })
|
|
138
|
+
}), subtitle == null ? null : /* @__PURE__ */ jsx("p", {
|
|
139
|
+
"data-slot": "alert-dialog-subtitle",
|
|
140
|
+
className: "m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary",
|
|
141
|
+
children: /* @__PURE__ */ jsx(Swappable, { value: subtitle })
|
|
142
|
+
})]
|
|
143
|
+
}),
|
|
144
|
+
/* @__PURE__ */ jsx(Button, {
|
|
145
|
+
slot: "close",
|
|
146
|
+
"aria-label": closeLabel,
|
|
147
|
+
"data-slot": "alert-dialog-close",
|
|
148
|
+
className: alertDialogCloseVariants(),
|
|
149
|
+
children: /* @__PURE__ */ jsx("svg", {
|
|
150
|
+
"aria-hidden": "true",
|
|
151
|
+
viewBox: "0 0 16 16",
|
|
152
|
+
fill: "none",
|
|
153
|
+
stroke: "currentColor",
|
|
154
|
+
strokeWidth: "1.75",
|
|
155
|
+
strokeLinecap: "round",
|
|
156
|
+
children: /* @__PURE__ */ jsx("path", { d: "m4 4 8 8M12 4l-8 8" })
|
|
157
|
+
})
|
|
133
158
|
})
|
|
134
|
-
|
|
159
|
+
]
|
|
135
160
|
}),
|
|
136
161
|
/* @__PURE__ */ jsx(Text, {
|
|
137
162
|
slot: "description",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert-dialog.js","names":["Button","AriaModalOverlay","AriaModal","AriaDialog","AriaHeading","AriaButton","AriaText"],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Dialog as AriaDialog,\n Heading as AriaHeading,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Text as AriaText,\n} from \"react-aria-components\";\n\nimport { Button, type ButtonProps } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { dialogOverlayVariants, dialogPanelVariants, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * Built from `Dialog`'s own overlay and panel styles and the same React Aria\n * parts, but with its own layout: a header (title, a grey subtitle and a\n * circular close button), a thin divider, the description sentence, an\n * optional `body` of rich content, then the actions.\n * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this\n * shares the tokens and the motion classes instead, so the two cannot drift\n * on radius, scrim or entrance.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - THE CLOSE (X) IS CANCEL. It is `slot=\"close\"`, the same dismiss wiring as\n * a Cancel button, so it goes through the same `onOpenChange(false)` and\n * focus returns to the trigger. It is not a third answer: it is the\n * header's copy of the second one.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there. It is the sentence under the divider.\n *\n * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary\n * button (white, light border) and cancels through `slot=\"close\"`;\n * `AlertDialogAction` is the primary button painted destructive red, because a\n * confirmation here is a destructive one. The legacy `AlertDialogAction`\n * defaulted to the gradient primary and BOTH call sites overrode it back to\n * destructive by hand (`data-table.tsx:146`, `calendar-view.tsx:638`); the red\n * is the default now, so nobody fights it. Plain `Button`s still work in the\n * actions slot for anything else.\n */\nexport interface AlertDialogProps extends Omit<\n DialogProps,\n \"role\" | \"closeButton\" | \"isDismissable\"\n> {\n /**\n * What is about to happen, and whether it can be undone. Rendered as the\n * sentence under the divider, above the `body` slot (if any) and the actions.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** A short grey line under the title, in the header. */\n subtitle?: ReactNode;\n /**\n * Rich content between the description and the actions: a plan comparison,\n * a before/after diff, a quoted reply, a form field or a choice to make.\n *\n * It is NOT part of the accessible description: the screen reader announces\n * the one `description` sentence with the name, and the body is read in\n * place or as focus reaches its controls (each with its own label). So the\n * `description` must say everything needed to answer, including that a\n * choice or field below is required (\"Pick what happens to your data.\").\n * `false`, `true` and `\"\"` (as from `cond && <X />`) render no body.\n */\n body?: ReactNode;\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nconst alertDialogActionsVariants = cva(\"flex flex-wrap items-center justify-end gap-2 px-8 pb-8\");\n\n/**\n * The body slot: the description's side padding, so the two share an edge,\n * and the description's own bottom space repeated below it before the actions.\n */\nconst alertDialogBodyVariants = cva(\n \"min-w-0 px-8 pb-6 text-sm leading-(--leading-normal) text-foreground\",\n);\n\n/**\n * The cancel action: the secondary button, wired to dismiss the dialog.\n *\n * @example\n * <AlertDialogCancel>Cancel</AlertDialogCancel>\n */\nexport const AlertDialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogCancel(props, ref) {\n return <Button ref={ref} slot=\"close\" variant=\"secondary\" size=\"sm\" {...props} />;\n },\n);\n\n/**\n * The confirm action: the primary button in the destructive red.\n *\n * It is the primary button with its fill, hover, pressed and label roles\n * pointed at the destructive tokens, so it keeps the primary's press motion\n * and shadow. Variables on the element, rather than a competing background\n * class, so no stylesheet order decides which colour wins.\n *\n * @example\n * <AlertDialogAction onPress={remove}>Delete</AlertDialogAction>\n */\nexport const AlertDialogAction = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogAction({ className, ...props }, ref) {\n return (\n <Button\n ref={ref}\n variant=\"primary\"\n size=\"sm\"\n className={cnAll(\n \"[--color-primary:var(--color-destructive)] [--color-primary-hover:var(--color-destructive-hover)] [--color-primary-pressed:var(--color-destructive-pressed)] [--color-primary-foreground:var(--color-destructive-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst alertDialogHeaderVariants = cva(\n \"flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6\",\n);\n\n/**\n * The title column. With no subtitle it is at least the close button's height\n * and centres the title in it, so a one-line title sits level with the close;\n * a longer title grows the column from the top as before. With a subtitle the\n * two lines start at the top of the button, as they always have.\n */\nconst alertDialogTitleBlockVariants = cva(\"min-w-0 flex-1\", {\n variants: {\n subtitle: {\n true: \"\",\n false: \"flex min-h-9 flex-col justify-center\",\n },\n },\n});\n\nconst alertDialogTitleVariants = cva(\n \"m-0 text-lg leading-(--leading-tight) font-semibold text-foreground\",\n);\n\nconst alertDialogCloseVariants = cva([\n \"inline-flex size-9 shrink-0 items-center justify-center\",\n \"rounded-circle box-border cursor-pointer\",\n \"border-(length:--border-width) border-border bg-surface p-0 text-text-secondary\",\n // Fill on hover, a quick press to .96 and a spring release (see calendar-nav-motion).\n \"calendar-nav-motion\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]);\n\n/** A string swaps as a whole when its text changes while the dialog is open. */\nfunction Swappable({ value }: { value: ReactNode }) {\n if (typeof value !== \"string\") return <>{value}</>;\n return (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n );\n}\n\n/** `cond && <X />` hands over `false`; React renders nothing for it, so neither does the slot. */\nfunction isEmpty(node: ReactNode) {\n return node == null || typeof node === \"boolean\" || node === \"\";\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n {\n title,\n subtitle,\n beforeTitle,\n description,\n body,\n children,\n className,\n closeLabel = \"Close\",\n overlayClassName,\n entrance = \"rise\",\n ...props\n },\n ref,\n) {\n return (\n <AriaModalOverlay\n {...props}\n isDismissable={false}\n data-slot=\"dialog-overlay\"\n className={cnAll(\n dialogOverlayVariants(),\n entrance === \"rise\" && \"dialog-overlay-motion\",\n overlayClassName,\n )}\n >\n <AriaModal className=\"contents\">\n <AriaDialog\n ref={ref}\n role=\"alertdialog\"\n data-slot=\"dialog\"\n className={cnAll(\n dialogPanelVariants(),\n // The panel has no padding of its own: the header, body and actions pad themselves,\n // so the divider can run edge to edge.\n \"gap-0 p-0\",\n entrance === \"rise\" && \"dialog-panel-motion\",\n className,\n )}\n >\n {beforeTitle}\n <div data-slot=\"alert-dialog-header\" className={alertDialogHeaderVariants()}>\n <div className={alertDialogTitleBlockVariants({ subtitle: subtitle != null })}>\n {/* slot=\"title\" is what React Aria wires aria-labelledby to. */}\n <AriaHeading\n slot=\"title\"\n data-slot=\"dialog-title\"\n className={alertDialogTitleVariants()}\n >\n <Swappable value={title} />\n </AriaHeading>\n {subtitle == null ? null : (\n <p\n data-slot=\"alert-dialog-subtitle\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n <Swappable value={subtitle} />\n </p>\n )}\n </div>\n {/* slot=\"close\" is the same dismiss wiring as a Cancel button: one handler, focus back to the trigger. */}\n <AriaButton\n slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"alert-dialog-close\"\n className={alertDialogCloseVariants()}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </div>\n <AriaText\n slot=\"description\"\n elementType=\"p\"\n data-slot=\"dialog-description\"\n className=\"m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground\"\n >\n <Swappable value={description} />\n </AriaText>\n {isEmpty(body) ? null : (\n <div data-slot=\"alert-dialog-body\" className={alertDialogBodyVariants()}>\n {body}\n </div>\n )}\n {children == null ? null : (\n <div data-slot=\"alert-dialog-actions\" className={alertDialogActionsVariants()}>\n {children}\n </div>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"alert-dialog.js","names":["Button","AriaModalOverlay","AriaModal","AriaDialog","AriaHeading","AriaButton","AriaText"],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Dialog as AriaDialog,\n Heading as AriaHeading,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Text as AriaText,\n} from \"react-aria-components\";\n\nimport { Button, type ButtonProps } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { dialogOverlayVariants, dialogPanelVariants, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * Built from `Dialog`'s own overlay and panel styles and the same React Aria\n * parts, but with its own layout: a header (title, a grey subtitle and a\n * circular close button), a thin divider, the description sentence, an\n * optional `body` of rich content, then the actions.\n * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this\n * shares the tokens and the motion classes instead, so the two cannot drift\n * on radius, scrim or entrance.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - THE CLOSE (X) IS CANCEL. It is `slot=\"close\"`, the same dismiss wiring as\n * a Cancel button, so it goes through the same `onOpenChange(false)` and\n * focus returns to the trigger. It is not a third answer: it is the\n * header's copy of the second one.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there. It is the sentence under the divider.\n *\n * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary\n * button (white, light border) and cancels through `slot=\"close\"`;\n * `AlertDialogAction` is the primary button painted destructive red, because a\n * confirmation here is a destructive one. The legacy `AlertDialogAction`\n * defaulted to the gradient primary and BOTH call sites overrode it back to\n * destructive by hand (`data-table.tsx:146`, `calendar-view.tsx:638`); the red\n * is the default now, so nobody fights it. Plain `Button`s still work in the\n * actions slot for anything else.\n */\nexport interface AlertDialogProps extends Omit<\n DialogProps,\n \"role\" | \"closeButton\" | \"isDismissable\"\n> {\n /**\n * What is about to happen, and whether it can be undone. Rendered as the\n * sentence under the divider, above the `body` slot (if any) and the actions.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** A short grey line under the title, in the header. */\n subtitle?: ReactNode;\n /**\n * Rich content between the description and the actions: a plan comparison,\n * a before/after diff, a quoted reply, a form field or a choice to make.\n *\n * It is NOT part of the accessible description: the screen reader announces\n * the one `description` sentence with the name, and the body is read in\n * place or as focus reaches its controls (each with its own label). So the\n * `description` must say everything needed to answer, including that a\n * choice or field below is required (\"Pick what happens to your data.\").\n * `false`, `true` and `\"\"` (as from `cond && <X />`) render no body.\n */\n body?: ReactNode;\n /**\n * A decorative icon at the start of the header, in a round tile beside the\n * title: a warning triangle before a wide-reaching switch, a trash can before\n * a delete. Hidden from assistive technology, so the title must still say\n * what is at stake. Leave it out and the header is as before.\n */\n icon?: ReactNode;\n /**\n * The colour of the icon's tile, from the status tokens: `neutral` (the\n * default), `warning`, `destructive`, `info` or `success`. It paints the tile\n * only, never the actions. Without an `icon` it does nothing. Mirrored to\n * `data-tone` on the tile.\n */\n tone?: \"neutral\" | \"warning\" | \"destructive\" | \"info\" | \"success\";\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nconst alertDialogActionsVariants = cva(\"flex flex-wrap items-center justify-end gap-2 px-8 pb-8\");\n\n/**\n * The body slot: the description's side padding, so the two share an edge,\n * and the description's own bottom space repeated below it before the actions.\n */\nconst alertDialogBodyVariants = cva(\n \"min-w-0 px-8 pb-6 text-sm leading-(--leading-normal) text-foreground\",\n);\n\n/**\n * The cancel action: the secondary button, wired to dismiss the dialog.\n *\n * @example\n * <AlertDialogCancel>Cancel</AlertDialogCancel>\n */\nexport const AlertDialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogCancel(props, ref) {\n return <Button ref={ref} slot=\"close\" variant=\"secondary\" size=\"sm\" {...props} />;\n },\n);\n\n/**\n * The confirm action: the primary button in the destructive red.\n *\n * It is the primary button with its fill, hover, pressed and label roles\n * pointed at the destructive tokens, so it keeps the primary's press motion\n * and shadow. Variables on the element, rather than a competing background\n * class, so no stylesheet order decides which colour wins.\n *\n * @example\n * <AlertDialogAction onPress={remove}>Delete</AlertDialogAction>\n */\nexport const AlertDialogAction = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogAction({ className, ...props }, ref) {\n return (\n <Button\n ref={ref}\n variant=\"primary\"\n size=\"sm\"\n className={cnAll(\n \"[--color-primary:var(--color-destructive)] [--color-primary-hover:var(--color-destructive-hover)] [--color-primary-pressed:var(--color-destructive-pressed)] [--color-primary-foreground:var(--color-destructive-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst alertDialogHeaderVariants = cva(\n \"flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6\",\n);\n\n/**\n * The header icon's tile: the close button's size, so a one-line title centres\n * on both. Each tone is a subtle tint with the ink the token contrast contract\n * grades on it.\n */\nconst alertDialogIconVariants = cva(\n \"inline-flex size-9 shrink-0 items-center justify-center rounded-circle [&_svg]:pointer-events-none [&_svg]:size-5\",\n {\n variants: {\n tone: {\n neutral: \"bg-muted text-foreground\",\n warning: \"bg-warning-subtle text-warning-foreground\",\n destructive: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n info: \"bg-info-subtle text-info-foreground\",\n success: \"bg-success-subtle text-success-foreground\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * The title column. With no subtitle it is at least the close button's height\n * and centres the title in it, so a one-line title sits level with the close;\n * a longer title grows the column from the top as before. With a subtitle the\n * two lines start at the top of the button, as they always have.\n */\nconst alertDialogTitleBlockVariants = cva(\"min-w-0 flex-1\", {\n variants: {\n subtitle: {\n true: \"\",\n false: \"flex min-h-9 flex-col justify-center\",\n },\n },\n});\n\nconst alertDialogTitleVariants = cva(\n \"m-0 text-lg leading-(--leading-tight) font-semibold text-foreground\",\n);\n\nconst alertDialogCloseVariants = cva([\n \"inline-flex size-9 shrink-0 items-center justify-center\",\n \"rounded-circle box-border cursor-pointer\",\n \"border-(length:--border-width) border-border bg-surface p-0 text-text-secondary\",\n // Fill on hover, a quick press to .96 and a spring release (see calendar-nav-motion).\n \"calendar-nav-motion\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]);\n\n/** A string swaps as a whole when its text changes while the dialog is open. */\nfunction Swappable({ value }: { value: ReactNode }) {\n if (typeof value !== \"string\") return <>{value}</>;\n return (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n );\n}\n\n/** `cond && <X />` hands over `false`; React renders nothing for it, so neither does the slot. */\nfunction isEmpty(node: ReactNode) {\n return node == null || typeof node === \"boolean\" || node === \"\";\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n {\n title,\n subtitle,\n beforeTitle,\n description,\n body,\n icon,\n tone = \"neutral\",\n children,\n className,\n closeLabel = \"Close\",\n overlayClassName,\n entrance = \"rise\",\n ...props\n },\n ref,\n) {\n return (\n <AriaModalOverlay\n {...props}\n isDismissable={false}\n data-slot=\"dialog-overlay\"\n className={cnAll(\n dialogOverlayVariants(),\n entrance === \"rise\" && \"dialog-overlay-motion\",\n overlayClassName,\n )}\n >\n <AriaModal className=\"contents\">\n <AriaDialog\n ref={ref}\n role=\"alertdialog\"\n data-slot=\"dialog\"\n className={cnAll(\n dialogPanelVariants(),\n // The panel has no padding of its own: the header, body and actions pad themselves,\n // so the divider can run edge to edge.\n \"gap-0 p-0\",\n entrance === \"rise\" && \"dialog-panel-motion\",\n className,\n )}\n >\n {beforeTitle}\n <div data-slot=\"alert-dialog-header\" className={alertDialogHeaderVariants()}>\n {isEmpty(icon) ? null : (\n <span\n aria-hidden=\"true\"\n data-slot=\"alert-dialog-icon\"\n data-tone={tone}\n className={alertDialogIconVariants({ tone })}\n >\n {icon}\n </span>\n )}\n <div className={alertDialogTitleBlockVariants({ subtitle: subtitle != null })}>\n {/* slot=\"title\" is what React Aria wires aria-labelledby to. */}\n <AriaHeading\n slot=\"title\"\n data-slot=\"dialog-title\"\n className={alertDialogTitleVariants()}\n >\n <Swappable value={title} />\n </AriaHeading>\n {subtitle == null ? null : (\n <p\n data-slot=\"alert-dialog-subtitle\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n <Swappable value={subtitle} />\n </p>\n )}\n </div>\n {/* slot=\"close\" is the same dismiss wiring as a Cancel button: one handler, focus back to the trigger. */}\n <AriaButton\n slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"alert-dialog-close\"\n className={alertDialogCloseVariants()}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </div>\n <AriaText\n slot=\"description\"\n elementType=\"p\"\n data-slot=\"dialog-description\"\n className=\"m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground\"\n >\n <Swappable value={description} />\n </AriaText>\n {isEmpty(body) ? null : (\n <div data-slot=\"alert-dialog-body\" className={alertDialogBodyVariants()}>\n {body}\n </div>\n )}\n {children == null ? null : (\n <div data-slot=\"alert-dialog-actions\" className={alertDialogActionsVariants()}>\n {children}\n </div>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n});\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
1
|
+
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
2
|
//#region src/announcement-bar/announcement-bar.d.ts
|
|
3
3
|
export interface AnnouncementAction {
|
|
4
4
|
label: string;
|
|
@@ -28,8 +28,15 @@ export interface Announcement {
|
|
|
28
28
|
* collapses the bar's height so the page below eases up, and with an `id` the
|
|
29
29
|
* dismissal is remembered in `localStorage`. While rotation runs the message is
|
|
30
30
|
* not announced on each change; when it holds, changes are announced politely.
|
|
31
|
+
*
|
|
32
|
+
* Other attributes (`data-*`, `role`, `aria-*`, event handlers, `style`) reach
|
|
33
|
+
* the bar's `<section>`. `role` lets a consumer make the bar a live `alert`
|
|
34
|
+
* or `status`; without it the bar stays a labelled region. `label` remains the
|
|
35
|
+
* accessible name, and `id` is not passed through: it names the remembered
|
|
36
|
+
* dismissal, as before. Handlers for pointer enter and leave, focus and blur
|
|
37
|
+
* run before the bar's own rotation hold.
|
|
31
38
|
*/
|
|
32
|
-
export interface AnnouncementBarProps {
|
|
39
|
+
export interface AnnouncementBarProps extends Omit<ComponentPropsWithoutRef<"section">, "id" | "children" | "className"> {
|
|
33
40
|
messages: Announcement[];
|
|
34
41
|
/** Remembers dismissal under this id. Change the id to show a new campaign to everyone again. */
|
|
35
42
|
id?: string;
|
|
@@ -51,8 +58,12 @@ export interface AnnouncementBarProps {
|
|
|
51
58
|
controls?: boolean;
|
|
52
59
|
/** @default true */
|
|
53
60
|
dismissible?: boolean;
|
|
54
|
-
/**
|
|
55
|
-
|
|
61
|
+
/**
|
|
62
|
+
* `inverted` is the foreground colour as the fill. `warning` is the warning
|
|
63
|
+
* tint with its ink and a decorative warning triangle before the message, so
|
|
64
|
+
* the tone is not carried by colour alone. @default "neutral"
|
|
65
|
+
*/
|
|
66
|
+
tone?: "neutral" | "inverted" | "warning";
|
|
56
67
|
onAction?: (announcement: Announcement) => void;
|
|
57
68
|
onCountdownEnd?: (announcement: Announcement) => void;
|
|
58
69
|
/** Accessible name of the region. @default "Announcements" */
|
|
@@ -123,7 +123,7 @@ const Glyph = ({ path, size = 16 }) => /* @__PURE__ */ jsx("svg", {
|
|
|
123
123
|
"aria-hidden": "true",
|
|
124
124
|
children: /* @__PURE__ */ jsx("path", { d: path })
|
|
125
125
|
});
|
|
126
|
-
const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open: openProp, defaultOpen = true, onOpenChange, index: indexProp, defaultIndex = 0, onIndexChange, interval = 6e3, autoPlay = true, controls = false, dismissible = true, tone = "neutral", onAction, onCountdownEnd, label = "Announcements", className }, ref) {
|
|
126
|
+
const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open: openProp, defaultOpen = true, onOpenChange, index: indexProp, defaultIndex = 0, onIndexChange, interval = 6e3, autoPlay = true, controls = false, dismissible = true, tone = "neutral", onAction, onCountdownEnd, label = "Announcements", className, style, onPointerEnter, onPointerLeave, onFocus, onBlur, ...rest }, ref) {
|
|
127
127
|
const uid = useId();
|
|
128
128
|
const reduced = useReducedMotion();
|
|
129
129
|
const total = messages.length;
|
|
@@ -238,6 +238,7 @@ const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open
|
|
|
238
238
|
},
|
|
239
239
|
className: "alert-presence announcement-frame",
|
|
240
240
|
children: /* @__PURE__ */ jsxs("section", {
|
|
241
|
+
...rest,
|
|
241
242
|
ref: barRef,
|
|
242
243
|
"data-slot": "announcement-bar",
|
|
243
244
|
"data-tone": tone,
|
|
@@ -245,11 +246,24 @@ const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open
|
|
|
245
246
|
"aria-roledescription": total > 1 ? "carousel" : void 0,
|
|
246
247
|
"aria-hidden": leaving || void 0,
|
|
247
248
|
className: cn("announcement-bar", className),
|
|
248
|
-
style: {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
style: {
|
|
250
|
+
...style,
|
|
251
|
+
"--controls": controlCount
|
|
252
|
+
},
|
|
253
|
+
onPointerEnter: (event) => {
|
|
254
|
+
onPointerEnter?.(event);
|
|
255
|
+
if (event.pointerType === "mouse") setHovered(true);
|
|
256
|
+
},
|
|
257
|
+
onPointerLeave: (event) => {
|
|
258
|
+
onPointerLeave?.(event);
|
|
259
|
+
setHovered(false);
|
|
260
|
+
},
|
|
261
|
+
onFocus: (event) => {
|
|
262
|
+
onFocus?.(event);
|
|
263
|
+
setFocused(event.target.matches(":focus-visible") && !event.target.closest("[data-pause]"));
|
|
264
|
+
},
|
|
252
265
|
onBlur: (event) => {
|
|
266
|
+
onBlur?.(event);
|
|
253
267
|
if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);
|
|
254
268
|
},
|
|
255
269
|
children: [
|
|
@@ -274,7 +288,18 @@ const AnnouncementBar = forwardRef(function AnnouncementBar({ messages, id, open
|
|
|
274
288
|
children: /* @__PURE__ */ jsxs("p", {
|
|
275
289
|
className: "announcement-message",
|
|
276
290
|
children: [
|
|
277
|
-
/* @__PURE__ */
|
|
291
|
+
/* @__PURE__ */ jsxs("span", { children: [tone === "warning" ? /* @__PURE__ */ jsx("svg", {
|
|
292
|
+
"data-slot": "announcement-tone-icon",
|
|
293
|
+
viewBox: "0 0 16 16",
|
|
294
|
+
fill: "none",
|
|
295
|
+
stroke: "currentColor",
|
|
296
|
+
strokeWidth: 1.75,
|
|
297
|
+
strokeLinecap: "round",
|
|
298
|
+
strokeLinejoin: "round",
|
|
299
|
+
"aria-hidden": "true",
|
|
300
|
+
className: "announcement-tone-icon",
|
|
301
|
+
children: /* @__PURE__ */ jsx("path", { d: "M8 2.5 14.5 13.5h-13ZM8 6.5v3M8 11.5h.01" })
|
|
302
|
+
}) : null, announcement.message] }),
|
|
278
303
|
countdown ? /* @__PURE__ */ jsx(Countdown, {
|
|
279
304
|
to: countdown.to,
|
|
280
305
|
label: countdown.label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"announcement-bar.js","names":[],"sources":["../../src/announcement-bar/announcement-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useReducedMotion } from \"../lib/motion.js\";\nimport { usePausableTimeout } from \"../lib/pausable-timeout.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\nexport interface AnnouncementAction {\n label: string;\n /** Renders a link. Without it the action is a button. */\n href?: string;\n onClick?: () => void;\n}\n\nexport interface Announcement {\n /** Stable id, reported to callbacks. */\n id: string;\n message: ReactNode;\n action?: AnnouncementAction;\n /** Counts down to a moment, for example the end of a sale. */\n countdown?: { to: Date | string | number; label?: string };\n}\n\n/**\n * AnnouncementBar: a slim bar for the top of a page.\n *\n * It can rotate several messages: the next one rises in from below (the\n * previous one from above) while the bar follows the new height. Rotation pauses\n * on hover, keyboard focus, a hidden tab and, with the `controls`, a Pause\n * button whose ring is the clock; it is off under reduced motion. Messages can\n * carry a call to action and a live countdown whose digits roll. Dismissing\n * collapses the bar's height so the page below eases up, and with an `id` the\n * dismissal is remembered in `localStorage`. While rotation runs the message is\n * not announced on each change; when it holds, changes are announced politely.\n */\nexport interface AnnouncementBarProps {\n messages: Announcement[];\n /** Remembers dismissal under this id. Change the id to show a new campaign to everyone again. */\n id?: string;\n /** Whether the bar is shown. Leave it out to let the component manage it. */\n open?: boolean;\n /** @default true */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Index of the visible message. */\n index?: number;\n /** @default 0 */\n defaultIndex?: number;\n onIndexChange?: (index: number) => void;\n /** Milliseconds each message stays before the next one. @default 6000 */\n interval?: number;\n /** Rotate automatically. Off under reduced motion. @default true */\n autoPlay?: boolean;\n /** Show previous, next and pause controls when there are several messages. By default only the close button shows. @default false */\n controls?: boolean;\n /** @default true */\n dismissible?: boolean;\n /** `inverted` is the foreground colour as the fill. @default \"neutral\" */\n tone?: \"neutral\" | \"inverted\";\n onAction?: (announcement: Announcement) => void;\n onCountdownEnd?: (announcement: Announcement) => void;\n /** Accessible name of the region. @default \"Announcements\" */\n label?: string;\n /** Extra classes for the bar. Merged last. */\n className?: string;\n}\n\nconst storageKey = (id: string) => `alfred-announcement:${id}`;\n\n/** Forgets a remembered dismissal so the bar with this id shows again. */\nexport function clearAnnouncementDismissal(id: string) {\n try {\n window.localStorage.removeItem(storageKey(id));\n } catch {\n // Storage can be blocked.\n }\n}\n\nfunction parts(ms: number) {\n const total = Math.max(0, Math.floor(ms / 1000));\n return { d: Math.floor(total / 86400), h: Math.floor((total % 86400) / 3600), m: Math.floor((total % 3600) / 60), s: total % 60 };\n}\nconst pad = (value: number) => String(value).padStart(2, \"0\");\n\n/** One character cell: a changed digit rises in while the old one lifts away, like a counter winding down. */\nconst Digits = ({ value, pending }: { value: string; pending: boolean }) =>\n value.split(\"\").map((char, index) => (\n <span key={value.length - index} className=\"announcement-digit\">\n <SwapText swapKey={pending ? \"0\" : char}>{pending ? \"0\" : char}</SwapText>\n </span>\n ));\n\nfunction Countdown({ to, label, onEnd }: { to: Date | string | number; label?: string; onEnd?: () => void }) {\n const target = new Date(to).getTime();\n const [now, setNow] = useState<number | null>(null);\n const ended = useRef(false);\n const endRef = useRef(onEnd);\n useEffect(() => {\n endRef.current = onEnd;\n });\n useEffect(() => {\n let timer: ReturnType<typeof setTimeout>;\n const tick = () => {\n const current = Date.now();\n setNow(current);\n if (current >= target) {\n if (!ended.current) {\n ended.current = true;\n endRef.current?.();\n }\n return;\n }\n // Wake just after each whole second, so the digits change on the beat.\n timer = setTimeout(tick, 1000 - (current % 1000) + 8);\n };\n timer = setTimeout(tick, 0);\n return () => clearTimeout(timer);\n }, [target]);\n const pending = now === null;\n const { d, h, m, s } = parts(pending ? 0 : target - now);\n const spoken = pending ? \"\" : `${d ? `${d} days ` : \"\"}${h} hours ${m} minutes`;\n return (\n <span className=\"announcement-countdown\">\n {label ? <span>{label}</span> : null}\n <span role=\"timer\" aria-live=\"off\" aria-label={spoken} data-pending={pending || undefined} className=\"announcement-time\">\n {d ? (\n <>\n <Digits value={String(d)} pending={pending} />\n <span aria-hidden=\"true\" className=\"announcement-unit\">\n d\n </span>\n <span aria-hidden=\"true\" className=\"announcement-gap\" />\n </>\n ) : null}\n <Digits value={pad(h)} pending={pending} />\n <span aria-hidden=\"true\" className=\"announcement-sep\">:</span>\n <Digits value={pad(m)} pending={pending} />\n <span aria-hidden=\"true\" className=\"announcement-sep\">:</span>\n <Digits value={pad(s)} pending={pending} />\n </span>\n </span>\n );\n}\n\nconst Glyph = ({ path, size = 16 }: { path: string; size?: number }) => (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.75} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d={path} />\n </svg>\n);\n\nexport const AnnouncementBar = forwardRef<HTMLElement, AnnouncementBarProps>(function AnnouncementBar(\n {\n messages,\n id,\n open: openProp,\n defaultOpen = true,\n onOpenChange,\n index: indexProp,\n defaultIndex = 0,\n onIndexChange,\n interval = 6000,\n autoPlay = true,\n controls = false,\n dismissible = true,\n tone = \"neutral\",\n onAction,\n onCountdownEnd,\n label = \"Announcements\",\n className,\n },\n ref,\n) {\n const uid = useId();\n const reduced = useReducedMotion();\n const total = messages.length;\n\n // Open: controlled, or internal and closed at once when this id was dismissed before.\n const [openInternal, setOpenInternal] = useState(defaultOpen);\n const [remembered, setRemembered] = useState(false);\n const wanted = openProp ?? (openInternal && !remembered);\n\n // Presence: a bar that comes and goes collapses its height rather than vanishing (the alert-presence utility).\n const [rendered, setRendered] = useState(wanted);\n const [shown, setShown] = useState(wanted);\n if (wanted && !rendered) setRendered(true);\n if (!wanted && shown) setShown(false);\n useEffect(() => {\n // A newly shown bar mounts collapsed and opens a frame later, so it grows rather than appears.\n if (wanted && rendered && !shown) {\n const frame = requestAnimationFrame(() => setShown(true));\n return () => cancelAnimationFrame(frame);\n }\n }, [wanted, rendered, shown]);\n const leaving = rendered && !wanted;\n const finish = useCallback(() => setRendered(false), []);\n useExitBackstop(leaving, finish, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) finish();\n }, [leaving, finish]);\n\n // Storage is only readable on the client; a dismissed bar is hidden before paint so it never flashes.\n useIsomorphicLayoutEffect(() => {\n if (!id || openProp !== undefined) return;\n let stored = false;\n try {\n stored = window.localStorage.getItem(storageKey(id)) === \"dismissed\";\n } catch {\n // Storage can be blocked.\n }\n if (stored) {\n setRemembered(true);\n setRendered(false);\n setShown(false);\n }\n }, [id, openProp]);\n\n function dismiss() {\n if (id) {\n try {\n window.localStorage.setItem(storageKey(id), \"dismissed\");\n } catch {\n // Storage can be blocked.\n }\n }\n if (openProp === undefined) setOpenInternal(false);\n onOpenChange?.(false);\n }\n\n // Rotation.\n const [indexInternal, setIndexInternal] = useState(defaultIndex);\n const current = total ? (((indexProp ?? indexInternal) % total) + total) % total : 0;\n const [direction, setDirection] = useState(1);\n function go(step: number) {\n if (total < 2) return;\n const next = (((current + step) % total) + total) % total;\n setDirection(step > 0 ? 1 : -1);\n if (indexProp === undefined) setIndexInternal(next);\n onIndexChange?.(next);\n }\n\n const [userPaused, setUserPaused] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [focused, setFocused] = useState(false);\n const [hidden, setHidden] = useState(false);\n useEffect(() => {\n const read = () => setHidden(document.visibilityState === \"hidden\");\n document.addEventListener(\"visibilitychange\", read);\n return () => document.removeEventListener(\"visibilitychange\", read);\n }, []);\n const rotating = autoPlay && !reduced && total > 1;\n const held = hovered || focused || userPaused;\n // The clock holds while the viewer is looking, and finishes the rest of the time when released. It also holds in a hidden tab.\n const setHeld = usePausableTimeout(rotating && wanted ? interval : 0, current, () => go(1));\n useEffect(() => {\n setHeld(held);\n }, [held, setHeld]);\n\n // The bar follows the height of the current message, which can wrap on narrow screens.\n const frame = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n useHeightFollow(frame, content, String(current));\n\n // A bar on its way out is out of the tab order and the accessibility tree at once.\n const bar = useRef<HTMLElement | null>(null);\n const barRef = useMemo(() => mergeRefs(ref, bar), [ref]);\n useIsomorphicLayoutEffect(() => {\n bar.current?.toggleAttribute(\"inert\", leaving);\n }, [leaving]);\n\n const announcement = messages[current];\n if (!rendered || !announcement) return null;\n const navigable = controls && total > 1;\n const controlCount = (navigable ? 2 + (rotating ? 1 : 0) : 0) + (dismissible ? 1 : 0);\n const { action, countdown } = announcement;\n const cta = action ? (\n action.href ? (\n <a\n href={action.href}\n className=\"announcement-cta\"\n onClick={() => {\n action.onClick?.();\n onAction?.(announcement);\n }}\n >\n {action.label}\n <Glyph path=\"M3 8h10M9 4l4 4-4 4\" size={14} />\n </a>\n ) : (\n <button\n type=\"button\"\n className=\"announcement-cta\"\n onClick={() => {\n action.onClick?.();\n onAction?.(announcement);\n }}\n >\n {action.label}\n <Glyph path=\"M3 8h10M9 4l4 4-4 4\" size={14} />\n </button>\n )\n ) : null;\n\n return (\n <div\n data-slot=\"announcement-presence\"\n data-shown={shown || undefined}\n onTransitionEnd={(event) => {\n if (leaving && event.target === event.currentTarget && event.propertyName === \"grid-template-rows\") finish();\n }}\n className=\"alert-presence announcement-frame\"\n >\n <section\n ref={barRef}\n data-slot=\"announcement-bar\"\n data-tone={tone}\n aria-label={label}\n aria-roledescription={total > 1 ? \"carousel\" : undefined}\n aria-hidden={leaving || undefined}\n className={cn(\"announcement-bar\", className)}\n style={{ \"--controls\": controlCount } as CSSProperties}\n onPointerEnter={(event) => event.pointerType === \"mouse\" && setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n // Keyboard focus holds the clock; a mouse click that leaves focus on a button does not, and neither does the pause button itself.\n onFocus={(event) => setFocused(event.target.matches(\":focus-visible\") && !event.target.closest(\"[data-pause]\"))}\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);\n }}\n >\n <div aria-hidden=\"true\" />\n <div\n ref={frame}\n id={`${uid}-slides`}\n aria-live={rotating && !held ? \"off\" : \"polite\"}\n aria-atomic=\"false\"\n className=\"announcement-viewport height-follow\"\n >\n <div ref={content}>\n <SwapText swapKey={announcement.id} dir={direction} className=\"block\">\n <div\n role={total > 1 ? \"group\" : undefined}\n aria-roledescription={total > 1 ? \"slide\" : undefined}\n aria-label={total > 1 ? `${current + 1} of ${total}` : undefined}\n className=\"announcement-face\"\n >\n <p className=\"announcement-message\">\n <span>{announcement.message}</span>\n {countdown ? <Countdown to={countdown.to} label={countdown.label} onEnd={() => onCountdownEnd?.(announcement)} /> : null}\n {cta}\n </p>\n </div>\n </SwapText>\n </div>\n </div>\n <div className=\"announcement-controls\">\n {navigable ? (\n <>\n <button type=\"button\" aria-label=\"Previous announcement\" aria-controls={`${uid}-slides`} className=\"announcement-icon\" onClick={() => go(-1)}>\n <Glyph path=\"M4 10l4-4 4 4\" />\n </button>\n {rotating ? (\n <button\n type=\"button\"\n aria-label={userPaused ? \"Resume announcements\" : \"Pause announcements\"}\n aria-pressed={userPaused}\n data-pause=\"\"\n className=\"announcement-icon\"\n onClick={() => setUserPaused(!userPaused)}\n >\n <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.75} aria-hidden=\"true\" className=\"announcement-ring\">\n <circle cx=\"10\" cy=\"10\" r=\"8\" pathLength={1} className=\"announcement-ring-track\" />\n <circle\n key={current}\n cx=\"10\"\n cy=\"10\"\n r=\"8\"\n pathLength={1}\n strokeLinecap=\"round\"\n className=\"announcement-ring-fill\"\n style={{ animationDuration: `${interval}ms`, animationPlayState: held || hidden ? \"paused\" : \"running\" }}\n />\n </svg>\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\" className=\"announcement-ring-icon\">\n {userPaused ? <path d=\"M6 4.5v7l5.5-3.5Z\" /> : <path d=\"M5.5 4.5h1.8v7H5.5ZM8.7 4.5h1.8v7H8.7Z\" />}\n </svg>\n </button>\n ) : null}\n <button type=\"button\" aria-label=\"Next announcement\" aria-controls={`${uid}-slides`} className=\"announcement-icon\" onClick={() => go(1)}>\n <Glyph path=\"M4 6l4 4 4-4\" />\n </button>\n </>\n ) : null}\n {dismissible ? (\n <button type=\"button\" aria-label=\"Dismiss\" className=\"announcement-icon\" onClick={dismiss}>\n <Glyph path=\"M4 4l8 8M12 4l-8 8\" />\n </button>\n ) : null}\n </div>\n </section>\n </div>\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"announcement-bar.js","names":[],"sources":["../../src/announcement-bar/announcement-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useReducedMotion } from \"../lib/motion.js\";\nimport { usePausableTimeout } from \"../lib/pausable-timeout.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\nexport interface AnnouncementAction {\n label: string;\n /** Renders a link. Without it the action is a button. */\n href?: string;\n onClick?: () => void;\n}\n\nexport interface Announcement {\n /** Stable id, reported to callbacks. */\n id: string;\n message: ReactNode;\n action?: AnnouncementAction;\n /** Counts down to a moment, for example the end of a sale. */\n countdown?: { to: Date | string | number; label?: string };\n}\n\n/**\n * AnnouncementBar: a slim bar for the top of a page.\n *\n * It can rotate several messages: the next one rises in from below (the\n * previous one from above) while the bar follows the new height. Rotation pauses\n * on hover, keyboard focus, a hidden tab and, with the `controls`, a Pause\n * button whose ring is the clock; it is off under reduced motion. Messages can\n * carry a call to action and a live countdown whose digits roll. Dismissing\n * collapses the bar's height so the page below eases up, and with an `id` the\n * dismissal is remembered in `localStorage`. While rotation runs the message is\n * not announced on each change; when it holds, changes are announced politely.\n *\n * Other attributes (`data-*`, `role`, `aria-*`, event handlers, `style`) reach\n * the bar's `<section>`. `role` lets a consumer make the bar a live `alert`\n * or `status`; without it the bar stays a labelled region. `label` remains the\n * accessible name, and `id` is not passed through: it names the remembered\n * dismissal, as before. Handlers for pointer enter and leave, focus and blur\n * run before the bar's own rotation hold.\n */\nexport interface AnnouncementBarProps\n extends Omit<ComponentPropsWithoutRef<\"section\">, \"id\" | \"children\" | \"className\"> {\n messages: Announcement[];\n /** Remembers dismissal under this id. Change the id to show a new campaign to everyone again. */\n id?: string;\n /** Whether the bar is shown. Leave it out to let the component manage it. */\n open?: boolean;\n /** @default true */\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Index of the visible message. */\n index?: number;\n /** @default 0 */\n defaultIndex?: number;\n onIndexChange?: (index: number) => void;\n /** Milliseconds each message stays before the next one. @default 6000 */\n interval?: number;\n /** Rotate automatically. Off under reduced motion. @default true */\n autoPlay?: boolean;\n /** Show previous, next and pause controls when there are several messages. By default only the close button shows. @default false */\n controls?: boolean;\n /** @default true */\n dismissible?: boolean;\n /**\n * `inverted` is the foreground colour as the fill. `warning` is the warning\n * tint with its ink and a decorative warning triangle before the message, so\n * the tone is not carried by colour alone. @default \"neutral\"\n */\n tone?: \"neutral\" | \"inverted\" | \"warning\";\n onAction?: (announcement: Announcement) => void;\n onCountdownEnd?: (announcement: Announcement) => void;\n /** Accessible name of the region. @default \"Announcements\" */\n label?: string;\n /** Extra classes for the bar. Merged last. */\n className?: string;\n}\n\nconst storageKey = (id: string) => `alfred-announcement:${id}`;\n\n/** Forgets a remembered dismissal so the bar with this id shows again. */\nexport function clearAnnouncementDismissal(id: string) {\n try {\n window.localStorage.removeItem(storageKey(id));\n } catch {\n // Storage can be blocked.\n }\n}\n\nfunction parts(ms: number) {\n const total = Math.max(0, Math.floor(ms / 1000));\n return { d: Math.floor(total / 86400), h: Math.floor((total % 86400) / 3600), m: Math.floor((total % 3600) / 60), s: total % 60 };\n}\nconst pad = (value: number) => String(value).padStart(2, \"0\");\n\n/** One character cell: a changed digit rises in while the old one lifts away, like a counter winding down. */\nconst Digits = ({ value, pending }: { value: string; pending: boolean }) =>\n value.split(\"\").map((char, index) => (\n <span key={value.length - index} className=\"announcement-digit\">\n <SwapText swapKey={pending ? \"0\" : char}>{pending ? \"0\" : char}</SwapText>\n </span>\n ));\n\nfunction Countdown({ to, label, onEnd }: { to: Date | string | number; label?: string; onEnd?: () => void }) {\n const target = new Date(to).getTime();\n const [now, setNow] = useState<number | null>(null);\n const ended = useRef(false);\n const endRef = useRef(onEnd);\n useEffect(() => {\n endRef.current = onEnd;\n });\n useEffect(() => {\n let timer: ReturnType<typeof setTimeout>;\n const tick = () => {\n const current = Date.now();\n setNow(current);\n if (current >= target) {\n if (!ended.current) {\n ended.current = true;\n endRef.current?.();\n }\n return;\n }\n // Wake just after each whole second, so the digits change on the beat.\n timer = setTimeout(tick, 1000 - (current % 1000) + 8);\n };\n timer = setTimeout(tick, 0);\n return () => clearTimeout(timer);\n }, [target]);\n const pending = now === null;\n const { d, h, m, s } = parts(pending ? 0 : target - now);\n const spoken = pending ? \"\" : `${d ? `${d} days ` : \"\"}${h} hours ${m} minutes`;\n return (\n <span className=\"announcement-countdown\">\n {label ? <span>{label}</span> : null}\n <span role=\"timer\" aria-live=\"off\" aria-label={spoken} data-pending={pending || undefined} className=\"announcement-time\">\n {d ? (\n <>\n <Digits value={String(d)} pending={pending} />\n <span aria-hidden=\"true\" className=\"announcement-unit\">\n d\n </span>\n <span aria-hidden=\"true\" className=\"announcement-gap\" />\n </>\n ) : null}\n <Digits value={pad(h)} pending={pending} />\n <span aria-hidden=\"true\" className=\"announcement-sep\">:</span>\n <Digits value={pad(m)} pending={pending} />\n <span aria-hidden=\"true\" className=\"announcement-sep\">:</span>\n <Digits value={pad(s)} pending={pending} />\n </span>\n </span>\n );\n}\n\nconst Glyph = ({ path, size = 16 }: { path: string; size?: number }) => (\n <svg width={size} height={size} viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.75} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d={path} />\n </svg>\n);\n\nexport const AnnouncementBar = forwardRef<HTMLElement, AnnouncementBarProps>(function AnnouncementBar(\n {\n messages,\n id,\n open: openProp,\n defaultOpen = true,\n onOpenChange,\n index: indexProp,\n defaultIndex = 0,\n onIndexChange,\n interval = 6000,\n autoPlay = true,\n controls = false,\n dismissible = true,\n tone = \"neutral\",\n onAction,\n onCountdownEnd,\n label = \"Announcements\",\n className,\n style,\n onPointerEnter,\n onPointerLeave,\n onFocus,\n onBlur,\n ...rest\n },\n ref,\n) {\n const uid = useId();\n const reduced = useReducedMotion();\n const total = messages.length;\n\n // Open: controlled, or internal and closed at once when this id was dismissed before.\n const [openInternal, setOpenInternal] = useState(defaultOpen);\n const [remembered, setRemembered] = useState(false);\n const wanted = openProp ?? (openInternal && !remembered);\n\n // Presence: a bar that comes and goes collapses its height rather than vanishing (the alert-presence utility).\n const [rendered, setRendered] = useState(wanted);\n const [shown, setShown] = useState(wanted);\n if (wanted && !rendered) setRendered(true);\n if (!wanted && shown) setShown(false);\n useEffect(() => {\n // A newly shown bar mounts collapsed and opens a frame later, so it grows rather than appears.\n if (wanted && rendered && !shown) {\n const frame = requestAnimationFrame(() => setShown(true));\n return () => cancelAnimationFrame(frame);\n }\n }, [wanted, rendered, shown]);\n const leaving = rendered && !wanted;\n const finish = useCallback(() => setRendered(false), []);\n useExitBackstop(leaving, finish, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) finish();\n }, [leaving, finish]);\n\n // Storage is only readable on the client; a dismissed bar is hidden before paint so it never flashes.\n useIsomorphicLayoutEffect(() => {\n if (!id || openProp !== undefined) return;\n let stored = false;\n try {\n stored = window.localStorage.getItem(storageKey(id)) === \"dismissed\";\n } catch {\n // Storage can be blocked.\n }\n if (stored) {\n setRemembered(true);\n setRendered(false);\n setShown(false);\n }\n }, [id, openProp]);\n\n function dismiss() {\n if (id) {\n try {\n window.localStorage.setItem(storageKey(id), \"dismissed\");\n } catch {\n // Storage can be blocked.\n }\n }\n if (openProp === undefined) setOpenInternal(false);\n onOpenChange?.(false);\n }\n\n // Rotation.\n const [indexInternal, setIndexInternal] = useState(defaultIndex);\n const current = total ? (((indexProp ?? indexInternal) % total) + total) % total : 0;\n const [direction, setDirection] = useState(1);\n function go(step: number) {\n if (total < 2) return;\n const next = (((current + step) % total) + total) % total;\n setDirection(step > 0 ? 1 : -1);\n if (indexProp === undefined) setIndexInternal(next);\n onIndexChange?.(next);\n }\n\n const [userPaused, setUserPaused] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [focused, setFocused] = useState(false);\n const [hidden, setHidden] = useState(false);\n useEffect(() => {\n const read = () => setHidden(document.visibilityState === \"hidden\");\n document.addEventListener(\"visibilitychange\", read);\n return () => document.removeEventListener(\"visibilitychange\", read);\n }, []);\n const rotating = autoPlay && !reduced && total > 1;\n const held = hovered || focused || userPaused;\n // The clock holds while the viewer is looking, and finishes the rest of the time when released. It also holds in a hidden tab.\n const setHeld = usePausableTimeout(rotating && wanted ? interval : 0, current, () => go(1));\n useEffect(() => {\n setHeld(held);\n }, [held, setHeld]);\n\n // The bar follows the height of the current message, which can wrap on narrow screens.\n const frame = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n useHeightFollow(frame, content, String(current));\n\n // A bar on its way out is out of the tab order and the accessibility tree at once.\n const bar = useRef<HTMLElement | null>(null);\n const barRef = useMemo(() => mergeRefs(ref, bar), [ref]);\n useIsomorphicLayoutEffect(() => {\n bar.current?.toggleAttribute(\"inert\", leaving);\n }, [leaving]);\n\n const announcement = messages[current];\n if (!rendered || !announcement) return null;\n const navigable = controls && total > 1;\n const controlCount = (navigable ? 2 + (rotating ? 1 : 0) : 0) + (dismissible ? 1 : 0);\n const { action, countdown } = announcement;\n const cta = action ? (\n action.href ? (\n <a\n href={action.href}\n className=\"announcement-cta\"\n onClick={() => {\n action.onClick?.();\n onAction?.(announcement);\n }}\n >\n {action.label}\n <Glyph path=\"M3 8h10M9 4l4 4-4 4\" size={14} />\n </a>\n ) : (\n <button\n type=\"button\"\n className=\"announcement-cta\"\n onClick={() => {\n action.onClick?.();\n onAction?.(announcement);\n }}\n >\n {action.label}\n <Glyph path=\"M3 8h10M9 4l4 4-4 4\" size={14} />\n </button>\n )\n ) : null;\n\n return (\n <div\n data-slot=\"announcement-presence\"\n data-shown={shown || undefined}\n onTransitionEnd={(event) => {\n if (leaving && event.target === event.currentTarget && event.propertyName === \"grid-template-rows\") finish();\n }}\n className=\"alert-presence announcement-frame\"\n >\n <section\n {...rest}\n ref={barRef}\n data-slot=\"announcement-bar\"\n data-tone={tone}\n aria-label={label}\n aria-roledescription={total > 1 ? \"carousel\" : undefined}\n aria-hidden={leaving || undefined}\n className={cn(\"announcement-bar\", className)}\n style={{ ...style, \"--controls\": controlCount } as CSSProperties}\n onPointerEnter={(event) => {\n onPointerEnter?.(event);\n if (event.pointerType === \"mouse\") setHovered(true);\n }}\n onPointerLeave={(event) => {\n onPointerLeave?.(event);\n setHovered(false);\n }}\n // Keyboard focus holds the clock; a mouse click that leaves focus on a button does not, and neither does the pause button itself.\n onFocus={(event) => {\n onFocus?.(event);\n setFocused(event.target.matches(\":focus-visible\") && !event.target.closest(\"[data-pause]\"));\n }}\n onBlur={(event) => {\n onBlur?.(event);\n if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);\n }}\n >\n <div aria-hidden=\"true\" />\n <div\n ref={frame}\n id={`${uid}-slides`}\n aria-live={rotating && !held ? \"off\" : \"polite\"}\n aria-atomic=\"false\"\n className=\"announcement-viewport height-follow\"\n >\n <div ref={content}>\n <SwapText swapKey={announcement.id} dir={direction} className=\"block\">\n <div\n role={total > 1 ? \"group\" : undefined}\n aria-roledescription={total > 1 ? \"slide\" : undefined}\n aria-label={total > 1 ? `${current + 1} of ${total}` : undefined}\n className=\"announcement-face\"\n >\n <p className=\"announcement-message\">\n <span>\n {tone === \"warning\" ? (\n <svg\n data-slot=\"announcement-tone-icon\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"announcement-tone-icon\"\n >\n <path d=\"M8 2.5 14.5 13.5h-13ZM8 6.5v3M8 11.5h.01\" />\n </svg>\n ) : null}\n {announcement.message}\n </span>\n {countdown ? <Countdown to={countdown.to} label={countdown.label} onEnd={() => onCountdownEnd?.(announcement)} /> : null}\n {cta}\n </p>\n </div>\n </SwapText>\n </div>\n </div>\n <div className=\"announcement-controls\">\n {navigable ? (\n <>\n <button type=\"button\" aria-label=\"Previous announcement\" aria-controls={`${uid}-slides`} className=\"announcement-icon\" onClick={() => go(-1)}>\n <Glyph path=\"M4 10l4-4 4 4\" />\n </button>\n {rotating ? (\n <button\n type=\"button\"\n aria-label={userPaused ? \"Resume announcements\" : \"Pause announcements\"}\n aria-pressed={userPaused}\n data-pause=\"\"\n className=\"announcement-icon\"\n onClick={() => setUserPaused(!userPaused)}\n >\n <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.75} aria-hidden=\"true\" className=\"announcement-ring\">\n <circle cx=\"10\" cy=\"10\" r=\"8\" pathLength={1} className=\"announcement-ring-track\" />\n <circle\n key={current}\n cx=\"10\"\n cy=\"10\"\n r=\"8\"\n pathLength={1}\n strokeLinecap=\"round\"\n className=\"announcement-ring-fill\"\n style={{ animationDuration: `${interval}ms`, animationPlayState: held || hidden ? \"paused\" : \"running\" }}\n />\n </svg>\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\" className=\"announcement-ring-icon\">\n {userPaused ? <path d=\"M6 4.5v7l5.5-3.5Z\" /> : <path d=\"M5.5 4.5h1.8v7H5.5ZM8.7 4.5h1.8v7H8.7Z\" />}\n </svg>\n </button>\n ) : null}\n <button type=\"button\" aria-label=\"Next announcement\" aria-controls={`${uid}-slides`} className=\"announcement-icon\" onClick={() => go(1)}>\n <Glyph path=\"M4 6l4 4 4-4\" />\n </button>\n </>\n ) : null}\n {dismissible ? (\n <button type=\"button\" aria-label=\"Dismiss\" className=\"announcement-icon\" onClick={dismiss}>\n <Glyph path=\"M4 4l8 8M12 4l-8 8\" />\n </button>\n ) : null}\n </div>\n </section>\n </div>\n );\n});\n"],"mappings":"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"}
|
package/dist/avatar/avatar.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
18
18
|
*/
|
|
19
19
|
export declare const avatarVariants: (props?: ({
|
|
20
20
|
size?: "sm" | "md" | "lg" | "xs" | "xl" | null | undefined;
|
|
21
|
+
tone?: "neutral" | "primary" | "brand" | null | undefined;
|
|
21
22
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
22
23
|
/**
|
|
23
24
|
* A user avatar.
|
|
@@ -50,6 +51,12 @@ export interface AvatarProps extends Omit<ComponentPropsWithoutRef<"span">, "chi
|
|
|
50
51
|
* initials.
|
|
51
52
|
*/
|
|
52
53
|
fallback?: ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* The fill behind the fallback: `neutral` (the default, a muted grey),
|
|
56
|
+
* `primary` (the accent fill) or `brand` (the periwinkle-to-teal gradient of a
|
|
57
|
+
* highlighted Card). A loaded photo covers it. Mirrored to `data-tone`.
|
|
58
|
+
*/
|
|
59
|
+
tone?: "neutral" | "primary" | "brand";
|
|
53
60
|
}
|
|
54
61
|
export declare const Avatar: import("react").ForwardRefExoticComponent<AvatarProps & import("react").RefAttributes<HTMLImageElement>>;
|
|
55
62
|
//#endregion
|