@seekalfred/primitives 0.7.4 → 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 +255 -3
- 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.js +1 -1
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/avatar/avatar.d.ts +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/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/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/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/index.d.ts +8 -4
- package/dist/index.js +12 -8
- 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.js +1 -1
- package/dist/lib/selection-control.js +32 -1
- package/dist/lib/selection-control.js.map +1 -1
- package/dist/line-chart/line-chart.d.ts +2 -1
- package/dist/line-chart/line-chart.js +2 -2
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/mention-input/mention-input.js +1 -1
- package/dist/money-input/money-input.js +1 -1
- package/dist/multi-select/multi-select.js +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/progress.d.ts +3 -3
- 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/search-field/search-field.js +1 -1
- package/dist/select/select.d.ts +1 -1
- package/dist/select/select.js +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/shortcut-recorder/shortcut-recorder.js +1 -1
- package/dist/stat-card/stat-card.d.ts +2 -2
- 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 +99 -1
- package/dist/table/table.js +123 -23
- 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/textarea/textarea.js +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
|
|
@@ -1286,9 +1426,27 @@ a decorative `icon`. The group becomes a grid, so the caller sets the columns.
|
|
|
1286
1426
|
together. The icon is `aria-hidden`.
|
|
1287
1427
|
- Selected shows twice: a `primary-strong` edge (StatCard's selected pair) and
|
|
1288
1428
|
the same dot as the default radio, so it never rests on colour alone.
|
|
1289
|
-
- Keyboard is unchanged: one tab stop, arrow keys move and select.
|
|
1290
|
-
|
|
1291
|
-
|
|
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
|
+
```
|
|
1292
1450
|
|
|
1293
1451
|
### Migrating from the legacy RadioGroup
|
|
1294
1452
|
|
|
@@ -1701,6 +1859,66 @@ depends on row *data*, use `rowClassName`.
|
|
|
1701
1859
|
| `density` | `default` · `compact` — `py-2` rows in small type, the product's tight report tables |
|
|
1702
1860
|
| `headerHidden` | hides the header row from sight and keeps it for assistive tech (key/value tables); widths still apply, sorting is off |
|
|
1703
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.
|
|
1704
1922
|
|
|
1705
1923
|
### Using it in alfred-ui
|
|
1706
1924
|
|
|
@@ -1741,6 +1959,8 @@ not worth a palette entry. If you want it exactly, override
|
|
|
1741
1959
|
| `meta.width` / `<colgroup>` / width classes | `Column.width` | one mechanism instead of three |
|
|
1742
1960
|
| `onRowClick(row)` | `onRowAction(row)` | also fires on Enter |
|
|
1743
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 |
|
|
1744
1964
|
| `rowIdField="keywordId"` | `getRowId={(r) => r.keywordId}` | |
|
|
1745
1965
|
|
|
1746
1966
|
Sticky headers, loading skeletons, pinned columns, the total
|
|
@@ -1885,6 +2105,38 @@ only, so 38 of 67 calls passed a variant that could not say "this worked".
|
|
|
1885
2105
|
|
|
1886
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.
|
|
1887
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
|
+
|
|
1888
2140
|
## Tooltip
|
|
1889
2141
|
|
|
1890
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":"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"}
|
|
@@ -75,7 +75,7 @@ const alertDialogTitleBlockVariants = cva("min-w-0 flex-1", { variants: { subtit
|
|
|
75
75
|
true: "",
|
|
76
76
|
false: "flex min-h-9 flex-col justify-center"
|
|
77
77
|
} } });
|
|
78
|
-
const alertDialogTitleVariants = cva("m-0 text-lg leading-(--leading-tight) font-semibold text-foreground");
|
|
78
|
+
const alertDialogTitleVariants = cva("m-0 font-display text-lg leading-(--leading-tight) font-semibold text-foreground");
|
|
79
79
|
const alertDialogCloseVariants = cva([
|
|
80
80
|
"inline-flex size-9 shrink-0 items-center justify-center",
|
|
81
81
|
"rounded-circle box-border cursor-pointer",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert-dialog.js","names":["Button","AriaModalOverlay","AriaModal","AriaDialog","AriaHeading","AriaButton","AriaText"],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Dialog as AriaDialog,\n Heading as AriaHeading,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Text as AriaText,\n} from \"react-aria-components\";\n\nimport { Button, type ButtonProps } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { dialogOverlayVariants, dialogPanelVariants, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * Built from `Dialog`'s own overlay and panel styles and the same React Aria\n * parts, but with its own layout: a header (title, a grey subtitle and a\n * circular close button), a thin divider, the description sentence, an\n * optional `body` of rich content, then the actions.\n * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this\n * shares the tokens and the motion classes instead, so the two cannot drift\n * on radius, scrim or entrance.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - THE CLOSE (X) IS CANCEL. It is `slot=\"close\"`, the same dismiss wiring as\n * a Cancel button, so it goes through the same `onOpenChange(false)` and\n * focus returns to the trigger. It is not a third answer: it is the\n * header's copy of the second one.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there. It is the sentence under the divider.\n *\n * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary\n * button (white, light border) and cancels through `slot=\"close\"`;\n * `AlertDialogAction` is the primary button painted destructive red, because a\n * confirmation here is a destructive one. The legacy `AlertDialogAction`\n * defaulted to the gradient primary and BOTH call sites overrode it back to\n * destructive by hand (`data-table.tsx:146`, `calendar-view.tsx:638`); the red\n * is the default now, so nobody fights it. Plain `Button`s still work in the\n * actions slot for anything else.\n */\nexport interface AlertDialogProps extends Omit<\n DialogProps,\n \"role\" | \"closeButton\" | \"isDismissable\"\n> {\n /**\n * What is about to happen, and whether it can be undone. Rendered as the\n * sentence under the divider, above the `body` slot (if any) and the actions.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** A short grey line under the title, in the header. */\n subtitle?: ReactNode;\n /**\n * Rich content between the description and the actions: a plan comparison,\n * a before/after diff, a quoted reply, a form field or a choice to make.\n *\n * It is NOT part of the accessible description: the screen reader announces\n * the one `description` sentence with the name, and the body is read in\n * place or as focus reaches its controls (each with its own label). So the\n * `description` must say everything needed to answer, including that a\n * choice or field below is required (\"Pick what happens to your data.\").\n * `false`, `true` and `\"\"` (as from `cond && <X />`) render no body.\n */\n body?: ReactNode;\n /**\n * A decorative icon at the start of the header, in a round tile beside the\n * title: a warning triangle before a wide-reaching switch, a trash can before\n * a delete. Hidden from assistive technology, so the title must still say\n * what is at stake. Leave it out and the header is as before.\n */\n icon?: ReactNode;\n /**\n * The colour of the icon's tile, from the status tokens: `neutral` (the\n * default), `warning`, `destructive`, `info` or `success`. It paints the tile\n * only, never the actions. Without an `icon` it does nothing. Mirrored to\n * `data-tone` on the tile.\n */\n tone?: \"neutral\" | \"warning\" | \"destructive\" | \"info\" | \"success\";\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nconst alertDialogActionsVariants = cva(\"flex flex-wrap items-center justify-end gap-2 px-8 pb-8\");\n\n/**\n * The body slot: the description's side padding, so the two share an edge,\n * and the description's own bottom space repeated below it before the actions.\n */\nconst alertDialogBodyVariants = cva(\n \"min-w-0 px-8 pb-6 text-sm leading-(--leading-normal) text-foreground\",\n);\n\n/**\n * The cancel action: the secondary button, wired to dismiss the dialog.\n *\n * @example\n * <AlertDialogCancel>Cancel</AlertDialogCancel>\n */\nexport const AlertDialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogCancel(props, ref) {\n return <Button ref={ref} slot=\"close\" variant=\"secondary\" size=\"sm\" {...props} />;\n },\n);\n\n/**\n * The confirm action: the primary button in the destructive red.\n *\n * It is the primary button with its fill, hover, pressed and label roles\n * pointed at the destructive tokens, so it keeps the primary's press motion\n * and shadow. Variables on the element, rather than a competing background\n * class, so no stylesheet order decides which colour wins.\n *\n * @example\n * <AlertDialogAction onPress={remove}>Delete</AlertDialogAction>\n */\nexport const AlertDialogAction = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogAction({ className, ...props }, ref) {\n return (\n <Button\n ref={ref}\n variant=\"primary\"\n size=\"sm\"\n className={cnAll(\n \"[--color-primary:var(--color-destructive)] [--color-primary-hover:var(--color-destructive-hover)] [--color-primary-pressed:var(--color-destructive-pressed)] [--color-primary-foreground:var(--color-destructive-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst alertDialogHeaderVariants = cva(\n \"flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6\",\n);\n\n/**\n * The header icon's tile: the close button's size, so a one-line title centres\n * on both. Each tone is a subtle tint with the ink the token contrast contract\n * grades on it.\n */\nconst alertDialogIconVariants = cva(\n \"inline-flex size-9 shrink-0 items-center justify-center rounded-circle [&_svg]:pointer-events-none [&_svg]:size-5\",\n {\n variants: {\n tone: {\n neutral: \"bg-muted text-foreground\",\n warning: \"bg-warning-subtle text-warning-foreground\",\n destructive: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n info: \"bg-info-subtle text-info-foreground\",\n success: \"bg-success-subtle text-success-foreground\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * The title column. With no subtitle it is at least the close button's height\n * and centres the title in it, so a one-line title sits level with the close;\n * a longer title grows the column from the top as before. With a subtitle the\n * two lines start at the top of the button, as they always have.\n */\nconst alertDialogTitleBlockVariants = cva(\"min-w-0 flex-1\", {\n variants: {\n subtitle: {\n true: \"\",\n false: \"flex min-h-9 flex-col justify-center\",\n },\n },\n});\n\nconst alertDialogTitleVariants = cva(\n \"m-0 text-lg leading-(--leading-tight) font-semibold text-foreground\",\n);\n\nconst alertDialogCloseVariants = cva([\n \"inline-flex size-9 shrink-0 items-center justify-center\",\n \"rounded-circle box-border cursor-pointer\",\n \"border-(length:--border-width) border-border bg-surface p-0 text-text-secondary\",\n // Fill on hover, a quick press to .96 and a spring release (see calendar-nav-motion).\n \"calendar-nav-motion\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]);\n\n/** A string swaps as a whole when its text changes while the dialog is open. */\nfunction Swappable({ value }: { value: ReactNode }) {\n if (typeof value !== \"string\") return <>{value}</>;\n return (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n );\n}\n\n/** `cond && <X />` hands over `false`; React renders nothing for it, so neither does the slot. */\nfunction isEmpty(node: ReactNode) {\n return node == null || typeof node === \"boolean\" || node === \"\";\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n {\n title,\n subtitle,\n beforeTitle,\n description,\n body,\n icon,\n tone = \"neutral\",\n children,\n className,\n closeLabel = \"Close\",\n overlayClassName,\n entrance = \"rise\",\n ...props\n },\n ref,\n) {\n return (\n <AriaModalOverlay\n {...props}\n isDismissable={false}\n data-slot=\"dialog-overlay\"\n className={cnAll(\n dialogOverlayVariants(),\n entrance === \"rise\" && \"dialog-overlay-motion\",\n overlayClassName,\n )}\n >\n <AriaModal className=\"contents\">\n <AriaDialog\n ref={ref}\n role=\"alertdialog\"\n data-slot=\"dialog\"\n className={cnAll(\n dialogPanelVariants(),\n // The panel has no padding of its own: the header, body and actions pad themselves,\n // so the divider can run edge to edge.\n \"gap-0 p-0\",\n entrance === \"rise\" && \"dialog-panel-motion\",\n className,\n )}\n >\n {beforeTitle}\n <div data-slot=\"alert-dialog-header\" className={alertDialogHeaderVariants()}>\n {isEmpty(icon) ? null : (\n <span\n aria-hidden=\"true\"\n data-slot=\"alert-dialog-icon\"\n data-tone={tone}\n className={alertDialogIconVariants({ tone })}\n >\n {icon}\n </span>\n )}\n <div className={alertDialogTitleBlockVariants({ subtitle: subtitle != null })}>\n {/* slot=\"title\" is what React Aria wires aria-labelledby to. */}\n <AriaHeading\n slot=\"title\"\n data-slot=\"dialog-title\"\n className={alertDialogTitleVariants()}\n >\n <Swappable value={title} />\n </AriaHeading>\n {subtitle == null ? null : (\n <p\n data-slot=\"alert-dialog-subtitle\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n <Swappable value={subtitle} />\n </p>\n )}\n </div>\n {/* slot=\"close\" is the same dismiss wiring as a Cancel button: one handler, focus back to the trigger. */}\n <AriaButton\n slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"alert-dialog-close\"\n className={alertDialogCloseVariants()}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </div>\n <AriaText\n slot=\"description\"\n elementType=\"p\"\n data-slot=\"dialog-description\"\n className=\"m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground\"\n >\n <Swappable value={description} />\n </AriaText>\n {isEmpty(body) ? null : (\n <div data-slot=\"alert-dialog-body\" className={alertDialogBodyVariants()}>\n {body}\n </div>\n )}\n {children == null ? null : (\n <div data-slot=\"alert-dialog-actions\" className={alertDialogActionsVariants()}>\n {children}\n </div>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n});\n"],"mappings":";;;;;;;;;;AAuGA,MAAM,6BAA6B,IAAI,yDAAyD;;;;;AAMhG,MAAM,0BAA0B,IAC9B,sEACF;;;;;;;AAQA,MAAa,oBAAoB,WAC/B,SAAS,kBAAkB,OAAO,KAAK;CACrC,OAAO,oBAACA,UAAD;EAAa;EAAK,MAAK;EAAQ,SAAQ;EAAY,MAAK;EAAK,GAAI;CAAQ,CAAA;AAClF,CACF;;;;;;;;;;;;AAaA,MAAa,oBAAoB,WAC/B,SAAS,kBAAkB,EAAE,WAAW,GAAG,SAAS,KAAK;CACvD,OACE,oBAACA,UAAD;EACO;EACL,SAAQ;EACR,MAAK;EACL,WAAW,MACT,iOACA,SACF;EACA,GAAI;CACL,CAAA;AAEL,CACF;AAEA,MAAM,4BAA4B,IAChC,sFACF;;;;;;AAOA,MAAM,0BAA0B,IAC9B,qHACA;CACE,UAAU,EACR,MAAM;EACJ,SAAS;EACT,SAAS;EACT,aAAa;EACb,MAAM;EACN,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,MAAM,UAAU;AACrC,CACF;;;;;;;AAQA,MAAM,gCAAgC,IAAI,kBAAkB,EAC1D,UAAU,EACR,UAAU;CACR,MAAM;CACN,OAAO;AACT,EACF,EACF,CAAC;AAED,MAAM,2BAA2B,IAC/B,qEACF;AAEA,MAAM,2BAA2B,IAAI;CACnC;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAS,UAAU,EAAE,SAA+B;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,oBAAA,YAAA,EAAA,UAAG,MAAQ,CAAA;CACjD,OACE,oBAAC,UAAD;EAAU,SAAS;EAAO,WAAU;EACjC,UAAA;CACO,CAAA;AAEd;;AAGA,SAAS,QAAQ,MAAiB;CAChC,OAAO,QAAQ,QAAQ,OAAO,SAAS,aAAa,SAAS;AAC/D;AAEA,MAAa,cAAc,WAA0C,SAAS,YAC5E,EACE,OACA,UACA,aACA,aACA,MACA,MACA,OAAO,WACP,UACA,WACA,aAAa,SACb,kBACA,WAAW,QACX,GAAG,SAEL,KACA;CACA,OACE,oBAACC,cAAD;EACE,GAAI;EACJ,eAAe;EACf,aAAU;EACV,WAAW,MACT,sBAAsB,GACtB,aAAa,UAAU,yBACvB,gBACF;EAEA,UAAA,oBAACC,OAAD;GAAW,WAAU;GACnB,UAAA,qBAACC,QAAD;IACO;IACL,MAAK;IACL,aAAU;IACV,WAAW,MACT,oBAAoB,GAGpB,aACA,aAAa,UAAU,uBACvB,SACF;IAXF,UAAA;KAaG;KACD,qBAAC,OAAD;MAAK,aAAU;MAAsB,WAAW,0BAA0B;MAA1E,UAAA;OACG,QAAQ,IAAI,IAAI,OACf,oBAAC,QAAD;QACE,eAAY;QACZ,aAAU;QACV,aAAW;QACX,WAAW,wBAAwB,EAAE,KAAK,CAAC;QAE1C,UAAA;OACG,CAAA;OAER,qBAAC,OAAD;QAAK,WAAW,8BAA8B,EAAE,UAAU,YAAY,KAAK,CAAC;QAA5E,UAAA,CAEE,oBAACC,SAAD;SACE,MAAK;SACL,aAAU;SACV,WAAW,yBAAyB;SAEpC,UAAA,oBAAC,WAAD,EAAW,OAAO,MAAQ,CAAA;QACf,CAAA,GACZ,YAAY,OAAO,OAClB,oBAAC,KAAD;SACE,aAAU;SACV,WAAU;SAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,SAAW,CAAA;QAC5B,CAAA,CAEF;;OAEL,oBAACC,QAAD;QACE,MAAK;QACL,cAAY;QACZ,aAAU;QACV,WAAW,yBAAyB;QAEpC,UAAA,oBAAC,OAAD;SACE,eAAY;SACZ,SAAQ;SACR,MAAK;SACL,QAAO;SACP,aAAY;SACZ,eAAc;SAEd,UAAA,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA;QAC3B,CAAA;OACK,CAAA;MACT;;KACL,oBAACC,MAAD;MACE,MAAK;MACL,aAAY;MACZ,aAAU;MACV,WAAU;MAEV,UAAA,oBAAC,WAAD,EAAW,OAAO,YAAc,CAAA;KACxB,CAAA;KACT,QAAQ,IAAI,IAAI,OACf,oBAAC,OAAD;MAAK,aAAU;MAAoB,WAAW,wBAAwB;MACnE,UAAA;KACE,CAAA;KAEN,YAAY,OAAO,OAClB,oBAAC,OAAD;MAAK,aAAU;MAAuB,WAAW,2BAA2B;MACzE;KACE,CAAA;IAEG;;EACH,CAAA;CACK,CAAA;AAEtB,CAAC"}
|
|
1
|
+
{"version":3,"file":"alert-dialog.js","names":["Button","AriaModalOverlay","AriaModal","AriaDialog","AriaHeading","AriaButton","AriaText"],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n Dialog as AriaDialog,\n Heading as AriaHeading,\n Modal as AriaModal,\n ModalOverlay as AriaModalOverlay,\n Text as AriaText,\n} from \"react-aria-components\";\n\nimport { Button, type ButtonProps } from \"../button/button.js\";\nimport { cnAll } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { dialogOverlayVariants, dialogPanelVariants, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * Built from `Dialog`'s own overlay and panel styles and the same React Aria\n * parts, but with its own layout: a header (title, a grey subtitle and a\n * circular close button), a thin divider, the description sentence, an\n * optional `body` of rich content, then the actions.\n * The legacy `alert-dialog.tsx` was a 157-line clone of `dialog.tsx`; this\n * shares the tokens and the motion classes instead, so the two cannot drift\n * on radius, scrim or entrance.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - THE CLOSE (X) IS CANCEL. It is `slot=\"close\"`, the same dismiss wiring as\n * a Cancel button, so it goes through the same `onOpenChange(false)` and\n * focus returns to the trigger. It is not a third answer: it is the\n * header's copy of the second one.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there. It is the sentence under the divider.\n *\n * THE TWO ACTIONS MUST NEVER LOOK ALIKE. `AlertDialogCancel` is the secondary\n * button (white, light border) and cancels through `slot=\"close\"`;\n * `AlertDialogAction` is the primary button painted destructive red, because a\n * confirmation here is a destructive one. The legacy `AlertDialogAction`\n * defaulted to the gradient primary and BOTH call sites overrode it back to\n * destructive by hand (`data-table.tsx:146`, `calendar-view.tsx:638`); the red\n * is the default now, so nobody fights it. Plain `Button`s still work in the\n * actions slot for anything else.\n */\nexport interface AlertDialogProps extends Omit<\n DialogProps,\n \"role\" | \"closeButton\" | \"isDismissable\"\n> {\n /**\n * What is about to happen, and whether it can be undone. Rendered as the\n * sentence under the divider, above the `body` slot (if any) and the actions.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** A short grey line under the title, in the header. */\n subtitle?: ReactNode;\n /**\n * Rich content between the description and the actions: a plan comparison,\n * a before/after diff, a quoted reply, a form field or a choice to make.\n *\n * It is NOT part of the accessible description: the screen reader announces\n * the one `description` sentence with the name, and the body is read in\n * place or as focus reaches its controls (each with its own label). So the\n * `description` must say everything needed to answer, including that a\n * choice or field below is required (\"Pick what happens to your data.\").\n * `false`, `true` and `\"\"` (as from `cond && <X />`) render no body.\n */\n body?: ReactNode;\n /**\n * A decorative icon at the start of the header, in a round tile beside the\n * title: a warning triangle before a wide-reaching switch, a trash can before\n * a delete. Hidden from assistive technology, so the title must still say\n * what is at stake. Leave it out and the header is as before.\n */\n icon?: ReactNode;\n /**\n * The colour of the icon's tile, from the status tokens: `neutral` (the\n * default), `warning`, `destructive`, `info` or `success`. It paints the tile\n * only, never the actions. Without an `icon` it does nothing. Mirrored to\n * `data-tone` on the tile.\n */\n tone?: \"neutral\" | \"warning\" | \"destructive\" | \"info\" | \"success\";\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nconst alertDialogActionsVariants = cva(\"flex flex-wrap items-center justify-end gap-2 px-8 pb-8\");\n\n/**\n * The body slot: the description's side padding, so the two share an edge,\n * and the description's own bottom space repeated below it before the actions.\n */\nconst alertDialogBodyVariants = cva(\n \"min-w-0 px-8 pb-6 text-sm leading-(--leading-normal) text-foreground\",\n);\n\n/**\n * The cancel action: the secondary button, wired to dismiss the dialog.\n *\n * @example\n * <AlertDialogCancel>Cancel</AlertDialogCancel>\n */\nexport const AlertDialogCancel = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogCancel(props, ref) {\n return <Button ref={ref} slot=\"close\" variant=\"secondary\" size=\"sm\" {...props} />;\n },\n);\n\n/**\n * The confirm action: the primary button in the destructive red.\n *\n * It is the primary button with its fill, hover, pressed and label roles\n * pointed at the destructive tokens, so it keeps the primary's press motion\n * and shadow. Variables on the element, rather than a competing background\n * class, so no stylesheet order decides which colour wins.\n *\n * @example\n * <AlertDialogAction onPress={remove}>Delete</AlertDialogAction>\n */\nexport const AlertDialogAction = forwardRef<HTMLButtonElement, ButtonProps>(\n function AlertDialogAction({ className, ...props }, ref) {\n return (\n <Button\n ref={ref}\n variant=\"primary\"\n size=\"sm\"\n className={cnAll(\n \"[--color-primary:var(--color-destructive)] [--color-primary-hover:var(--color-destructive-hover)] [--color-primary-pressed:var(--color-destructive-pressed)] [--color-primary-foreground:var(--color-destructive-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n },\n);\n\nconst alertDialogHeaderVariants = cva(\n \"flex items-start gap-4 border-b-(length:--border-width) border-border px-8 pt-8 pb-6\",\n);\n\n/**\n * The header icon's tile: the close button's size, so a one-line title centres\n * on both. Each tone is a subtle tint with the ink the token contrast contract\n * grades on it.\n */\nconst alertDialogIconVariants = cva(\n \"inline-flex size-9 shrink-0 items-center justify-center rounded-circle [&_svg]:pointer-events-none [&_svg]:size-5\",\n {\n variants: {\n tone: {\n neutral: \"bg-muted text-foreground\",\n warning: \"bg-warning-subtle text-warning-foreground\",\n destructive: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n info: \"bg-info-subtle text-info-foreground\",\n success: \"bg-success-subtle text-success-foreground\",\n },\n },\n defaultVariants: { tone: \"neutral\" },\n },\n);\n\n/**\n * The title column. With no subtitle it is at least the close button's height\n * and centres the title in it, so a one-line title sits level with the close;\n * a longer title grows the column from the top as before. With a subtitle the\n * two lines start at the top of the button, as they always have.\n */\nconst alertDialogTitleBlockVariants = cva(\"min-w-0 flex-1\", {\n variants: {\n subtitle: {\n true: \"\",\n false: \"flex min-h-9 flex-col justify-center\",\n },\n },\n});\n\nconst alertDialogTitleVariants = cva(\n \"m-0 font-display text-lg leading-(--leading-tight) font-semibold text-foreground\",\n);\n\nconst alertDialogCloseVariants = cva([\n \"inline-flex size-9 shrink-0 items-center justify-center\",\n \"rounded-circle box-border cursor-pointer\",\n \"border-(length:--border-width) border-border bg-surface p-0 text-text-secondary\",\n // Fill on hover, a quick press to .96 and a spring release (see calendar-nav-motion).\n \"calendar-nav-motion\",\n \"data-[hovered]:bg-muted data-[hovered]:text-foreground\",\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4\",\n]);\n\n/** A string swaps as a whole when its text changes while the dialog is open. */\nfunction Swappable({ value }: { value: ReactNode }) {\n if (typeof value !== \"string\") return <>{value}</>;\n return (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n );\n}\n\n/** `cond && <X />` hands over `false`; React renders nothing for it, so neither does the slot. */\nfunction isEmpty(node: ReactNode) {\n return node == null || typeof node === \"boolean\" || node === \"\";\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n {\n title,\n subtitle,\n beforeTitle,\n description,\n body,\n icon,\n tone = \"neutral\",\n children,\n className,\n closeLabel = \"Close\",\n overlayClassName,\n entrance = \"rise\",\n ...props\n },\n ref,\n) {\n return (\n <AriaModalOverlay\n {...props}\n isDismissable={false}\n data-slot=\"dialog-overlay\"\n className={cnAll(\n dialogOverlayVariants(),\n entrance === \"rise\" && \"dialog-overlay-motion\",\n overlayClassName,\n )}\n >\n <AriaModal className=\"contents\">\n <AriaDialog\n ref={ref}\n role=\"alertdialog\"\n data-slot=\"dialog\"\n className={cnAll(\n dialogPanelVariants(),\n // The panel has no padding of its own: the header, body and actions pad themselves,\n // so the divider can run edge to edge.\n \"gap-0 p-0\",\n entrance === \"rise\" && \"dialog-panel-motion\",\n className,\n )}\n >\n {beforeTitle}\n <div data-slot=\"alert-dialog-header\" className={alertDialogHeaderVariants()}>\n {isEmpty(icon) ? null : (\n <span\n aria-hidden=\"true\"\n data-slot=\"alert-dialog-icon\"\n data-tone={tone}\n className={alertDialogIconVariants({ tone })}\n >\n {icon}\n </span>\n )}\n <div className={alertDialogTitleBlockVariants({ subtitle: subtitle != null })}>\n {/* slot=\"title\" is what React Aria wires aria-labelledby to. */}\n <AriaHeading\n slot=\"title\"\n data-slot=\"dialog-title\"\n className={alertDialogTitleVariants()}\n >\n <Swappable value={title} />\n </AriaHeading>\n {subtitle == null ? null : (\n <p\n data-slot=\"alert-dialog-subtitle\"\n className=\"m-0 mt-1 text-sm leading-(--leading-normal) text-text-secondary\"\n >\n <Swappable value={subtitle} />\n </p>\n )}\n </div>\n {/* slot=\"close\" is the same dismiss wiring as a Cancel button: one handler, focus back to the trigger. */}\n <AriaButton\n slot=\"close\"\n aria-label={closeLabel}\n data-slot=\"alert-dialog-close\"\n className={alertDialogCloseVariants()}\n >\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n >\n <path d=\"m4 4 8 8M12 4l-8 8\" />\n </svg>\n </AriaButton>\n </div>\n <AriaText\n slot=\"description\"\n elementType=\"p\"\n data-slot=\"dialog-description\"\n className=\"m-0 block px-8 py-6 text-sm leading-(--leading-normal) text-foreground\"\n >\n <Swappable value={description} />\n </AriaText>\n {isEmpty(body) ? null : (\n <div data-slot=\"alert-dialog-body\" className={alertDialogBodyVariants()}>\n {body}\n </div>\n )}\n {children == null ? null : (\n <div data-slot=\"alert-dialog-actions\" className={alertDialogActionsVariants()}>\n {children}\n </div>\n )}\n </AriaDialog>\n </AriaModal>\n </AriaModalOverlay>\n );\n});\n"],"mappings":"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"}
|
package/dist/avatar/avatar.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
18
18
|
*/
|
|
19
19
|
export declare const avatarVariants: (props?: ({
|
|
20
20
|
size?: "sm" | "md" | "lg" | "xs" | "xl" | null | undefined;
|
|
21
|
-
tone?: "
|
|
21
|
+
tone?: "primary" | "neutral" | "brand" | null | undefined;
|
|
22
22
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
23
23
|
/**
|
|
24
24
|
* A user avatar.
|
package/dist/badge/badge.d.ts
CHANGED
|
@@ -36,10 +36,11 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
|
36
36
|
* across every shadcn control.
|
|
37
37
|
*/
|
|
38
38
|
export declare const badgeVariants: (props?: ({
|
|
39
|
-
tone?: "
|
|
39
|
+
tone?: "primary" | "neutral" | "success" | "warning" | "danger" | "info" | null | undefined;
|
|
40
40
|
appearance?: "outline" | "solid" | "soft" | null | undefined;
|
|
41
41
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
42
42
|
shape?: "rounded" | "pill" | null | undefined;
|
|
43
|
+
wrap?: boolean | null | undefined;
|
|
43
44
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
44
45
|
export interface BadgeProps extends ComponentPropsWithoutRef<"span">, VariantProps<typeof badgeVariants> {
|
|
45
46
|
/**
|
|
@@ -48,6 +49,20 @@ export interface BadgeProps extends ComponentPropsWithoutRef<"span">, VariantPro
|
|
|
48
49
|
* `aria-hidden` when the label already says what it means.
|
|
49
50
|
*/
|
|
50
51
|
icon?: ReactNode;
|
|
52
|
+
/**
|
|
53
|
+
* Let the label wrap onto more lines instead of staying on one. For a tag in
|
|
54
|
+
* a table cell or a narrow column: the badge shrinks to its container, the
|
|
55
|
+
* label breaks (even a long word with no spaces), the text starts at the
|
|
56
|
+
* leading edge and the height grows with the lines. Off by default, and then
|
|
57
|
+
* a badge is one line exactly as before. With a long label prefer
|
|
58
|
+
* `shape="rounded"`: a multi-line `pill` has very round ends.
|
|
59
|
+
*
|
|
60
|
+
* The width does not spring to a new label while this is on (that morph
|
|
61
|
+
* measures the unwrapped width).
|
|
62
|
+
*
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
wrap?: boolean;
|
|
51
66
|
}
|
|
52
67
|
export declare const Badge: import("react").ForwardRefExoticComponent<BadgeProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
53
68
|
//#endregion
|