@seekalfred/primitives 0.7.1 → 0.7.3
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 +195 -4
- package/dist/alert/alert.d.ts +49 -4
- package/dist/alert/alert.js +66 -38
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +17 -4
- package/dist/alert-dialog/alert-dialog.js +26 -2
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/badge/badge.d.ts +1 -1
- package/dist/button/button.d.ts +2 -2
- package/dist/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- package/dist/card/card.js.map +1 -1
- package/dist/carousel/carousel.d.ts +21 -0
- package/dist/carousel/carousel.js +34 -8
- package/dist/carousel/carousel.js.map +1 -1
- package/dist/copy-button/copy-button.d.ts +18 -2
- package/dist/copy-button/copy-button.js +29 -8
- package/dist/copy-button/copy-button.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +22 -0
- package/dist/donut-chart/donut-chart.js +35 -18
- package/dist/donut-chart/donut-chart.js.map +1 -1
- package/dist/donut-chart/index.d.ts +2 -2
- package/dist/empty-state/empty-state.d.ts +21 -0
- package/dist/empty-state/empty-state.js +16 -4
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/filter-bar/filter-bar.d.ts +4 -1
- package/dist/filter-bar/filter-bar.js +53 -9
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +7 -7
- package/dist/input/input.js +4 -4
- package/dist/input/input.js.map +1 -1
- package/dist/lib/field-shell.js +10 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +22 -1
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts +42 -2
- package/dist/line-chart/line-chart.js +143 -14
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/money-input/money-input.d.ts +22 -1
- package/dist/money-input/money-input.js +13 -7
- package/dist/money-input/money-input.js.map +1 -1
- package/dist/morph-select/morph-select.d.ts +19 -1
- package/dist/morph-select/morph-select.js +21 -2
- package/dist/morph-select/morph-select.js.map +1 -1
- package/dist/multi-select/index.d.ts +2 -2
- package/dist/multi-select/index.js +2 -2
- package/dist/multi-select/multi-select.d.ts +43 -2
- package/dist/multi-select/multi-select.js +93 -36
- package/dist/multi-select/multi-select.js.map +1 -1
- package/dist/password-field/password-field.d.ts +16 -0
- package/dist/password-field/password-field.js +37 -8
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/popover/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +39 -2
- package/dist/popover/popover.js +29 -2
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +16 -2
- package/dist/progress/progress.js +15 -8
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +109 -9
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.js +4 -3
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/search-field/search-field.d.ts +22 -1
- package/dist/search-field/search-field.js +22 -7
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +1 -1
- package/dist/select/select.js +8 -6
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +21 -9
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.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 +34 -1
- package/dist/stat-card/stat-card.js +51 -21
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table/table.d.ts +1 -1
- package/dist/time-picker/time-picker.d.ts +36 -1
- package/dist/time-picker/time-picker.js +33 -5
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/dist/usage-meter/index.d.ts +2 -2
- package/dist/usage-meter/usage-meter.d.ts +30 -0
- package/dist/usage-meter/usage-meter.js +56 -38
- package/dist/usage-meter/usage-meter.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -132,6 +132,34 @@ makes `description` **required** — React Aria associates the description
|
|
|
132
132
|
for that role, so a confirmation built from a plain `Dialog` loses the
|
|
133
133
|
sentence explaining what is about to happen.
|
|
134
134
|
|
|
135
|
+
### Rich content: `body`
|
|
136
|
+
|
|
137
|
+
`body` puts rich content between the description and the actions: a plan
|
|
138
|
+
comparison, a before/after diff, a quoted reply, a field to fill in or a choice
|
|
139
|
+
to make (a `RadioGroup`). It shares the description's edge and is in the
|
|
140
|
+
dialog's tab order before the actions. `false`, `true` and `""` (as from
|
|
141
|
+
`cond && <X />`) render no body.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<AlertDialog
|
|
145
|
+
isOpen={open}
|
|
146
|
+
onOpenChange={setOpen}
|
|
147
|
+
title="Cancel subscription?"
|
|
148
|
+
description="You keep Pro until the end of the period, then move to Free."
|
|
149
|
+
body={<PlanSummary plan={plan} />}
|
|
150
|
+
>
|
|
151
|
+
<AlertDialogCancel>Keep plan</AlertDialogCancel>
|
|
152
|
+
<AlertDialogAction onPress={cancel}>Cancel subscription</AlertDialogAction>
|
|
153
|
+
</AlertDialog>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The body is not part of the accessible description: a screen reader announces
|
|
157
|
+
only the `description` sentence with the title, and reads the body in place or
|
|
158
|
+
as focus reaches its controls, each with its own label. So a field or a choice
|
|
159
|
+
belongs in `body`, but the `description` must say everything needed to answer,
|
|
160
|
+
including that something below is required ("Pick what happens to your
|
|
161
|
+
recordings, then confirm.").
|
|
162
|
+
|
|
135
163
|
### Migrating from the legacy shadcn AlertDialog
|
|
136
164
|
|
|
137
165
|
| legacy | Alfred | |
|
|
@@ -528,6 +556,28 @@ Padding is **block-only on the shell and inline-only on the sections**, which
|
|
|
528
556
|
is what lets a divider or a full-bleed image run edge to edge. A single `p-6`
|
|
529
557
|
on the shell would make that impossible.
|
|
530
558
|
|
|
559
|
+
### Featured and flush cards
|
|
560
|
+
|
|
561
|
+
```tsx
|
|
562
|
+
<Card highlighted>…</Card> {/* the one featured card in a view */}
|
|
563
|
+
<Card flush>…</Card> {/* a table, list or chart that meets the top and bottom */}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
`highlighted` replaces the hairline with a brand gradient edge (secondary to
|
|
567
|
+
tertiary, so it follows the theme) without changing the card's box. To repaint
|
|
568
|
+
the edge, set the `--card-highlight` custom property on the card element itself
|
|
569
|
+
(`className` or `style` on `Card`): the utility declares it on the card, so a
|
|
570
|
+
value set on an ancestor does not reach it. Use it for one card per view and
|
|
571
|
+
say in the content why it is featured: the edge is decoration. `flush` drops
|
|
572
|
+
the shell's block padding and the gap between parts and clips the content to
|
|
573
|
+
the rounded border, keeping the outline, radius, surface and shadow; a
|
|
574
|
+
`className` padding still wins. The header and footer keep a 24px block
|
|
575
|
+
inset, so their text clears the corner curve; the content stays block-flush.
|
|
576
|
+
Every section keeps its inline padding, so text never sits in the corner, and
|
|
577
|
+
a section's fill (`<CardContent className="bg-muted">`) still reaches the side
|
|
578
|
+
edges because background paints the padding. Both default to `false`, and the two
|
|
579
|
+
compose: a flush featured card keeps its gradient edge.
|
|
580
|
+
|
|
531
581
|
### Migrating from the legacy shadcn Card
|
|
532
582
|
|
|
533
583
|
| legacy | Alfred | |
|
|
@@ -682,6 +732,16 @@ What a list shows when it has nothing to show: one muted line, centred in a
|
|
|
682
732
|
bordered box the width of the list. `action` is an optional next step under the
|
|
683
733
|
text and is omitted from the DOM when absent.
|
|
684
734
|
|
|
735
|
+
`image` is an optional illustration above the icon and the title (an `<img>`,
|
|
736
|
+
an inline SVG or any element). It is centred, shrinks to fit a narrow box and
|
|
737
|
+
keeps its aspect ratio; its size is the artwork's. The slot reserves no space,
|
|
738
|
+
so set the artwork's intrinsic `width` and `height`, or the layout jumps when
|
|
739
|
+
it loads. The slot does not hide it from assistive technology, so name it:
|
|
740
|
+
give an `<img>` an `alt` (`alt=""` when the title already says it all), and an
|
|
741
|
+
inline `<svg>` `role="img"` with an `aria-label`, or `aria-hidden="true"` when
|
|
742
|
+
it is decorative. Omitted from the DOM when absent; `false` (from
|
|
743
|
+
`cond && <img />`) counts as absent.
|
|
744
|
+
|
|
685
745
|
### Motion (EmptyState)
|
|
686
746
|
|
|
687
747
|
New optional `icon` settles in once when the state first appears (small and transparent to rest over 480ms); a different icon component later swaps in place. A string message swaps as a whole when it changes and the message column follows its height on the smooth spring. The message now sits in an `empty-state-copy` frame.
|
|
@@ -1044,6 +1104,20 @@ default). Native input props apply to the input, and `className` goes to the
|
|
|
1044
1104
|
input; `disabled` also disables the toggle, and `aria-invalid` gives the box its
|
|
1045
1105
|
invalid border. The box shares the field look of `Input` and `Textarea`.
|
|
1046
1106
|
|
|
1107
|
+
`error` is `Input`'s prop: error text under the field, announced as an alert,
|
|
1108
|
+
that sets `aria-invalid` and is named in `aria-describedby`. `isRequired` makes
|
|
1109
|
+
the input `required` and shows a required marker (an asterisk, hidden from
|
|
1110
|
+
assistive technology, which hears the input's own `required`) after the label.
|
|
1111
|
+
|
|
1112
|
+
```tsx
|
|
1113
|
+
<PasswordField
|
|
1114
|
+
label="API secret"
|
|
1115
|
+
isRequired
|
|
1116
|
+
error={errors.secret?.message}
|
|
1117
|
+
{...register("secret")}
|
|
1118
|
+
/>
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1047
1121
|
**The toggle's name changes with its state and it is also `aria-pressed`**, so a
|
|
1048
1122
|
screen reader may announce both ("Hide password, pressed"); this is not tested
|
|
1049
1123
|
with a screen reader. Reduced motion drops every transition and the press.
|
|
@@ -1135,9 +1209,18 @@ and no number. `formatOptions` reaches it too, so a currency or "3 of 7" needs n
|
|
|
1135
1209
|
second prop. `trackClassName` and `fillClassName` are escape hatches for a colour
|
|
1136
1210
|
no tone covers.
|
|
1137
1211
|
|
|
1212
|
+
`isIndeterminate` draws a segment 40% of the track wide that sweeps across it
|
|
1213
|
+
from the start edge on a loop (right to left in RTL), and announces no value.
|
|
1214
|
+
With `labelHidden` and no `showValue` nothing is printed above the track, so the
|
|
1215
|
+
6px header gap collapses and the bar sits at the top of its box.
|
|
1216
|
+
|
|
1217
|
+
`showCompletion={false}` keeps a bar from saying "done" at 100%: the fill keeps
|
|
1218
|
+
its tone and no check lands. Use it for a timer-driven estimate that can reach
|
|
1219
|
+
100% before the work finishes.
|
|
1220
|
+
|
|
1138
1221
|
### Motion (Progress)
|
|
1139
1222
|
|
|
1140
|
-
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, and a bar that is full at mount shows its check with no motion. A value that is not a percentage (a price) is shown as it is. A changed string label swaps as a whole.
|
|
1223
|
+
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.
|
|
1141
1224
|
|
|
1142
1225
|
## RadioGroup
|
|
1143
1226
|
|
|
@@ -1148,6 +1231,32 @@ The fill follows the value on the smooth spring (it was a 200ms ease) and the pr
|
|
|
1148
1231
|
</RadioGroup>
|
|
1149
1232
|
```
|
|
1150
1233
|
|
|
1234
|
+
### Card variant
|
|
1235
|
+
|
|
1236
|
+
`variant="card"` draws each choice as a selectable card: the whole card is the
|
|
1237
|
+
target, the title is `children`, and each `Radio` can take a `description` and
|
|
1238
|
+
a decorative `icon`. The group becomes a grid, so the caller sets the columns.
|
|
1239
|
+
|
|
1240
|
+
```tsx
|
|
1241
|
+
<RadioGroup variant="card" value={mode} onChange={setMode} aria-label="How to run" className="sm:grid-cols-2">
|
|
1242
|
+
<Radio value="draft" icon={<DraftIcon />} description="Save as a draft and review it first.">
|
|
1243
|
+
Draft only
|
|
1244
|
+
</Radio>
|
|
1245
|
+
<Radio value="everywhere" description="Apply to every category now.">
|
|
1246
|
+
Run everywhere
|
|
1247
|
+
</Radio>
|
|
1248
|
+
</RadioGroup>
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
- The title alone is the option's name; the description is wired as its
|
|
1252
|
+
description (`aria-describedby`), so a screen reader does not run the two
|
|
1253
|
+
together. The icon is `aria-hidden`.
|
|
1254
|
+
- Selected shows twice: a `primary-strong` edge (StatCard's selected pair) and
|
|
1255
|
+
the same dot as the default radio, so it never rests on colour alone.
|
|
1256
|
+
- Keyboard is unchanged: one tab stop, arrow keys move and select. The focus
|
|
1257
|
+
ring is drawn on the card, not the dot.
|
|
1258
|
+
- `description` and `icon` are ignored by the default variant.
|
|
1259
|
+
|
|
1151
1260
|
### Migrating from the legacy RadioGroup
|
|
1152
1261
|
|
|
1153
1262
|
| legacy | Alfred | |
|
|
@@ -1192,6 +1301,8 @@ New opt-in `fade` (px, default `0` = off, so an area is unchanged) fades the con
|
|
|
1192
1301
|
|
|
1193
1302
|
```tsx
|
|
1194
1303
|
<SearchField label="Search contacts" value={query} onValueChange={setQuery} />
|
|
1304
|
+
<SearchField label="Search tickets" labelHidden size="sm" placeholder="Search tickets"
|
|
1305
|
+
value={query} onValueChange={setQuery} />
|
|
1195
1306
|
```
|
|
1196
1307
|
|
|
1197
1308
|
A search input with a clear button. It is controlled: `value` and
|
|
@@ -1205,7 +1316,13 @@ animation, and while the button leaves it cannot be pressed or focused. The
|
|
|
1205
1316
|
magnifier is muted at rest and takes full contrast while the field has focus or
|
|
1206
1317
|
a value.
|
|
1207
1318
|
|
|
1208
|
-
`label` is required and visible
|
|
1319
|
+
`label` is required and visible unless `labelHidden` is set, which keeps it as
|
|
1320
|
+
the input's accessible name (`aria-label`) without printing it — for a toolbar
|
|
1321
|
+
search whose purpose the surrounding UI already shows. That holds inside a
|
|
1322
|
+
React Aria `TextField` too, including one with no `Label` child. `size` (`sm` · `md` ·
|
|
1323
|
+
`lg`, default `md`) takes the same `--density-control-*` height and padding as
|
|
1324
|
+
Input, Select and Button, and `sm` takes Input's 14px text (16px on a touch
|
|
1325
|
+
screen). `clearLabel` renames the button (default "Clear
|
|
1209
1326
|
search"). Native input props apply to the input, `className` goes to the input,
|
|
1210
1327
|
and `disabled` also disables the clear button. The browser's own clear button
|
|
1211
1328
|
and search decoration are hidden. The box shares the field look of `Input`.
|
|
@@ -1270,17 +1387,26 @@ only — WCAG 1.4.1. The message is wired into `aria-describedby` for you.
|
|
|
1270
1387
|
```tsx
|
|
1271
1388
|
<Separator />
|
|
1272
1389
|
<Separator orientation="vertical" />
|
|
1390
|
+
<Separator orientation="vertical" decorative />
|
|
1273
1391
|
```
|
|
1274
1392
|
|
|
1275
1393
|
React Aria renders an `<hr>` horizontally and a `<div role="separator">`
|
|
1276
|
-
vertically.
|
|
1394
|
+
vertically. The line is painted with `--color-separator`, the hairline between
|
|
1395
|
+
rows and groups inside a surface, not `--color-border`, which is the surface's
|
|
1396
|
+
own outline.
|
|
1397
|
+
|
|
1398
|
+
`decorative` draws the same line as a plain `aria-hidden` div, for a rule that
|
|
1399
|
+
only spaces inline items (a metadata row) where announcing "separator" between
|
|
1400
|
+
each pair is noise. Leave it off for a line between two groups of content.
|
|
1401
|
+
Other props (`id`, `data-*`, handlers) reach the div in this mode too; a name
|
|
1402
|
+
(`aria-label`, `aria-labelledby`) is dropped.
|
|
1277
1403
|
|
|
1278
1404
|
### Migrating from the legacy Separator
|
|
1279
1405
|
|
|
1280
1406
|
| legacy | Alfred | |
|
|
1281
1407
|
| ------------- | --------------- | ------------------------------------------------------------ |
|
|
1282
1408
|
| `orientation` | `orientation` | unchanged |
|
|
1283
|
-
| `decorative` |
|
|
1409
|
+
| `decorative` | `decorative` | default flipped to `false`; see below |
|
|
1284
1410
|
|
|
1285
1411
|
**This changes the semantics of all six existing uses.** The legacy defaulted
|
|
1286
1412
|
to `decorative: true`, rendering `role="none"` — so every divider in the app
|
|
@@ -1635,6 +1761,28 @@ builds a `Time` at runtime, so the peer dependency is a real import here.
|
|
|
1635
1761
|
(1, 5, 10, 15 or 30; a time between steps is added to the list so it is never lost),
|
|
1636
1762
|
and `hourCycle` chooses 12 or 24 hour times.
|
|
1637
1763
|
|
|
1764
|
+
**In a form**: `id` goes on the trigger (so an outside `<label htmlFor>` or a form
|
|
1765
|
+
library reaches it), `name` submits the time as `"HH:MM"` in 24-hour time, as
|
|
1766
|
+
`<input type="time">` does, and `onBlur` fires when focus leaves the trigger.
|
|
1767
|
+
`isInvalid` paints the invalid border and `errorMessage` shows under the control
|
|
1768
|
+
while it is invalid, named in `aria-describedby`, as on `Select`. With
|
|
1769
|
+
react-hook-form, wire them from a `Controller`. Pass `field.value ?? null`, not
|
|
1770
|
+
`field.value`: `undefined` makes the picker uncontrolled, so a form reset to
|
|
1771
|
+
`undefined` would leave the last time on screen, while `null` clears it. With no
|
|
1772
|
+
time chosen, `name` submits an empty string.
|
|
1773
|
+
|
|
1774
|
+
```tsx
|
|
1775
|
+
<Controller
|
|
1776
|
+
name="syncTime"
|
|
1777
|
+
control={control}
|
|
1778
|
+
render={({ field, fieldState }) => (
|
|
1779
|
+
<TimePicker label="Sync time" id={field.name} name={field.name}
|
|
1780
|
+
value={field.value ?? null} onChange={field.onChange} onBlur={field.onBlur}
|
|
1781
|
+
isInvalid={!!fieldState.error} errorMessage={fieldState.error?.message} />
|
|
1782
|
+
)}
|
|
1783
|
+
/>
|
|
1784
|
+
```
|
|
1785
|
+
|
|
1638
1786
|
**Motion** (plain CSS and a little plain React, no animation library): the shown
|
|
1639
1787
|
time rolls like a clock face (a later time rises in from below, an earlier one
|
|
1640
1788
|
drops in from above, and the old one lifts away); the chevron turns half a turn on
|
|
@@ -1707,3 +1855,46 @@ inherits from the document. Set both if you support RTL.
|
|
|
1707
1855
|
|
|
1708
1856
|
A tooltip rises 3px from its trigger and settles from .97 over 160ms (offset follows the side it opens on), and leaves in 120ms. While any tooltip is open, and for 300ms after the last one closes, the next one only fades in (no travel). The open delay stays 0. Reduced motion fades only. **DOM:** an empty hidden `span` inside the tooltip marks the warm case. Not ported: the crossfade and size spring when a string tooltip's text changes while open.
|
|
1709
1857
|
|
|
1858
|
+
|
|
1859
|
+
## UsageMeter
|
|
1860
|
+
|
|
1861
|
+
```tsx
|
|
1862
|
+
<UsageMeter label="Workspace storage" segments={segments} limit={10} unit="GB" />
|
|
1863
|
+
```
|
|
1864
|
+
|
|
1865
|
+
The meter works its state out from the numbers: almost full from `warnAt` (a
|
|
1866
|
+
share of the limit, 0.9 by default), over once the total passes the limit.
|
|
1867
|
+
|
|
1868
|
+
### A status from the backend
|
|
1869
|
+
|
|
1870
|
+
When the product decides the state, pass `status` and the meter shows that
|
|
1871
|
+
instead of its own reading (badge, `data-status`, the bar's name and the
|
|
1872
|
+
announcement). The bar and legend agree with it:
|
|
1873
|
+
|
|
1874
|
+
- `full` and `exhausted` under the limit mean the allowance is used up, so
|
|
1875
|
+
there is no free share: the bar spans what is used and the free item reads 0.
|
|
1876
|
+
The headline still states the plan size ("7.7 GB of 10 GB used").
|
|
1877
|
+
- Past the limit, `ok` and `warn` give way to the meter's own over state, since
|
|
1878
|
+
the hatch already shows the overage. `full` and `exhausted` keep their words.
|
|
1879
|
+
|
|
1880
|
+
| `status` | meaning | badge | `data-status` |
|
|
1881
|
+
| ----------- | --------------------------- | --------------- | ------------- |
|
|
1882
|
+
| `ok` | under the limit | "N free" | `ok` |
|
|
1883
|
+
| `warn` | almost full | "Almost full" | `near` |
|
|
1884
|
+
| `full` | allowance used up | "Limit reached" | `full` |
|
|
1885
|
+
| `exhausted` | nothing left to draw on | "None left" | `exhausted` |
|
|
1886
|
+
|
|
1887
|
+
`statusLabel` is the product's own words for the status ("Credits paused"). It
|
|
1888
|
+
replaces the badge's words in every mode (including "Unlimited"), the status in
|
|
1889
|
+
the bar's name and the announcement; the tone and icon still follow the status.
|
|
1890
|
+
|
|
1891
|
+
### Unlimited
|
|
1892
|
+
|
|
1893
|
+
`unlimited` is a plan with no limit. The badge reads "Unlimited" (unless
|
|
1894
|
+
`statusLabel` is set), the bar shows
|
|
1895
|
+
each category's share of what is used, and there is no free share, limit marker
|
|
1896
|
+
or overflow hatch. `limit`, `warnAt` and `status` are ignored.
|
|
1897
|
+
|
|
1898
|
+
```tsx
|
|
1899
|
+
<UsageMeter label="Keyword slots" segments={segments} limit={0} unlimited />
|
|
1900
|
+
```
|
package/dist/alert/alert.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
|
-
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { AriaRole, ComponentPropsWithoutRef, ReactNode, SVGProps } from "react";
|
|
3
3
|
//#region src/alert/alert.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Alert, ported from the legacy shadcn component.
|
|
@@ -24,14 +24,42 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
24
24
|
* problems rather than porting them.
|
|
25
25
|
*/
|
|
26
26
|
export declare const alertVariants: (props?: ({
|
|
27
|
-
variant?: "success" | "warning" | "
|
|
27
|
+
variant?: "destructive" | "success" | "warning" | "default" | "info" | null | undefined;
|
|
28
28
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
29
|
+
/**
|
|
30
|
+
* The icon's colour: the state's own hue on every variant. Inside an Alert the
|
|
31
|
+
* destructive red is also the whole alert's text colour, so it changes nothing
|
|
32
|
+
* there; outside one it is what colours the icon.
|
|
33
|
+
*
|
|
34
|
+
* Exported with the status icons so a notice that is not an `Alert` (a row, a
|
|
35
|
+
* banner of the app's own) can show a tone the same way the alert does.
|
|
36
|
+
*/
|
|
37
|
+
export declare const alertIconVariants: (props?: ({
|
|
38
|
+
variant?: "destructive" | "success" | "warning" | "default" | "info" | null | undefined;
|
|
39
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
40
|
+
/**
|
|
41
|
+
* The built-in glyph of each variant, drawn with currentColor so the icon
|
|
42
|
+
* wrapper's colour paints it. Each is its own component so a variant change
|
|
43
|
+
* crossfades the icon (see iconKey).
|
|
44
|
+
*
|
|
45
|
+
* Exported as AlertInfoIcon, AlertSuccessIcon, AlertWarningIcon and
|
|
46
|
+
* AlertDestructiveIcon. They are decorative (`aria-hidden`) and `--icon-sm`
|
|
47
|
+
* by default; extra props, such as a `className` with a size, land on the svg.
|
|
48
|
+
* Given an `aria-label` or `aria-labelledby` (with `role="img"`), an icon is
|
|
49
|
+
* no longer hidden, and an explicit `aria-hidden` always wins.
|
|
50
|
+
*/
|
|
51
|
+
type StatusIconProps = Omit<SVGProps<SVGSVGElement>, "children">;
|
|
52
|
+
declare function InfoGlyph(props: StatusIconProps): import("react").JSX.Element;
|
|
53
|
+
declare function SuccessGlyph(props: StatusIconProps): import("react").JSX.Element;
|
|
54
|
+
declare function WarningGlyph(props: StatusIconProps): import("react").JSX.Element;
|
|
55
|
+
declare function DestructiveGlyph(props: StatusIconProps): import("react").JSX.Element;
|
|
29
56
|
/**
|
|
30
57
|
* An inline status message.
|
|
31
58
|
*
|
|
32
59
|
* Announced by assistive technology the moment it renders, via `role="alert"`.
|
|
33
|
-
* That makes it the wrong
|
|
34
|
-
* merely styled as a notice --
|
|
60
|
+
* That makes it the wrong default for content that is present on load and
|
|
61
|
+
* merely styled as a notice -- pass `role="note"` for those, or
|
|
62
|
+
* `role="status"` for an outcome that should be announced politely.
|
|
35
63
|
*
|
|
36
64
|
* @example
|
|
37
65
|
* <Alert variant="destructive" icon={<WarningIcon aria-hidden />} title="Save failed">
|
|
@@ -80,7 +108,24 @@ export interface AlertProps extends Omit<ComponentPropsWithoutRef<"div">, "title
|
|
|
80
108
|
onDismiss?: () => void;
|
|
81
109
|
/** The Dismiss button's accessible name. @default "Dismiss" */
|
|
82
110
|
dismissLabel?: string;
|
|
111
|
+
/**
|
|
112
|
+
* How assistive technology treats the message.
|
|
113
|
+
*
|
|
114
|
+
* - `alert`: announced assertively the moment it renders. For an error or a
|
|
115
|
+
* warning that appears after the user did something.
|
|
116
|
+
* - `status`: a live region announced politely, after what the user is
|
|
117
|
+
* hearing. For an outcome reported after an action ("Saved").
|
|
118
|
+
* - `note`: not live. For a notice that is simply there when the page loads;
|
|
119
|
+
* an alert there would interrupt the page's own announcement.
|
|
120
|
+
*
|
|
121
|
+
* Typed as any ARIA role, as the inherited `role` was, so existing callers
|
|
122
|
+
* still compile; those three are the ones an inline notice should use.
|
|
123
|
+
*
|
|
124
|
+
* @default "alert"
|
|
125
|
+
*/
|
|
126
|
+
role?: "alert" | "status" | "note" | AriaRole;
|
|
83
127
|
}
|
|
84
128
|
export declare const Alert: import("react").ForwardRefExoticComponent<AlertProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
85
129
|
//#endregion
|
|
130
|
+
export { DestructiveGlyph as AlertDestructiveIcon, InfoGlyph as AlertInfoIcon, type StatusIconProps as AlertStatusIconProps, SuccessGlyph as AlertSuccessIcon, WarningGlyph as AlertWarningIcon };
|
|
86
131
|
//# sourceMappingURL=alert.d.ts.map
|
package/dist/alert/alert.js
CHANGED
|
@@ -9,6 +9,17 @@ import { Button } from "react-aria-components";
|
|
|
9
9
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/alert/alert.tsx
|
|
11
11
|
/**
|
|
12
|
+
* `info` is `default` said by name: a call site that means "information" should
|
|
13
|
+
* not depend on `default` happening to look like it. Every per-variant map below
|
|
14
|
+
* derives `info` here, from its `default`, so the two cannot drift.
|
|
15
|
+
*/
|
|
16
|
+
function withInfo(map) {
|
|
17
|
+
return {
|
|
18
|
+
...map,
|
|
19
|
+
info: map.default
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
12
23
|
* Alert, ported from the legacy shadcn component.
|
|
13
24
|
*
|
|
14
25
|
* There is no React Aria base: RAC ships no static inline alert (its Toast
|
|
@@ -35,71 +46,88 @@ const alertVariants = cva([
|
|
|
35
46
|
"rounded-panel border-(length:--border-width) p-(--density-control-pad-md)",
|
|
36
47
|
"font-sans text-sm leading-(--leading-normal)"
|
|
37
48
|
], {
|
|
38
|
-
variants: { variant: {
|
|
49
|
+
variants: { variant: withInfo({
|
|
39
50
|
default: "bg-surface text-foreground border-border",
|
|
40
51
|
success: "bg-surface text-foreground border-border",
|
|
41
52
|
warning: "bg-surface text-foreground border-border",
|
|
42
53
|
destructive: "bg-surface text-destructive-strong border-destructive-strong"
|
|
43
|
-
} },
|
|
54
|
+
}) },
|
|
44
55
|
defaultVariants: { variant: "default" }
|
|
45
56
|
});
|
|
46
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* The icon's colour: the state's own hue on every variant. Inside an Alert the
|
|
59
|
+
* destructive red is also the whole alert's text colour, so it changes nothing
|
|
60
|
+
* there; outside one it is what colours the icon.
|
|
61
|
+
*
|
|
62
|
+
* Exported with the status icons so a notice that is not an `Alert` (a row, a
|
|
63
|
+
* banner of the app's own) can show a tone the same way the alert does.
|
|
64
|
+
*/
|
|
47
65
|
const alertIconVariants = cva("flex h-lh shrink-0 items-center [&_svg]:block [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-(--icon-sm)", {
|
|
48
|
-
variants: { variant: {
|
|
66
|
+
variants: { variant: withInfo({
|
|
49
67
|
default: "text-info-strong",
|
|
50
68
|
success: "text-success-strong",
|
|
51
69
|
warning: "text-warning-strong",
|
|
52
|
-
destructive: ""
|
|
53
|
-
} },
|
|
70
|
+
destructive: "text-destructive-strong"
|
|
71
|
+
}) },
|
|
54
72
|
defaultVariants: { variant: "default" }
|
|
55
73
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* wrapper's colour paints it. Each is its own component so a variant change
|
|
59
|
-
* crossfades the icon (see iconKey).
|
|
60
|
-
*/
|
|
61
|
-
function StatusGlyph({ children }) {
|
|
74
|
+
function StatusGlyph({ children, className, ...props }) {
|
|
75
|
+
const labelled = props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0;
|
|
62
76
|
return /* @__PURE__ */ jsx("svg", {
|
|
77
|
+
"aria-hidden": labelled ? void 0 : "true",
|
|
78
|
+
...props,
|
|
79
|
+
className: cn("size-(--icon-sm)", className),
|
|
63
80
|
viewBox: "0 0 16 16",
|
|
64
81
|
fill: "none",
|
|
65
82
|
stroke: "currentColor",
|
|
66
83
|
strokeWidth: "1.5",
|
|
67
84
|
strokeLinecap: "round",
|
|
68
85
|
strokeLinejoin: "round",
|
|
69
|
-
"aria-hidden": "true",
|
|
70
86
|
children
|
|
71
87
|
});
|
|
72
88
|
}
|
|
73
|
-
function InfoGlyph() {
|
|
74
|
-
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
function InfoGlyph(props) {
|
|
90
|
+
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
91
|
+
...props,
|
|
92
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
93
|
+
cx: "8",
|
|
94
|
+
cy: "8",
|
|
95
|
+
r: "6.25"
|
|
96
|
+
}), /* @__PURE__ */ jsx("path", { d: "M8 7.25v3.5M8 5h.01" })]
|
|
97
|
+
});
|
|
79
98
|
}
|
|
80
|
-
function SuccessGlyph() {
|
|
81
|
-
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
function SuccessGlyph(props) {
|
|
100
|
+
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
101
|
+
...props,
|
|
102
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
103
|
+
cx: "8",
|
|
104
|
+
cy: "8",
|
|
105
|
+
r: "6.25"
|
|
106
|
+
}), /* @__PURE__ */ jsx("path", { d: "m5.25 8.25 1.9 1.9 3.6-3.9" })]
|
|
107
|
+
});
|
|
86
108
|
}
|
|
87
|
-
function WarningGlyph() {
|
|
88
|
-
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
109
|
+
function WarningGlyph(props) {
|
|
110
|
+
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
111
|
+
...props,
|
|
112
|
+
children: [/* @__PURE__ */ jsx("path", { d: "M6.9 2.6 1.9 11.4a1.2 1.2 0 0 0 1 1.8h10.2a1.2 1.2 0 0 0 1-1.8L9.1 2.6a1.2 1.2 0 0 0-2.2 0Z" }), /* @__PURE__ */ jsx("path", { d: "M8 6.4v2.6M8 11.2h.01" })]
|
|
113
|
+
});
|
|
89
114
|
}
|
|
90
|
-
function DestructiveGlyph() {
|
|
91
|
-
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
115
|
+
function DestructiveGlyph(props) {
|
|
116
|
+
return /* @__PURE__ */ jsxs(StatusGlyph, {
|
|
117
|
+
...props,
|
|
118
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
119
|
+
cx: "8",
|
|
120
|
+
cy: "8",
|
|
121
|
+
r: "6.25"
|
|
122
|
+
}), /* @__PURE__ */ jsx("path", { d: "m6 6 4 4M10 6l-4 4" })]
|
|
123
|
+
});
|
|
96
124
|
}
|
|
97
|
-
const statusGlyphs = {
|
|
125
|
+
const statusGlyphs = withInfo({
|
|
98
126
|
default: /* @__PURE__ */ jsx(InfoGlyph, {}),
|
|
99
127
|
success: /* @__PURE__ */ jsx(SuccessGlyph, {}),
|
|
100
128
|
warning: /* @__PURE__ */ jsx(WarningGlyph, {}),
|
|
101
129
|
destructive: /* @__PURE__ */ jsx(DestructiveGlyph, {})
|
|
102
|
-
};
|
|
130
|
+
});
|
|
103
131
|
/** A string title or description swaps as a whole when its text changes. */
|
|
104
132
|
function Swappable({ value, children }) {
|
|
105
133
|
if (typeof value !== "string" && typeof value !== "number") return /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
@@ -127,7 +155,7 @@ function DismissGlyph() {
|
|
|
127
155
|
children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" })
|
|
128
156
|
});
|
|
129
157
|
}
|
|
130
|
-
const Alert = forwardRef(function Alert({ className, variant, icon: iconProp, title, headingLevel = 3, children, open, onDismiss, dismissLabel = "Dismiss", ...props }, ref) {
|
|
158
|
+
const Alert = forwardRef(function Alert({ className, variant, icon: iconProp, title, headingLevel = 3, children, open, onDismiss, dismissLabel = "Dismiss", role = "alert", ...props }, ref) {
|
|
131
159
|
const Title = `h${headingLevel}`;
|
|
132
160
|
const icon = iconProp === void 0 ? statusGlyphs[variant ?? "default"] : iconProp;
|
|
133
161
|
const frame = useRef(null);
|
|
@@ -163,7 +191,7 @@ const Alert = forwardRef(function Alert({ className, variant, icon: iconProp, ti
|
|
|
163
191
|
const alert = /* @__PURE__ */ jsxs("div", {
|
|
164
192
|
ref,
|
|
165
193
|
...props,
|
|
166
|
-
role
|
|
194
|
+
role,
|
|
167
195
|
"data-slot": "alert",
|
|
168
196
|
className: cn(alertVariants({ variant }), className),
|
|
169
197
|
children: [
|
|
@@ -221,6 +249,6 @@ const Alert = forwardRef(function Alert({ className, variant, icon: iconProp, ti
|
|
|
221
249
|
});
|
|
222
250
|
});
|
|
223
251
|
//#endregion
|
|
224
|
-
export { Alert, alertVariants };
|
|
252
|
+
export { Alert, DestructiveGlyph as AlertDestructiveIcon, InfoGlyph as AlertInfoIcon, SuccessGlyph as AlertSuccessIcon, WarningGlyph as AlertWarningIcon, alertIconVariants, alertVariants };
|
|
225
253
|
|
|
226
254
|
//# sourceMappingURL=alert.js.map
|
package/dist/alert/alert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","names":["AriaButton"],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-panel border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-surface text-foreground border-border\",\n // The status variants paint only their icon; the card stays neutral\n // (dark title, grey description, light border), like the reference\n // Sync status alert. See alertIconVariants.\n success: \"bg-surface text-foreground border-border\",\n warning: \"bg-surface text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-surface text-destructive-strong border-destructive-strong\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/** The icon's colour: the state's own hue, on the status variants. Destructive keeps its whole-alert red. */\nconst alertIconVariants = cva(\n \"flex h-lh shrink-0 items-center [&_svg]:block [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n {\n variants: {\n variant: {\n default: \"text-info-strong\",\n success: \"text-success-strong\",\n warning: \"text-warning-strong\",\n destructive: \"\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * The built-in glyph of each variant, drawn with currentColor so the icon\n * wrapper's colour paints it. Each is its own component so a variant change\n * crossfades the icon (see iconKey).\n */\nfunction StatusGlyph({ children }: { children: ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {children}\n </svg>\n );\n}\nfunction InfoGlyph() {\n return (\n <StatusGlyph>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"M8 7.25v3.5M8 5h.01\" />\n </StatusGlyph>\n );\n}\nfunction SuccessGlyph() {\n return (\n <StatusGlyph>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m5.25 8.25 1.9 1.9 3.6-3.9\" />\n </StatusGlyph>\n );\n}\nfunction WarningGlyph() {\n return (\n <StatusGlyph>\n <path d=\"M6.9 2.6 1.9 11.4a1.2 1.2 0 0 0 1 1.8h10.2a1.2 1.2 0 0 0 1-1.8L9.1 2.6a1.2 1.2 0 0 0-2.2 0Z\" />\n <path d=\"M8 6.4v2.6M8 11.2h.01\" />\n </StatusGlyph>\n );\n}\nfunction DestructiveGlyph() {\n return (\n <StatusGlyph>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m6 6 4 4M10 6l-4 4\" />\n </StatusGlyph>\n );\n}\nconst statusGlyphs = {\n default: <InfoGlyph />,\n success: <SuccessGlyph />,\n warning: <WarningGlyph />,\n destructive: <DestructiveGlyph />,\n} as const;\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong component for content that is present on load and\n * merely styled as a notice -- use a plain region for those.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * Left out, the alert shows its variant's own icon (info, check, warning\n * triangle, cross), coloured for the state and centred on the first line of\n * text. Pass `null` for no icon, or your own glyph to replace it.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /**\n * Whether the alert is shown. Controlled: hiding it collapses its height and\n * fades it out, and showing it again grows it back. An alert that is open on\n * first render does not animate. Leave it out for an alert that is always there.\n */\n open?: boolean;\n /**\n * Adds a Dismiss button. Pressed, a controlled alert (`open` given) only calls\n * this and waits to be told `open={false}`; an uncontrolled alert (no `open`)\n * collapses first and then calls it.\n */\n onDismiss?: () => void;\n /** The Dismiss button's accessible name. @default \"Dismiss\" */\n dismissLabel?: string;\n}\n\n/** A string title or description swaps as a whole when its text changes. */\nfunction Swappable({ value, children }: { value: ReactNode; children: ReactNode }) {\n if (typeof value !== \"string\" && typeof value !== \"number\") return <>{children}</>;\n return (\n <SwapText swapKey={String(value)} className=\"block\">\n {children}\n </SwapText>\n );\n}\n\n/** The name an icon swaps under: a different icon component crossfades; the same one re-rendering stays still. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nfunction DismissGlyph() {\n return (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n className=\"size-4\"\n >\n <path d=\"M4 4l8 8M12 4l-8 8\" />\n </svg>\n );\n}\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n {\n className,\n variant,\n icon: iconProp,\n title,\n headingLevel = 3,\n children,\n open,\n onDismiss,\n dismissLabel = \"Dismiss\",\n ...props\n },\n ref,\n) {\n const Title = `h${headingLevel}` as const;\n const icon = iconProp === undefined ? statusGlyphs[variant ?? \"default\"] : iconProp;\n const frame = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const text = typeof children === \"string\" || typeof children === \"number\" ? String(children) : \"\";\n useHeightFollow(\n frame,\n content,\n `${typeof title === \"string\" ? title : \"\"}\\n${children === undefined ? \"\" : \"details\"}\\n${text}`,\n );\n\n // Presence. Without `open` or `onDismiss` the alert is simply there.\n const managed = open !== undefined || onDismiss !== undefined;\n const [dismissed, setDismissed] = useState(false);\n const wanted = open ?? !dismissed;\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 alert mounts collapsed and opens a frame later, so it grows rather than appears.\n if (wanted && rendered && !shown) {\n const frameId = requestAnimationFrame(() => setShown(true));\n return () => cancelAnimationFrame(frameId);\n }\n }, [wanted, rendered, shown]);\n const leaving = rendered && !wanted;\n const finish = useCallback(() => {\n setRendered(false);\n if (open === undefined) onDismiss?.();\n }, [onDismiss, open]);\n useExitBackstop(leaving, finish, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) finish();\n }, [leaving, finish]);\n if (managed && !rendered) return null;\n\n const alert = (\n <div\n ref={ref}\n {...props}\n role=\"alert\"\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n The icon is decorative or labelled by the consumer; this wrapper adds\n no name. It keeps the icon from shrinking and sizes a bare svg to the\n icon scale, unless the consumer sized it themselves.\n */}\n {icon === null || icon === false ? null : (\n <span data-slot=\"alert-icon\" className={alertIconVariants({ variant })}>\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </span>\n )}\n {/* The copy column follows its content's height, so longer or shorter text never snaps the alert. */}\n <div ref={frame} data-slot=\"alert-body\" className=\"height-follow min-w-0 flex-1\">\n <div ref={content}>\n {title === undefined ? null : (\n <Title\n data-slot=\"alert-title\"\n className=\"m-0 mb-1 text-sm leading-(--leading-normal) font-medium\"\n >\n <Swappable value={title}>{title}</Swappable>\n </Title>\n )}\n {children === undefined ? null : (\n <div\n data-slot=\"alert-description\"\n className={title === undefined ? undefined : \"text-xs leading-(--leading-normal) text-text-secondary\"}\n >\n <Swappable value={children}>{children}</Swappable>\n </div>\n )}\n </div>\n </div>\n {onDismiss ? (\n <AriaButton\n data-slot=\"alert-dismiss\"\n aria-label={typeof title === \"string\" ? `${dismissLabel}: ${title}` : dismissLabel}\n onPress={() => (open === undefined ? setDismissed(true) : onDismiss())}\n className=\"calendar-nav-motion -my-1 -me-1.5 grid size-7 shrink-0 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring)\"\n >\n <DismissGlyph />\n </AriaButton>\n ) : null}\n </div>\n );\n if (!managed) return alert;\n return (\n <div\n data-slot=\"alert-presence\"\n data-shown={shown || undefined}\n onTransitionEnd={(event) => {\n if (leaving && event.target === event.currentTarget && event.propertyName === \"grid-template-rows\")\n finish();\n }}\n className=\"alert-presence\"\n >\n <div>{alert}</div>\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,gBAAgB,IAC3B;CAOE;CACA;CAKA;AACF,GACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAIT,SAAS;EACT,SAAS;EAMT,aAAa;CACf,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;;AAGA,MAAM,oBAAoB,IACxB,4HACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EACT,SAAS;EACT,SAAS;EACT,aAAa;CACf,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;;;;;;AAOA,SAAS,YAAY,EAAE,YAAqC;CAC1D,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAc;EACd,gBAAe;EACf,eAAY;EAEX;CACE,CAAA;AAET;AACA,SAAS,YAAY;CACnB,OACE,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,UAAD;EAAQ,IAAG;EAAI,IAAG;EAAI,GAAE;CAAQ,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,sBAAuB,CAAA,CACpB,EAAA,CAAA;AAEjB;AACA,SAAS,eAAe;CACtB,OACE,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,UAAD;EAAQ,IAAG;EAAI,IAAG;EAAI,GAAE;CAAQ,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,6BAA8B,CAAA,CAC3B,EAAA,CAAA;AAEjB;AACA,SAAS,eAAe;CACtB,OACE,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,QAAD,EAAM,GAAE,8FAA+F,CAAA,GACvG,oBAAC,QAAD,EAAM,GAAE,wBAAyB,CAAA,CACtB,EAAA,CAAA;AAEjB;AACA,SAAS,mBAAmB;CAC1B,OACE,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,UAAD;EAAQ,IAAG;EAAI,IAAG;EAAI,GAAE;CAAQ,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA,CACnB,EAAA,CAAA;AAEjB;AACA,MAAM,eAAe;CACnB,SAAS,oBAAC,WAAD,CAAY,CAAA;CACrB,SAAS,oBAAC,cAAD,CAAe,CAAA;CACxB,SAAS,oBAAC,cAAD,CAAe,CAAA;CACxB,aAAa,oBAAC,kBAAD,CAAmB,CAAA;AAClC;;AAiEA,SAAS,UAAU,EAAE,OAAO,YAAuD;CACjF,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU,OAAO,oBAAA,YAAA,EAAG,SAAW,CAAA;CACjF,OACE,oBAAC,UAAD;EAAU,SAAS,OAAO,KAAK;EAAG,WAAU;EACzC;CACO,CAAA;AAEd;;AAGA,SAAS,QAAQ,MAAiB;CAChC,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,KAAK,eAAe,KAAK,QAAQ;AAC7E;AAEA,SAAS,eAAe;CACtB,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAc;EACd,eAAY;EACZ,WAAU;EAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA;CAC3B,CAAA;AAET;AAEA,MAAa,QAAQ,WAAuC,SAAS,MACnE,EACE,WACA,SACA,MAAM,UACN,OACA,eAAe,GACf,UACA,MACA,WACA,eAAe,WACf,GAAG,SAEL,KACA;CACA,MAAM,QAAQ,IAAI;CAClB,MAAM,OAAO,aAAa,KAAA,IAAY,aAAa,WAAW,aAAa;CAC3E,MAAM,QAAQ,OAAuB,IAAI;CACzC,MAAM,UAAU,OAAuB,IAAI;CAE3C,gBACE,OACA,SACA,GAAG,OAAO,UAAU,WAAW,QAAQ,GAAG,IAAI,aAAa,KAAA,IAAY,KAAK,UAAU,IAJ3E,OAAO,aAAa,YAAY,OAAO,aAAa,WAAW,OAAO,QAAQ,IAAI,IAK/F;CAGA,MAAM,UAAU,SAAS,KAAA,KAAa,cAAc,KAAA;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,SAAS,QAAQ,CAAC;CACxB,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAC/C,MAAM,CAAC,OAAO,YAAY,SAAS,MAAM;CACzC,IAAI,UAAU,CAAC,UAAU,YAAY,IAAI;CACzC,IAAI,CAAC,UAAU,OAAO,SAAS,KAAK;CACpC,gBAAgB;EAEd,IAAI,UAAU,YAAY,CAAC,OAAO;GAChC,MAAM,UAAU,4BAA4B,SAAS,IAAI,CAAC;GAC1D,aAAa,qBAAqB,OAAO;EAC3C;CACF,GAAG;EAAC;EAAQ;EAAU;CAAK,CAAC;CAC5B,MAAM,UAAU,YAAY,CAAC;CAC7B,MAAM,SAAS,kBAAkB;EAC/B,YAAY,KAAK;EACjB,IAAI,SAAS,KAAA,GAAW,YAAY;CACtC,GAAG,CAAC,WAAW,IAAI,CAAC;CACpB,gBAAgB,SAAS,QAAQ,GAAG;CACpC,gCAAgC;EAC9B,IAAI,WAAW,qBAAqB,GAAG,OAAO;CAChD,GAAG,CAAC,SAAS,MAAM,CAAC;CACpB,IAAI,WAAW,CAAC,UAAU,OAAO;CAEjC,MAAM,QACJ,qBAAC,OAAD;EACO;EACL,GAAI;EACJ,MAAK;EACL,aAAU;EACV,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EALrD,UAAA;GAYG,SAAS,QAAQ,SAAS,QAAQ,OACjC,oBAAC,QAAD;IAAM,aAAU;IAAa,WAAW,kBAAkB,EAAE,QAAQ,CAAC;IACnE,UAAA,oBAAC,UAAD;KAAU,SAAS,QAAQ,IAAI;KAAG,QAAO;KAAQ,WAAU;KACxD,UAAA;IACO,CAAA;GACN,CAAA;GAGR,oBAAC,OAAD;IAAK,KAAK;IAAO,aAAU;IAAa,WAAU;IAChD,UAAA,qBAAC,OAAD;KAAK,KAAK;KAAV,UAAA,CACG,UAAU,KAAA,IAAY,OACrB,oBAAC,OAAD;MACE,aAAU;MACV,WAAU;MAEV,UAAA,oBAAC,WAAD;OAAW,OAAO;OAAQ,UAAA;MAAiB,CAAA;KACtC,CAAA,GAER,aAAa,KAAA,IAAY,OACxB,oBAAC,OAAD;MACE,aAAU;MACV,WAAW,UAAU,KAAA,IAAY,KAAA,IAAY;MAE7C,UAAA,oBAAC,WAAD;OAAW,OAAO;OAAW;MAAoB,CAAA;KAC9C,CAAA,CAEJ;;GACF,CAAA;GACJ,YACC,oBAACA,QAAD;IACE,aAAU;IACV,cAAY,OAAO,UAAU,WAAW,GAAG,aAAa,IAAI,UAAU;IACtE,eAAgB,SAAS,KAAA,IAAY,aAAa,IAAI,IAAI,UAAU;IACpE,WAAU;IAEV,UAAA,oBAAC,cAAD,CAAe,CAAA;GACL,CAAA,IACV;EACD;;CAEP,IAAI,CAAC,SAAS,OAAO;CACrB,OACE,oBAAC,OAAD;EACE,aAAU;EACV,cAAY,SAAS,KAAA;EACrB,kBAAkB,UAAU;GAC1B,IAAI,WAAW,MAAM,WAAW,MAAM,iBAAiB,MAAM,iBAAiB,sBAC5E,OAAO;EACX;EACA,WAAU;EAEV,UAAA,oBAAC,OAAD,EAAA,UAAM,MAAW,CAAA;CACd,CAAA;AAET,CAAC"}
|
|
1
|
+
{"version":3,"file":"alert.js","names":["AriaButton"],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type AriaRole,\n type ComponentPropsWithoutRef,\n type ReactNode,\n type SVGProps,\n} from \"react\";\n\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * `info` is `default` said by name: a call site that means \"information\" should\n * not depend on `default` happening to look like it. Every per-variant map below\n * derives `info` here, from its `default`, so the two cannot drift.\n */\nfunction withInfo<T extends { default: unknown }>(map: T): T & { info: T[\"default\"] } {\n return { ...map, info: map.default };\n}\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-panel border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: withInfo({\n default: \"bg-surface text-foreground border-border\",\n // The status variants paint only their icon; the card stays neutral\n // (dark title, grey description, light border), like the reference\n // Sync status alert. See alertIconVariants.\n success: \"bg-surface text-foreground border-border\",\n warning: \"bg-surface text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-surface text-destructive-strong border-destructive-strong\",\n }),\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * The icon's colour: the state's own hue on every variant. Inside an Alert the\n * destructive red is also the whole alert's text colour, so it changes nothing\n * there; outside one it is what colours the icon.\n *\n * Exported with the status icons so a notice that is not an `Alert` (a row, a\n * banner of the app's own) can show a tone the same way the alert does.\n */\nexport const alertIconVariants = cva(\n \"flex h-lh shrink-0 items-center [&_svg]:block [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n {\n variants: {\n variant: withInfo({\n default: \"text-info-strong\",\n success: \"text-success-strong\",\n warning: \"text-warning-strong\",\n destructive: \"text-destructive-strong\",\n }),\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * The built-in glyph of each variant, drawn with currentColor so the icon\n * wrapper's colour paints it. Each is its own component so a variant change\n * crossfades the icon (see iconKey).\n *\n * Exported as AlertInfoIcon, AlertSuccessIcon, AlertWarningIcon and\n * AlertDestructiveIcon. They are decorative (`aria-hidden`) and `--icon-sm`\n * by default; extra props, such as a `className` with a size, land on the svg.\n * Given an `aria-label` or `aria-labelledby` (with `role=\"img\"`), an icon is\n * no longer hidden, and an explicit `aria-hidden` always wins.\n */\ntype StatusIconProps = Omit<SVGProps<SVGSVGElement>, \"children\">;\nfunction StatusGlyph({\n children,\n className,\n ...props\n}: StatusIconProps & { children: ReactNode }) {\n const labelled = props[\"aria-label\"] !== undefined || props[\"aria-labelledby\"] !== undefined;\n return (\n <svg\n // Before the spread, so a caller's own aria-hidden wins; a labelled icon\n // is meant to be read, so it is not hidden by default.\n aria-hidden={labelled ? undefined : \"true\"}\n {...props}\n // Sized on its own, so an exported icon outside an alert is not 0x0; the\n // same --icon-sm the alert's icon slot gives an unsized svg. A caller's\n // size class wins.\n className={cn(\"size-(--icon-sm)\", className)}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n {children}\n </svg>\n );\n}\nfunction InfoGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"M8 7.25v3.5M8 5h.01\" />\n </StatusGlyph>\n );\n}\nfunction SuccessGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m5.25 8.25 1.9 1.9 3.6-3.9\" />\n </StatusGlyph>\n );\n}\nfunction WarningGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <path d=\"M6.9 2.6 1.9 11.4a1.2 1.2 0 0 0 1 1.8h10.2a1.2 1.2 0 0 0 1-1.8L9.1 2.6a1.2 1.2 0 0 0-2.2 0Z\" />\n <path d=\"M8 6.4v2.6M8 11.2h.01\" />\n </StatusGlyph>\n );\n}\nfunction DestructiveGlyph(props: StatusIconProps) {\n return (\n <StatusGlyph {...props}>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" />\n <path d=\"m6 6 4 4M10 6l-4 4\" />\n </StatusGlyph>\n );\n}\nexport {\n InfoGlyph as AlertInfoIcon,\n SuccessGlyph as AlertSuccessIcon,\n WarningGlyph as AlertWarningIcon,\n DestructiveGlyph as AlertDestructiveIcon,\n};\nexport type { StatusIconProps as AlertStatusIconProps };\n\nconst statusGlyphs = withInfo({\n default: <InfoGlyph />,\n success: <SuccessGlyph />,\n warning: <WarningGlyph />,\n destructive: <DestructiveGlyph />,\n});\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong default for content that is present on load and\n * merely styled as a notice -- pass `role=\"note\"` for those, or\n * `role=\"status\"` for an outcome that should be announced politely.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * Left out, the alert shows its variant's own icon (info, check, warning\n * triangle, cross), coloured for the state and centred on the first line of\n * text. Pass `null` for no icon, or your own glyph to replace it.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /**\n * Whether the alert is shown. Controlled: hiding it collapses its height and\n * fades it out, and showing it again grows it back. An alert that is open on\n * first render does not animate. Leave it out for an alert that is always there.\n */\n open?: boolean;\n /**\n * Adds a Dismiss button. Pressed, a controlled alert (`open` given) only calls\n * this and waits to be told `open={false}`; an uncontrolled alert (no `open`)\n * collapses first and then calls it.\n */\n onDismiss?: () => void;\n /** The Dismiss button's accessible name. @default \"Dismiss\" */\n dismissLabel?: string;\n /**\n * How assistive technology treats the message.\n *\n * - `alert`: announced assertively the moment it renders. For an error or a\n * warning that appears after the user did something.\n * - `status`: a live region announced politely, after what the user is\n * hearing. For an outcome reported after an action (\"Saved\").\n * - `note`: not live. For a notice that is simply there when the page loads;\n * an alert there would interrupt the page's own announcement.\n *\n * Typed as any ARIA role, as the inherited `role` was, so existing callers\n * still compile; those three are the ones an inline notice should use.\n *\n * @default \"alert\"\n */\n role?: \"alert\" | \"status\" | \"note\" | AriaRole;\n}\n\n/** A string title or description swaps as a whole when its text changes. */\nfunction Swappable({ value, children }: { value: ReactNode; children: ReactNode }) {\n if (typeof value !== \"string\" && typeof value !== \"number\") return <>{children}</>;\n return (\n <SwapText swapKey={String(value)} className=\"block\">\n {children}\n </SwapText>\n );\n}\n\n/** The name an icon swaps under: a different icon component crossfades; the same one re-rendering stays still. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nfunction DismissGlyph() {\n return (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n className=\"size-4\"\n >\n <path d=\"M4 4l8 8M12 4l-8 8\" />\n </svg>\n );\n}\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n {\n className,\n variant,\n icon: iconProp,\n title,\n headingLevel = 3,\n children,\n open,\n onDismiss,\n dismissLabel = \"Dismiss\",\n role = \"alert\",\n ...props\n },\n ref,\n) {\n const Title = `h${headingLevel}` as const;\n const icon = iconProp === undefined ? statusGlyphs[variant ?? \"default\"] : iconProp;\n const frame = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const text = typeof children === \"string\" || typeof children === \"number\" ? String(children) : \"\";\n useHeightFollow(\n frame,\n content,\n `${typeof title === \"string\" ? title : \"\"}\\n${children === undefined ? \"\" : \"details\"}\\n${text}`,\n );\n\n // Presence. Without `open` or `onDismiss` the alert is simply there.\n const managed = open !== undefined || onDismiss !== undefined;\n const [dismissed, setDismissed] = useState(false);\n const wanted = open ?? !dismissed;\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 alert mounts collapsed and opens a frame later, so it grows rather than appears.\n if (wanted && rendered && !shown) {\n const frameId = requestAnimationFrame(() => setShown(true));\n return () => cancelAnimationFrame(frameId);\n }\n }, [wanted, rendered, shown]);\n const leaving = rendered && !wanted;\n const finish = useCallback(() => {\n setRendered(false);\n if (open === undefined) onDismiss?.();\n }, [onDismiss, open]);\n useExitBackstop(leaving, finish, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) finish();\n }, [leaving, finish]);\n if (managed && !rendered) return null;\n\n const alert = (\n <div\n ref={ref}\n {...props}\n role={role}\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n The icon is decorative or labelled by the consumer; this wrapper adds\n no name. It keeps the icon from shrinking and sizes a bare svg to the\n icon scale, unless the consumer sized it themselves.\n */}\n {icon === null || icon === false ? null : (\n <span data-slot=\"alert-icon\" className={alertIconVariants({ variant })}>\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </span>\n )}\n {/* The copy column follows its content's height, so longer or shorter text never snaps the alert. */}\n <div ref={frame} data-slot=\"alert-body\" className=\"height-follow min-w-0 flex-1\">\n <div ref={content}>\n {title === undefined ? null : (\n <Title\n data-slot=\"alert-title\"\n className=\"m-0 mb-1 text-sm leading-(--leading-normal) font-medium\"\n >\n <Swappable value={title}>{title}</Swappable>\n </Title>\n )}\n {children === undefined ? null : (\n <div\n data-slot=\"alert-description\"\n className={title === undefined ? undefined : \"text-xs leading-(--leading-normal) text-text-secondary\"}\n >\n <Swappable value={children}>{children}</Swappable>\n </div>\n )}\n </div>\n </div>\n {onDismiss ? (\n <AriaButton\n data-slot=\"alert-dismiss\"\n aria-label={typeof title === \"string\" ? `${dismissLabel}: ${title}` : dismissLabel}\n onPress={() => (open === undefined ? setDismissed(true) : onDismiss())}\n className=\"calendar-nav-motion -my-1 -me-1.5 grid size-7 shrink-0 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring)\"\n >\n <DismissGlyph />\n </AriaButton>\n ) : null}\n </div>\n );\n if (!managed) return alert;\n return (\n <div\n data-slot=\"alert-presence\"\n data-shown={shown || undefined}\n onTransitionEnd={(event) => {\n if (leaving && event.target === event.currentTarget && event.propertyName === \"grid-template-rows\")\n finish();\n }}\n className=\"alert-presence\"\n >\n <div>{alert}</div>\n </div>\n );\n});\n"],"mappings":"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"}
|
package/dist/alert/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Alert, AlertProps, alertVariants } from "./alert.js";
|
|
2
|
-
export { Alert, type AlertProps, alertVariants };
|
|
1
|
+
import { Alert, AlertDestructiveIcon as DestructiveGlyph, AlertInfoIcon as InfoGlyph, AlertProps, AlertStatusIconProps as StatusIconProps, AlertSuccessIcon as SuccessGlyph, AlertWarningIcon as WarningGlyph, alertIconVariants, alertVariants } from "./alert.js";
|
|
2
|
+
export { Alert, DestructiveGlyph as AlertDestructiveIcon, InfoGlyph as AlertInfoIcon, type AlertProps, type StatusIconProps as AlertStatusIconProps, SuccessGlyph as AlertSuccessIcon, WarningGlyph as AlertWarningIcon, alertIconVariants, alertVariants };
|
package/dist/alert/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Alert, alertVariants } from "./alert.js";
|
|
2
|
-
export { Alert, alertVariants };
|
|
1
|
+
import { Alert, AlertDestructiveIcon as DestructiveGlyph, AlertInfoIcon as InfoGlyph, AlertSuccessIcon as SuccessGlyph, AlertWarningIcon as WarningGlyph, alertIconVariants, alertVariants } from "./alert.js";
|
|
2
|
+
export { Alert, DestructiveGlyph as AlertDestructiveIcon, InfoGlyph as AlertInfoIcon, SuccessGlyph as AlertSuccessIcon, WarningGlyph as AlertWarningIcon, alertIconVariants, alertVariants };
|