@nanisoft/prism-ui 0.10.1 → 0.13.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/LICENSE +21 -21
- package/dist/.tsbuildinfo +1 -1
- package/dist/blocks/app-shell-01/app-shell.js +4 -4
- package/dist/blocks/app-shell-01/index.d.ts +2 -2
- package/dist/blocks/app-shell-01/index.js +1 -1
- package/dist/blocks/auth-form-01/auth-form.d.ts +11 -1
- package/dist/blocks/auth-form-01/auth-form.d.ts.map +1 -1
- package/dist/blocks/auth-form-01/auth-form.js +11 -8
- package/dist/blocks/auth-form-01/index.d.ts +2 -2
- package/dist/blocks/auth-form-01/index.js +1 -1
- package/dist/blocks/cta-01/cta.d.ts +26 -21
- package/dist/blocks/cta-01/cta.d.ts.map +1 -1
- package/dist/blocks/cta-01/cta.js +2 -2
- package/dist/blocks/cta-01/index.d.ts +2 -2
- package/dist/blocks/cta-01/index.js +1 -1
- package/dist/blocks/data-table-01/data-table.js +7 -7
- package/dist/blocks/data-table-01/index.d.ts +2 -2
- package/dist/blocks/data-table-01/index.js +1 -1
- package/dist/blocks/empty-state-01/empty-state.d.ts +143 -0
- package/dist/blocks/empty-state-01/empty-state.d.ts.map +1 -0
- package/dist/blocks/empty-state-01/empty-state.js +104 -0
- package/dist/blocks/empty-state-01/index.d.ts +3 -0
- package/dist/blocks/empty-state-01/index.d.ts.map +1 -0
- package/dist/blocks/empty-state-01/index.js +1 -0
- package/dist/blocks/feature-grid-01/feature-grid.d.ts +28 -1
- package/dist/blocks/feature-grid-01/feature-grid.d.ts.map +1 -1
- package/dist/blocks/feature-grid-01/feature-grid.js +34 -3
- package/dist/blocks/feature-grid-01/index.d.ts +2 -2
- package/dist/blocks/feature-grid-01/index.js +1 -1
- package/dist/blocks/hero-01/hero.d.ts +49 -17
- package/dist/blocks/hero-01/hero.d.ts.map +1 -1
- package/dist/blocks/hero-01/hero.js +14 -9
- package/dist/blocks/hero-01/index.d.ts +2 -1
- package/dist/blocks/hero-01/index.d.ts.map +1 -1
- package/dist/blocks/hero-01/index.js +1 -1
- package/dist/blocks/index.d.ts +20 -20
- package/dist/blocks/index.js +19 -19
- package/dist/blocks/instrument-panel-01/index.d.ts +2 -2
- package/dist/blocks/instrument-panel-01/index.js +1 -1
- package/dist/blocks/instrument-panel-01/instrument-panel.js +1 -1
- package/dist/blocks/logo-strip-01/index.d.ts +2 -2
- package/dist/blocks/logo-strip-01/index.js +1 -1
- package/dist/blocks/logo-strip-01/logo-strip.d.ts +1 -1
- package/dist/blocks/logo-strip-01/logo-strip.js +2 -2
- package/dist/blocks/note-grid-01/index.d.ts +2 -2
- package/dist/blocks/note-grid-01/index.js +1 -1
- package/dist/blocks/note-grid-01/note-grid.d.ts +1 -1
- package/dist/blocks/note-grid-01/note-grid.js +1 -1
- package/dist/blocks/page-header-01/index.d.ts +2 -2
- package/dist/blocks/page-header-01/index.js +1 -1
- package/dist/blocks/page-header-01/page-header.d.ts +1 -1
- package/dist/blocks/page-header-01/page-header.js +4 -4
- package/dist/blocks/pricing-01/index.d.ts +1 -1
- package/dist/blocks/pricing-01/index.js +1 -1
- package/dist/blocks/pricing-01/pricing.d.ts +8 -1
- package/dist/blocks/pricing-01/pricing.d.ts.map +1 -1
- package/dist/blocks/pricing-01/pricing.js +15 -5
- package/dist/blocks/process-flow-01/index.d.ts +3 -0
- package/dist/blocks/process-flow-01/index.d.ts.map +1 -0
- package/dist/blocks/process-flow-01/index.js +1 -0
- package/dist/blocks/process-flow-01/process-flow.d.ts +118 -0
- package/dist/blocks/process-flow-01/process-flow.d.ts.map +1 -0
- package/dist/blocks/process-flow-01/process-flow.js +71 -0
- package/dist/blocks/process-rail-01/index.d.ts +2 -2
- package/dist/blocks/process-rail-01/index.js +1 -1
- package/dist/blocks/process-rail-01/process-rail.d.ts +1 -1
- package/dist/blocks/process-rail-01/process-rail.js +2 -2
- package/dist/blocks/product-grid-01/index.d.ts +2 -2
- package/dist/blocks/product-grid-01/index.js +1 -1
- package/dist/blocks/product-grid-01/product-grid.d.ts +37 -5
- package/dist/blocks/product-grid-01/product-grid.d.ts.map +1 -1
- package/dist/blocks/product-grid-01/product-grid.js +20 -6
- package/dist/blocks/run-console-01/index.d.ts +3 -0
- package/dist/blocks/run-console-01/index.d.ts.map +1 -0
- package/dist/blocks/run-console-01/index.js +1 -0
- package/dist/blocks/run-console-01/run-console.d.ts +111 -0
- package/dist/blocks/run-console-01/run-console.d.ts.map +1 -0
- package/dist/blocks/run-console-01/run-console.js +48 -0
- package/dist/blocks/settings-panel-01/index.d.ts +2 -2
- package/dist/blocks/settings-panel-01/index.js +1 -1
- package/dist/blocks/settings-panel-01/settings-panel.d.ts +11 -1
- package/dist/blocks/settings-panel-01/settings-panel.d.ts.map +1 -1
- package/dist/blocks/settings-panel-01/settings-panel.js +15 -10
- package/dist/blocks/site-footer/index.d.ts +2 -2
- package/dist/blocks/site-footer/index.js +1 -1
- package/dist/blocks/site-footer/site-footer.d.ts +1 -1
- package/dist/blocks/site-footer/site-footer.js +2 -2
- package/dist/blocks/site-header/index.d.ts +2 -2
- package/dist/blocks/site-header/index.js +1 -1
- package/dist/blocks/site-header/site-header.d.ts +2 -2
- package/dist/blocks/site-header/site-header.js +3 -3
- package/dist/blocks/site-navbar/index.d.ts +3 -0
- package/dist/blocks/site-navbar/index.d.ts.map +1 -0
- package/dist/blocks/site-navbar/index.js +1 -0
- package/dist/blocks/site-navbar/mobile-nav.d.ts +48 -0
- package/dist/blocks/site-navbar/mobile-nav.d.ts.map +1 -0
- package/dist/blocks/site-navbar/mobile-nav.js +35 -0
- package/dist/blocks/site-navbar/mode-toggle.d.ts +39 -0
- package/dist/blocks/site-navbar/mode-toggle.d.ts.map +1 -0
- package/dist/blocks/site-navbar/mode-toggle.js +39 -0
- package/dist/blocks/site-navbar/navbar-controls.d.ts +79 -0
- package/dist/blocks/site-navbar/navbar-controls.d.ts.map +1 -0
- package/dist/blocks/site-navbar/navbar-controls.js +40 -0
- package/dist/blocks/site-navbar/search-entry.d.ts +29 -0
- package/dist/blocks/site-navbar/search-entry.d.ts.map +1 -0
- package/dist/blocks/site-navbar/search-entry.js +52 -0
- package/dist/blocks/site-navbar/site-navbar.d.ts +212 -0
- package/dist/blocks/site-navbar/site-navbar.d.ts.map +1 -0
- package/dist/blocks/site-navbar/site-navbar.js +61 -0
- package/dist/blocks/site-navbar/sites-menu.d.ts +40 -0
- package/dist/blocks/site-navbar/sites-menu.d.ts.map +1 -0
- package/dist/blocks/site-navbar/sites-menu.js +48 -0
- package/dist/blocks/site-navbar/theme-menu.d.ts +47 -0
- package/dist/blocks/site-navbar/theme-menu.d.ts.map +1 -0
- package/dist/blocks/site-navbar/theme-menu.js +55 -0
- package/dist/blocks/stack-grid-01/index.d.ts +2 -2
- package/dist/blocks/stack-grid-01/index.js +1 -1
- package/dist/blocks/stack-grid-01/stack-grid.d.ts +42 -11
- package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -1
- package/dist/blocks/stack-grid-01/stack-grid.js +15 -7
- package/dist/blocks/stats-01/index.d.ts +9 -1
- package/dist/blocks/stats-01/index.d.ts.map +1 -1
- package/dist/blocks/stats-01/index.js +8 -1
- package/dist/blocks/stats-01/stats.d.ts +1 -1
- package/dist/blocks/stats-01/stats.js +2 -2
- package/dist/blocks/status-ledger-01/index.d.ts +2 -2
- package/dist/blocks/status-ledger-01/index.js +1 -1
- package/dist/blocks/status-ledger-01/status-ledger.d.ts +1 -1
- package/dist/blocks/status-ledger-01/status-ledger.js +2 -2
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +473 -1
- package/dist/components/index.d.ts +104 -66
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +74 -40
- package/dist/components/ui/accordion.js +1 -1
- package/dist/components/ui/alert-dialog.d.ts +119 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +82 -0
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/aspect-ratio.d.ts +60 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +61 -0
- package/dist/components/ui/avatar.js +1 -1
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumb.js +1 -1
- package/dist/components/ui/button-group.d.ts +68 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button-group.js +54 -0
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/calendar.d.ts +158 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +216 -0
- package/dist/components/ui/card.d.ts +18 -6
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/card.js +23 -8
- package/dist/components/ui/carousel.d.ts +118 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +152 -0
- package/dist/components/ui/chart-frame.d.ts +216 -0
- package/dist/components/ui/chart-frame.d.ts.map +1 -0
- package/dist/components/ui/chart-frame.js +286 -0
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/collapsible.d.ts +97 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +54 -0
- package/dist/components/ui/combobox.d.ts +141 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +243 -0
- package/dist/components/ui/command-palette.d.ts +106 -0
- package/dist/components/ui/command-palette.d.ts.map +1 -0
- package/dist/components/ui/command-palette.js +176 -0
- package/dist/components/ui/command.d.ts +82 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +37 -0
- package/dist/components/ui/context-menu.d.ts +151 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +97 -0
- package/dist/components/ui/cta-link.js +1 -1
- package/dist/components/ui/date-picker.d.ts +111 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +74 -0
- package/dist/components/ui/diagram.d.ts +84 -15
- package/dist/components/ui/diagram.d.ts.map +1 -1
- package/dist/components/ui/diagram.js +77 -7
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/diff.d.ts +105 -0
- package/dist/components/ui/diff.d.ts.map +1 -0
- package/dist/components/ui/diff.js +113 -0
- package/dist/components/ui/dropdown-menu.d.ts +18 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -1
- package/dist/components/ui/fact-list.js +1 -1
- package/dist/components/ui/field.js +1 -1
- package/dist/components/ui/form.d.ts +151 -0
- package/dist/components/ui/form.d.ts.map +1 -0
- package/dist/components/ui/form.js +73 -0
- package/dist/components/ui/hover-card.d.ts +105 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +49 -0
- package/dist/components/ui/input-group.d.ts +99 -0
- package/dist/components/ui/input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group.js +83 -0
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/item.d.ts +113 -0
- package/dist/components/ui/item.d.ts.map +1 -0
- package/dist/components/ui/item.js +61 -0
- package/dist/components/ui/kbd.js +1 -1
- package/dist/components/ui/label.d.ts +58 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +29 -0
- package/dist/components/ui/live-region.d.ts +9 -0
- package/dist/components/ui/live-region.d.ts.map +1 -1
- package/dist/components/ui/live-region.js +10 -2
- package/dist/components/ui/mark.js +1 -1
- package/dist/components/ui/menubar.d.ts +197 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +109 -0
- package/dist/components/ui/meter.d.ts +121 -0
- package/dist/components/ui/meter.d.ts.map +1 -0
- package/dist/components/ui/meter.js +82 -0
- package/dist/components/ui/native-select.d.ts +72 -0
- package/dist/components/ui/native-select.d.ts.map +1 -0
- package/dist/components/ui/native-select.js +55 -0
- package/dist/components/ui/navigation-menu.d.ts +186 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +94 -0
- package/dist/components/ui/number-field.d.ts +111 -0
- package/dist/components/ui/number-field.d.ts.map +1 -0
- package/dist/components/ui/number-field.js +84 -0
- package/dist/components/ui/one-time-code.d.ts +127 -0
- package/dist/components/ui/one-time-code.d.ts.map +1 -0
- package/dist/components/ui/one-time-code.js +57 -0
- package/dist/components/ui/pagination.js +1 -1
- package/dist/components/ui/popover.js +1 -1
- package/dist/components/ui/product-mark.d.ts +1 -1
- package/dist/components/ui/product-mark.js +1 -1
- package/dist/components/ui/product-switcher.d.ts +1 -1
- package/dist/components/ui/product-switcher.js +2 -2
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/prose.js +1 -1
- package/dist/components/ui/pulse-graph.js +1 -1
- package/dist/components/ui/pulse-series.js +1 -1
- package/dist/components/ui/radio-group.js +1 -1
- package/dist/components/ui/resizable.d.ts +129 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +205 -0
- package/dist/components/ui/scroll-area.d.ts +97 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +68 -0
- package/dist/components/ui/search-dialog.d.ts +139 -0
- package/dist/components/ui/search-dialog.d.ts.map +1 -0
- package/dist/components/ui/search-dialog.js +178 -0
- package/dist/components/ui/section.d.ts +26 -0
- package/dist/components/ui/section.d.ts.map +1 -1
- package/dist/components/ui/section.js +32 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/separator.js +1 -1
- package/dist/components/ui/sheet.d.ts +103 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +58 -0
- package/dist/components/ui/sidebar.d.ts +228 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +172 -0
- package/dist/components/ui/signal-field.js +1 -1
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/spinner.d.ts +79 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +79 -0
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/table-sort.d.ts +97 -0
- package/dist/components/ui/table-sort.d.ts.map +1 -0
- package/dist/components/ui/table-sort.js +102 -0
- package/dist/components/ui/table.js +1 -1
- package/dist/components/ui/tabs.js +1 -1
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/timeline.d.ts +106 -0
- package/dist/components/ui/timeline.d.ts.map +1 -0
- package/dist/components/ui/timeline.js +111 -0
- package/dist/components/ui/toast.d.ts +219 -0
- package/dist/components/ui/toast.d.ts.map +1 -0
- package/dist/components/ui/toast.js +270 -0
- package/dist/components/ui/toggle-group.d.ts +118 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +194 -0
- package/dist/components/ui/toggle.d.ts +73 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +44 -0
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tree.js +1 -1
- package/dist/components/ui/typography.js +1 -1
- package/dist/fonts/Inter-OFL.txt +93 -93
- package/dist/index.d.ts +72 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -60
- package/dist/lib/rank.d.ts +69 -0
- package/dist/lib/rank.d.ts.map +1 -0
- package/dist/lib/rank.js +77 -0
- package/dist/live/index.d.ts +22 -0
- package/dist/live/index.d.ts.map +1 -0
- package/dist/live/index.js +20 -0
- package/dist/live/run-stream-01/index.d.ts +3 -0
- package/dist/live/run-stream-01/index.d.ts.map +1 -0
- package/dist/live/run-stream-01/index.js +1 -0
- package/dist/live/run-stream-01/run-stream.d.ts +125 -0
- package/dist/live/run-stream-01/run-stream.d.ts.map +1 -0
- package/dist/live/run-stream-01/run-stream.js +111 -0
- package/dist/pages/auth-page/index.d.ts +15 -2
- package/dist/pages/auth-page/index.d.ts.map +1 -1
- package/dist/pages/auth-page/index.js +8 -4
- package/dist/pages/blog-post-page/index.js +5 -5
- package/dist/pages/dashboard-page/index.d.ts +4 -4
- package/dist/pages/dashboard-page/index.js +4 -4
- package/dist/pages/docs-shell/docs-shell.d.ts +37 -4
- package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
- package/dist/pages/docs-shell/docs-shell.js +107 -16
- package/dist/pages/docs-shell/index.d.ts +2 -2
- package/dist/pages/docs-shell/index.js +1 -1
- package/dist/pages/index.d.ts +14 -14
- package/dist/pages/index.js +7 -7
- package/dist/pages/marketing-page/index.d.ts +6 -6
- package/dist/pages/marketing-page/index.js +5 -5
- package/dist/pages/not-found-page/index.js +4 -4
- package/dist/pages/settings-page/index.d.ts +3 -3
- package/dist/pages/settings-page/index.js +4 -4
- package/dist/provider/index.d.ts +4 -4
- package/dist/provider/index.js +2 -2
- package/dist/provider/provider.d.ts +1 -1
- package/dist/provider/provider.js +1 -1
- package/dist/provider/theme-script.d.ts +1 -1
- package/dist/provider/theme-script.js +1 -1
- package/dist/styles.css +689 -0
- package/package.json +9 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* The surface and the four tones, and nothing else.
|
|
5
|
+
*
|
|
6
|
+
* Every tone states its own fill AND its own ink, and that is the
|
|
7
|
+
* `check-variant-ink` rule rather than a style preference. A toast is the one
|
|
8
|
+
* surface in this package that is a *coloured* surface with body text sitting
|
|
9
|
+
* on it, so an inherited ink is not the page's foreground by accident: it is
|
|
10
|
+
* whatever happened to be behind the viewport, and the failure `Cta01`
|
|
11
|
+
* measured was 1.01:1 on a filled `bg-primary` band. A tone that set only a
|
|
12
|
+
* fill would be the same button in a different colour.
|
|
13
|
+
*/
|
|
14
|
+
declare const toastVariants: (props?: ({
|
|
15
|
+
variant?: "default" | "destructive" | "success" | "warning" | null | undefined;
|
|
16
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
17
|
+
/**
|
|
18
|
+
* The props the Toast root accepts.
|
|
19
|
+
*
|
|
20
|
+
* Every reader-facing string on this surface is a prop, and the reason is the
|
|
21
|
+
* library's reason: this package is installed into four products in at least
|
|
22
|
+
* two languages, and a toast that shipped a close button named in English
|
|
23
|
+
* would be a control those products cannot localise.
|
|
24
|
+
*/
|
|
25
|
+
export interface ToastProps extends Omit<ComponentProps<'div'>, 'children' | 'title'>, VariantProps<typeof toastVariants> {
|
|
26
|
+
/**
|
|
27
|
+
* The short line: what happened, in the fewest words that are still true.
|
|
28
|
+
*
|
|
29
|
+
* A `<p>` and not a heading, deliberately. A notification that appears and
|
|
30
|
+
* then disappears must not put an entry in the page outline, because a reader
|
|
31
|
+
* who navigates by heading lands on the document, selects "Saved", and finds
|
|
32
|
+
* nothing there any more. `Alert` is a heading for the same words because an
|
|
33
|
+
* Alert is on the page for as long as the condition is.
|
|
34
|
+
*
|
|
35
|
+
* A `ReactNode`, so a caller can put an emphasis or an icon inside it, and it
|
|
36
|
+
* is also why the native `title` attribute is removed from the pass-through
|
|
37
|
+
* rather than forwarded. The two names are the same words with opposite
|
|
38
|
+
* consequences: a toast's title is the notification itself, read by everyone
|
|
39
|
+
* at once, while `title` is a tooltip that appears under the pointer to
|
|
40
|
+
* whoever happens to hover, and nothing else. A toast that forwarded both
|
|
41
|
+
* would say the same sentence twice to a mouse reader and once to a keyboard
|
|
42
|
+
* reader, in a surface whose whole purpose is to say a thing once.
|
|
43
|
+
*/
|
|
44
|
+
title?: ReactNode;
|
|
45
|
+
/**
|
|
46
|
+
* The sentence under the title, when there is one.
|
|
47
|
+
*
|
|
48
|
+
* Separate from the title for the same reason `Alert` separates them: a
|
|
49
|
+
* title is scanned and a sentence is read, and a toast that merged them
|
|
50
|
+
* forces a reader to read a heading.
|
|
51
|
+
*/
|
|
52
|
+
description?: ReactNode;
|
|
53
|
+
/**
|
|
54
|
+
* The one control the toast offers, as a slot.
|
|
55
|
+
*
|
|
56
|
+
* A `ReactNode` rather than a label and a handler, and that is the design
|
|
57
|
+
* decision rather than a convenience: **a toast's action is never the only
|
|
58
|
+
* route to what it does.** A toast is transient by definition, and a reader
|
|
59
|
+
* who missed it, or whose screen reader had not finished announcing it when
|
|
60
|
+
* it went, cannot take an action that exists only inside it. So whatever the
|
|
61
|
+
* action does has to be reachable in the surface the reader is already
|
|
62
|
+
* looking at, and the toast is the shortcut rather than the gate.
|
|
63
|
+
*/
|
|
64
|
+
action?: ReactNode;
|
|
65
|
+
/**
|
|
66
|
+
* Called when the toast wants to be taken off the page: the clock ran out, the
|
|
67
|
+
* reader pressed the dismiss control, or the reader pressed Escape while the
|
|
68
|
+
* toast held focus.
|
|
69
|
+
*
|
|
70
|
+
* **The caller unmounts; this Component never hides itself.** That is what
|
|
71
|
+
* makes the leave transition possible at all, and it is stated here because a
|
|
72
|
+
* caller who expects the toast to remove itself will write a no-op and leave
|
|
73
|
+
* a toast parked on the page forever.
|
|
74
|
+
*
|
|
75
|
+
* **Omitting it makes the toast permanent, and that is deliberate.** Without a
|
|
76
|
+
* handoff there is nobody to take the toast off the page, so a clock with
|
|
77
|
+
* nothing to fire and a dismiss control with nothing to do would both be
|
|
78
|
+
* controls that lie. A toast with no `onDismiss` therefore does not run its
|
|
79
|
+
* clock, renders no dismiss control and ignores Escape: it stays until the
|
|
80
|
+
* caller unmounts it. The alternative is worse, because a toast that faded out
|
|
81
|
+
* and was never removed is an invisible element still in the document, still
|
|
82
|
+
* in the live region and still taking the pointer.
|
|
83
|
+
*/
|
|
84
|
+
onDismiss?: () => void;
|
|
85
|
+
/**
|
|
86
|
+
* How long the toast stays up before it asks to be dismissed, in milliseconds.
|
|
87
|
+
*
|
|
88
|
+
* **The default is a judgement about a reader, and it is deliberately longer
|
|
89
|
+
* than a toast usually wants to be.** The failure this number exists to avoid
|
|
90
|
+
* is a toast vanishing before a screen reader has finished announcing it,
|
|
91
|
+
* which is worse than no toast at all: the reader is told something happened
|
|
92
|
+
* and then told, by its absence, that they missed it. Four seconds clears a
|
|
93
|
+
* title and a sentence at a conservative assistive-technology speaking rate.
|
|
94
|
+
* The pause on hover and on focus covers the reader who is looking at the
|
|
95
|
+
* toast rather than hearing it.
|
|
96
|
+
*
|
|
97
|
+
* `0` turns the clock off, for the toast the reader must act on. That is a
|
|
98
|
+
* real case and it is the one case where a toast earns the name, because the
|
|
99
|
+
* reader's next step is the toast's entire purpose.
|
|
100
|
+
*
|
|
101
|
+
* @defaultValue 4000
|
|
102
|
+
*/
|
|
103
|
+
duration?: number;
|
|
104
|
+
/**
|
|
105
|
+
* The accessible name of the dismiss control.
|
|
106
|
+
*
|
|
107
|
+
* **Required, with no default, and this is the one surface in the package
|
|
108
|
+
* where a default would be wrong rather than merely unhelpful.** Every other
|
|
109
|
+
* close button here sits inside a dialog the reader opened, so its name is
|
|
110
|
+
* already implied by the thing it is closing. A toast arrived on its own: an
|
|
111
|
+
* icon-only control the reader reaches towards has to say what it is before
|
|
112
|
+
* they press it, and four products in two languages cannot all be told the
|
|
113
|
+
* control says "Close".
|
|
114
|
+
*/
|
|
115
|
+
closeLabel: string;
|
|
116
|
+
/**
|
|
117
|
+
* Whether the dismiss control is rendered.
|
|
118
|
+
*
|
|
119
|
+
* A prop and not a decision made here, because a toast the reader cannot
|
|
120
|
+
* dismiss is a toast that can only be waited out, and a toast the reader must
|
|
121
|
+
* not dismiss is a dialog. Neither is this Component's call.
|
|
122
|
+
*
|
|
123
|
+
* Set to `false` for a toast the reader must not dismiss, and note that it
|
|
124
|
+
* costs the toast its only keyboard route out: with no control and no Escape,
|
|
125
|
+
* the caller has to offer the way to leave somewhere else, or the toast is a
|
|
126
|
+
* dialog with the focus trap left out. The control is also not rendered when
|
|
127
|
+
* there is no `onDismiss` to call, because a control that cannot do anything
|
|
128
|
+
* is worse than no control.
|
|
129
|
+
*
|
|
130
|
+
* @defaultValue true
|
|
131
|
+
*/
|
|
132
|
+
showCloseButton?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Whether the clock is paused regardless of the pointer or focus.
|
|
135
|
+
*
|
|
136
|
+
* The escape hatch for a caller holding a toast open while something else is
|
|
137
|
+
* still in flight, which is the ordinary case for a batch of operations
|
|
138
|
+
* reporting one at a time.
|
|
139
|
+
*
|
|
140
|
+
* @defaultValue false
|
|
141
|
+
*/
|
|
142
|
+
paused?: boolean;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A transient notification for something the reader did not have to be told.
|
|
146
|
+
*
|
|
147
|
+
* **What a toast is FOR, stated narrowly, because the broad reading is why
|
|
148
|
+
* toasts get used for the wrong things.** A toast reports a fact the reader
|
|
149
|
+
* would otherwise miss: work finished in the background, a document was shared
|
|
150
|
+
* with them, a setting took effect. It is deliberately NOT the place for an
|
|
151
|
+
* error the reader caused and must act on, because a message that requires an
|
|
152
|
+
* action cannot be allowed to expire before the action is taken. That is an
|
|
153
|
+
* `Alert` beside the control, or a `Dialog` when it blocks. A toast carrying a
|
|
154
|
+
* blocking message is a modal in a costume.
|
|
155
|
+
*
|
|
156
|
+
* **It never takes focus, and that is the load-bearing accessibility claim.** A
|
|
157
|
+
* surface that moves focus to itself has interrupted the reader, and being
|
|
158
|
+
* interrupted is the whole thing a toast exists to avoid: the reader is typing,
|
|
159
|
+
* or reading, or doing something else entirely. So the root is a
|
|
160
|
+
* `role="status"` region that is announced without being entered, the dismiss
|
|
161
|
+
* control is reachable by Tab from wherever the reader already was, and nothing
|
|
162
|
+
* in this Component calls `focus()`. A consumer who needs attention right now
|
|
163
|
+
* wants a `Dialog`; one who needs the reader to act before the message goes
|
|
164
|
+
* away wants `duration={0}`.
|
|
165
|
+
*
|
|
166
|
+
* **It is announced once, and that is why there is no countdown on screen.** A
|
|
167
|
+
* visible timer would have to be a text or an attribute that changes, and
|
|
168
|
+
* anything that changes inside a live region is something a screen reader may
|
|
169
|
+
* announce again. A toast that read out its remaining time every second would
|
|
170
|
+
* be read thirty times. So the clock lives in a ref, it changes no text, and
|
|
171
|
+
* the region speaks once on arrival and again only if the caller changes the
|
|
172
|
+
* words.
|
|
173
|
+
*
|
|
174
|
+
* **It pauses on hover and on focus, for the same reason the default is long.**
|
|
175
|
+
* A reader who has moved the pointer onto a toast is reading it, and one who
|
|
176
|
+
* has tabbed into it is about to press something; in both cases the clock is
|
|
177
|
+
* the enemy of what the reader is doing. The pause *holds* the remaining time
|
|
178
|
+
* rather than restarting it, so a reader who hovers for ten seconds and then
|
|
179
|
+
* leaves gets the four seconds they had left, not four more.
|
|
180
|
+
*
|
|
181
|
+
* **The enter and the leave are the one motion in this package that is
|
|
182
|
+
* legitimate, because they are a state change and not decoration.** A toast is
|
|
183
|
+
* arriving and a toast is leaving, the reader can watch both happen, and that
|
|
184
|
+
* is what state feedback is. It is a fade and a short rise on `duration-slow`
|
|
185
|
+
* with `ease-out`, expressed with utilities this package already uses and with
|
|
186
|
+
* no keyframe: a transition plus a `data-phase` attribute carries both
|
|
187
|
+
* directions. There is no slide, because a travelling toast is a transition
|
|
188
|
+
* between two positions rather than a transition of one element, and expressing
|
|
189
|
+
* that without a keyframe would mean a transform the element does not otherwise
|
|
190
|
+
* have.
|
|
191
|
+
*
|
|
192
|
+
* **It never removes its own transition, and the reason is the caller's
|
|
193
|
+
* unmount.** The exit hands over on the end of the root's own opacity
|
|
194
|
+
* transition, so under reduced motion the fade is *shortened* rather than
|
|
195
|
+
* removed and the handoff still happens. Removing the fade there would have
|
|
196
|
+
* needed this Component to read the media query and hand over at once instead,
|
|
197
|
+
* which would be the same rule written twice: once in the stylesheet and once in
|
|
198
|
+
* TypeScript, free to disagree the day the motion policy is retuned. Leaving
|
|
199
|
+
* the fade in and shortening it keeps the decision in one place, and it is the
|
|
200
|
+
* same choice `DESIGN.md` states for the package: motion is state feedback,
|
|
201
|
+
* shortened rather than removed under reduced motion. The travel is the part
|
|
202
|
+
* that is movement, so that is the part `motion-safe:` guards.
|
|
203
|
+
*
|
|
204
|
+
* **It is a client Component** because it holds a clock, runs effects, and
|
|
205
|
+
* attaches three handlers. The clock is a single `setTimeout` whose
|
|
206
|
+
* `clearTimeout` is the effect's own return, so nothing outlives the Component:
|
|
207
|
+
* unmounting a toast takes its timer with it rather than leaving one to fire
|
|
208
|
+
* against a component that is no longer on the page.
|
|
209
|
+
*
|
|
210
|
+
* **A toast with no `onDismiss` never leaves, and that is the safe answer.**
|
|
211
|
+
* Every way out of this Component hands over to the caller, so with nobody to
|
|
212
|
+
* hand to there is no clock to run, no dismiss control to render and no Escape
|
|
213
|
+
* to answer. The alternative would be a toast that faded to `opacity-0` and was
|
|
214
|
+
* never removed from the document: invisible, still in the live region, still
|
|
215
|
+
* taking the pointer, and gone in every way except the ones that matter.
|
|
216
|
+
*/
|
|
217
|
+
declare function Toast({ className, variant, title, description, action, onDismiss, duration, closeLabel, showCloseButton, paused, ...props }: ToastProps): import("react").JSX.Element;
|
|
218
|
+
export { Toast };
|
|
219
|
+
//# sourceMappingURL=toast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../src/components/ui/toast.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,EAA4C,KAAK,cAAc,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAIzH;;;;;;;;;;GAUG;AACH,QAAA,MAAM,aAAa;;8EAsBlB,CAAA;AAaD;;;;;;;GAOG;AACH,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,EACvD,YAAY,CAAC,OAAO,aAAa,CAAC;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;OAUG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,iBAAS,KAAK,CAAC,EACb,SAAS,EACT,OAAO,EACP,KAAK,EACL,WAAW,EACX,MAAM,EACN,SAAS,EACT,QAAe,EACf,UAAU,EACV,eAAsB,EACtB,MAAc,EACd,GAAG,KAAK,EACT,EAAE,UAAU,+BA4NZ;AAED,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { XIcon } from 'lucide-react';
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { cn } from '../../lib/utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* The surface and the four tones, and nothing else.
|
|
9
|
+
*
|
|
10
|
+
* Every tone states its own fill AND its own ink, and that is the
|
|
11
|
+
* `check-variant-ink` rule rather than a style preference. A toast is the one
|
|
12
|
+
* surface in this package that is a *coloured* surface with body text sitting
|
|
13
|
+
* on it, so an inherited ink is not the page's foreground by accident: it is
|
|
14
|
+
* whatever happened to be behind the viewport, and the failure `Cta01`
|
|
15
|
+
* measured was 1.01:1 on a filled `bg-primary` band. A tone that set only a
|
|
16
|
+
* fill would be the same button in a different colour.
|
|
17
|
+
*/
|
|
18
|
+
const toastVariants = cva(
|
|
19
|
+
// `pointer-events-auto` because the viewport that hosts a toast is a fixed
|
|
20
|
+
// layer over one corner of the page, and a toast that inherited
|
|
21
|
+
// `pointer-events-none` from that layer would be a toast nobody can click.
|
|
22
|
+
'bg-popover text-popover-foreground pointer-events-auto relative flex w-full items-start gap-3 rounded-lg border p-4 pr-10 shadow-md', {
|
|
23
|
+
variants: {
|
|
24
|
+
variant: {
|
|
25
|
+
default: 'border-border',
|
|
26
|
+
success: 'border-transparent bg-success text-success-foreground',
|
|
27
|
+
warning: 'border-transparent bg-warning text-warning-foreground',
|
|
28
|
+
// A destructive toast is for a failure the reader did not cause and
|
|
29
|
+
// would not otherwise learn: a payment declined on the server, a session
|
|
30
|
+
// that expired, a deploy that failed. It is NOT for a mistake the reader
|
|
31
|
+
// made, which belongs in an `Alert` beside the control where they can
|
|
32
|
+
// see what to change. That distinction is the reason a destructive tone
|
|
33
|
+
// exists here at all.
|
|
34
|
+
destructive: 'border-transparent bg-destructive text-destructive-foreground',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: { variant: 'default' },
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* A transient notification for something the reader did not have to be told.
|
|
41
|
+
*
|
|
42
|
+
* **What a toast is FOR, stated narrowly, because the broad reading is why
|
|
43
|
+
* toasts get used for the wrong things.** A toast reports a fact the reader
|
|
44
|
+
* would otherwise miss: work finished in the background, a document was shared
|
|
45
|
+
* with them, a setting took effect. It is deliberately NOT the place for an
|
|
46
|
+
* error the reader caused and must act on, because a message that requires an
|
|
47
|
+
* action cannot be allowed to expire before the action is taken. That is an
|
|
48
|
+
* `Alert` beside the control, or a `Dialog` when it blocks. A toast carrying a
|
|
49
|
+
* blocking message is a modal in a costume.
|
|
50
|
+
*
|
|
51
|
+
* **It never takes focus, and that is the load-bearing accessibility claim.** A
|
|
52
|
+
* surface that moves focus to itself has interrupted the reader, and being
|
|
53
|
+
* interrupted is the whole thing a toast exists to avoid: the reader is typing,
|
|
54
|
+
* or reading, or doing something else entirely. So the root is a
|
|
55
|
+
* `role="status"` region that is announced without being entered, the dismiss
|
|
56
|
+
* control is reachable by Tab from wherever the reader already was, and nothing
|
|
57
|
+
* in this Component calls `focus()`. A consumer who needs attention right now
|
|
58
|
+
* wants a `Dialog`; one who needs the reader to act before the message goes
|
|
59
|
+
* away wants `duration={0}`.
|
|
60
|
+
*
|
|
61
|
+
* **It is announced once, and that is why there is no countdown on screen.** A
|
|
62
|
+
* visible timer would have to be a text or an attribute that changes, and
|
|
63
|
+
* anything that changes inside a live region is something a screen reader may
|
|
64
|
+
* announce again. A toast that read out its remaining time every second would
|
|
65
|
+
* be read thirty times. So the clock lives in a ref, it changes no text, and
|
|
66
|
+
* the region speaks once on arrival and again only if the caller changes the
|
|
67
|
+
* words.
|
|
68
|
+
*
|
|
69
|
+
* **It pauses on hover and on focus, for the same reason the default is long.**
|
|
70
|
+
* A reader who has moved the pointer onto a toast is reading it, and one who
|
|
71
|
+
* has tabbed into it is about to press something; in both cases the clock is
|
|
72
|
+
* the enemy of what the reader is doing. The pause *holds* the remaining time
|
|
73
|
+
* rather than restarting it, so a reader who hovers for ten seconds and then
|
|
74
|
+
* leaves gets the four seconds they had left, not four more.
|
|
75
|
+
*
|
|
76
|
+
* **The enter and the leave are the one motion in this package that is
|
|
77
|
+
* legitimate, because they are a state change and not decoration.** A toast is
|
|
78
|
+
* arriving and a toast is leaving, the reader can watch both happen, and that
|
|
79
|
+
* is what state feedback is. It is a fade and a short rise on `duration-slow`
|
|
80
|
+
* with `ease-out`, expressed with utilities this package already uses and with
|
|
81
|
+
* no keyframe: a transition plus a `data-phase` attribute carries both
|
|
82
|
+
* directions. There is no slide, because a travelling toast is a transition
|
|
83
|
+
* between two positions rather than a transition of one element, and expressing
|
|
84
|
+
* that without a keyframe would mean a transform the element does not otherwise
|
|
85
|
+
* have.
|
|
86
|
+
*
|
|
87
|
+
* **It never removes its own transition, and the reason is the caller's
|
|
88
|
+
* unmount.** The exit hands over on the end of the root's own opacity
|
|
89
|
+
* transition, so under reduced motion the fade is *shortened* rather than
|
|
90
|
+
* removed and the handoff still happens. Removing the fade there would have
|
|
91
|
+
* needed this Component to read the media query and hand over at once instead,
|
|
92
|
+
* which would be the same rule written twice: once in the stylesheet and once in
|
|
93
|
+
* TypeScript, free to disagree the day the motion policy is retuned. Leaving
|
|
94
|
+
* the fade in and shortening it keeps the decision in one place, and it is the
|
|
95
|
+
* same choice `DESIGN.md` states for the package: motion is state feedback,
|
|
96
|
+
* shortened rather than removed under reduced motion. The travel is the part
|
|
97
|
+
* that is movement, so that is the part `motion-safe:` guards.
|
|
98
|
+
*
|
|
99
|
+
* **It is a client Component** because it holds a clock, runs effects, and
|
|
100
|
+
* attaches three handlers. The clock is a single `setTimeout` whose
|
|
101
|
+
* `clearTimeout` is the effect's own return, so nothing outlives the Component:
|
|
102
|
+
* unmounting a toast takes its timer with it rather than leaving one to fire
|
|
103
|
+
* against a component that is no longer on the page.
|
|
104
|
+
*
|
|
105
|
+
* **A toast with no `onDismiss` never leaves, and that is the safe answer.**
|
|
106
|
+
* Every way out of this Component hands over to the caller, so with nobody to
|
|
107
|
+
* hand to there is no clock to run, no dismiss control to render and no Escape
|
|
108
|
+
* to answer. The alternative would be a toast that faded to `opacity-0` and was
|
|
109
|
+
* never removed from the document: invisible, still in the live region, still
|
|
110
|
+
* taking the pointer, and gone in every way except the ones that matter.
|
|
111
|
+
*/
|
|
112
|
+
function Toast({ className, variant, title, description, action, onDismiss, duration = 4000, closeLabel, showCloseButton = true, paused = false, ...props }) {
|
|
113
|
+
const [phase, setPhase] = useState('entering');
|
|
114
|
+
const [hovered, setHovered] = useState(false);
|
|
115
|
+
const [focused, setFocused] = useState(false);
|
|
116
|
+
const rootRef = useRef(null);
|
|
117
|
+
// The remaining time, in a ref rather than in state, because it is written by
|
|
118
|
+
// this Component's own clock and read by nothing the reader can see. State
|
|
119
|
+
// would re-render the whole toast four times a second to update a number no
|
|
120
|
+
// reader is shown, and every re-render of a live region is a chance to be
|
|
121
|
+
// announced again.
|
|
122
|
+
const remaining = useRef(duration);
|
|
123
|
+
const startedAt = useRef(0);
|
|
124
|
+
// The three reasons the clock stops, and the reason the pointer and the
|
|
125
|
+
// keyboard are two of them rather than one: a reader who has tabbed into the
|
|
126
|
+
// dismiss control has not finished with the toast, and neither has a reader
|
|
127
|
+
// who is reading it.
|
|
128
|
+
const held = paused || hovered || focused;
|
|
129
|
+
// The one thing without which nothing can leave: without a handoff there is
|
|
130
|
+
// nobody to take the toast off the page, so the clock has nothing to fire and
|
|
131
|
+
// the dismiss control has nothing to do. Every way out is gated on it, so a
|
|
132
|
+
// toast rendered without an `onDismiss` is a toast that stays rather than one
|
|
133
|
+
// that fades to invisible and is never removed.
|
|
134
|
+
const canLeave = onDismiss !== undefined;
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
// Entering is one frame and not a duration. A transition needs a rendered
|
|
137
|
+
// state to transition *from*, and the first frame is the one that has it.
|
|
138
|
+
// The frame is cancelled on unmount, so a toast removed before its first
|
|
139
|
+
// paint does not leave a request behind.
|
|
140
|
+
//
|
|
141
|
+
// Deliberately not awaited by anything. A frame is a frame: if the browser
|
|
142
|
+
// never delivers one, because the tab is in the background, the toast is
|
|
143
|
+
// still on the page, still dismissible and still on its clock. The enter is
|
|
144
|
+
// the only part of this Component that is allowed to be skipped, and it is
|
|
145
|
+
// skipped on its own.
|
|
146
|
+
if (phase !== 'entering')
|
|
147
|
+
return;
|
|
148
|
+
const frame = requestAnimationFrame(() => setPhase('open'));
|
|
149
|
+
return () => cancelAnimationFrame(frame);
|
|
150
|
+
}, [phase]);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
// A new duration is a new clock. Written before the clock effect below
|
|
153
|
+
// re-runs, because that effect reads this one on its first line.
|
|
154
|
+
remaining.current = duration;
|
|
155
|
+
startedAt.current = 0;
|
|
156
|
+
}, [duration]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
// `duration === 0` is the "the reader must act on this" case, and a toast
|
|
159
|
+
// with the clock off is the one toast entitled to stay.
|
|
160
|
+
//
|
|
161
|
+
// The guard is `leaving` and not `open`, because the clock is about the
|
|
162
|
+
// reader's patience rather than about the animation: tying the clock to the
|
|
163
|
+
// visual phase would mean a backgrounded tab, which never gets a frame, kept
|
|
164
|
+
// a toast up forever for a reason the reader never saw.
|
|
165
|
+
if (phase === 'leaving' || held || duration === 0 || !canLeave)
|
|
166
|
+
return;
|
|
167
|
+
startedAt.current = Date.now();
|
|
168
|
+
const timer = setTimeout(() => setPhase('leaving'), remaining.current);
|
|
169
|
+
// The single place the clock is stopped. It runs on unmount as well as on
|
|
170
|
+
// every pause, which is what keeps a toast's timer from outliving it, and
|
|
171
|
+
// the subtraction is what makes a resume a *continuation* rather than a
|
|
172
|
+
// restart.
|
|
173
|
+
return () => {
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
remaining.current = Math.max(0, remaining.current - (Date.now() - startedAt.current));
|
|
176
|
+
};
|
|
177
|
+
}, [phase, held, duration, canLeave]);
|
|
178
|
+
const dismiss = useCallback(() => {
|
|
179
|
+
// Already leaving does not restart the exit, so the handoff to the caller
|
|
180
|
+
// is one call rather than one per reason the toast was dismissed.
|
|
181
|
+
setPhase((current) => (current === 'leaving' ? current : 'leaving'));
|
|
182
|
+
}, []);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (phase !== 'leaving')
|
|
185
|
+
return;
|
|
186
|
+
const root = rootRef.current;
|
|
187
|
+
if (root === null)
|
|
188
|
+
return;
|
|
189
|
+
// The leave ends when the transition the CSS declared has run, and not on a
|
|
190
|
+
// second clock of this Component's own: the length of the leave is a CSS
|
|
191
|
+
// decision, and a number in TypeScript would be a second source of truth
|
|
192
|
+
// for it that would stop agreeing with the stylesheet the day the token
|
|
193
|
+
// moved.
|
|
194
|
+
//
|
|
195
|
+
// It is the root's own opacity rather than "the first transition to end",
|
|
196
|
+
// and that holds under reduced motion too. The fade is shortened there and
|
|
197
|
+
// not removed, so an opacity transition always runs and always ends, and the
|
|
198
|
+
// caller is always told to unmount. Reading the media query in JavaScript
|
|
199
|
+
// would put the same decision in two places: the stylesheet would shorten
|
|
200
|
+
// the fade and this code would have to guess the same rule to know whether
|
|
201
|
+
// to expect the event.
|
|
202
|
+
//
|
|
203
|
+
// A `const` arrow and not a hoisted declaration, for one reason that is not
|
|
204
|
+
// taste: a function declaration could be called before the null check above
|
|
205
|
+
// ran, so TypeScript drops the narrowing on `root` inside it, while an arrow
|
|
206
|
+
// assigned to a `const` cannot exist until the check has passed.
|
|
207
|
+
const finish = (event) => {
|
|
208
|
+
// Filtered to this element's own opacity, because a child control's
|
|
209
|
+
// colour transition would otherwise end the toast's exit early and the
|
|
210
|
+
// caller would unmount it half way through the fade.
|
|
211
|
+
if (event.target !== root)
|
|
212
|
+
return;
|
|
213
|
+
if (event.propertyName !== 'opacity')
|
|
214
|
+
return;
|
|
215
|
+
// Removed on the way through, because the phase does not change on the
|
|
216
|
+
// way out and so this effect is not re-run to clean up. A listener left
|
|
217
|
+
// attached would call the caller's unmount again on the next opacity
|
|
218
|
+
// transition on this element, and an unmount that runs twice is a state
|
|
219
|
+
// update on a component that is no longer there.
|
|
220
|
+
root.removeEventListener('transitionend', finish);
|
|
221
|
+
onDismiss?.();
|
|
222
|
+
};
|
|
223
|
+
root.addEventListener('transitionend', finish);
|
|
224
|
+
return () => root.removeEventListener('transitionend', finish);
|
|
225
|
+
}, [phase, onDismiss]);
|
|
226
|
+
const onKeyDown = (event) => {
|
|
227
|
+
// Escape dismisses, and only when the toast itself holds focus. A toast
|
|
228
|
+
// that answered a document-level Escape would swallow the key from whatever
|
|
229
|
+
// the reader is actually typing in, which is the failure every floating
|
|
230
|
+
// surface causes when it listens globally.
|
|
231
|
+
if (event.key !== 'Escape')
|
|
232
|
+
return;
|
|
233
|
+
if (!canLeave)
|
|
234
|
+
return;
|
|
235
|
+
if (!rootRef.current?.contains(event.target))
|
|
236
|
+
return;
|
|
237
|
+
event.stopPropagation();
|
|
238
|
+
dismiss();
|
|
239
|
+
};
|
|
240
|
+
return (_jsxs("div", { ref: rootRef, "data-slot": "toast", "data-phase": phase,
|
|
241
|
+
// The role is what makes the announcement happen at all, and it is the one
|
|
242
|
+
// `LiveRegion` uses and for the same reason: a toast is content that
|
|
243
|
+
// changed and is not waiting for the reader. `aria-live` is written
|
|
244
|
+
// explicitly so the politeness is a stated fact rather than an implication
|
|
245
|
+
// of the role, and `aria-atomic` is on because a title and a sentence
|
|
246
|
+
// arrive together and a reader should hear one notification rather than
|
|
247
|
+
// two.
|
|
248
|
+
role: "status", "aria-live": "polite", "aria-atomic": "true", onKeyDown: onKeyDown, onPointerEnter: () => setHovered(true), onPointerLeave: () => setHovered(false), onFocus: () => setFocused(true), onBlur: () => setFocused(false), className: cn(toastVariants({ variant }),
|
|
249
|
+
// The enter and the leave as one transition on two properties.
|
|
250
|
+
// `duration-slow` rather than `duration-base` because a toast crosses
|
|
251
|
+
// the page and a fast one is over before a reader has registered that
|
|
252
|
+
// anything arrived; `ease-out` because both directions are decelerating
|
|
253
|
+
// into a resting state rather than accelerating out of one.
|
|
254
|
+
'transition-[opacity,transform] duration-slow ease-out',
|
|
255
|
+
// Reduced motion shortens the fade and removes the travel, rather than
|
|
256
|
+
// removing the transition. The fade is what tells a reader the toast is
|
|
257
|
+
// there and then that it is gone, and a reader who asked for less motion
|
|
258
|
+
// has not asked to be told nothing; the rise is movement, and movement is
|
|
259
|
+
// what they asked not to have. It is also why the handoff below waits on
|
|
260
|
+
// an opacity transition rather than reading the media query in JavaScript:
|
|
261
|
+
// an opacity that always transitions always ends, so there is no case in
|
|
262
|
+
// which the caller is never told to unmount.
|
|
263
|
+
'motion-reduce:duration-fast', 'motion-safe:data-[phase=entering]:translate-y-1 motion-safe:data-[phase=leaving]:translate-y-1', 'data-[phase=entering]:opacity-0 data-[phase=leaving]:opacity-0', className), ...props, children: [_jsxs("div", { "data-slot": "toast-body", className: "flex min-w-0 flex-1 flex-col gap-1", children: [title === undefined ? null : (_jsx("p", { "data-slot": "toast-title", className: "text-sm font-medium", children: title })), description === undefined ? null : (_jsx("p", { "data-slot": "toast-description", className: "text-pretty text-sm opacity-90", children: description })), action === undefined ? null : (_jsx("div", { "data-slot": "toast-action", className: "mt-1.5 flex items-center", children: action }))] }), showCloseButton && canLeave ? (_jsx("button", { type: "button", "data-slot": "toast-close", "aria-label": closeLabel, onClick: dismiss,
|
|
264
|
+
// The ring is at full strength for the reason `Button`'s is: half alpha
|
|
265
|
+
// composites to under 3:1 against every surface in every pack, and this
|
|
266
|
+
// is the one indicator on a control a reader reaches for without
|
|
267
|
+
// having opened anything.
|
|
268
|
+
className: cn('ring-offset-background focus-visible:ring-ring absolute top-2 right-2 rounded-sm opacity-70 outline-none', 'transition-opacity duration-fast ease-out', 'hover:opacity-100 focus-visible:ring-[3px]'), children: _jsx(XIcon, { className: "size-4", "aria-hidden": "true" }) })) : null] }));
|
|
269
|
+
}
|
|
270
|
+
export { Toast };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
/** How many members of a ToggleGroup may be pressed at once. */
|
|
3
|
+
export type ToggleGroupSelectionMode = 'single' | 'multiple';
|
|
4
|
+
/** The props the ToggleGroup accepts. */
|
|
5
|
+
export interface ToggleGroupProps extends Omit<ComponentProps<'div'>, 'onChange' | 'children'> {
|
|
6
|
+
/** The members, as `ToggleGroupItem` elements. */
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Whether one member or several may be pressed.
|
|
10
|
+
*
|
|
11
|
+
* This is the caller's decision and it is a real one, which is why it is a prop
|
|
12
|
+
* with two values rather than two Components. See the group's JSDoc for what
|
|
13
|
+
* each mode means in the accessibility tree, because the two are not the same
|
|
14
|
+
* control wearing two looks.
|
|
15
|
+
*/
|
|
16
|
+
selectionMode?: ToggleGroupSelectionMode;
|
|
17
|
+
/** The pressed values, when the group is controlled. */
|
|
18
|
+
value?: readonly string[];
|
|
19
|
+
/** The pressed values to start with, for an uncontrolled group. */
|
|
20
|
+
defaultValue?: readonly string[];
|
|
21
|
+
/** Called with the group's whole set of pressed values when it changes. */
|
|
22
|
+
onValueChange?: (value: readonly string[]) => void;
|
|
23
|
+
/** Whether every member ignores user interaction. */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The group's accessible name.
|
|
27
|
+
*
|
|
28
|
+
* Required rather than defaulted. A group of toggles with no name is announced
|
|
29
|
+
* as an unnamed group, and a toolbar with two of them is a toolbar where a
|
|
30
|
+
* reader cannot tell which set of controls they are in. The name is the
|
|
31
|
+
* caller's word, for the reason every region's name in this package is.
|
|
32
|
+
*/
|
|
33
|
+
'aria-label'?: string;
|
|
34
|
+
/** The direction the arrow keys move in. @defaultValue 'horizontal' */
|
|
35
|
+
orientation?: 'horizontal' | 'vertical';
|
|
36
|
+
/** Layout only. */
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
/** The props the ToggleGroupItem accepts. */
|
|
40
|
+
export interface ToggleGroupItemProps extends Omit<ComponentProps<'button'>, 'value' | 'children'> {
|
|
41
|
+
/**
|
|
42
|
+
* The value this member contributes to the group.
|
|
43
|
+
*
|
|
44
|
+
* Required, because a member the group cannot name is a member it cannot report
|
|
45
|
+
* and cannot restore, and the group reports the whole set rather than a
|
|
46
|
+
* difference.
|
|
47
|
+
*/
|
|
48
|
+
value: string;
|
|
49
|
+
/** Whether this member ignores user interaction. */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/** The words on the member, and its accessible name. */
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A set of toggles that behave as one control.
|
|
56
|
+
*
|
|
57
|
+
* **The single or multiple decision is the caller's, and it changes what the
|
|
58
|
+
* group is rather than how it looks.** This is the decision worth stating because
|
|
59
|
+
* getting it wrong is the usual failure, and it is invisible in a screenshot: two
|
|
60
|
+
* groups of three buttons, one where at most one is pressed and one where any
|
|
61
|
+
* number are, look identical until you read the roles.
|
|
62
|
+
*
|
|
63
|
+
* In `single` mode the group is a **radiogroup** and its members are **radios**
|
|
64
|
+
* carrying `aria-checked`. That is not a stylistic preference, it is the only
|
|
65
|
+
* description of the constraint that is true. The choices are mutually exclusive,
|
|
66
|
+
* there is exactly one answer, and the whole set is one question rather than three
|
|
67
|
+
* independent facts. Three buttons carrying `aria-pressed` say three independent
|
|
68
|
+
* things can be on at once, which is a lie, and a reader of the accessibility tree
|
|
69
|
+
* is left to infer the constraint from nothing.
|
|
70
|
+
*
|
|
71
|
+
* In `multiple` mode the group is a **toolbar** and its members are **pressed
|
|
72
|
+
* buttons** carrying `aria-pressed`, and the arrow keys move focus without
|
|
73
|
+
* choosing. That is the right behaviour for a toolbar of independent toggles,
|
|
74
|
+
* where arrowing past a member must not silently turn it on, and Space is what
|
|
75
|
+
* presses. So the two modes differ in the role of the group, the role of the
|
|
76
|
+
* member, the state attribute, and what an arrow key does, and none of the four is
|
|
77
|
+
* reachable from the other by passing a prop.
|
|
78
|
+
*
|
|
79
|
+
* **The group is one Tab stop, and it is the current answer.** Tab enters on the
|
|
80
|
+
* member that is pressed, or on the first member when nothing is pressed, and Tab
|
|
81
|
+
* leaves the group in one step. That is the composite pattern, and it is why a
|
|
82
|
+
* consumer cannot assemble this from a row of Toggles: composing them gives the
|
|
83
|
+
* Tab order of the document, which is every member of the group and none of the
|
|
84
|
+
* arrows. A radiogroup whose Tab lands on the first option when a different one is
|
|
85
|
+
* checked is a group that reports the wrong current value through the tab order
|
|
86
|
+
* alone, and with seven members in a row the reader pays for it.
|
|
87
|
+
*
|
|
88
|
+
* **The tab stop follows the reader once they move.** It is a piece of state
|
|
89
|
+
* rather than a derivation, because a reader who arrows across a group of seven
|
|
90
|
+
* and Tabs away expects to arrive back where they were, and a stop recomputed
|
|
91
|
+
* from the pressed value on every render is a stop that follows the pressed value
|
|
92
|
+
* instead of the reader.
|
|
93
|
+
*
|
|
94
|
+
* **The keyboard model lives here rather than in each member,** for the reason
|
|
95
|
+
* `Tree` keeps its own: three members each handling an arrow key is three
|
|
96
|
+
* implementations of one rule, and the third is the one that is wrong. The order
|
|
97
|
+
* the arrows walk is the group's own children rather than the node list, so it is
|
|
98
|
+
* the order the reader sees and the order a reordering caller declared, with no
|
|
99
|
+
* second list to fall behind the first.
|
|
100
|
+
*
|
|
101
|
+
* It is a client Component: it holds the pressed set and the roving tab stop, and
|
|
102
|
+
* both are the control rather than a decoration of it.
|
|
103
|
+
*/
|
|
104
|
+
declare function ToggleGroup({ children, className, selectionMode, value, defaultValue, onValueChange, disabled, orientation, ...props }: ToggleGroupProps): import("react").JSX.Element;
|
|
105
|
+
/**
|
|
106
|
+
* One member of a ToggleGroup.
|
|
107
|
+
*
|
|
108
|
+
* A native `<button>` in both modes, because it is focusable, it submits nothing
|
|
109
|
+
* it was not asked to, and it is the element the browser's own activation already
|
|
110
|
+
* handles. What changes between the modes is the role and the state attribute, and
|
|
111
|
+
* both are read from the group: a radio carrying `aria-checked` in single mode, a
|
|
112
|
+
* pressed button in multiple mode. Render a Prism `Toggle` outside a group when a
|
|
113
|
+
* member stands alone; render this inside one, where the group owns the keyboard
|
|
114
|
+
* model and the pressed set.
|
|
115
|
+
*/
|
|
116
|
+
declare function ToggleGroupItem({ className, value, disabled, children, ...props }: ToggleGroupItemProps): import("react").JSX.Element;
|
|
117
|
+
export { ToggleGroup, ToggleGroupItem };
|
|
118
|
+
//# sourceMappingURL=toggle-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toggle-group.d.ts","sourceRoot":"","sources":["../../../src/components/ui/toggle-group.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AAId,gEAAgE;AAChE,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,UAAU,CAAA;AAE5D,yCAAyC;AACzC,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IAC5F,kDAAkD;IAClD,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,wBAAwB,CAAA;IACxC,wDAAwD;IACxD,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,2EAA2E;IAC3E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAA;IAClD,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,uEAAuE;IACvE,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,mBAAmB;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,6CAA6C;AAC7C,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAChG;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAyCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,iBAAS,WAAW,CAAC,EACnB,QAAQ,EACR,SAAS,EACT,aAAwB,EACxB,KAAK,EACL,YAAY,EACZ,aAAa,EACb,QAAgB,EAChB,WAA0B,EAC1B,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAoHlB;AAED;;;;;;;;;;GAUG;AACH,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,oBAAoB,+BAiDtB;AAED,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,CAAA"}
|