@seekalfred/primitives 0.7.3 → 0.8.0
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 +315 -4
- package/dist/alert/alert.d.ts +2 -2
- package/dist/alert/alert.js +1 -1
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +14 -0
- package/dist/alert-dialog/alert-dialog.js +52 -27
- 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 +16 -1
- package/dist/badge/badge.js +12 -5
- package/dist/badge/badge.js.map +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 +24 -2
- package/dist/button/button.js +62 -13
- package/dist/button/button.js.map +1 -1
- package/dist/card/card.js +1 -1
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +25 -1
- package/dist/checkbox/checkbox.js +103 -28
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/checkbox/index.d.ts +2 -2
- package/dist/checkbox/index.js +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/code-block/code-block.d.ts +45 -0
- package/dist/code-block/code-block.js +74 -0
- package/dist/code-block/code-block.js.map +1 -0
- package/dist/code-block/index.d.ts +2 -0
- package/dist/code-block/index.js +2 -0
- package/dist/command-palette/command-palette.d.ts +128 -0
- package/dist/command-palette/command-palette.js +371 -0
- package/dist/command-palette/command-palette.js.map +1 -0
- package/dist/command-palette/index.d.ts +2 -0
- package/dist/command-palette/index.js +2 -0
- 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/dialog/dialog.js +1 -1
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/empty-state/empty-state.d.ts +17 -0
- package/dist/empty-state/empty-state.js +9 -7
- package/dist/empty-state/empty-state.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 +14 -9
- package/dist/index.js +14 -10
- package/dist/info-tooltip/index.d.ts +2 -0
- package/dist/info-tooltip/index.js +2 -0
- package/dist/info-tooltip/info-tooltip.d.ts +35 -0
- package/dist/info-tooltip/info-tooltip.js +49 -0
- package/dist/info-tooltip/info-tooltip.js.map +1 -0
- package/dist/input/input.d.ts +7 -0
- package/dist/input/input.js +4 -2
- 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/lib/selection-control.js +32 -1
- package/dist/lib/selection-control.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts +106 -3
- package/dist/line-chart/line-chart.js +185 -46
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/mention-input/mention-input.js +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 +123 -6
- package/dist/multi-select/multi-select.js.map +1 -1
- package/dist/number-field/number-field.js +1 -1
- package/dist/password-field/password-field.js +1 -1
- package/dist/password-strength/password-strength.js +1 -1
- package/dist/phone-input/phone-input.js +1 -1
- package/dist/popover/popover.js +1 -1
- package/dist/popover/popover.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 +12 -3
- package/dist/radio-group/radio-group.js +62 -52
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/rich-text-editor/rich-text-editor.js +1 -1
- package/dist/scroll-area/scroll-area.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/search-field/search-field.js +1 -1
- package/dist/select/select.d.ts +10 -1
- package/dist/select/select.js +4 -3
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/shortcut-recorder/shortcut-recorder.js +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 +211 -3
- package/dist/table/table.js +473 -93
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.d.ts +4 -2
- package/dist/tabs/tabs.js +7 -5
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/tag-input/tag-input.js +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 +4 -2
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/dist/toggle-button/index.d.ts +2 -0
- package/dist/toggle-button/index.js +2 -0
- package/dist/toggle-button/toggle-button.d.ts +93 -0
- package/dist/toggle-button/toggle-button.js +199 -0
- package/dist/toggle-button/toggle-button.js.map +1 -0
- package/package.json +19 -3
package/README.md
CHANGED
|
@@ -292,6 +292,13 @@ darkening it — colour alone disappears under forced-colors.
|
|
|
292
292
|
|
|
293
293
|
A crumb added after the first render slides in 8px from the left over 240ms and the crumbs that stay slide to their new place; link colour eases over 160ms. The trail present at first render is still. A removed crumb disappears at once (it would need a leaving copy), and the reference's underline fade is not ported: Alfred's links carry no underline at rest by design.
|
|
294
294
|
|
|
295
|
+
### Wrapping (Badge)
|
|
296
|
+
|
|
297
|
+
`wrap` lets the label break onto more lines, for a tag in a table cell or a narrow column. The badge shrinks to its
|
|
298
|
+
container, breaks long unspaced words, starts the text at the leading edge and grows in height. Off by default and then
|
|
299
|
+
a badge is one line that never shrinks, as before. Prefer `shape="rounded"` for long labels: a multi-line `pill` has
|
|
300
|
+
very round ends. The width does not spring to a new label while `wrap` is on.
|
|
301
|
+
|
|
295
302
|
## Button
|
|
296
303
|
|
|
297
304
|
| variant | |
|
|
@@ -300,6 +307,7 @@ A crumb added after the first render slides in 8px from the left over 240ms and
|
|
|
300
307
|
| `secondary` | solid brand periwinkle, ink label |
|
|
301
308
|
| `outline` | background fill, brand border and label |
|
|
302
309
|
| `ghost` | transparent until hovered |
|
|
310
|
+
| `ai` | orange gradient, ink label, wand icon; **generative actions only** |
|
|
303
311
|
| `destructive` | solid `destructive` |
|
|
304
312
|
| `link` | text only, underlines on hover |
|
|
305
313
|
|
|
@@ -308,6 +316,13 @@ The primary label is **ink, not white**. Brand orange carries white at only
|
|
|
308
316
|
same reason `link` and `outline` use `primary-strong` — a darkened tone of the
|
|
309
317
|
brand hue — because `primary` itself cannot legally be text or a border.
|
|
310
318
|
|
|
319
|
+
**`ai` is for generative actions only** (Generate, Summarise, Seek Alfred) and
|
|
320
|
+
nothing else. Primary stays flat; the gradient belongs to `ai` alone. The button
|
|
321
|
+
brings its own wand: pass `startIcon={false}` for none, or your own node in
|
|
322
|
+
`startIcon` to replace it (never as a child, which adds a second icon). Disabled
|
|
323
|
+
swaps the gradient for the muted fill. On keyboard focus its own border turns
|
|
324
|
+
ink instead of drawing a ring (the outline returns under forced-colors).
|
|
325
|
+
|
|
311
326
|
Sizes: `sm` (32px) · `md` (36px, default) · `lg` (40px) · `icon` (36px square).
|
|
312
327
|
|
|
313
328
|
`size="icon"` has no visible text, so it **must** carry an `aria-label`. Without
|
|
@@ -362,6 +377,18 @@ The `@seekalfred/styles` motion tokens behind it are `--ease-*` and
|
|
|
362
377
|
`--duration-*` (`settle`, `enter`, `snappy`, `morph`, `gentle`, `calm`,
|
|
363
378
|
`smooth`, `quick`, `instant`, …).
|
|
364
379
|
|
|
380
|
+
### Compact icon button and wrapping label (Button)
|
|
381
|
+
|
|
382
|
+
`size="icon-sm"` is a 24px square icon-only button, for a glyph that sits beside text (an info trigger next to a
|
|
383
|
+
heading). 24px is the WCAG 2.5.8 target minimum, so the 14px glyph stays hittable. It is fixed, not on the density
|
|
384
|
+
scale. Same rules as `size="icon"`: the glyph is `children` and the button must carry an `aria-label`.
|
|
385
|
+
|
|
386
|
+
`wrap` lets the label break across lines, for long text that is not ours (a prompt in a list, a name in a cell). The
|
|
387
|
+
button shrinks to its container, breaks even long unspaced words, starts the text at the leading edge, and grows
|
|
388
|
+
taller than `size`, whose height becomes its minimum. Add `className="justify-start"` to start a `link` button at the
|
|
389
|
+
edge of its row. Off by default, and then a button is one line at a fixed height as before. A wrapping button's
|
|
390
|
+
width does not spring when its label changes.
|
|
391
|
+
|
|
365
392
|
## Migrating from the legacy shadcn Button
|
|
366
393
|
|
|
367
394
|
The variant and size vocabulary is unchanged, so most call sites move by
|
|
@@ -616,6 +643,35 @@ The label is `children`, and that is also how the control gets its accessible
|
|
|
616
643
|
name. Pass `aria-label` instead only for a genuinely label-less checkbox, such
|
|
617
644
|
as a table row selector.
|
|
618
645
|
|
|
646
|
+
### Card variant
|
|
647
|
+
|
|
648
|
+
`variant="card"` draws one option as a selectable card, the counterpart of
|
|
649
|
+
`RadioGroup variant="card"` for a choice where several can be on. The whole card
|
|
650
|
+
is the target, the title is `children`, and it takes a `description`, a
|
|
651
|
+
decorative `icon` and `media` (a thumbnail or preview above the title). The box
|
|
652
|
+
sits at the end of the title row.
|
|
653
|
+
|
|
654
|
+
```tsx
|
|
655
|
+
<Checkbox variant="card" isSelected={on} onChange={setOn} description="gpt-5 · $1.25 in · $10 out per 1M" icon={<CpuIcon />}>
|
|
656
|
+
GPT-5
|
|
657
|
+
</Checkbox>
|
|
658
|
+
<Checkbox variant="card" media={<img src={thumb} alt="" className="aspect-square w-full rounded-md object-cover" />}>
|
|
659
|
+
hero-banner.png
|
|
660
|
+
</Checkbox>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
- The title alone is the option's name; the description is its description
|
|
664
|
+
(`aria-describedby`). The icon is `aria-hidden`; give `media` its own `alt`.
|
|
665
|
+
- Selected shows twice: a `primary-strong` edge and the tick, so it never rests on
|
|
666
|
+
colour alone.
|
|
667
|
+
- **Focus** is the card's own 1px border turning dark, with no ring (as on a text
|
|
668
|
+
field). Forced colours get an outline back, because they override border colours.
|
|
669
|
+
- `description`, `icon` and `media` are ignored by the default variant, which renders
|
|
670
|
+
exactly as before. `checkboxCardVariants` is exported.
|
|
671
|
+
- Card options are for choosing (state that is kept). A tile that does something on
|
|
672
|
+
press, and closes the dialog it is in, is a Button: radio selection follows the
|
|
673
|
+
arrow keys, so a radio card that acts on `onChange` would act on the first arrow press.
|
|
674
|
+
|
|
619
675
|
### Migrating from the legacy Checkbox
|
|
620
676
|
|
|
621
677
|
The legacy component had **zero call sites**, so nothing here breaks. It is
|
|
@@ -636,6 +692,67 @@ other form controls will take.
|
|
|
636
692
|
|
|
637
693
|
The chosen colour grows in from small on the snappy spring while it fades in, the mark draws itself, and the check morphs into the dash and back on the morph spring (the `d` property transitions in Chromium; elsewhere it swaps). The square (not the hit area) presses to .95 in 100ms and springs back; its border eases over 160ms and darkens on hover while unchosen. **DOM:** the two glyph svgs are now one `checkbox-mark` (with `data-mark` of `check` or `indeterminate`) and a `checkbox-fill` element carries the colour, so the box's own background no longer turns primary.
|
|
638
694
|
|
|
695
|
+
## CodeBlock
|
|
696
|
+
|
|
697
|
+
```tsx
|
|
698
|
+
<CodeBlock value="npm install @seekalfred/primitives" onCopied={() => toast("Copied")} />
|
|
699
|
+
<CodeBlock value={longLine} wrap copyAriaLabel="Copy command" />
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
A snippet of code with a copy button, composed from CopyButton (which owns the copy, its feedback and its announcement).
|
|
703
|
+
`value` is shown and copied; `children` replaces what is shown (highlighted markup) while `value` is still copied. By
|
|
704
|
+
default a long line scrolls sideways and the scroll area takes keyboard focus (named by `codeLabel`); `wrap` breaks
|
|
705
|
+
lines instead. `copyLabel` is the visible button text, `copyAriaLabel` its name (keep the label inside it),
|
|
706
|
+
`iconOnly` drops the text; `onCopied` and `onCopyError` pass through.
|
|
707
|
+
|
|
708
|
+
## CommandPalette
|
|
709
|
+
|
|
710
|
+
```tsx
|
|
711
|
+
const [open, setOpen] = useState(false);
|
|
712
|
+
|
|
713
|
+
<CommandPalette
|
|
714
|
+
items={[
|
|
715
|
+
{ id: "home", group: "Pages", label: "Home", icon: <HomeIcon aria-hidden />, hint: "Go to" },
|
|
716
|
+
{ id: "theme", group: "Actions", label: "Switch theme", keywords: ["dark", "light"], shortcut: "mod+shift+l" },
|
|
717
|
+
]}
|
|
718
|
+
recent={[{ id: "posts", label: "Published posts", hint: "Go to" }]}
|
|
719
|
+
isOpen={open}
|
|
720
|
+
onOpenChange={setOpen}
|
|
721
|
+
onSelect={(item) => run(item.id)}
|
|
722
|
+
/>
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
A modal search-and-run list, built on Dialog, Input and ShortcutKeys. It listens for **Cmd+K or Ctrl+K**
|
|
726
|
+
on the document (`shortcut`, default `"mod+k"`, where `mod` is either key; `shortcut={false}` removes the
|
|
727
|
+
listener) and toggles itself. The chord's default is prevented, so a browser that binds Ctrl+K does not
|
|
728
|
+
take it. `useCommandPaletteShortcut(onTrigger, { shortcut, enabled })` is the same listener as a hook, for a
|
|
729
|
+
different chord or an app that opens the palette from its own state.
|
|
730
|
+
|
|
731
|
+
It is controlled with `isOpen` and `onOpenChange`, or uncontrolled with `defaultOpen`. `onSelect(item)` runs
|
|
732
|
+
after the palette has asked to close, so an action that opens another dialog finds focus already returned.
|
|
733
|
+
|
|
734
|
+
**Items.** `id` and `label` are required; `group` is the heading (groups appear in the order first seen, items
|
|
735
|
+
kept together), `icon` is decorative, `shortcut` draws key caps (a hint, it binds nothing), `hint` is plain
|
|
736
|
+
trailing text shown when there is no `shortcut`, `keywords` match without being shown, and `isDisabled` lists an
|
|
737
|
+
item that the arrow keys skip and a click ignores. The default filter needs every word of the query to appear in
|
|
738
|
+
the label, group or keywords, case-insensitively; `filter(item, query)` replaces it. `recent` is a group that leads
|
|
739
|
+
while the box is empty and gives way to the search once typing starts.
|
|
740
|
+
|
|
741
|
+
**Keyboard.** Arrow keys move the active row and wrap, Home and End jump to the first and last enabled row, Enter
|
|
742
|
+
chooses, Escape closes. Typing returns the highlight to the first match. Home and End are claimed from the text
|
|
743
|
+
caret on purpose. Hovering a row makes it active.
|
|
744
|
+
|
|
745
|
+
**Accessibility.** The search box is a `combobox` (`aria-expanded`, `aria-controls`, `aria-autocomplete="list"`,
|
|
746
|
+
`aria-haspopup="listbox"`) that keeps DOM focus; `aria-activedescendant` names the active `option` in the
|
|
747
|
+
`listbox`, and groups are `role="group"` labelled by their heading. The dialog is labelled by a visually hidden
|
|
748
|
+
heading (`label`, default "Search and commands"). Focus lands in the box on open, is trapped while open, and returns
|
|
749
|
+
to the element that had it on close. A polite live region announces the result count, and the empty state is a
|
|
750
|
+
`status`. The enter and exit motion is Dialog's and is off under `prefers-reduced-motion`; the row highlight
|
|
751
|
+
fades only under `motion-safe`.
|
|
752
|
+
|
|
753
|
+
**Copy.** Every string is a prop (`label`, `searchLabel`, `placeholder`, `recentLabel`, `emptyMessage`,
|
|
754
|
+
`resultsLabel`, `footer`); `footer={null}` removes the key-hint strip.
|
|
755
|
+
|
|
639
756
|
## ConfirmMorph
|
|
640
757
|
|
|
641
758
|
```tsx
|
|
@@ -746,6 +863,17 @@ it is decorative. Omitted from the DOM when absent; `false` (from
|
|
|
746
863
|
|
|
747
864
|
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.
|
|
748
865
|
|
|
866
|
+
### Frame and alignment (EmptyState)
|
|
867
|
+
|
|
868
|
+
`frame="dashed"` draws the empty state as a block of its own: a dashed border, rounded corner and the muted surface,
|
|
869
|
+
for a placeholder where a chart or panel will go. `align="start"` puts icon, text and action at the leading edge
|
|
870
|
+
(the default `center` is the page-level empty state); with no icon or image the title also drops its top margin. Both
|
|
871
|
+
are off by default. The description keeps EmptyState's own measure (18rem).
|
|
872
|
+
|
|
873
|
+
```tsx
|
|
874
|
+
<EmptyState frame="dashed" align="start" title="Connect Search Console" description="Rankings appear here once connected." action={<Button>Connect</Button>} />
|
|
875
|
+
```
|
|
876
|
+
|
|
749
877
|
## ExpandingButtonGroup
|
|
750
878
|
|
|
751
879
|
```tsx
|
|
@@ -848,6 +976,18 @@ interruptible spring, so toggling quickly reverses from wherever the icon is. Th
|
|
|
848
976
|
icons are decorative: put them in a button that carries the name and
|
|
849
977
|
`aria-pressed`. Each is also exported on its own (`MenuIcon`, `HeartIcon`, …).
|
|
850
978
|
|
|
979
|
+
## InfoTooltip
|
|
980
|
+
|
|
981
|
+
```tsx
|
|
982
|
+
<InfoTooltip label="More about Visibility score" content="Share of answers that mention you." />
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
A small "i" button that explains something in a tooltip: the compact `icon-sm` Button and a Tooltip. It is a real
|
|
986
|
+
button, so the help is reachable by keyboard as well as hover. `label` is its accessible name (default "More
|
|
987
|
+
information"); say what the help is about, because several triggers on a page otherwise read the same. `placement`,
|
|
988
|
+
`className` (button) and `tooltipClassName` (bubble) are the only other props. It is new to the library: the app's own
|
|
989
|
+
`InfoTooltip` wraps the app's Tooltip with a fixed label.
|
|
990
|
+
|
|
851
991
|
## Input and Textarea
|
|
852
992
|
|
|
853
993
|
```tsx
|
|
@@ -968,6 +1108,18 @@ The wiring, done for you:
|
|
|
968
1108
|
- The error row has `role="alert"`.
|
|
969
1109
|
- Screen readers get the plain copy. The animated words are `aria-hidden`.
|
|
970
1110
|
|
|
1111
|
+
**`isRequired`** makes the control `required` (announced by screen readers and
|
|
1112
|
+
checked by native form validation) and shows the required marker after the
|
|
1113
|
+
label: the same red asterisk as `PasswordField` and `Select`, `aria-hidden` so
|
|
1114
|
+
the name stays the label's text. Off by default. Without a `label` the control
|
|
1115
|
+
is still `required`, with no label to mark. A native `required` on its own still
|
|
1116
|
+
only sets the attribute and draws no marker, as before.
|
|
1117
|
+
|
|
1118
|
+
```tsx
|
|
1119
|
+
<Input label="Email" isRequired />
|
|
1120
|
+
<Textarea label="Message" isRequired />
|
|
1121
|
+
```
|
|
1122
|
+
|
|
971
1123
|
Limits worth knowing:
|
|
972
1124
|
|
|
973
1125
|
- **Do not pass `aria-label` together with `label`.** `aria-label` wins, so the
|
|
@@ -1218,6 +1370,27 @@ With `labelHidden` and no `showValue` nothing is printed above the track, so the
|
|
|
1218
1370
|
its tone and no check lands. Use it for a timer-driven estimate that can reach
|
|
1219
1371
|
100% before the work finishes.
|
|
1220
1372
|
|
|
1373
|
+
`target` draws an upright tick where the bar should be by now, on the same
|
|
1374
|
+
scale as `value` (clamped to `minValue`..`maxValue`), standing proud of the
|
|
1375
|
+
track with a 1px halo so it reads over the fill too. The tick is decorative:
|
|
1376
|
+
`targetLabel` (default "Target") and the formatted target ("Expected by now:
|
|
1377
|
+
40%", formatted like the bar's own value) become the bar's accessible
|
|
1378
|
+
description, kept alongside any `aria-describedby` of yours. `showTargetLabel`
|
|
1379
|
+
also prints that text under the tick, centred on it and kept inside the bar
|
|
1380
|
+
near either end. No `target`, no tick and the same DOM as before.
|
|
1381
|
+
|
|
1382
|
+
`surface="inverse"` is for a bar on the inverted, foreground-coloured surface
|
|
1383
|
+
(a dark strip in the light theme): the track is the inverse text colour mixed
|
|
1384
|
+
into that surface, and the default fill, the tick and the copy take the inverse
|
|
1385
|
+
text colour. Explicit tones keep their colours.
|
|
1386
|
+
|
|
1387
|
+
```tsx
|
|
1388
|
+
<Progress label="Organic sessions" value={340} maxValue={1000} target={450} targetLabel="Expected by now" labelHidden />
|
|
1389
|
+
<div className="bg-foreground p-4">
|
|
1390
|
+
<Progress label="Plan reaches" value={72} target={100} surface="inverse" labelHidden />
|
|
1391
|
+
</div>
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1221
1394
|
### Motion (Progress)
|
|
1222
1395
|
|
|
1223
1396
|
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.
|
|
@@ -1253,9 +1426,27 @@ a decorative `icon`. The group becomes a grid, so the caller sets the columns.
|
|
|
1253
1426
|
together. The icon is `aria-hidden`.
|
|
1254
1427
|
- Selected shows twice: a `primary-strong` edge (StatCard's selected pair) and
|
|
1255
1428
|
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.
|
|
1257
|
-
|
|
1258
|
-
|
|
1429
|
+
- Keyboard is unchanged: one tab stop, arrow keys move and select.
|
|
1430
|
+
- **Focus** is the card's own 1px border turning dark, with no ring (the approved
|
|
1431
|
+
style, as on a text field and on `Checkbox variant="card"`). It was an outline ring
|
|
1432
|
+
on the card up to 0.7.4. Forced colours get the outline back.
|
|
1433
|
+
- `media` puts a thumbnail or preview edge to edge above the title (give an `<img>`
|
|
1434
|
+
its own `alt`). `description`, `icon` and `media` are ignored by the other variants.
|
|
1435
|
+
|
|
1436
|
+
### Framed variant
|
|
1437
|
+
|
|
1438
|
+
`variant="framed"` keeps ordinary radios and draws one bordered frame around the
|
|
1439
|
+
row (or column, by `orientation`). Use it for a small inline picker that should read as one
|
|
1440
|
+
control. **Focus** is the frame's own border turning dark when any radio in it takes
|
|
1441
|
+
keyboard focus; the dot's ring is off in this variant. Radios can be wrapped in a
|
|
1442
|
+
`Tooltip` trigger.
|
|
1443
|
+
|
|
1444
|
+
```tsx
|
|
1445
|
+
<RadioGroup variant="framed" orientation="horizontal" aria-label="AI provider" value={v} onChange={setV}>
|
|
1446
|
+
<Radio value="platform">Platform</Radio>
|
|
1447
|
+
<Radio value="azure">Azure</Radio>
|
|
1448
|
+
</RadioGroup>
|
|
1449
|
+
```
|
|
1259
1450
|
|
|
1260
1451
|
### Migrating from the legacy RadioGroup
|
|
1261
1452
|
|
|
@@ -1382,6 +1573,15 @@ only — WCAG 1.4.1. The message is wired into `aria-describedby` for you.
|
|
|
1382
1573
|
<Select label="Country" isInvalid errorMessage="Pick a country to continue." />
|
|
1383
1574
|
```
|
|
1384
1575
|
|
|
1576
|
+
**`isRequired`** is React Aria's prop: the hidden native select is `required`
|
|
1577
|
+
and the root gets `data-required`. It also shows the required marker after the
|
|
1578
|
+
label, the same asterisk as `Input`, `Textarea` and `PasswordField`, hidden from
|
|
1579
|
+
the trigger's accessible name.
|
|
1580
|
+
|
|
1581
|
+
```tsx
|
|
1582
|
+
<Select label="Country" isRequired>…</Select>
|
|
1583
|
+
```
|
|
1584
|
+
|
|
1385
1585
|
## Separator
|
|
1386
1586
|
|
|
1387
1587
|
```tsx
|
|
@@ -1530,6 +1730,17 @@ link, `onPress` a button, and `isSelected` with `onPress` a filter toggle with
|
|
|
1530
1730
|
use `Badge`, whose `tone` already covers success, warning, danger, info,
|
|
1531
1731
|
primary and neutral.
|
|
1532
1732
|
|
|
1733
|
+
`isLoading` keeps the card's box while its figure loads: the label stays (an
|
|
1734
|
+
empty `label` pulses in its line), the value and the context line (pass `sub`
|
|
1735
|
+
to keep it) become placeholders one line tall, the change pill pulses at its
|
|
1736
|
+
own size, the content is `aria-busy` and the root carries `data-loading`.
|
|
1737
|
+
Screen readers hear `loadingLabel` ("Loading") in place of the figure. A row of
|
|
1738
|
+
loading cards is exactly as tall as the row that replaces it.
|
|
1739
|
+
|
|
1740
|
+
```tsx
|
|
1741
|
+
<StatCard label={metric?.name ?? ""} value={metric?.value ?? ""} sub="vs previous period" isLoading={!metric} />
|
|
1742
|
+
```
|
|
1743
|
+
|
|
1533
1744
|
## Switch
|
|
1534
1745
|
|
|
1535
1746
|
```tsx
|
|
@@ -1642,6 +1853,72 @@ depends on row *data*, use `rowClassName`.
|
|
|
1642
1853
|
| `striped` | zebra-stripes alternate rows |
|
|
1643
1854
|
| `totalRow` | `(column) => ReactNode`; renders a real `<tfoot>` |
|
|
1644
1855
|
| `Column.numeric` | tabular numerals, end-aligned by default |
|
|
1856
|
+
| `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) |
|
|
1857
|
+
| `expandedKeys` / `defaultExpandedKeys` / `onExpandedChange` | the open rows, controlled or not; nothing is open by default |
|
|
1858
|
+
| `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 |
|
|
1859
|
+
| `density` | `default` · `compact` — `py-2` rows in small type, the product's tight report tables |
|
|
1860
|
+
| `headerHidden` | hides the header row from sight and keeps it for assistive tech (key/value tables); widths still apply, sorting is off |
|
|
1861
|
+
| `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 |
|
|
1862
|
+
| `Column.colSpan` | `(row) => number`; the cell spans that many columns on the rows that ask, and the covered cells are not drawn. Clamped to the columns left; ignored on a sticky first column and on group rows |
|
|
1863
|
+
| `renderExpandedRow` | `(row) => ReactNode`; a full-width detail row under an open row (nothing for null/undefined/false). Opened with a trigger of your own using `cell`'s `isExpanded` / `toggleExpanded`, or by `expandedKeys`; not selected, sorted or acted on |
|
|
1864
|
+
| `Column.headerAddon` | `ReactNode` beside the header label and outside the sort control: an info icon with a tooltip. Pressing it does not sort. It must name itself and be 24px |
|
|
1865
|
+
| `Column.highlighted` | tints the column's header and cells (`data-highlighted`); say why in words too |
|
|
1866
|
+
| `getRowProps` | `(row) => { "data-*": … }`; extra data attributes on each row (`data-testid`). Library attributes win a clash. No `aria-*` (React Aria drops them on a grid row) and no `onClick` (use `onRowAction`) |
|
|
1867
|
+
|
|
1868
|
+
### Spans, detail rows, header add-ons, highlighted columns, row attributes
|
|
1869
|
+
|
|
1870
|
+
All five are off until you use them; a table that uses none renders as before.
|
|
1871
|
+
|
|
1872
|
+
```tsx
|
|
1873
|
+
const columns = [
|
|
1874
|
+
column({ id: "keyword", header: "Keyword", accessor: (r) => r.keyword }),
|
|
1875
|
+
column({
|
|
1876
|
+
id: "clicks",
|
|
1877
|
+
header: "Clicks",
|
|
1878
|
+
accessor: (r) => r.clicks,
|
|
1879
|
+
// One message across clicks and CTR on a row with no impressions.
|
|
1880
|
+
colSpan: (r) => (r.impressions === 0 ? 2 : 1),
|
|
1881
|
+
cell: ({ row, value }) => (row.impressions === 0 ? "No impressions yet" : value),
|
|
1882
|
+
}),
|
|
1883
|
+
column({ id: "ctr", header: "CTR", accessor: (r) => r.ctr }),
|
|
1884
|
+
column({
|
|
1885
|
+
id: "overall",
|
|
1886
|
+
header: "Overall",
|
|
1887
|
+
accessor: (r) => r.overall,
|
|
1888
|
+
sortable: true,
|
|
1889
|
+
headerAddon: (
|
|
1890
|
+
<TooltipTrigger>
|
|
1891
|
+
<AriaButton aria-label="About Overall">i</AriaButton>
|
|
1892
|
+
<Tooltip>The blend of every metric.</Tooltip>
|
|
1893
|
+
</TooltipTrigger>
|
|
1894
|
+
),
|
|
1895
|
+
highlighted: true,
|
|
1896
|
+
}),
|
|
1897
|
+
];
|
|
1898
|
+
|
|
1899
|
+
<Table
|
|
1900
|
+
aria-label="Keywords"
|
|
1901
|
+
data={rows}
|
|
1902
|
+
columns={columns}
|
|
1903
|
+
getRowId={(r) => r.id}
|
|
1904
|
+
getRowProps={(r) => ({ "data-testid": `keyword-row-${r.id}` })}
|
|
1905
|
+
renderExpandedRow={(r) => <History keyword={r} />}
|
|
1906
|
+
/>;
|
|
1907
|
+
```
|
|
1908
|
+
|
|
1909
|
+
**`renderExpandedRow` is not `getSubRows`.** Sub-rows are data rows with the
|
|
1910
|
+
table's columns, selection and actions, drawn with a disclosure button. A detail
|
|
1911
|
+
row is one cell spanning every column, holds anything, and is never selected,
|
|
1912
|
+
sorted, counted or acted on. Both read the same `expandedKeys` set, so a row
|
|
1913
|
+
with both opens both: the detail row first, directly under its row, then the
|
|
1914
|
+
sub-rows. There is no built-in button for a detail row, so the first cell's
|
|
1915
|
+
indent does not change: open it from a button in a cell (`aria-expanded`,
|
|
1916
|
+
`toggleExpanded`) or from `expandedKeys`.
|
|
1917
|
+
|
|
1918
|
+
**`Column.colSpan` leaves the header alone.** The header keeps one cell per
|
|
1919
|
+
column, so columns stay named and sortable; only body rows span. In the grid the
|
|
1920
|
+
spans, the checkbox column and the chevron column always add up to the table's
|
|
1921
|
+
columns.
|
|
1645
1922
|
|
|
1646
1923
|
### Using it in alfred-ui
|
|
1647
1924
|
|
|
@@ -1682,9 +1959,11 @@ not worth a palette entry. If you want it exactly, override
|
|
|
1682
1959
|
| `meta.width` / `<colgroup>` / width classes | `Column.width` | one mechanism instead of three |
|
|
1683
1960
|
| `onRowClick(row)` | `onRowAction(row)` | also fires on Enter |
|
|
1684
1961
|
| `rowClassName` | unchanged | |
|
|
1962
|
+
| `renderExpandedRow(row)` (`DataTableBody`) + `row.toggleExpanded()` | `renderExpandedRow(row)` + `cell`'s `toggleExpanded()` / `isExpanded` | the detail row is one full-width cell; the toggle button stays yours |
|
|
1963
|
+
| `rowProps(row)` (`data-*`) | `getRowProps(row)` | `data-*` only |
|
|
1685
1964
|
| `rowIdField="keywordId"` | `getRowId={(r) => r.keywordId}` | |
|
|
1686
1965
|
|
|
1687
|
-
|
|
1966
|
+
Sticky headers, loading skeletons, pinned columns, the total
|
|
1688
1967
|
row, filtering, column visibility and CSV export are **not ported** — each had
|
|
1689
1968
|
one consumer or none across the six legacy tables, and they belong to a future
|
|
1690
1969
|
`DataGrid`.
|
|
@@ -1826,6 +2105,38 @@ only, so 38 of 67 calls passed a variant that could not say "this worked".
|
|
|
1826
2105
|
|
|
1827
2106
|
A toast rises 16px from below and grows from .96 on the morph spring while it fades in over 240ms. Pressing its close control sinks it out (8px, .97, 180ms) before it leaves the queue; the control dips to .96 under a press and springs back. Reduced motion fades only. React Aria 1.21 unmounts a closed toast at once and has no exit of its own, so a toast that leaves by its timeout or by `toastQueue.close` still leaves without the exit. Not ported: swipe-to-dismiss, the status icon.
|
|
1828
2107
|
|
|
2108
|
+
## ToggleButton
|
|
2109
|
+
|
|
2110
|
+
```tsx
|
|
2111
|
+
<ToggleButton isSelected={unread} onChange={setUnread}>Has new reply</ToggleButton>
|
|
2112
|
+
<ToggleButton iconOnly aria-label="Primary key"><KeyIcon /></ToggleButton>
|
|
2113
|
+
|
|
2114
|
+
<ToggleButtonGroup aria-label="Range" selectionMode="single" disallowEmptySelection defaultSelectedKeys={["7d"]}>
|
|
2115
|
+
<ToggleButton id="7d">7 days</ToggleButton>
|
|
2116
|
+
<ToggleButton id="30d">30 days</ToggleButton>
|
|
2117
|
+
</ToggleButtonGroup>
|
|
2118
|
+
```
|
|
2119
|
+
|
|
2120
|
+
A button that stays pressed, on React Aria's `ToggleButton`: a real `<button>` with
|
|
2121
|
+
`aria-pressed`. Controlled with `isSelected` and `onChange(boolean)`, or uncontrolled
|
|
2122
|
+
with `defaultSelected`. `size` is `sm` (default) or `md`; `iconOnly` makes a square button
|
|
2123
|
+
that holds a glyph and **must** carry an `aria-label` (name the control, not its state).
|
|
2124
|
+
|
|
2125
|
+
- **Selected is not colour alone.** A tint, a `primary-strong` edge and a check mark drawn in
|
|
2126
|
+
`currentColor`, inside the button's own box in the corner, so it never shifts the layout
|
|
2127
|
+
and is not clipped by a table row. An icon-only toggle carries it too.
|
|
2128
|
+
- **Focus** is the button's own 1px border turning dark, no ring. Forced colours get an outline.
|
|
2129
|
+
- It composes with `TooltipTrigger` and `isDisabled` (a disabled toggle keeps its mark but loses the selected tint and edge).
|
|
2130
|
+
- `ToggleButtonGroup` shares a selection and is always a toolbar of `aria-pressed` buttons:
|
|
2131
|
+
one tab stop, arrow keys move focus, Space or Enter toggles (selection never follows focus).
|
|
2132
|
+
`selectionMode="single"` (default) keeps one button pressed (pressing another moves it; add
|
|
2133
|
+
`disallowEmptySelection` to forbid clearing) and Tab enters on the pressed button;
|
|
2134
|
+
`"multiple"` allows any number. It is not `role="radiogroup"`: React Aria's single mode has
|
|
2135
|
+
radio roles but arrows that do not check, which is not the APG radio pattern. For a real
|
|
2136
|
+
radio group use `RadioGroup`. Children need an `id`, their key in `selectedKeys`.
|
|
2137
|
+
- Not a `Button` with `aria-pressed`, a `ChipGroup` or `FilterBar`: those are collections of
|
|
2138
|
+
options with their own input model. This is one on/off question.
|
|
2139
|
+
|
|
1829
2140
|
## Tooltip
|
|
1830
2141
|
|
|
1831
2142
|
```tsx
|
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?: "default" | "success" | "warning" | "info" | "destructive" | 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?: "default" | "success" | "warning" | "info" | "destructive" | 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
|
package/dist/alert/alert.js
CHANGED
|
@@ -213,7 +213,7 @@ const Alert = forwardRef(function Alert({ className, variant, icon: iconProp, ti
|
|
|
213
213
|
ref: content,
|
|
214
214
|
children: [title === void 0 ? null : /* @__PURE__ */ jsx(Title, {
|
|
215
215
|
"data-slot": "alert-title",
|
|
216
|
-
className: "m-0 mb-1 text-sm leading-(--leading-normal) font-medium",
|
|
216
|
+
className: "m-0 mb-1 font-display text-sm leading-(--leading-normal) font-medium",
|
|
217
217
|
children: /* @__PURE__ */ jsx(Swappable, {
|
|
218
218
|
value: title,
|
|
219
219
|
children: title
|
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 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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"}
|
|
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 font-display 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":";;;;;;;;;;;;;;;AA4BA,SAAS,SAAyC,KAAoC;CACpF,OAAO;EAAE,GAAG;EAAK,MAAM,IAAI;CAAQ;AACrC;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,gBAAgB,IAC3B;CAOE;CACA;CAKA;AACF,GACA;CACE,UAAU,EACR,SAAS,SAAS;EAChB,SAAS;EAIT,SAAS;EACT,SAAS;EAMT,aAAa;CACf,CAAC,EACH;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;;;;;;;;;AAUA,MAAa,oBAAoB,IAC/B,4HACA;CACE,UAAU,EACR,SAAS,SAAS;EAChB,SAAS;EACT,SAAS;EACT,SAAS;EACT,aAAa;CACf,CAAC,EACH;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AAcA,SAAS,YAAY,EACnB,UACA,WACA,GAAG,SACyC;CAC5C,MAAM,WAAW,MAAM,kBAAkB,KAAA,KAAa,MAAM,uBAAuB,KAAA;CACnF,OACE,oBAAC,OAAD;EAGE,eAAa,WAAW,KAAA,IAAY;EACpC,GAAI;EAIJ,WAAW,GAAG,oBAAoB,SAAS;EAC3C,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAc;EACd,gBAAe;EAEd;CACE,CAAA;AAET;AACA,SAAS,UAAU,OAAwB;CACzC,OACE,qBAAC,aAAD;EAAa,GAAI;EAAjB,UAAA,CACE,oBAAC,UAAD;GAAQ,IAAG;GAAI,IAAG;GAAI,GAAE;EAAQ,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,sBAAuB,CAAA,CACpB;;AAEjB;AACA,SAAS,aAAa,OAAwB;CAC5C,OACE,qBAAC,aAAD;EAAa,GAAI;EAAjB,UAAA,CACE,oBAAC,UAAD;GAAQ,IAAG;GAAI,IAAG;GAAI,GAAE;EAAQ,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,6BAA8B,CAAA,CAC3B;;AAEjB;AACA,SAAS,aAAa,OAAwB;CAC5C,OACE,qBAAC,aAAD;EAAa,GAAI;EAAjB,UAAA,CACE,oBAAC,QAAD,EAAM,GAAE,8FAA+F,CAAA,GACvG,oBAAC,QAAD,EAAM,GAAE,wBAAyB,CAAA,CACtB;;AAEjB;AACA,SAAS,iBAAiB,OAAwB;CAChD,OACE,qBAAC,aAAD;EAAa,GAAI;EAAjB,UAAA,CACE,oBAAC,UAAD;GAAQ,IAAG;GAAI,IAAG;GAAI,GAAE;EAAQ,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA,CACnB;;AAEjB;AASA,MAAM,eAAe,SAAS;CAC5B,SAAS,oBAAC,WAAD,CAAY,CAAA;CACrB,SAAS,oBAAC,cAAD,CAAe,CAAA;CACxB,SAAS,oBAAC,cAAD,CAAe,CAAA;CACxB,aAAa,oBAAC,kBAAD,CAAmB,CAAA;AAClC,CAAC;;AAkFD,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,OAAO,SACP,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;EACE;EACN,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"}
|
|
@@ -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
|
}
|