@mitcsutt/kiln-ui 0.6.0 → 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/dist/components/actions/CopyButton/CopyButton.d.ts +58 -0
- package/dist/components/actions/CopyButton/CopyButton.js +63 -0
- package/dist/components/actions/CopyButton/CopyButton.js.map +1 -0
- package/dist/components/actions/CopyButton/CopyButton.module.js +9 -0
- package/dist/components/actions/CopyButton/CopyButton.module.js.map +1 -0
- package/dist/components/actions/CopyButton/index.d.ts +4 -0
- package/dist/components/actions/CopyButton/useClipboard.d.ts +35 -0
- package/dist/components/actions/CopyButton/useClipboard.js +62 -0
- package/dist/components/actions/CopyButton/useClipboard.js.map +1 -0
- package/dist/components/actions/ToggleChip/ToggleChip.d.ts +6 -0
- package/dist/components/actions/ToggleChip/ToggleChip.js +26 -24
- package/dist/components/actions/ToggleChip/ToggleChip.js.map +1 -1
- package/dist/components/display/Badge/Badge.d.ts +6 -0
- package/dist/components/display/Badge/Badge.js +16 -15
- package/dist/components/display/Badge/Badge.js.map +1 -1
- package/dist/components/display/CodeBlock/CodeBlock.js +45 -58
- package/dist/components/display/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/display/Swatch/Swatch.d.ts +52 -0
- package/dist/components/display/Swatch/Swatch.js +27 -0
- package/dist/components/display/Swatch/Swatch.js.map +1 -0
- package/dist/components/display/Swatch/Swatch.module.js +6 -0
- package/dist/components/display/Swatch/Swatch.module.js.map +1 -0
- package/dist/components/display/Swatch/index.d.ts +2 -0
- package/dist/components/display/Tag/Tag.d.ts +7 -0
- package/dist/components/display/Tag/Tag.js +31 -26
- package/dist/components/display/Tag/Tag.js.map +1 -1
- package/dist/components/display/TreeTable/TreeTable.d.ts +69 -0
- package/dist/components/display/TreeTable/TreeTable.js +152 -0
- package/dist/components/display/TreeTable/TreeTable.js.map +1 -0
- package/dist/components/display/TreeTable/TreeTable.module.js +12 -0
- package/dist/components/display/TreeTable/TreeTable.module.js.map +1 -0
- package/dist/components/display/TreeTable/index.d.ts +2 -0
- package/dist/components/feedback/Indicator/Indicator.d.ts +74 -0
- package/dist/components/feedback/Indicator/Indicator.js +50 -0
- package/dist/components/feedback/Indicator/Indicator.js.map +1 -0
- package/dist/components/feedback/Indicator/Indicator.module.js +12 -0
- package/dist/components/feedback/Indicator/Indicator.module.js.map +1 -0
- package/dist/components/feedback/Indicator/index.d.ts +2 -0
- package/dist/components/feedback/Toaster/Toaster.d.ts +71 -0
- package/dist/components/feedback/Toaster/Toaster.js +121 -0
- package/dist/components/feedback/Toaster/Toaster.js.map +1 -0
- package/dist/components/feedback/Toaster/Toaster.module.js +20 -0
- package/dist/components/feedback/Toaster/Toaster.module.js.map +1 -0
- package/dist/components/feedback/Toaster/index.d.ts +4 -0
- package/dist/components/feedback/Toaster/toast.d.ts +96 -0
- package/dist/components/feedback/Toaster/toast.js +131 -0
- package/dist/components/feedback/Toaster/toast.js.map +1 -0
- package/dist/components/inputs/Calendar/Calendar.d.ts +87 -0
- package/dist/components/inputs/Calendar/Calendar.js +217 -0
- package/dist/components/inputs/Calendar/Calendar.js.map +1 -0
- package/dist/components/inputs/Calendar/Calendar.module.js +18 -0
- package/dist/components/inputs/Calendar/Calendar.module.js.map +1 -0
- package/dist/components/inputs/Calendar/index.d.ts +2 -0
- package/dist/components/inputs/Combobox/Combobox.js +36 -44
- package/dist/components/inputs/Combobox/Combobox.js.map +1 -1
- package/dist/components/inputs/DateField/DateField.d.ts +17 -0
- package/dist/components/inputs/DateField/DateField.js +26 -0
- package/dist/components/inputs/DateField/DateField.js.map +1 -0
- package/dist/components/inputs/DateField/index.d.ts +2 -0
- package/dist/components/inputs/DateInput/DateInput.d.ts +75 -0
- package/dist/components/inputs/DateInput/DateInput.js +139 -0
- package/dist/components/inputs/DateInput/DateInput.js.map +1 -0
- package/dist/components/inputs/DateInput/index.d.ts +3 -0
- package/dist/components/inputs/DateRangeField/DateRangeField.d.ts +18 -3
- package/dist/components/inputs/DateRangeField/DateRangeField.js.map +1 -1
- package/dist/components/inputs/DateRangeField/DateRangeField.module.js +8 -3
- package/dist/components/inputs/DateRangeField/DateRangeField.module.js.map +1 -1
- package/dist/components/inputs/DateRangeField/DateRangePickerField.d.ts +48 -0
- package/dist/components/inputs/DateRangeField/DateRangePickerField.js +246 -0
- package/dist/components/inputs/DateRangeField/DateRangePickerField.js.map +1 -0
- package/dist/components/inputs/DateRangeField/index.d.ts +2 -0
- package/dist/components/inputs/DateTimeField/DateTimeField.d.ts +17 -0
- package/dist/components/inputs/DateTimeField/DateTimeField.js +26 -0
- package/dist/components/inputs/DateTimeField/DateTimeField.js.map +1 -0
- package/dist/components/inputs/DateTimeField/index.d.ts +2 -0
- package/dist/components/inputs/DateTimeInput/DateTimeInput.d.ts +83 -0
- package/dist/components/inputs/DateTimeInput/DateTimeInput.js +216 -0
- package/dist/components/inputs/DateTimeInput/DateTimeInput.js.map +1 -0
- package/dist/components/inputs/DateTimeInput/index.d.ts +2 -0
- package/dist/components/inputs/JsonField/JsonField.d.ts +23 -0
- package/dist/components/inputs/JsonField/JsonField.js +33 -0
- package/dist/components/inputs/JsonField/JsonField.js.map +1 -0
- package/dist/components/inputs/JsonField/index.d.ts +2 -0
- package/dist/components/inputs/JsonInput/JsonInput.d.ts +119 -0
- package/dist/components/inputs/JsonInput/JsonInput.js +282 -0
- package/dist/components/inputs/JsonInput/JsonInput.js.map +1 -0
- package/dist/components/inputs/JsonInput/JsonInput.module.js +16 -0
- package/dist/components/inputs/JsonInput/JsonInput.module.js.map +1 -0
- package/dist/components/inputs/JsonInput/index.d.ts +4 -0
- package/dist/components/inputs/JsonInput/json.d.ts +59 -0
- package/dist/components/inputs/JsonInput/json.js +231 -0
- package/dist/components/inputs/JsonInput/json.js.map +1 -0
- package/dist/components/inputs/TagsInput/TagsInput.js +5 -5
- package/dist/components/inputs/TimeField/TimeField.d.ts +17 -0
- package/dist/components/inputs/TimeField/TimeField.js +26 -0
- package/dist/components/inputs/TimeField/TimeField.js.map +1 -0
- package/dist/components/inputs/TimeField/index.d.ts +2 -0
- package/dist/components/inputs/TimeInput/TimeInput.d.ts +54 -0
- package/dist/components/inputs/TimeInput/TimeInput.js +107 -0
- package/dist/components/inputs/TimeInput/TimeInput.js.map +1 -0
- package/dist/components/inputs/TimeInput/index.d.ts +2 -0
- package/dist/components/inputs/internal/DateSegments.d.ts +80 -0
- package/dist/components/inputs/internal/DateSegments.js +345 -0
- package/dist/components/inputs/internal/DateSegments.js.map +1 -0
- package/dist/components/inputs/internal/dates.d.ts +101 -0
- package/dist/components/inputs/internal/dates.js +262 -0
- package/dist/components/inputs/internal/dates.js.map +1 -0
- package/dist/components/inputs/internal/filterOptions.d.ts +22 -1
- package/dist/components/inputs/internal/filterOptions.js +32 -1
- package/dist/components/inputs/internal/filterOptions.js.map +1 -1
- package/dist/components/inputs/internal/picker.d.ts +31 -0
- package/dist/components/inputs/internal/picker.js +29 -0
- package/dist/components/inputs/internal/picker.js.map +1 -0
- package/dist/components/inputs/internal/picker.module.js +15 -0
- package/dist/components/inputs/internal/picker.module.js.map +1 -0
- package/dist/components/inputs/internal/segments.module.js +10 -0
- package/dist/components/inputs/internal/segments.module.js.map +1 -0
- package/dist/components/layout/Collapse/Collapse.d.ts +39 -0
- package/dist/components/layout/Collapse/Collapse.js +24 -0
- package/dist/components/layout/Collapse/Collapse.js.map +1 -0
- package/dist/components/layout/Collapse/Collapse.module.js +6 -0
- package/dist/components/layout/Collapse/Collapse.module.js.map +1 -0
- package/dist/components/layout/Collapse/index.d.ts +2 -0
- package/dist/components/layout/Collapse/useHeightMotion.d.ts +16 -0
- package/dist/components/layout/Collapse/useHeightMotion.js +60 -0
- package/dist/components/layout/Collapse/useHeightMotion.js.map +1 -0
- package/dist/components/layout/ShowMore/ShowMore.d.ts +59 -0
- package/dist/components/layout/ShowMore/ShowMore.js +114 -0
- package/dist/components/layout/ShowMore/ShowMore.js.map +1 -0
- package/dist/components/layout/ShowMore/ShowMore.module.js +12 -0
- package/dist/components/layout/ShowMore/ShowMore.module.js.map +1 -0
- package/dist/components/layout/ShowMore/index.d.ts +2 -0
- package/dist/components/layout/Transition/Transition.d.ts +63 -0
- package/dist/components/layout/Transition/Transition.js +63 -0
- package/dist/components/layout/Transition/Transition.js.map +1 -0
- package/dist/components/layout/Transition/Transition.module.js +17 -0
- package/dist/components/layout/Transition/Transition.module.js.map +1 -0
- package/dist/components/layout/Transition/index.d.ts +2 -0
- package/dist/components/navigation/Collapsible/Collapsible.d.ts +49 -0
- package/dist/components/navigation/Collapsible/Collapsible.js +83 -0
- package/dist/components/navigation/Collapsible/Collapsible.js.map +1 -0
- package/dist/components/navigation/Collapsible/Collapsible.module.js +11 -0
- package/dist/components/navigation/Collapsible/Collapsible.module.js.map +1 -0
- package/dist/components/navigation/Collapsible/index.d.ts +2 -0
- package/dist/components/navigation/NavLinks/NavLinks.d.ts +32 -4
- package/dist/components/navigation/NavLinks/NavLinks.js +69 -39
- package/dist/components/navigation/NavLinks/NavLinks.js.map +1 -1
- package/dist/components/navigation/NavLinks/NavLinks.module.js +13 -3
- package/dist/components/navigation/NavLinks/NavLinks.module.js.map +1 -1
- package/dist/components/navigation/NavLinks/NavLinksGroup.d.ts +49 -0
- package/dist/components/navigation/NavLinks/NavLinksGroup.js +185 -0
- package/dist/components/navigation/NavLinks/NavLinksGroup.js.map +1 -0
- package/dist/components/navigation/NavLinks/context.d.ts +28 -0
- package/dist/components/navigation/NavLinks/context.js +13 -0
- package/dist/components/navigation/NavLinks/context.js.map +1 -0
- package/dist/components/navigation/NavLinks/index.d.ts +2 -0
- package/dist/components/navigation/NavLinks/treeKeys.d.ts +8 -0
- package/dist/components/navigation/NavLinks/treeKeys.js +57 -0
- package/dist/components/navigation/NavLinks/treeKeys.js.map +1 -0
- package/dist/components/overlays/CommandPalette/CommandPalette.d.ts +120 -0
- package/dist/components/overlays/CommandPalette/CommandPalette.js +386 -0
- package/dist/components/overlays/CommandPalette/CommandPalette.js.map +1 -0
- package/dist/components/overlays/CommandPalette/CommandPalette.module.js +31 -0
- package/dist/components/overlays/CommandPalette/CommandPalette.module.js.map +1 -0
- package/dist/components/overlays/CommandPalette/index.d.ts +2 -0
- package/dist/components/overlays/CommandPalette/shortcut.d.ts +28 -0
- package/dist/components/overlays/CommandPalette/shortcut.js +104 -0
- package/dist/components/overlays/CommandPalette/shortcut.js.map +1 -0
- package/dist/index.d.ts +42 -4
- package/dist/index.js +124 -100
- package/dist/styles.css +1 -1
- package/dist/themes/fiesta.css +1 -1
- package/dist/themes/flightdeck.css +1 -1
- package/dist/themes/ledger.css +1 -1
- package/dist/themes/monograph.css +1 -1
- package/dist/themes/riso.css +1 -1
- package/package.json +2 -1
- package/skills/custom-theme/SKILL.md +32 -0
- package/skills/custom-theme/references/colour.md +20 -0
- package/skills/custom-theme/references/motion.md +9 -0
- package/skills/custom-theme/references/tokens.md +35 -0
- package/skills/design-rules/SKILL.md +5 -2
- package/skills/setup-and-theming/SKILL.md +1 -1
- package/skills/setup-and-theming/references/paper.md +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactElement } from 'react';
|
|
2
|
+
import { type Space, type Tone } from '../../../utils/tokens.js';
|
|
3
|
+
import { type SwatchColor } from '../../display/Swatch/index.js';
|
|
4
|
+
export type IndicatorPosition = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
|
|
5
|
+
export type IndicatorShape = 'rect' | 'round' | 'avatar';
|
|
6
|
+
export interface IndicatorProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'color'> {
|
|
7
|
+
/**
|
|
8
|
+
* The element the mark is pinned to: an `IconButton`, an `Avatar`, a link. It must accept
|
|
9
|
+
* `aria-describedby`, which carries `label`. Omit it to show the mark on its own, in the flow.
|
|
10
|
+
*/
|
|
11
|
+
children?: ReactElement;
|
|
12
|
+
/**
|
|
13
|
+
* What the mark means, said in full: "3 unread messages", "Waiting for your answer". It's added
|
|
14
|
+
* to the child's accessible description, so the child is announced as "Inbox, 3 unread messages"
|
|
15
|
+
* rather than a bare number. Required, because a dot or a number alone is not information.
|
|
16
|
+
*/
|
|
17
|
+
label: string;
|
|
18
|
+
/** A number to show. Without it the mark is a dot. */
|
|
19
|
+
count?: number;
|
|
20
|
+
/** The largest count shown in full. Above it the mark reads "99+". Default `99`. */
|
|
21
|
+
max?: number;
|
|
22
|
+
/** Show the mark when `count` is `0`. Hidden by default, since nothing is waiting. */
|
|
23
|
+
showZero?: boolean;
|
|
24
|
+
/** Hide the mark (and its description) without unmounting the child. */
|
|
25
|
+
invisible?: boolean;
|
|
26
|
+
/** Colour of the mark, from the shared tone set. Default `accent`. */
|
|
27
|
+
tone?: Tone;
|
|
28
|
+
/**
|
|
29
|
+
* Draw the mark as a colour swatch dot instead: identity, like a team's colour on an avatar,
|
|
30
|
+
* never status. A categorical slot (`1` to `8`) or a CSS colour from data. `count` and `tone`
|
|
31
|
+
* don't apply.
|
|
32
|
+
*/
|
|
33
|
+
swatch?: SwatchColor;
|
|
34
|
+
/** A slow ring swells out of the mark, for something in progress. It stills under reduced motion. */
|
|
35
|
+
processing?: boolean;
|
|
36
|
+
/** Which corner, in the reading direction: `top-end` (default) is top right in English. */
|
|
37
|
+
position?: IndicatorPosition;
|
|
38
|
+
/**
|
|
39
|
+
* The child's outline, so the mark sits on its edge rather than in the empty corner of its box.
|
|
40
|
+
* `rect` (default) for square-cornered children, `round` for circles (a round `IconButton`),
|
|
41
|
+
* `avatar` to follow the theme's avatar radius.
|
|
42
|
+
*/
|
|
43
|
+
shape?: IndicatorShape;
|
|
44
|
+
/** Move the mark in toward the child's centre by a space step, for a child with an empty edge. */
|
|
45
|
+
offset?: Space;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A count or a dot pinned to the corner of another element: unread messages on a button, a
|
|
49
|
+
* waiting dot on an avatar.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* `Indicator` wraps one element and places a small mark on its edge. With `count` it's a number,
|
|
53
|
+
* capped at `max` ("99+"), and hidden at zero unless you pass `showZero`. Without it, it's a dot.
|
|
54
|
+
* `invisible` hides the mark without moving or remounting the child.
|
|
55
|
+
*
|
|
56
|
+
* The mark is hidden from screen readers. What it means is `label`, which is added to the
|
|
57
|
+
* child's accessible description, so give the child its own name (an `IconButton`'s `label`, an
|
|
58
|
+
* `Avatar`'s `name`) and write `label` as the rest of the sentence.
|
|
59
|
+
*
|
|
60
|
+
* `position` is logical, so `top-end` is the top left in a right-to-left page. `shape` keeps the
|
|
61
|
+
* mark on the outline of a round child instead of floating in the corner of its box, and `offset`
|
|
62
|
+
* moves it in toward a child whose visible edge is inside its box, like a ghost `IconButton`.
|
|
63
|
+
*
|
|
64
|
+
* ## When it changes
|
|
65
|
+
*
|
|
66
|
+
* An `Indicator` doesn't announce changes: a count that speaks every time it ticks is noise.
|
|
67
|
+
* Where a change matters, such as a reply arriving, announce it where it happens. `processing`
|
|
68
|
+
* pulses a ring for something under way, and stills under reduced motion.
|
|
69
|
+
*
|
|
70
|
+
* @privateRemarks
|
|
71
|
+
* The tone colours match StatusDot and Badge's solid fill, the pulse is LiveIndicator's, and a
|
|
72
|
+
* `swatch` mark is the shared Swatch part, so the dots in the system stay one family.
|
|
73
|
+
*/
|
|
74
|
+
export declare const Indicator: import("react").ForwardRefExoticComponent<IndicatorProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { cx as e } from "../../../utils/cx.js";
|
|
2
|
+
import { mergeStyles as t } from "../../../utils/responsive.js";
|
|
3
|
+
import { space as n } from "../../../utils/tokens.js";
|
|
4
|
+
import { joinIds as r } from "../../inputs/internal/refs.js";
|
|
5
|
+
import { Swatch as i } from "../../display/Swatch/Swatch.js";
|
|
6
|
+
import a from "./Indicator.module.js";
|
|
7
|
+
import { cloneElement as o, forwardRef as s, isValidElement as c, useId as l } from "react";
|
|
8
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
9
|
+
//#region src/components/feedback/Indicator/Indicator.tsx
|
|
10
|
+
var f = (e, t) => e > t ? `${String(t)}+` : String(e), p = s(function({ children: s, label: p, count: m, max: h = 99, showZero: g = !1, invisible: _ = !1, tone: v = "accent", swatch: y, processing: b = !1, position: x = "top-end", shape: S = "rect", offset: C, className: w, style: T, ...E }, D) {
|
|
11
|
+
let O = l(), k = y === void 0 && m !== void 0, A = !_ && !(k && m === 0 && !g), j = c(s), M = A ? y === void 0 ? /* @__PURE__ */ u("span", {
|
|
12
|
+
className: a.mark,
|
|
13
|
+
"data-kind": k ? "count" : "dot",
|
|
14
|
+
"data-tone": v,
|
|
15
|
+
"data-processing": b || void 0,
|
|
16
|
+
"aria-hidden": "true",
|
|
17
|
+
children: k ? f(m, h) : null
|
|
18
|
+
}) : /* @__PURE__ */ u("span", {
|
|
19
|
+
className: a.mark,
|
|
20
|
+
"data-kind": "swatch",
|
|
21
|
+
"data-processing": b || void 0,
|
|
22
|
+
"aria-hidden": "true",
|
|
23
|
+
children: /* @__PURE__ */ u(i, {
|
|
24
|
+
color: y,
|
|
25
|
+
className: a.fill
|
|
26
|
+
})
|
|
27
|
+
}) : null;
|
|
28
|
+
return /* @__PURE__ */ d("span", {
|
|
29
|
+
ref: D,
|
|
30
|
+
className: e(a.indicator, w),
|
|
31
|
+
"data-position": x,
|
|
32
|
+
"data-shape": S,
|
|
33
|
+
"data-pinned": j || void 0,
|
|
34
|
+
style: t(C ? { "--_offset": n(C) } : void 0, T),
|
|
35
|
+
...E,
|
|
36
|
+
children: [
|
|
37
|
+
j ? o(s, { "aria-describedby": r(s.props["aria-describedby"], A ? O : void 0) }) : null,
|
|
38
|
+
M,
|
|
39
|
+
A ? /* @__PURE__ */ u("span", {
|
|
40
|
+
id: O,
|
|
41
|
+
className: a.label,
|
|
42
|
+
children: p
|
|
43
|
+
}) : null
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//#endregion
|
|
48
|
+
export { p as Indicator };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=Indicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Indicator.js","names":["styles","Indicator_module_default","formatCount","count","max","Indicator","forwardRef","rest","labelId","useId","hasCount","swatch","shown","invisible","showZero","pinned","isValidElement","children","mark","tone","processing","Swatch","cx","className","position","shape","mergeStyles","offset","space","style","cloneElement","joinIds","label"],"sources":["../../../../src/components/feedback/Indicator/Indicator.tsx"],"sourcesContent":["import {\n cloneElement,\n forwardRef,\n isValidElement,\n useId,\n type CSSProperties,\n type HTMLAttributes,\n type ReactElement,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport { mergeStyles } from '#utils/responsive'\nimport { space, type Space, type Tone } from '#utils/tokens'\nimport { joinIds } from '#components/inputs/internal/refs'\nimport { Swatch, type SwatchColor } from '#components/display/Swatch'\nimport styles from './Indicator.module.css'\n\nexport type IndicatorPosition = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'\nexport type IndicatorShape = 'rect' | 'round' | 'avatar'\n\nexport interface IndicatorProps extends Omit<\n HTMLAttributes<HTMLSpanElement>,\n 'children' | 'color'\n> {\n /**\n * The element the mark is pinned to: an `IconButton`, an `Avatar`, a link. It must accept\n * `aria-describedby`, which carries `label`. Omit it to show the mark on its own, in the flow.\n */\n children?: ReactElement\n /**\n * What the mark means, said in full: \"3 unread messages\", \"Waiting for your answer\". It's added\n * to the child's accessible description, so the child is announced as \"Inbox, 3 unread messages\"\n * rather than a bare number. Required, because a dot or a number alone is not information.\n */\n label: string\n /** A number to show. Without it the mark is a dot. */\n count?: number\n /** The largest count shown in full. Above it the mark reads \"99+\". Default `99`. */\n max?: number\n /** Show the mark when `count` is `0`. Hidden by default, since nothing is waiting. */\n showZero?: boolean\n /** Hide the mark (and its description) without unmounting the child. */\n invisible?: boolean\n /** Colour of the mark, from the shared tone set. Default `accent`. */\n tone?: Tone\n /**\n * Draw the mark as a colour swatch dot instead: identity, like a team's colour on an avatar,\n * never status. A categorical slot (`1` to `8`) or a CSS colour from data. `count` and `tone`\n * don't apply.\n */\n swatch?: SwatchColor\n /** A slow ring swells out of the mark, for something in progress. It stills under reduced motion. */\n processing?: boolean\n /** Which corner, in the reading direction: `top-end` (default) is top right in English. */\n position?: IndicatorPosition\n /**\n * The child's outline, so the mark sits on its edge rather than in the empty corner of its box.\n * `rect` (default) for square-cornered children, `round` for circles (a round `IconButton`),\n * `avatar` to follow the theme's avatar radius.\n */\n shape?: IndicatorShape\n /** Move the mark in toward the child's centre by a space step, for a child with an empty edge. */\n offset?: Space\n}\n\nconst formatCount = (count: number, max: number) =>\n count > max ? `${String(max)}+` : String(count)\n\n/**\n * A count or a dot pinned to the corner of another element: unread messages on a button, a\n * waiting dot on an avatar.\n *\n * @remarks\n * `Indicator` wraps one element and places a small mark on its edge. With `count` it's a number,\n * capped at `max` (\"99+\"), and hidden at zero unless you pass `showZero`. Without it, it's a dot.\n * `invisible` hides the mark without moving or remounting the child.\n *\n * The mark is hidden from screen readers. What it means is `label`, which is added to the\n * child's accessible description, so give the child its own name (an `IconButton`'s `label`, an\n * `Avatar`'s `name`) and write `label` as the rest of the sentence.\n *\n * `position` is logical, so `top-end` is the top left in a right-to-left page. `shape` keeps the\n * mark on the outline of a round child instead of floating in the corner of its box, and `offset`\n * moves it in toward a child whose visible edge is inside its box, like a ghost `IconButton`.\n *\n * ## When it changes\n *\n * An `Indicator` doesn't announce changes: a count that speaks every time it ticks is noise.\n * Where a change matters, such as a reply arriving, announce it where it happens. `processing`\n * pulses a ring for something under way, and stills under reduced motion.\n *\n * @privateRemarks\n * The tone colours match StatusDot and Badge's solid fill, the pulse is LiveIndicator's, and a\n * `swatch` mark is the shared Swatch part, so the dots in the system stay one family.\n */\nexport const Indicator = forwardRef<HTMLSpanElement, IndicatorProps>(function Indicator(\n {\n children,\n label,\n count,\n max = 99,\n showZero = false,\n invisible = false,\n tone = 'accent',\n swatch,\n processing = false,\n position = 'top-end',\n shape = 'rect',\n offset,\n className,\n style,\n ...rest\n },\n ref,\n) {\n const labelId = useId()\n const hasCount = swatch === undefined && count !== undefined\n const shown = !invisible && !(hasCount && count === 0 && !showZero)\n const pinned = isValidElement<{ 'aria-describedby'?: string }>(children)\n\n const mark = shown ? (\n swatch !== undefined ? (\n <span\n className={styles.mark}\n data-kind=\"swatch\"\n data-processing={processing || undefined}\n aria-hidden=\"true\"\n >\n <Swatch color={swatch} className={styles.fill} />\n </span>\n ) : (\n <span\n className={styles.mark}\n data-kind={hasCount ? 'count' : 'dot'}\n data-tone={tone}\n data-processing={processing || undefined}\n aria-hidden=\"true\"\n >\n {hasCount ? formatCount(count, max) : null}\n </span>\n )\n ) : null\n\n return (\n <span\n ref={ref}\n className={cx(styles.indicator, className)}\n data-position={position}\n data-shape={shape}\n data-pinned={pinned || undefined}\n style={mergeStyles(\n offset ? ({ '--_offset': space(offset) } as CSSProperties) : undefined,\n style,\n )}\n {...rest}\n >\n {pinned\n ? cloneElement(children, {\n 'aria-describedby': joinIds(\n children.props['aria-describedby'],\n shown ? labelId : undefined,\n ),\n })\n : null}\n {mark}\n {shown ? (\n <span id={labelId} className={styles.label}>\n {label}\n </span>\n ) : null}\n </span>\n )\n})\n"],"mappings":";;;;;;;;;AAgEA,IAAME,KAAe,GAAe,MAClCC,IAAQC,IAAM,GAAG,OAAOA,CAAG,EAAE,KAAK,OAAOD,CAAK,GA6BnCE,IAAYC,EAA4C,SACnE,EACE,aACA,UACA,UACA,SAAM,IACN,cAAW,IACX,eAAY,IACZ,UAAO,UACP,WACA,gBAAa,IACb,cAAW,WACX,WAAQ,QACR,WACA,cACA,UACA,GAAGC,KAEL,GACA;CACA,IAAMC,IAAUC,EAAM,GAChBC,IAAWC,MAAW,KAAA,KAAaR,MAAU,KAAA,GAC7CS,IAAQ,CAACC,KAAa,EAAEH,KAAYP,MAAU,KAAK,CAACW,IACpDC,IAASC,EAAgDC,CAAQ,GAEjEC,IAAON,IACXD,MAAW,KAAA,IAUT,kBAAC,QAAD;EACE,WAAWX,EAAO;EAClB,aAAWU,IAAW,UAAU;EAChC,aAAWS;EACX,mBAAiBC,KAAc,KAAA;EAC/B,eAAY;EAEX,UAAAV,IAAWR,EAAYC,GAAOC,CAAG,IAAI;CAClC,CAAA,IAjBN,kBAAC,QAAD;EACE,WAAWJ,EAAO;EAClB,aAAU;EACV,mBAAiBoB,KAAc,KAAA;EAC/B,eAAY;EAEZ,UAAA,kBAACC,GAAD;GAAQ,OAAOV;GAAQ,WAAWX,EAAO;EAAO,CAAA;CAC5C,CAAA,IAYN;CAEJ,OACE,kBAAC,QAAD;EACO;EACL,WAAWsB,EAAGtB,EAAO,WAAWuB,CAAS;EACzC,iBAAeC;EACf,cAAYC;EACZ,eAAaV,KAAU,KAAA;EACvB,OAAOW,EACLC,IAAU,EAAE,aAAaC,EAAMD,CAAM,EAAE,IAAsB,KAAA,GAC7DE,CACF;EACA,GAAItB;EAVN,UAAA;GAYGQ,IACGe,EAAab,GAAU,EACrB,oBAAoBc,EAClBd,EAAS,MAAM,qBACfL,IAAQJ,IAAU,KAAA,CACpB,EACF,CAAC,IACD;GACHU;GACAN,IACC,kBAAC,QAAD;IAAM,IAAIJ;IAAS,WAAWR,EAAO;IAClC,UAAAgC;GACG,CAAA,IACJ;EACA;;AAEV,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/components/feedback/Indicator/Indicator.module.css
|
|
2
|
+
var e = "kiln-Indicator__indicator", t = "kiln-Indicator__mark", n = "kiln-Indicator__fill", r = "kiln-Indicator__ring", i = "kiln-Indicator__label", a = {
|
|
3
|
+
indicator: e,
|
|
4
|
+
mark: t,
|
|
5
|
+
fill: n,
|
|
6
|
+
ring: r,
|
|
7
|
+
label: i
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { a as default, n as fill, e as indicator, i as label, t as mark, r as ring };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=Indicator.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Indicator.module.js","names":["indicator","mark","fill","ring","label","Indicator_module_default"],"sources":["../../../../src/components/feedback/Indicator/Indicator.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --indicator-ring the gap drawn round the mark so it reads over its child,\n * default var(--color-surface)\n * --indicator-dot-size default var(--space-2)\n * --indicator-count-size height of a count, default var(--space-4)\n * --indicator-radius default var(--radius-chip)\n * --indicator-pulse-scale how far the processing ring swells, default 2.4\n *\n * The mark sits centred on the child's corner: the inset follows the child's outline (`shape`)\n * plus `offset`, and negative margins of half the mark's height centre it on that point. A count\n * grows inward from there, so it never needs a transform and RTL needs nothing extra.\n */\n.indicator {\n --_offset: 0px;\n --_inset: 0px;\n\n position: relative;\n display: inline-flex;\n flex: none;\n vertical-align: middle;\n}\n\n/* On a circle, the point of the outline nearest the corner is 1 − 1/√2 of the radius in. */\n.indicator[data-shape='round'] {\n --_inset: 14.645%;\n}\n.indicator[data-shape='avatar'] {\n --_inset: calc(var(--avatar-radius, var(--radius-avatar)) * 0.2929);\n}\n\n.mark {\n --_c: var(--color-accent);\n --_c-ink: var(--color-accent-ink);\n --_h: var(--indicator-dot-size, var(--space-2));\n --_edge: calc(var(--_inset) + var(--_offset));\n\n position: relative;\n display: inline-grid;\n place-items: center;\n box-sizing: border-box;\n min-inline-size: var(--_h);\n block-size: var(--_h);\n border-radius: var(--indicator-radius, var(--radius-chip));\n background: var(--_c);\n color: var(--_c-ink);\n box-shadow: 0 0 0 2px var(--indicator-ring, var(--color-surface));\n font-family: var(--font-numeric);\n font-stretch: var(--numeric-stretch);\n font-size: var(--text-2xs);\n font-weight: var(--label-weight);\n font-variant-numeric: tabular-nums lining-nums;\n line-height: 1;\n white-space: nowrap;\n pointer-events: none;\n}\n.mark[data-kind='count'] {\n --_h: var(--indicator-count-size, var(--space-4));\n\n padding-inline: calc(var(--_h) / 4);\n}\n.mark[data-kind='swatch'] {\n inline-size: var(--_h);\n background: none;\n}\n.mark > .fill {\n position: relative;\n inline-size: 100%;\n block-size: 100%;\n border-radius: inherit;\n}\n\n.indicator[data-pinned] > .mark {\n position: absolute;\n z-index: 1;\n}\n.indicator[data-position^='top'] > .mark {\n inset-block-start: var(--_edge);\n margin-block-start: calc(var(--_h) / -2);\n}\n.indicator[data-position^='bottom'] > .mark {\n inset-block-end: var(--_edge);\n margin-block-end: calc(var(--_h) / -2);\n}\n.indicator[data-position$='-start'] > .mark {\n inset-inline-start: var(--_edge);\n margin-inline-start: calc(var(--_h) / -2);\n}\n.indicator[data-position$='-end'] > .mark {\n inset-inline-end: var(--_edge);\n margin-inline-end: calc(var(--_h) / -2);\n}\n\n.mark[data-tone='neutral'] {\n --_c: var(--color-surface-inverse);\n --_c-ink: var(--color-ink-inverse);\n}\n.mark[data-tone='positive'] {\n --_c: var(--tone-positive);\n --_c-ink: var(--tone-positive-ink);\n}\n.mark[data-tone='caution'] {\n --_c: var(--tone-caution);\n --_c-ink: var(--tone-caution-ink);\n}\n.mark[data-tone='critical'] {\n --_c: var(--tone-critical);\n --_c-ink: var(--tone-critical-ink);\n}\n.mark[data-tone='info'] {\n --_c: var(--tone-info);\n --_c-ink: var(--tone-info-ink);\n}\n\n/* The same slow ring as LiveIndicator. */\n.mark[data-processing]:not([data-kind='swatch'])::after,\n.mark[data-processing] > .fill::after {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background: currentColor;\n color: var(--_c);\n animation: ring calc(var(--dur-3) * 5) var(--ease-out) infinite;\n}\n.mark[data-processing] > .fill::after {\n background: inherit;\n}\n\n@keyframes ring {\n 0% {\n scale: 1;\n opacity: 0.5;\n }\n 70%,\n 100% {\n scale: var(--indicator-pulse-scale, 2.4);\n opacity: 0;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .mark[data-processing]:not([data-kind='swatch'])::after,\n .mark[data-processing] > .fill::after {\n animation: none;\n opacity: 0;\n }\n}\n\n/* Read through the child's aria-describedby; kept in the tree for reading order. */\n.label {\n position: absolute;\n inline-size: 1px;\n block-size: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n@media (forced-colors: active) {\n .mark:not([data-kind='swatch']) {\n border: 1px solid CanvasText;\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export type ToastPosition = 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end';
|
|
2
|
+
export interface ToasterProps {
|
|
3
|
+
/** Where the toasts stack. Default `bottom-end`. */
|
|
4
|
+
position?: ToastPosition;
|
|
5
|
+
/**
|
|
6
|
+
* How many toasts show at once. Older ones beyond it wait, with their timers stopped, and come
|
|
7
|
+
* back as the newer ones close. Default 3.
|
|
8
|
+
*/
|
|
9
|
+
max?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Names the region for screen readers. `{hotkey}` is replaced with the shortcut. Default
|
|
12
|
+
* "Notifications ({hotkey})".
|
|
13
|
+
*/
|
|
14
|
+
label?: string;
|
|
15
|
+
/** The keys that move focus to the toasts. Default `['F8']`. */
|
|
16
|
+
hotkey?: string[];
|
|
17
|
+
/** Accessible name of each toast's close button. Default "Dismiss". */
|
|
18
|
+
closeLabel?: string;
|
|
19
|
+
/** Portal target. Defaults to `document.body`. */
|
|
20
|
+
container?: HTMLElement | null;
|
|
21
|
+
/** Class on the region the toasts stack in. */
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The region that shows toasts. Mount it once near the root, then raise toasts with `toast()` from
|
|
26
|
+
* anywhere.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* A toast tells someone that something happened away from where they're looking: a save
|
|
30
|
+
* finished, a background job failed, a link landed on the clipboard. For a message about the
|
|
31
|
+
* content beside it, use an {@link Alert | Alert} instead; it stays in the layout.
|
|
32
|
+
*
|
|
33
|
+
* ```tsx
|
|
34
|
+
* <ThemeProvider>
|
|
35
|
+
* <App />
|
|
36
|
+
* <Toaster />
|
|
37
|
+
* </ThemeProvider>
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* Each toast has the same frame as an `Alert`: a hairline, the tone in its glyph and a short tab
|
|
41
|
+
* on the top edge. It can carry one action, such as Undo, and a close button. It closes on its
|
|
42
|
+
* own after a few seconds; `critical` toasts and loading ones stay until they're dismissed. The
|
|
43
|
+
* timer pauses while the pointer or keyboard focus is on the toasts, and while the window is in
|
|
44
|
+
* the background.
|
|
45
|
+
*
|
|
46
|
+
* Toasts are announced as they appear, and again when one changes (a loading toast turning into
|
|
47
|
+
* "Saved"). `critical` and `caution` toasts interrupt; the rest wait their turn. F8 moves focus to
|
|
48
|
+
* the region, Escape closes the focused toast, and a swipe toward the nearest edge dismisses one.
|
|
49
|
+
* The toasts take the theme of the place the `Toaster` is mounted, and slide in from that edge;
|
|
50
|
+
* with reduced motion they appear and go without moving. If more than one `Toaster` is mounted,
|
|
51
|
+
* the first one shows the toasts.
|
|
52
|
+
*
|
|
53
|
+
* ## Updating and dismissing
|
|
54
|
+
*
|
|
55
|
+
* `toast()` returns an id. `toast.update(id, options)` changes that toast, `toast.dismiss(id)`
|
|
56
|
+
* closes it, and `toast.dismiss()` closes every toast. Raising a toast with an `id` that's still on
|
|
57
|
+
* screen updates it rather than stacking a copy.
|
|
58
|
+
*
|
|
59
|
+
* `toast.promise(promise, { loading, success, error })` shows a loading toast, then turns it into
|
|
60
|
+
* the success or error message when the promise settles.
|
|
61
|
+
*
|
|
62
|
+
* @privateRemarks
|
|
63
|
+
* Radix Toast underneath (swipe, hotkey, pause, live announcements). Radix announces a toast's
|
|
64
|
+
* text once, on mount, so a changed message bumps `version` and remounts the toast under a new
|
|
65
|
+
* key; `data-updated` stops that remount from replaying the entrance. The viewport is portalled
|
|
66
|
+
* and copies the theme of a hidden anchor where the Toaster sits (usePortalTheme).
|
|
67
|
+
*
|
|
68
|
+
* <Toaster position="bottom-end" />
|
|
69
|
+
* toast({ title: 'Invoice sent', tone: 'positive' })
|
|
70
|
+
*/
|
|
71
|
+
export declare function Toaster({ position, max, label, hotkey, closeLabel, container, className, }: ToasterProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { cx as e } from "../../../utils/cx.js";
|
|
2
|
+
import { CircleCheckIcon as t, CloseIcon as n, ErrorIcon as r, InfoIcon as i, WarningIcon as a } from "../../../icons/icons.js";
|
|
3
|
+
import { Spinner as o } from "../Spinner/Spinner.js";
|
|
4
|
+
import { PortalAnchorContext as s, composeRefs as c, usePortalAnchor as l, usePortalAnchorRef as u, usePortalTheme as d } from "../../overlays/usePortalTheme.js";
|
|
5
|
+
import { getServerToasts as f, getToasts as p, primaryToaster as m, registerToaster as h, subscribeToasters as g, subscribeToasts as _, toast as v } from "./toast.js";
|
|
6
|
+
import y from "./Toaster.module.js";
|
|
7
|
+
import { forwardRef as b, useEffect as x, useState as S, useSyncExternalStore as C } from "react";
|
|
8
|
+
import { jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
9
|
+
import { Portal as E, Toast as D } from "radix-ui";
|
|
10
|
+
//#region src/components/feedback/Toaster/Toaster.tsx
|
|
11
|
+
var O = {
|
|
12
|
+
neutral: /* @__PURE__ */ w(i, {}),
|
|
13
|
+
info: /* @__PURE__ */ w(i, {}),
|
|
14
|
+
positive: /* @__PURE__ */ w(t, {}),
|
|
15
|
+
caution: /* @__PURE__ */ w(a, {}),
|
|
16
|
+
critical: /* @__PURE__ */ w(r, {})
|
|
17
|
+
};
|
|
18
|
+
function k(e) {
|
|
19
|
+
if (e.duration !== void 0) return e.duration;
|
|
20
|
+
if (e.loading || e.tone === "critical") return Infinity;
|
|
21
|
+
if (e.tone === "caution") return 8e3;
|
|
22
|
+
}
|
|
23
|
+
function A(e) {
|
|
24
|
+
return e.endsWith("-end") ? "right" : e.endsWith("-start") ? "left" : e.startsWith("top") ? "up" : "down";
|
|
25
|
+
}
|
|
26
|
+
function j({ record: e, closeLabel: t }) {
|
|
27
|
+
let r = e.tone ?? "neutral", i = r === "critical" || r === "caution";
|
|
28
|
+
return /* @__PURE__ */ T(D.Root, {
|
|
29
|
+
className: y.toast,
|
|
30
|
+
open: e.open,
|
|
31
|
+
onOpenChange: (t) => {
|
|
32
|
+
t || v.dismiss(e.id);
|
|
33
|
+
},
|
|
34
|
+
duration: k(e),
|
|
35
|
+
type: i ? "foreground" : "background",
|
|
36
|
+
"data-tone": r,
|
|
37
|
+
"data-loading": e.loading === !0 ? "" : void 0,
|
|
38
|
+
"data-updated": e.version > 0 || void 0,
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ w("span", {
|
|
41
|
+
className: y.glyph,
|
|
42
|
+
"aria-hidden": "true",
|
|
43
|
+
children: e.loading ? /* @__PURE__ */ w(o, { label: null }) : O[r]
|
|
44
|
+
}),
|
|
45
|
+
/* @__PURE__ */ T("div", {
|
|
46
|
+
className: y.body,
|
|
47
|
+
children: [
|
|
48
|
+
e.title === void 0 ? null : /* @__PURE__ */ w(D.Title, {
|
|
49
|
+
className: y.title,
|
|
50
|
+
children: e.title
|
|
51
|
+
}),
|
|
52
|
+
e.description === void 0 ? null : /* @__PURE__ */ w(D.Description, {
|
|
53
|
+
className: y.description,
|
|
54
|
+
children: e.description
|
|
55
|
+
}),
|
|
56
|
+
e.action ? /* @__PURE__ */ w("div", {
|
|
57
|
+
className: y.actions,
|
|
58
|
+
children: /* @__PURE__ */ w(D.Action, {
|
|
59
|
+
className: y.action,
|
|
60
|
+
altText: e.action.altText ?? e.action.label,
|
|
61
|
+
onClick: e.action.onClick,
|
|
62
|
+
children: e.action.label
|
|
63
|
+
})
|
|
64
|
+
}) : null
|
|
65
|
+
]
|
|
66
|
+
}),
|
|
67
|
+
e.dismissible === !1 ? null : /* @__PURE__ */ w(D.Close, {
|
|
68
|
+
className: y.close,
|
|
69
|
+
"aria-label": t,
|
|
70
|
+
children: /* @__PURE__ */ w(n, {})
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
var M = b(function({ position: t, label: n, hotkey: r, container: i, className: a }, o) {
|
|
76
|
+
let s = d(i);
|
|
77
|
+
return /* @__PURE__ */ w(D.Viewport, {
|
|
78
|
+
ref: o,
|
|
79
|
+
className: e(y.viewport, a),
|
|
80
|
+
"data-position": t,
|
|
81
|
+
label: n,
|
|
82
|
+
hotkey: r,
|
|
83
|
+
...s
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
function N() {
|
|
87
|
+
let e = l();
|
|
88
|
+
return /* @__PURE__ */ w("span", {
|
|
89
|
+
ref: c(e ?? void 0),
|
|
90
|
+
hidden: !0
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function P({ position: e = "bottom-end", max: t = 3, label: n = "Notifications ({hotkey})", hotkey: r, closeLabel: i = "Dismiss", container: a, className: o }) {
|
|
94
|
+
let c = u(), l = C(_, p, f), [d] = S(() => ({}));
|
|
95
|
+
if (x(() => h(d), [d]), C(g, m, () => void 0) !== d) return null;
|
|
96
|
+
let v = new Set(l.filter((e) => e.open).slice(-Math.max(1, t)).map((e) => e.id)), y = l.filter((e) => !e.open || v.has(e.id));
|
|
97
|
+
return /* @__PURE__ */ T(s.Provider, {
|
|
98
|
+
value: c,
|
|
99
|
+
children: [/* @__PURE__ */ w(N, {}), /* @__PURE__ */ T(D.Provider, {
|
|
100
|
+
swipeDirection: A(e),
|
|
101
|
+
label: "Notification",
|
|
102
|
+
children: [y.map((e) => /* @__PURE__ */ w(j, {
|
|
103
|
+
record: e,
|
|
104
|
+
closeLabel: i
|
|
105
|
+
}, `${e.id}:${String(e.version)}`)), /* @__PURE__ */ w(E.Root, {
|
|
106
|
+
container: a,
|
|
107
|
+
children: /* @__PURE__ */ w(M, {
|
|
108
|
+
position: e,
|
|
109
|
+
label: n,
|
|
110
|
+
hotkey: r,
|
|
111
|
+
container: a,
|
|
112
|
+
className: o
|
|
113
|
+
})
|
|
114
|
+
})]
|
|
115
|
+
})]
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
export { P as Toaster };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=Toaster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toaster.js","names":["styles","Toaster_module_default","GLYPH","InfoIcon","CircleCheckIcon","WarningIcon","ErrorIcon","durationOf","record","swipeDirection","position","ToastItem","tone","interrupt","Toast","open","toast","Spinner","closeLabel","CloseIcon","Viewport","forwardRef","theme","usePortalTheme","container","cx","className","Anchor","anchor","usePortalAnchor","composeRefs","Toaster","usePortalAnchorRef","records","useSyncExternalStore","subscribeToasts","getToasts","getServerToasts","token","useState","useEffect","registerToaster","subscribeToasters","primaryToaster","shown","max","visible","PortalAnchorContext","Portal"],"sources":["../../../../src/components/feedback/Toaster/Toaster.tsx"],"sourcesContent":["import { forwardRef, useEffect, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport { Portal, Toast } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { CircleCheckIcon, CloseIcon, ErrorIcon, InfoIcon, WarningIcon } from '#icons'\nimport { Spinner } from '#components/feedback/Spinner'\nimport {\n PortalAnchorContext,\n composeRefs,\n usePortalAnchor,\n usePortalAnchorRef,\n usePortalTheme,\n} from '#components/overlays/usePortalTheme'\nimport {\n getServerToasts,\n getToasts,\n primaryToaster,\n registerToaster,\n subscribeToasters,\n subscribeToasts,\n toast,\n type ToastRecord,\n type ToastTone,\n} from './toast'\nimport styles from './Toaster.module.css'\n\nexport type ToastPosition =\n 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end'\n\nexport interface ToasterProps {\n /** Where the toasts stack. Default `bottom-end`. */\n position?: ToastPosition\n /**\n * How many toasts show at once. Older ones beyond it wait, with their timers stopped, and come\n * back as the newer ones close. Default 3.\n */\n max?: number\n /**\n * Names the region for screen readers. `{hotkey}` is replaced with the shortcut. Default\n * \"Notifications ({hotkey})\".\n */\n label?: string\n /** The keys that move focus to the toasts. Default `['F8']`. */\n hotkey?: string[]\n /** Accessible name of each toast's close button. Default \"Dismiss\". */\n closeLabel?: string\n /** Portal target. Defaults to `document.body`. */\n container?: HTMLElement | null\n /** Class on the region the toasts stack in. */\n className?: string\n}\n\nconst GLYPH: Record<ToastTone, ReactNode> = {\n neutral: <InfoIcon />,\n info: <InfoIcon />,\n positive: <CircleCheckIcon />,\n caution: <WarningIcon />,\n critical: <ErrorIcon />,\n}\n\nfunction durationOf(record: ToastRecord): number | undefined {\n if (record.duration !== undefined) return record.duration\n if (record.loading || record.tone === 'critical') return Infinity\n if (record.tone === 'caution') return 8000\n return undefined\n}\n\nfunction swipeDirection(position: ToastPosition): 'up' | 'down' | 'left' | 'right' {\n if (position.endsWith('-end')) return 'right'\n if (position.endsWith('-start')) return 'left'\n return position.startsWith('top') ? 'up' : 'down'\n}\n\nfunction ToastItem({ record, closeLabel }: { record: ToastRecord; closeLabel: string }) {\n const tone = record.tone ?? 'neutral'\n const interrupt = tone === 'critical' || tone === 'caution'\n return (\n <Toast.Root\n className={styles.toast}\n open={record.open}\n onOpenChange={(open) => {\n if (!open) toast.dismiss(record.id)\n }}\n duration={durationOf(record)}\n type={interrupt ? 'foreground' : 'background'}\n data-tone={tone}\n data-loading={record.loading === true ? '' : undefined}\n data-updated={record.version > 0 || undefined}\n >\n <span className={styles.glyph} aria-hidden=\"true\">\n {record.loading ? <Spinner label={null} /> : GLYPH[tone]}\n </span>\n <div className={styles.body}>\n {record.title !== undefined ? (\n <Toast.Title className={styles.title}>{record.title}</Toast.Title>\n ) : null}\n {record.description !== undefined ? (\n <Toast.Description className={styles.description}>{record.description}</Toast.Description>\n ) : null}\n {record.action ? (\n <div className={styles.actions}>\n <Toast.Action\n className={styles.action}\n altText={record.action.altText ?? record.action.label}\n onClick={record.action.onClick}\n >\n {record.action.label}\n </Toast.Action>\n </div>\n ) : null}\n </div>\n {record.dismissible !== false ? (\n <Toast.Close className={styles.close} aria-label={closeLabel}>\n <CloseIcon />\n </Toast.Close>\n ) : null}\n </Toast.Root>\n )\n}\n\nconst Viewport = forwardRef<\n HTMLOListElement,\n {\n position: ToastPosition\n label: string\n hotkey?: string[]\n container?: HTMLElement | null\n className?: string\n }\n>(function Viewport({ position, label, hotkey, container, className }, ref) {\n const theme = usePortalTheme(container)\n return (\n <Toast.Viewport\n ref={ref}\n className={cx(styles.viewport, className)}\n data-position={position}\n label={label}\n hotkey={hotkey}\n {...theme}\n />\n )\n})\n\n/** Marks where the Toaster is mounted, so the toasts take the theme of that place. */\nfunction Anchor() {\n const anchor = usePortalAnchor()\n return <span ref={composeRefs(anchor ?? undefined)} hidden />\n}\n\n/**\n * The region that shows toasts. Mount it once near the root, then raise toasts with `toast()` from\n * anywhere.\n *\n * @remarks\n * A toast tells someone that something happened away from where they're looking: a save\n * finished, a background job failed, a link landed on the clipboard. For a message about the\n * content beside it, use an {@link Alert | Alert} instead; it stays in the layout.\n *\n * ```tsx\n * <ThemeProvider>\n * <App />\n * <Toaster />\n * </ThemeProvider>\n * ```\n *\n * Each toast has the same frame as an `Alert`: a hairline, the tone in its glyph and a short tab\n * on the top edge. It can carry one action, such as Undo, and a close button. It closes on its\n * own after a few seconds; `critical` toasts and loading ones stay until they're dismissed. The\n * timer pauses while the pointer or keyboard focus is on the toasts, and while the window is in\n * the background.\n *\n * Toasts are announced as they appear, and again when one changes (a loading toast turning into\n * \"Saved\"). `critical` and `caution` toasts interrupt; the rest wait their turn. F8 moves focus to\n * the region, Escape closes the focused toast, and a swipe toward the nearest edge dismisses one.\n * The toasts take the theme of the place the `Toaster` is mounted, and slide in from that edge;\n * with reduced motion they appear and go without moving. If more than one `Toaster` is mounted,\n * the first one shows the toasts.\n *\n * ## Updating and dismissing\n *\n * `toast()` returns an id. `toast.update(id, options)` changes that toast, `toast.dismiss(id)`\n * closes it, and `toast.dismiss()` closes every toast. Raising a toast with an `id` that's still on\n * screen updates it rather than stacking a copy.\n *\n * `toast.promise(promise, { loading, success, error })` shows a loading toast, then turns it into\n * the success or error message when the promise settles.\n *\n * @privateRemarks\n * Radix Toast underneath (swipe, hotkey, pause, live announcements). Radix announces a toast's\n * text once, on mount, so a changed message bumps `version` and remounts the toast under a new\n * key; `data-updated` stops that remount from replaying the entrance. The viewport is portalled\n * and copies the theme of a hidden anchor where the Toaster sits (usePortalTheme).\n *\n * <Toaster position=\"bottom-end\" />\n * toast({ title: 'Invoice sent', tone: 'positive' })\n */\nexport function Toaster({\n position = 'bottom-end',\n max = 3,\n label = 'Notifications ({hotkey})',\n hotkey,\n closeLabel = 'Dismiss',\n container,\n className,\n}: ToasterProps) {\n const anchor = usePortalAnchorRef()\n const records = useSyncExternalStore(subscribeToasts, getToasts, getServerToasts)\n const [token] = useState(() => ({}))\n useEffect(() => registerToaster(token), [token])\n const primary = useSyncExternalStore(subscribeToasters, primaryToaster, () => undefined)\n if (primary !== token) return null\n // The newest `max` open toasts show; closing ones stay until their exit has played.\n const shown = new Set(\n records\n .filter((record) => record.open)\n .slice(-Math.max(1, max))\n .map((record) => record.id),\n )\n const visible = records.filter((record) => !record.open || shown.has(record.id))\n return (\n <PortalAnchorContext.Provider value={anchor}>\n <Anchor />\n <Toast.Provider swipeDirection={swipeDirection(position)} label=\"Notification\">\n {visible.map((record) => (\n <ToastItem\n key={`${record.id}:${String(record.version)}`}\n record={record}\n closeLabel={closeLabel}\n />\n ))}\n <Portal.Root container={container}>\n <Viewport\n position={position}\n label={label}\n hotkey={hotkey}\n container={container}\n className={className}\n />\n </Portal.Root>\n </Toast.Provider>\n </PortalAnchorContext.Provider>\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/components/feedback/Toaster/Toaster.module.css
|
|
2
|
+
var e = "kiln-Toaster__viewport", t = "kiln-Toaster__toast", n = "kiln-Toaster__glyph", r = "kiln-Toaster__body", i = "kiln-Toaster__title", a = "kiln-Toaster__description", o = "kiln-Toaster__actions", s = "kiln-Toaster__action", c = "kiln-Toaster__close", l = {
|
|
3
|
+
viewport: e,
|
|
4
|
+
toast: t,
|
|
5
|
+
glyph: n,
|
|
6
|
+
body: r,
|
|
7
|
+
title: i,
|
|
8
|
+
description: a,
|
|
9
|
+
actions: o,
|
|
10
|
+
action: s,
|
|
11
|
+
close: c,
|
|
12
|
+
"toast-in": "kiln-Toaster__toast-in",
|
|
13
|
+
"toast-out": "kiln-Toaster__toast-out",
|
|
14
|
+
"toast-swipe-out": "kiln-Toaster__toast-swipe-out",
|
|
15
|
+
"toast-fade-in": "kiln-Toaster__toast-fade-in"
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { s as action, o as actions, r as body, c as close, l as default, a as description, n as glyph, i as title, t as toast, e as viewport };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=Toaster.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toaster.module.js","names":["viewport","toast","glyph","body","title","description","actions","action","close","Toaster_module_default"],"sources":["../../../../src/components/feedback/Toaster/Toaster.module.css"],"sourcesContent":["/*\n * Component tokens (themes may set any of these on [data-theme]):\n * --toast-width default 24rem (capped by the viewport)\n * --toast-radius default var(--radius-surface)\n * --toast-bg default var(--color-surface-raised)\n * --toast-edge default var(--color-line-strong)\n * --toast-rule-length the tone tab on the top edge, default var(--space-6)\n *\n * A toast floats, so it takes the floating shadow; otherwise it's the Alert frame: a hairline,\n * the tone only in the glyph and a short tab set into the top edge. No tinted box, no stripe.\n * It slides in from the edge it's pinned to (the same edge a swipe sends it back to) and fades\n * out; reduced motion keeps only the fade, which the collapsed durations make instant.\n */\n.viewport {\n --_gap: var(--space-3);\n --_edge-gap: var(--space-5);\n\n position: fixed;\n z-index: var(--z-toast);\n display: flex;\n flex-direction: column;\n gap: var(--_gap);\n inline-size: min(var(--toast-width, 24rem), calc(100vw - 2 * var(--_edge-gap)));\n max-block-size: 100vh;\n margin: 0;\n padding: 0;\n list-style: none;\n outline: none;\n}\n.viewport:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 4px;\n}\n\n.viewport[data-position^='top'] {\n inset-block-start: var(--_edge-gap);\n flex-direction: column-reverse;\n}\n.viewport[data-position^='bottom'] {\n inset-block-end: var(--_edge-gap);\n}\n.viewport[data-position$='-start'] {\n inset-inline-start: var(--_edge-gap);\n}\n.viewport[data-position$='-end'] {\n inset-inline-end: var(--_edge-gap);\n}\n.viewport[data-position$='-center'] {\n inset-inline-start: 50%;\n translate: -50% 0;\n}\n\n.toast {\n --_c: var(--tone-info);\n --_text: var(--tone-info-text);\n --_from-x: 0px;\n --_from-y: 0px;\n\n position: relative;\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto;\n align-items: start;\n column-gap: var(--space-3);\n padding-block: var(--space-3);\n padding-inline: var(--space-4);\n border: var(--border-width) solid var(--toast-edge, var(--color-line-strong));\n border-radius: var(--toast-radius, var(--radius-surface));\n background: var(--toast-bg, var(--color-surface-raised));\n box-shadow: var(--shadow-float);\n color: var(--color-ink);\n font-family: var(--font-text);\n font-size: var(--text-sm);\n line-height: var(--leading-body);\n}\n.toast:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n}\n\n.toast::before {\n content: '';\n position: absolute;\n inset-block-start: calc(var(--border-width) * -1);\n inset-inline-start: var(--space-4);\n inline-size: var(--toast-rule-length, var(--space-6));\n block-size: calc(var(--border-width) + 2px);\n background: var(--_c);\n}\n\n.toast[data-tone='neutral'] {\n --_c: var(--color-ink-subtle);\n --_text: var(--color-ink-muted);\n}\n.toast[data-tone='positive'] {\n --_c: var(--tone-positive);\n --_text: var(--tone-positive-text);\n}\n.toast[data-tone='caution'] {\n --_c: var(--tone-caution);\n --_text: var(--tone-caution-text);\n}\n.toast[data-tone='critical'] {\n --_c: var(--tone-critical);\n --_text: var(--tone-critical-text);\n}\n\n.glyph {\n display: inline-flex;\n block-size: calc(var(--text-md) * var(--leading-snug));\n align-items: center;\n color: var(--_text);\n font-size: var(--text-lg);\n}\n.toast[data-loading] .glyph {\n color: var(--color-ink-muted);\n font-size: var(--text-md);\n}\n\n.body {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-inline-size: 0;\n}\n\n.title {\n margin: 0;\n color: var(--color-ink);\n font-family: var(--heading-font);\n font-size: var(--text-md);\n font-weight: var(--heading-weight);\n letter-spacing: var(--heading-tracking);\n text-transform: var(--heading-transform);\n line-height: var(--leading-snug);\n text-wrap: balance;\n}\n\n.description {\n margin: 0;\n color: var(--color-ink-muted);\n text-wrap: pretty;\n}\n.body > .description:first-child {\n color: var(--color-ink);\n}\n\n.actions {\n display: flex;\n gap: var(--space-2);\n margin-block-start: var(--space-2);\n}\n\n.action {\n display: inline-flex;\n align-items: center;\n min-block-size: var(--control-sm);\n padding-inline: var(--space-3);\n border: var(--border-width) solid var(--color-line-control);\n border-radius: var(--radius-action);\n background: transparent;\n color: var(--color-ink);\n font-family: var(--font-text);\n font-size: var(--text-sm);\n font-weight: var(--label-weight);\n letter-spacing: var(--label-tracking);\n text-transform: var(--label-transform);\n cursor: pointer;\n transition: background-color var(--dur-1) var(--ease-out);\n}\n@media (hover: hover) {\n .action:hover {\n background: var(--color-surface-sunken);\n }\n}\n.action:focus-visible,\n.close:focus-visible {\n outline: 2px solid var(--color-focus);\n outline-offset: 2px;\n}\n\n.close {\n display: grid;\n place-items: center;\n inline-size: var(--control-sm);\n block-size: var(--control-sm);\n margin-block: calc(var(--space-2) * -1);\n margin-inline-end: calc(var(--space-2) * -1);\n padding: 0;\n border: 0;\n border-radius: var(--radius-action);\n background: transparent;\n color: var(--color-ink-muted);\n font-size: var(--text-md);\n cursor: pointer;\n transition:\n background-color var(--dur-1) var(--ease-out),\n color var(--dur-1) var(--ease-out);\n}\n@media (hover: hover) {\n .close:hover {\n background: var(--color-surface-sunken);\n color: var(--color-ink);\n }\n}\n\n/* ── Motion: in from the pinned edge, out by fading; a swipe follows the finger ─────── */\n.toast[data-swipe-direction='right'] {\n --_from-x: var(--space-7);\n}\n.toast[data-swipe-direction='left'] {\n --_from-x: calc(var(--space-7) * -1);\n}\n.toast[data-swipe-direction='up'] {\n --_from-y: calc(var(--space-7) * -1);\n}\n.toast[data-swipe-direction='down'] {\n --_from-y: var(--space-7);\n}\n\n.toast[data-state='open'] {\n animation: toast-in var(--dur-3) var(--ease-out);\n}\n.toast[data-state='open'][data-updated] {\n animation: none;\n}\n.toast[data-state='closed'] {\n animation: toast-out var(--dur-2) var(--ease-in-out) forwards;\n}\n.toast[data-swipe='move'] {\n translate: var(--radix-toast-swipe-move-x, 0px) var(--radix-toast-swipe-move-y, 0px);\n}\n.toast[data-swipe='cancel'] {\n translate: 0 0;\n transition: translate var(--dur-2) var(--ease-out);\n}\n.toast[data-swipe='end'] {\n animation: toast-swipe-out var(--dur-2) var(--ease-out) forwards;\n}\n\n@keyframes toast-in {\n from {\n opacity: 0;\n translate: var(--_from-x) var(--_from-y);\n }\n}\n@keyframes toast-out {\n to {\n opacity: 0;\n }\n}\n@keyframes toast-swipe-out {\n from {\n translate: var(--radix-toast-swipe-end-x, 0px) var(--radix-toast-swipe-end-y, 0px);\n }\n to {\n opacity: 0;\n translate: calc(var(--radix-toast-swipe-end-x, 0px) + var(--_from-x))\n calc(var(--radix-toast-swipe-end-y, 0px) + var(--_from-y));\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .toast[data-state='open'] {\n animation-name: toast-fade-in;\n }\n .toast[data-swipe='end'] {\n animation-name: toast-out;\n }\n .toast[data-swipe='cancel'] {\n transition: none;\n }\n}\n@keyframes toast-fade-in {\n from {\n opacity: 0;\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type ToastTone = 'neutral' | 'info' | 'positive' | 'caution' | 'critical';
|
|
3
|
+
export interface ToastAction {
|
|
4
|
+
/** The button's label: "Undo", "Retry", "View invoice". */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Runs when the button is pressed. The toast closes afterwards. */
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
/**
|
|
9
|
+
* What a screen reader user hears about how to do this without the toast, since it may have
|
|
10
|
+
* gone by the time they reach it: "Undo from the Trash page". Default: the label.
|
|
11
|
+
*/
|
|
12
|
+
altText?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ToastOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Names the toast. Raising a toast with the `id` of one still on screen updates it instead of
|
|
17
|
+
* stacking a second, so a repeated "Sync failed" shows once.
|
|
18
|
+
*/
|
|
19
|
+
id?: string;
|
|
20
|
+
/** The short message, read first: "Invoice sent". */
|
|
21
|
+
title?: ReactNode;
|
|
22
|
+
/** One more sentence when the title needs it: "Kittiwake Ltd, £1,240.00". */
|
|
23
|
+
description?: ReactNode;
|
|
24
|
+
/** Meaning: the glyph and the tab on the top edge. `critical` and `caution` interrupt. Default `neutral`. */
|
|
25
|
+
tone?: ToastTone;
|
|
26
|
+
/** Shows a spinner in place of the glyph, for work still in progress. */
|
|
27
|
+
loading?: boolean;
|
|
28
|
+
/** One button, such as Undo or Retry. */
|
|
29
|
+
action?: ToastAction;
|
|
30
|
+
/**
|
|
31
|
+
* How long it stays, in milliseconds. `Infinity` keeps it until it's dismissed. Default 5000,
|
|
32
|
+
* 8000 for `caution`, and `Infinity` for `critical` and `loading`, so errors wait to be read.
|
|
33
|
+
* The timer pauses while the pointer or focus is on the toasts, and while the window is in the
|
|
34
|
+
* background.
|
|
35
|
+
*/
|
|
36
|
+
duration?: number;
|
|
37
|
+
/** Show the close button. Default `true`. */
|
|
38
|
+
dismissible?: boolean;
|
|
39
|
+
/** Runs when the toast closes: on a timer, a swipe, Escape or the close button. */
|
|
40
|
+
onDismiss?: () => void;
|
|
41
|
+
}
|
|
42
|
+
/** A toast as the `Toaster` sees it. */
|
|
43
|
+
export interface ToastRecord extends ToastOptions {
|
|
44
|
+
id: string;
|
|
45
|
+
/** `false` once it's dismissed: it plays its exit and is then removed. */
|
|
46
|
+
open: boolean;
|
|
47
|
+
/** Goes up each time the message changes, so the change is announced again. */
|
|
48
|
+
version: number;
|
|
49
|
+
}
|
|
50
|
+
type Listener = () => void;
|
|
51
|
+
/** @internal The `Toaster`'s subscription. */
|
|
52
|
+
export declare function subscribeToasts(listener: Listener): () => void;
|
|
53
|
+
/** @internal The current toasts, oldest first. */
|
|
54
|
+
export declare function getToasts(): ToastRecord[];
|
|
55
|
+
/** @internal The server snapshot: nothing is raised during a server render. */
|
|
56
|
+
export declare function getServerToasts(): ToastRecord[];
|
|
57
|
+
/** A message for one stage of `toast.promise`: text, full options, or either worked out from the result. */
|
|
58
|
+
export type ToastPromiseMessage<T> = ReactNode | ToastOptions | ((value: T) => ReactNode | ToastOptions);
|
|
59
|
+
export interface ToastPromiseMessages<T> {
|
|
60
|
+
/** While it's pending. Shown with a spinner. */
|
|
61
|
+
loading: ReactNode | ToastOptions;
|
|
62
|
+
/** When it resolves. Shown in the `positive` tone. */
|
|
63
|
+
success: ToastPromiseMessage<T>;
|
|
64
|
+
/** When it rejects, with the error. Shown in the `critical` tone. */
|
|
65
|
+
error: ToastPromiseMessage<unknown>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Raises a toast and returns its id. Takes the message as text, or full options.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* Call it from anywhere, inside React or out of it: an event handler, a query's error callback, a
|
|
72
|
+
* socket listener. A {@link Toaster | Toaster} mounted once near the root shows it.
|
|
73
|
+
*
|
|
74
|
+
* - `toast.update(id, options)` changes a toast on screen, such as a retry count.
|
|
75
|
+
* - `toast.dismiss(id)` closes one; `toast.dismiss()` closes them all.
|
|
76
|
+
* - `toast.promise(promise, { loading, success, error })` shows a loading toast that turns into
|
|
77
|
+
* the success or error message when the promise settles, and returns the same promise.
|
|
78
|
+
*/
|
|
79
|
+
export declare function toast(message: ReactNode | ToastOptions): string;
|
|
80
|
+
export declare namespace toast {
|
|
81
|
+
var update: (id: string, options: ToastOptions) => void;
|
|
82
|
+
var dismiss: (id?: string) => void;
|
|
83
|
+
var promise: <T>(promise: Promise<T>, messages: ToastPromiseMessages<T>) => Promise<T>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* @internal Registers a mounted `Toaster`. Only the first one registered shows toasts, so a page
|
|
87
|
+
* that mounts two (a docs page with several examples) doesn't show each toast twice.
|
|
88
|
+
*/
|
|
89
|
+
export declare function registerToaster(token: object): () => void;
|
|
90
|
+
/** @internal Subscribes to changes in which `Toaster` shows toasts. */
|
|
91
|
+
export declare function subscribeToasters(listener: Listener): () => void;
|
|
92
|
+
/** @internal The `Toaster` that shows toasts, if any is mounted. */
|
|
93
|
+
export declare function primaryToaster(): object | undefined;
|
|
94
|
+
/** @internal Clears every toast at once, with no exit. For tests. */
|
|
95
|
+
export declare function resetToasts(): void;
|
|
96
|
+
export {};
|