@camunda/design-system 0.59.0 → 0.61.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/carbon-compat/filterable-multi-select.d.ts.map +1 -1
- package/dist/carbon-compat/filterable-multi-select.js +6 -1
- package/dist/carbon-compat/filterable-multi-select.js.map +1 -1
- package/dist/carbon-compat/multi-select.d.ts.map +1 -1
- package/dist/carbon-compat/multi-select.js +5 -1
- package/dist/carbon-compat/multi-select.js.map +1 -1
- package/dist/src/components/ui/alert-dialog.d.ts +11 -1
- package/dist/src/components/ui/alert-dialog.d.ts.map +1 -1
- package/dist/src/components/ui/alert-dialog.js +5 -4
- package/dist/src/components/ui/alert-dialog.js.map +1 -1
- package/dist/src/components/ui/alert.d.ts +1 -1
- package/dist/src/components/ui/alert.d.ts.map +1 -1
- package/dist/src/components/ui/alert.js +3 -1
- package/dist/src/components/ui/alert.js.map +1 -1
- package/dist/src/components/ui/app-sidebar.d.ts.map +1 -1
- package/dist/src/components/ui/app-sidebar.js +57 -16
- package/dist/src/components/ui/app-sidebar.js.map +1 -1
- package/dist/src/components/ui/card.d.ts.map +1 -1
- package/dist/src/components/ui/card.js +15 -1
- package/dist/src/components/ui/card.js.map +1 -1
- package/dist/src/components/ui/code-block.d.ts.map +1 -1
- package/dist/src/components/ui/code-block.js +1 -13
- package/dist/src/components/ui/code-block.js.map +1 -1
- package/dist/src/components/ui/combobox.d.ts +2 -2
- package/dist/src/components/ui/combobox.d.ts.map +1 -1
- package/dist/src/components/ui/combobox.js +68 -25
- package/dist/src/components/ui/combobox.js.map +1 -1
- package/dist/src/components/ui/date-range-picker.d.ts.map +1 -1
- package/dist/src/components/ui/date-range-picker.js +20 -1
- package/dist/src/components/ui/date-range-picker.js.map +1 -1
- package/dist/src/components/ui/dialog.d.ts +18 -2
- package/dist/src/components/ui/dialog.d.ts.map +1 -1
- package/dist/src/components/ui/dialog.js +9 -6
- package/dist/src/components/ui/dialog.js.map +1 -1
- package/dist/src/components/ui/file-uploader.d.ts.map +1 -1
- package/dist/src/components/ui/file-uploader.js +21 -2
- package/dist/src/components/ui/file-uploader.js.map +1 -1
- package/dist/src/components/ui/form-error-summary.d.ts +13 -0
- package/dist/src/components/ui/form-error-summary.d.ts.map +1 -0
- package/dist/src/components/ui/form-error-summary.js +53 -0
- package/dist/src/components/ui/form-error-summary.js.map +1 -0
- package/dist/src/components/ui/inline-edit.d.ts.map +1 -1
- package/dist/src/components/ui/inline-edit.js +14 -1
- package/dist/src/components/ui/inline-edit.js.map +1 -1
- package/dist/src/components/ui/multi-select.d.ts +7 -0
- package/dist/src/components/ui/multi-select.d.ts.map +1 -1
- package/dist/src/components/ui/multi-select.js +79 -27
- package/dist/src/components/ui/multi-select.js.map +1 -1
- package/dist/src/components/ui/multi-step-dialog.d.ts +11 -1
- package/dist/src/components/ui/multi-step-dialog.d.ts.map +1 -1
- package/dist/src/components/ui/multi-step-dialog.js +32 -4
- package/dist/src/components/ui/multi-step-dialog.js.map +1 -1
- package/dist/src/components/ui/notifications.d.ts +14 -1
- package/dist/src/components/ui/notifications.d.ts.map +1 -1
- package/dist/src/components/ui/notifications.js +17 -6
- package/dist/src/components/ui/notifications.js.map +1 -1
- package/dist/src/components/ui/overflow-strip.d.ts +18 -0
- package/dist/src/components/ui/overflow-strip.d.ts.map +1 -0
- package/dist/src/components/ui/overflow-strip.js +103 -0
- package/dist/src/components/ui/overflow-strip.js.map +1 -0
- package/dist/src/components/ui/scorecard.d.ts.map +1 -1
- package/dist/src/components/ui/scorecard.js +20 -1
- package/dist/src/components/ui/scorecard.js.map +1 -1
- package/dist/src/components/ui/select.d.ts +2 -2
- package/dist/src/components/ui/select.d.ts.map +1 -1
- package/dist/src/components/ui/select.js +123 -6
- package/dist/src/components/ui/select.js.map +1 -1
- package/dist/src/components/ui/tabs.d.ts +2 -1
- package/dist/src/components/ui/tabs.d.ts.map +1 -1
- package/dist/src/components/ui/tabs.js +16 -4
- package/dist/src/components/ui/tabs.js.map +1 -1
- package/dist/src/components/ui/toast.d.ts +4 -3
- package/dist/src/components/ui/toast.d.ts.map +1 -1
- package/dist/src/components/ui/toast.js +209 -23
- package/dist/src/components/ui/toast.js.map +1 -1
- package/dist/src/components/ui/toggle-group.d.ts +7 -1
- package/dist/src/components/ui/toggle-group.d.ts.map +1 -1
- package/dist/src/components/ui/toggle-group.js +23 -4
- package/dist/src/components/ui/toggle-group.js.map +1 -1
- package/dist/src/components/ui/tree-view.d.ts.map +1 -1
- package/dist/src/components/ui/tree-view.js +61 -45
- package/dist/src/components/ui/tree-view.js.map +1 -1
- package/dist/src/hooks/use-is-truncated.d.ts +9 -5
- package/dist/src/hooks/use-is-truncated.d.ts.map +1 -1
- package/dist/src/hooks/use-is-truncated.js +20 -7
- package/dist/src/hooks/use-is-truncated.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/focus-visible-on-close.d.ts +28 -0
- package/dist/src/lib/focus-visible-on-close.d.ts.map +1 -1
- package/dist/src/lib/focus-visible-on-close.js +54 -1
- package/dist/src/lib/focus-visible-on-close.js.map +1 -1
- package/dist/src/lib/i18n/dictionary.d.ts +5 -0
- package/dist/src/lib/i18n/dictionary.d.ts.map +1 -1
- package/dist/src/lib/i18n/dictionary.js +5 -0
- package/dist/src/lib/i18n/dictionary.js.map +1 -1
- package/dist/src/lib/merge-refs.d.ts +8 -0
- package/dist/src/lib/merge-refs.d.ts.map +1 -0
- package/dist/src/lib/merge-refs.js +15 -0
- package/dist/src/lib/merge-refs.js.map +1 -0
- package/dist/src/lib/with-ref.d.ts +3 -1
- package/dist/src/lib/with-ref.d.ts.map +1 -1
- package/dist/src/lib/with-ref.js +0 -10
- package/dist/src/lib/with-ref.js.map +1 -1
- package/dist/styles.css +68 -6
- package/package.json +2 -1
|
@@ -4,18 +4,59 @@ import * as React from "react";
|
|
|
4
4
|
import { Select as SelectPrimitive } from "radix-ui";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
6
|
import { composeCloseAutoFocus, useDialogFocusReturn, } from "../../lib/focus-visible-on-close.js";
|
|
7
|
+
import { useIsTruncated } from "../../hooks/use-is-truncated.js";
|
|
7
8
|
import { C4Provider } from "./c4-provider.js";
|
|
8
9
|
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "../../icons/lucide.js";
|
|
10
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
11
|
+
// No existing ref-merge utility in this package (single-consumer need so
|
|
12
|
+
// far, same reasoning as `code-block.tsx`'s own copy): the forwarded `ref`
|
|
13
|
+
// (an external consumer's, if passed via `...props` — SelectTrigger doesn't
|
|
14
|
+
// destructure it today) and the internal ref this file needs to read the
|
|
15
|
+
// rendered `[data-slot="select-value"]` node both have to land on the same
|
|
16
|
+
// `SelectPrimitive.Trigger` DOM node.
|
|
17
|
+
function mergeRefs(a, b) {
|
|
18
|
+
return (node) => {
|
|
19
|
+
if (typeof a === "function")
|
|
20
|
+
a(node);
|
|
21
|
+
else if (a)
|
|
22
|
+
a.current = node;
|
|
23
|
+
b.current = node;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
9
26
|
function Select({ ...props }) {
|
|
10
27
|
return _jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
11
28
|
}
|
|
12
29
|
function SelectGroup({ ...props }) {
|
|
13
30
|
return _jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
14
31
|
}
|
|
15
|
-
function SelectValue({ ...props }) {
|
|
16
|
-
|
|
32
|
+
function SelectValue({ children, ...props }) {
|
|
33
|
+
// Radix's `Select.Value` destructures `className`/`style` out of props and
|
|
34
|
+
// never forwards them to the rendered node (see
|
|
35
|
+
// `@radix-ui/react-select`'s own `SelectValue` implementation) — so styling
|
|
36
|
+
// this node's own display/truncation can't be done via a `className` prop
|
|
37
|
+
// on it directly, no matter how it's computed here. `data-*` attributes
|
|
38
|
+
// aren't stripped, though: they land in Radix's own `...valueProps` spread
|
|
39
|
+
// and reach the DOM, so `SelectTrigger`'s className reaches this node via
|
|
40
|
+
// a `[data-slot=select-value]` DESCENDANT selector (not the `*:` direct-
|
|
41
|
+
// child variant the previous, broken attempt used — this node isn't
|
|
42
|
+
// always a direct child of the trigger; see the icon+value composition in
|
|
43
|
+
// `filter-bar.stories.tsx`, which nests `SelectValue` inside a consumer
|
|
44
|
+
// span). `data-has-children` distinguishes the two layouts that selector
|
|
45
|
+
// needs to produce: plain `truncate` when Radix auto-renders the selected
|
|
46
|
+
// item's text (no `children` passed here — the common case, and the only
|
|
47
|
+
// one any consumer in this repo actually uses), or `flex` when a consumer
|
|
48
|
+
// passes its own `children` instead of relying on that auto-text.
|
|
49
|
+
//
|
|
50
|
+
// Radix's own `hasChildren` (`children !== void 0`) gates whether it
|
|
51
|
+
// auto-renders the selected item's text at all: passing this component
|
|
52
|
+
// *any* non-undefined `children` — even conditionally, even `null` — would
|
|
53
|
+
// suppress that auto-text for every consumer that doesn't. So this only
|
|
54
|
+
// ever wraps `children` when the consumer actually supplied some; it never
|
|
55
|
+
// passes an empty wrapper through in the common case.
|
|
56
|
+
const hasChildren = children !== undefined;
|
|
57
|
+
return (_jsx(SelectPrimitive.Value, { "data-slot": "select-value", "data-has-children": hasChildren ? "" : undefined, ...props, children: hasChildren ? (_jsx("span", { "data-slot": "select-value-text", className: "min-w-0 truncate", children: children })) : undefined }));
|
|
17
58
|
}
|
|
18
|
-
function SelectTrigger({ className, size = "default", children, id, invalidText, helperText, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, "aria-errormessage": ariaErrorMessage, ...props }) {
|
|
59
|
+
function SelectTrigger({ className, size = "default", children, id, invalidText, helperText, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, "aria-errormessage": ariaErrorMessage, ref, onPointerEnter, onFocus, ...props }) {
|
|
19
60
|
const generatedId = React.useId();
|
|
20
61
|
const resolvedId = id ?? generatedId;
|
|
21
62
|
const isInvalid = ariaInvalid === true || ariaInvalid === "true";
|
|
@@ -26,13 +67,89 @@ function SelectTrigger({ className, size = "default", children, id, invalidText,
|
|
|
26
67
|
const errorMessageId = ariaErrorMessage ?? errorId;
|
|
27
68
|
const describedBy = [errorId, helperId, ariaDescribedBy].filter(Boolean).join(" ") || undefined;
|
|
28
69
|
const hasMessage = showInvalidText || showHelperText;
|
|
29
|
-
|
|
70
|
+
// `SelectValue`'s content is opaque to `SelectTrigger` — it's rendered as
|
|
71
|
+
// `children`, an arbitrary consumer-authored subtree — so unlike the other
|
|
72
|
+
// 3 truncation-reveal sites there is no JSX ref to attach directly to the
|
|
73
|
+
// node the truncation utility clips (`data-slot="select-value"`, or its
|
|
74
|
+
// inner `data-slot="select-value-text"` span when `SelectValue` was given
|
|
75
|
+
// `children` — see the `[data-slot=select-value]` descendant selectors in
|
|
76
|
+
// this trigger's className below). `triggerRef` reads the rendered DOM
|
|
77
|
+
// instead: an ADDITIONAL internal ref alongside any consumer `ref` (merged
|
|
78
|
+
// via `mergeRefs`, not a replacement — `props` may still carry a consumer
|
|
79
|
+
// ref since it's not destructured above).
|
|
80
|
+
const triggerRef = React.useRef(null);
|
|
81
|
+
const mergedRef = React.useMemo(() => mergeRefs(ref, triggerRef), [ref, triggerRef]);
|
|
82
|
+
const { ref: valueRef, isTruncated, measure } = useIsTruncated();
|
|
83
|
+
const [tooltipText, setTooltipText] = React.useState("");
|
|
84
|
+
// Every commit, not just on a `children` change: which node currently
|
|
85
|
+
// carries `data-slot="select-value"`, and the room the trigger has, can
|
|
86
|
+
// both change with no reference-identity change on `children` to key on
|
|
87
|
+
// (e.g. picking a different option re-renders `SelectValue` under the same
|
|
88
|
+
// trigger). See "Truncated Text" in `docs/specs/shadcn/patterns.md`. No
|
|
89
|
+
// deps array is intentional (same reasoning `nav-breadcrumb.tsx` documents
|
|
90
|
+
// for its own equivalent effect) — the lint rule flags the direct
|
|
91
|
+
// `setTooltipText` call as a possible infinite-update chain, but `useState`
|
|
92
|
+
// bails out via `Object.is` when the computed text is unchanged, same as
|
|
93
|
+
// `measure()`'s own `setIsTruncated` call one layer down (which the rule
|
|
94
|
+
// can't see into, and so doesn't flag).
|
|
95
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
96
|
+
React.useLayoutEffect(() => {
|
|
97
|
+
// Prefer the inner `select-value-text` span — present only when
|
|
98
|
+
// `SelectValue` was given `children` — over the outer `select-value`
|
|
99
|
+
// node itself: that outer node is `flex` in that case (to lay out
|
|
100
|
+
// `children` alongside other content), and measuring `scrollWidth` /
|
|
101
|
+
// `clientWidth` on a flex container doesn't reflect text clipping the
|
|
102
|
+
// way it does on the actual `truncate`d node. See `select.tsx`'s
|
|
103
|
+
// `SelectValue` for why the split exists at all.
|
|
104
|
+
const valueEl = triggerRef.current?.querySelector('[data-slot="select-value-text"]') ??
|
|
105
|
+
triggerRef.current?.querySelector('[data-slot="select-value"]');
|
|
106
|
+
valueRef.current = valueEl ?? null;
|
|
107
|
+
measure();
|
|
108
|
+
setTooltipText(valueEl?.textContent ?? "");
|
|
109
|
+
});
|
|
110
|
+
const triggerEl = (_jsxs(SelectPrimitive.Trigger, { ref: mergedRef, id: resolvedId, "data-slot": "select-trigger", "data-size": size, "aria-invalid": ariaInvalid, "aria-errormessage": errorMessageId, "aria-describedby": describedBy,
|
|
111
|
+
// Composed rather than clobbered: `props` may still carry a consumer
|
|
112
|
+
// handler (only `ref` is destructured above, not these two), and
|
|
113
|
+
// `{...props}` spreads AFTER this element's own attributes — the
|
|
114
|
+
// opposite order from `combobox.tsx`, so the risk here is a consumer
|
|
115
|
+
// handler silently winning over `measure()`, not the other way round.
|
|
116
|
+
onPointerEnter: (e) => {
|
|
117
|
+
onPointerEnter?.(e);
|
|
118
|
+
measure();
|
|
119
|
+
}, onFocus: (e) => {
|
|
120
|
+
onFocus?.(e);
|
|
121
|
+
measure();
|
|
122
|
+
}, className: cn("flex w-fit min-w-0 items-center justify-between gap-2 rounded-lg border border-input bg-input-background px-3 py-2 text-sm whitespace-nowrap transition-all duration-200 outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20 dark:focus-visible:ring-ring/40 data-[focus-visible-restore]:border-ring data-[focus-visible-restore]:ring-3 data-[focus-visible-restore]:ring-ring/20 dark:data-[focus-visible-restore]:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger-foreground-subtle aria-invalid:ring-3 aria-invalid:ring-danger-foreground-subtle/20 data-placeholder:text-neutral-foreground-subtle data-[size=default]:h-9 data-[size=sm]:h-8 [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]:not([data-has-children])]:truncate [&_[data-slot=select-value][data-has-children]]:flex [&_[data-slot=select-value][data-has-children]]:items-center [&_[data-slot=select-value][data-has-children]]:gap-1.5 dark:hover:bg-neutral-background-medium dark:aria-invalid:ring-danger-foreground-subtle/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 opacity-50" }) })] }));
|
|
123
|
+
// DEVIATES from the other 3 truncation-reveal sites (and from
|
|
124
|
+
// `nav-breadcrumb.tsx`'s reference pattern), and empirically so — not by
|
|
125
|
+
// assumption: `Tooltip`/`TooltipTrigger` are mounted UNCONDITIONALLY here
|
|
126
|
+
// (only `TooltipContent` is conditional on `isTruncated`), instead of
|
|
127
|
+
// conditionally mounting the whole `Tooltip` subtree. Conditionally
|
|
128
|
+
// mounting the whole subtree was tried first and measurably broken —
|
|
129
|
+
// toggling `isTruncated` changes the JSX shape at `SelectPrimitive.Trigger`'s
|
|
130
|
+
// own tree position (bare element vs. wrapped in `Tooltip`), which makes
|
|
131
|
+
// React unmount and remount it, and with it Radix Select's own internal
|
|
132
|
+
// `SelectValue` registration. The remounted value node measures 0×0 on the
|
|
133
|
+
// commit right after the remount (confirmed with a throwaway console probe:
|
|
134
|
+
// `onPointerEnter`'s `measure()` read the correct 260×110 and set
|
|
135
|
+
// `isTruncated` true; the very next commit's layout effect then read the
|
|
136
|
+
// SAME query on the freshly-remounted node as 0×0 and flipped it back to
|
|
137
|
+
// false), so the tooltip opened and immediately closed on every hover
|
|
138
|
+
// instead of staying open — a real correctness bug, not a style
|
|
139
|
+
// preference. `SelectPrimitive.Trigger` needs a permanently stable tree
|
|
140
|
+
// position for its internal state to survive a truncation toggle; the
|
|
141
|
+
// other 3 sites don't have that constraint; the accepted cost versus
|
|
142
|
+
// `nav-breadcrumb.tsx`'s document (an unconditional `Tooltip` still runs
|
|
143
|
+
// Radix's open-request handling and touches `TooltipProvider`'s
|
|
144
|
+
// delay-skip state on every hover of a SHORT value too) is real but minor
|
|
145
|
+
// next to a tooltip that cannot stay open.
|
|
146
|
+
const wrappedTriggerEl = (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: triggerEl }), isTruncated ? _jsx(TooltipContent, { children: tooltipText }) : null] }));
|
|
30
147
|
if (!hasMessage) {
|
|
31
|
-
return
|
|
148
|
+
return wrappedTriggerEl;
|
|
32
149
|
}
|
|
33
150
|
// `w-fit` here would make a `width: 100%` trigger circular against its own
|
|
34
151
|
// shrink-to-fit parent, silently dropping a `w-full` className override.
|
|
35
|
-
return (_jsxs("div", { className: "flex w-full flex-col gap-1.5", children: [
|
|
152
|
+
return (_jsxs("div", { className: "flex w-full flex-col gap-1.5", children: [wrappedTriggerEl, showInvalidText ? (_jsx("span", { id: errorId, className: "text-xs text-danger-foreground-subtle", children: invalidText })) : null, showHelperText ? (_jsx("span", { id: helperId, className: "text-xs text-neutral-foreground-subtle", children: helperText })) : null] }));
|
|
36
153
|
}
|
|
37
154
|
function SelectContent({ className, children, position = "popper", onCloseAutoFocus, restoreFocusRef, ...props }) {
|
|
38
155
|
const dialogFocusReturn = useDialogFocusReturn(restoreFocusRef);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../../src/components/ui/select.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAErD,OAAO,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAC/B,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../../src/components/ui/select.tsx"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,8 @@ declare function Tabs({ className, orientation, ...props }: React.ComponentProps
|
|
|
5
5
|
declare const tabsListVariants: (props?: ({
|
|
6
6
|
variant?: "line" | "default" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
-
|
|
8
|
+
type TabsListProps = React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>;
|
|
9
|
+
declare const TabsList: React.ForwardRefExoticComponent<Omit<TabsListProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
9
10
|
declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
|
|
10
11
|
declare function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>): React.JSX.Element;
|
|
11
12
|
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,UAAU,CAAC;AAOjD,iBAAS,IAAI,CAAC,EACZ,SAAS,EACT,WAA0B,EAC1B,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,qBAYjD;AAID,QAAA,MAAM,gBAAgB;;8EAarB,CAAC;AAEF,KAAK,aAAa,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,GAClE,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAkDxC,QAAA,MAAM,QAAQ,mGAAuD,CAAC;AAEtE,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,qBAepD;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,qBAQpD;AAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
4
|
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { withRef } from "../../lib/with-ref.js";
|
|
7
|
+
import { mergeRefs } from "../../lib/merge-refs.js";
|
|
8
|
+
import { OverflowStripArrow, useOverflowStrip } from "./overflow-strip.js";
|
|
6
9
|
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
7
10
|
return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, className: cn("group/tabs flex gap-2 data-horizontal:flex-col", className), ...props }));
|
|
8
11
|
}
|
|
9
|
-
|
|
12
|
+
// `justify-start`, not `center`: a centred flex row that overflows its scroll
|
|
13
|
+
// container hangs past the start edge where no scroll position can reach it.
|
|
14
|
+
const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center justify-start gap-1 rounded-xl p-1 text-neutral-foreground-subtle group-data-horizontal/tabs:h-9 group-data-horizontal/tabs:min-w-0 group-data-horizontal/tabs:max-w-full group-data-horizontal/tabs:overflow-x-auto group-data-horizontal/tabs:[scrollbar-width:none] group-data-horizontal/tabs:[&::-webkit-scrollbar]:hidden group-data-horizontal/tabs:motion-safe:scroll-smooth group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col group-data-vertical/tabs:overflow-visible data-[variant=line]:rounded-none", {
|
|
10
15
|
variants: {
|
|
11
16
|
variant: {
|
|
12
17
|
default: "bg-neutral-background-medium",
|
|
@@ -17,9 +22,16 @@ const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center jus
|
|
|
17
22
|
variant: "default",
|
|
18
23
|
},
|
|
19
24
|
});
|
|
20
|
-
function
|
|
21
|
-
|
|
25
|
+
function TabsListImpl({ ref, className, variant = "default", ...props }) {
|
|
26
|
+
const { ref: scrollRef, hasMoreBefore, hasMoreAfter, scrollByStep, } = useOverflowStrip();
|
|
27
|
+
const listRef = React.useMemo(() => mergeRefs(ref, scrollRef), [ref, scrollRef]);
|
|
28
|
+
return (
|
|
29
|
+
// The arrows are siblings of the tablist inside this wrapper, never
|
|
30
|
+
// children of it — rendering them inside would count toward the tab
|
|
31
|
+
// total and break Radix's roving focus (Home/End, arrow-key wrap).
|
|
32
|
+
_jsxs("div", { "data-slot": "tabs-list-scroller", className: "flex min-w-0 max-w-full group-data-horizontal/tabs:items-center group-data-vertical/tabs:items-start", children: [hasMoreBefore && (_jsx(OverflowStripArrow, { direction: "before", onScroll: () => scrollByStep("before") })), _jsx(TabsPrimitive.List, { ref: listRef, "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }), className), ...props }), hasMoreAfter && (_jsx(OverflowStripArrow, { direction: "after", onScroll: () => scrollByStep("after") }))] }));
|
|
22
33
|
}
|
|
34
|
+
const TabsList = withRef(TabsListImpl);
|
|
23
35
|
function TabsTrigger({ className, ...props }) {
|
|
24
36
|
return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", className: cn("relative inline-flex h-[calc(100%-2px)] flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-lg border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap text-neutral-foreground-strong transition-all duration-200 group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 dark:text-neutral-foreground-subtle group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "not-data-active:hover:bg-neutral-background-strong", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-neutral-background-subtle data-active:text-neutral-foreground-strong dark:data-active:border-input dark:data-active:text-neutral-foreground-strong", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className), ...props }));
|
|
25
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../../../src/components/ui/tabs.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAqB,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,IAAI,IAAI,aAAa,EAAE,MAAM,UAAU,CAAC;AAEjD,OAAO,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../../../src/components/ui/tabs.tsx"],"names":[],"mappings":"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"}
|
|
@@ -43,9 +43,10 @@ type ToastFn = {
|
|
|
43
43
|
declare function Toaster({ position, dismissLabel }: ToasterProps): React.JSX.Element;
|
|
44
44
|
/**
|
|
45
45
|
* The callable used everywhere else — never sonner's own `toast` re-exported directly.
|
|
46
|
-
* `options.duration` resolution uses `??`, not `||`: sonner
|
|
47
|
-
* `duration: 0` as falsy and silently falls back to its own ~4s default
|
|
48
|
-
* source) — `??` is required so an explicit `0` isn't confused with "not
|
|
46
|
+
* `options.duration` resolution (inside `toExternalToast`) uses `??`, not `||`: sonner
|
|
47
|
+
* treats a literal `duration: 0` as falsy and silently falls back to its own ~4s default
|
|
48
|
+
* (verified in source) — `??` is required so an explicit `0` isn't confused with "not
|
|
49
|
+
* passed".
|
|
49
50
|
*/
|
|
50
51
|
declare const toast: ToastFn;
|
|
51
52
|
export { Toaster, toast };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAyB/B,KAAK,aAAa,GACd,UAAU,GACV,WAAW,GACX,YAAY,GACZ,aAAa,GACb,cAAc,GACd,eAAe,CAAC;AAEpB,KAAK,YAAY,GAAG;IAClB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AAE1D,KAAK,YAAY,GAAG;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,mBAAmB,CAAC,CAAC,IAAI;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,OAAO,EAAE,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1D,KAAK,EAAE,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;CAChE,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;IACpE,OAAO,EAAE,CACP,OAAO,EAAE,KAAK,CAAC,SAAS,EACxB,OAAO,CAAC,EAAE,YAAY,KACnB,MAAM,GAAG,MAAM,CAAC;IACrB,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,MAAM,GAAG,MAAM,CAAC;IAC5E,OAAO,EAAE,CACP,OAAO,EAAE,KAAK,CAAC,SAAS,EACxB,OAAO,CAAC,EAAE,YAAY,KACnB,MAAM,GAAG,MAAM,CAAC;IACrB,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,MAAM,GAAG,MAAM,CAAC;IAC7E,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC3E,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;CACzC,CAAC;AAiUF;;;;;;;GAOG;AACH,iBAAS,OAAO,CAAC,EAAE,QAAsB,EAAE,YAAY,EAAE,EAAE,YAAY,qBAiBtE;AAyID;;;;;;GAMG;AACH,QAAA,MAAM,KAAK,EAAE,OA4GZ,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC1B,YAAY,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,mBAAmB,GACpB,CAAC"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Toaster as SonnerToaster, toast as sonnerToast, } from "sonner";
|
|
4
4
|
import { CheckCircle2, CircleAlert, Info, Loader2, TriangleAlert, X, } from "../../icons/lucide.js";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
6
|
import { useC4Dictionary } from "../../lib/i18n/context.js";
|
|
7
|
+
import { useIsTruncated } from "../../hooks/use-is-truncated.js";
|
|
7
8
|
import { buttonVariants } from "./button.js";
|
|
9
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
|
|
8
10
|
import { typographyVariants } from "./typography.js";
|
|
9
11
|
// ── Styling contract — neutral floating surface, icon-only kind color ──────
|
|
10
12
|
//
|
|
@@ -101,18 +103,22 @@ const TOAST_CLASSNAMES = {
|
|
|
101
103
|
// sibling, not the `~` general-sibling used for the icon/close fix above) matches
|
|
102
104
|
// the actual DOM exactly: title and description are always immediate siblings
|
|
103
105
|
// inside `[data-content]`, so this only reserves the gap when one is actually there.
|
|
104
|
-
// `line-clamp-2`
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
106
|
+
// `line-clamp-2` used to live here, on sonner's own `[data-title]` wrapper div —
|
|
107
|
+
// moved onto `ToastTitleText`'s inner span instead (see below), which is what
|
|
108
|
+
// `useIsTruncated` needs a ref on to detect the clamp (scrollHeight > clientHeight)
|
|
109
|
+
// and reveal the full value via Tooltip on hover/focus (fixes #659 for the
|
|
110
|
+
// multi-line case; this div-level class list only carries type + spacing now).
|
|
111
|
+
// Consumers should still keep titles short (toast.specs.ts's antiPatterns) — the
|
|
112
|
+
// clamp+reveal is a backstop for when they don't, not a substitute for writing
|
|
113
|
+
// toast copy that fits a transient, glanceable notification.
|
|
114
|
+
title: cn(typographyVariants({ variant: "label-md-strong" }), "[&:has(+[data-description])]:mb-1"),
|
|
110
115
|
// `text-neutral-foreground-subtle` decouples description color from the (now neutral)
|
|
111
116
|
// container/title color — matches sonner's own native description color, a muted
|
|
112
117
|
// gray distinct from the title, not inherited from the card.
|
|
113
|
-
// `line-clamp-4` — same rationale as the title's
|
|
114
|
-
//
|
|
115
|
-
|
|
118
|
+
// `line-clamp-4` — same rationale as the title's `line-clamp-2` above (moved onto
|
|
119
|
+
// `ToastDescriptionText`'s inner span for the same reason) — with more room since
|
|
120
|
+
// descriptions are expected to carry more detail.
|
|
121
|
+
description: cn(typographyVariants({ variant: "label-md" }), "text-neutral-foreground-subtle"),
|
|
116
122
|
// `ml-auto` keeps the button against the trailing edge whether it shares the first
|
|
117
123
|
// line or has wrapped onto its own. It keeps `buttonVariants`' base `shrink-0` and
|
|
118
124
|
// `whitespace-nowrap`: with wrapping available, the label stays on one line at its
|
|
@@ -159,6 +165,38 @@ const TOAST_ICONS = {
|
|
|
159
165
|
// (Loader2 + animate-spin), colored with a semantic token instead.
|
|
160
166
|
loading: (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-neutral-foreground-subtle", "aria-hidden": "true" })),
|
|
161
167
|
};
|
|
168
|
+
function ToastTitleText({ children, onRevealOpen, onRevealClose, }) {
|
|
169
|
+
const { ref, isTruncated, measure } = useIsTruncated();
|
|
170
|
+
React.useLayoutEffect(() => {
|
|
171
|
+
measure();
|
|
172
|
+
});
|
|
173
|
+
const span = (_jsx("span", { ref: ref, tabIndex: 0, onPointerEnter: measure, onFocus: () => {
|
|
174
|
+
measure();
|
|
175
|
+
if (isTruncated)
|
|
176
|
+
onRevealOpen?.();
|
|
177
|
+
}, onBlur: () => {
|
|
178
|
+
if (isTruncated)
|
|
179
|
+
onRevealClose?.();
|
|
180
|
+
}, className: "line-clamp-2 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: children }));
|
|
181
|
+
// Mounted only when clipped — see nav-breadcrumb.tsx for why an
|
|
182
|
+
// unconditionally-mounted Tooltip is never the right default.
|
|
183
|
+
return isTruncated ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: span }), _jsx(TooltipContent, { children: children })] })) : (span);
|
|
184
|
+
}
|
|
185
|
+
function ToastDescriptionText({ children, onRevealOpen, onRevealClose, }) {
|
|
186
|
+
const { ref, isTruncated, measure } = useIsTruncated();
|
|
187
|
+
React.useLayoutEffect(() => {
|
|
188
|
+
measure();
|
|
189
|
+
});
|
|
190
|
+
const span = (_jsx("span", { ref: ref, tabIndex: 0, onPointerEnter: measure, onFocus: () => {
|
|
191
|
+
measure();
|
|
192
|
+
if (isTruncated)
|
|
193
|
+
onRevealOpen?.();
|
|
194
|
+
}, onBlur: () => {
|
|
195
|
+
if (isTruncated)
|
|
196
|
+
onRevealClose?.();
|
|
197
|
+
}, className: "line-clamp-4 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: children }));
|
|
198
|
+
return isTruncated ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: span }), _jsx(TooltipContent, { children: children })] })) : (span);
|
|
199
|
+
}
|
|
162
200
|
// ── Toaster ──────────────────────────────────────────────────────────────────
|
|
163
201
|
/**
|
|
164
202
|
* The one-time host, mounted once at the application's true root (inside
|
|
@@ -179,10 +217,52 @@ function Toaster({ position = "top-right", dismissLabel }) {
|
|
|
179
217
|
} }));
|
|
180
218
|
}
|
|
181
219
|
// ── toast ────────────────────────────────────────────────────────────────────
|
|
182
|
-
|
|
220
|
+
// ── Focus-driven dismiss pause ──────────────────────────────────────────────
|
|
221
|
+
//
|
|
222
|
+
// Sonner pauses each toast's own auto-dismiss timer while the pointer is over its
|
|
223
|
+
// stack, or while the stack has been expanded via its Alt+T hotkey (`expanded`/
|
|
224
|
+
// `interacting`, set only by `onMouseEnter`/`onMouseMove`/`onPointerDown`/`onPointerUp`
|
|
225
|
+
// on its own <ol> — verified in sonner@2.0.8's index.mjs, ~line 1190-1204) — but never
|
|
226
|
+
// on focus. The <ol>'s own `onFocus` handler (~line 1182) only tracks focus-restoration
|
|
227
|
+
// bookkeeping, never sets `expanded`/`interacting`. So a keyboard user who Tabs onto a
|
|
228
|
+
// clamped title/description, opens its Tooltip reveal via focus, and starts reading,
|
|
229
|
+
// gets no pause at all: the toast can vanish mid-read for a default-duration (5000ms)
|
|
230
|
+
// toast — a WCAG 1.4.13 (Content on Hover or Focus) violation for exactly the users the
|
|
231
|
+
// reveal above was built to help.
|
|
232
|
+
//
|
|
233
|
+
// `makeToast` below pauses each toast's OWN dismissal (via sonner's update-by-id path —
|
|
234
|
+
// re-calling the same creating method with a matching `id` merges into the existing
|
|
235
|
+
// toast rather than creating a new one) while its title/description has focus, and
|
|
236
|
+
// resumes it on blur. Resuming always restarts the FULL original duration rather than
|
|
237
|
+
// tracking exact remaining time — the simplest defensible choice, and also what sonner
|
|
238
|
+
// itself does to any duration change: its `remainingTime.current = duration` effect
|
|
239
|
+
// resets unconditionally whenever `duration` changes, so a full restart is not a special
|
|
240
|
+
// case bolted on top, it's what an update naturally produces.
|
|
241
|
+
//
|
|
242
|
+
// The `id`, and the exact sonner method that created a toast, are threaded through
|
|
243
|
+
// deliberately: sonner's own update-by-id merge sets `title` unconditionally from the
|
|
244
|
+
// `message` argument (so it must be re-passed identically on every update, not omitted)
|
|
245
|
+
// and sets `type` unconditionally from whichever public method handled the call (the
|
|
246
|
+
// bare call always resets it to `undefined`) — so pausing/resuming a `success` toast
|
|
247
|
+
// through the bare method would silently strip its icon back to neutral. Re-dispatching
|
|
248
|
+
// through the exact same method avoids that. Passing the SAME already-constructed title
|
|
249
|
+
// element back on every update (not a freshly created one) also matters: React bails out
|
|
250
|
+
// of re-rendering a child subtree when the element reference itself is unchanged (same
|
|
251
|
+
// type, same props object), which avoids remounting `ToastTitleText` — and thus avoids
|
|
252
|
+
// losing focus, or worse, unmounting the very element mid-focus-event and retriggering
|
|
253
|
+
// another focus/blur pair — verified live via Storybook + Playwright, see the dev
|
|
254
|
+
// report / toast.specs.ts.
|
|
255
|
+
let toastIdCounter = 0;
|
|
256
|
+
function nextToastId() {
|
|
257
|
+
return `c4-toast-${++toastIdCounter}`;
|
|
258
|
+
}
|
|
259
|
+
function toExternalToast(options, defaultDuration, reveal) {
|
|
183
260
|
const action = options?.action;
|
|
184
261
|
return {
|
|
185
|
-
|
|
262
|
+
// Wrapped only when present — sonner conditionally renders `[data-description]`
|
|
263
|
+
// at all based on truthiness, and `:has([data-description])` selectors elsewhere
|
|
264
|
+
// in TOAST_CLASSNAMES key off that same presence (see the Styling contract).
|
|
265
|
+
description: options?.description ? (_jsx(ToastDescriptionText, { onRevealOpen: reveal.onRevealOpen, onRevealClose: reveal.onRevealClose, children: options.description })) : undefined,
|
|
186
266
|
duration: options?.duration ?? defaultDuration,
|
|
187
267
|
action: action
|
|
188
268
|
? {
|
|
@@ -202,27 +282,133 @@ function toExternalToast(options, defaultDuration) {
|
|
|
202
282
|
: undefined,
|
|
203
283
|
};
|
|
204
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Builds and dispatches one toast, wiring its title/description with the
|
|
287
|
+
* focus-driven pause/resume above. `kind` must be the exact sonner method used to
|
|
288
|
+
* create it (`sonnerToast` itself for the untyped/default kind, or one of
|
|
289
|
+
* `.success`/`.info`/`.warning`/`.error`) — see the "Focus-driven dismiss pause"
|
|
290
|
+
* note for why re-dispatching through any other method would corrupt the toast's
|
|
291
|
+
* type/icon.
|
|
292
|
+
*/
|
|
293
|
+
function makeToast(kind, message, options, defaultDuration) {
|
|
294
|
+
const id = nextToastId();
|
|
295
|
+
const duration = options?.duration ?? defaultDuration;
|
|
296
|
+
// `pause`/`resume` reference `titleNode` before its own declaration below —
|
|
297
|
+
// safe because both only ever run later, from a focus/blur event, by which
|
|
298
|
+
// point `titleNode`'s initializer has long since run.
|
|
299
|
+
const pause = () => {
|
|
300
|
+
if (duration === Infinity)
|
|
301
|
+
return; // no auto-dismiss timer exists to pause
|
|
302
|
+
// Same guard as `resume` below: a focus event landing in the same animation
|
|
303
|
+
// frame as a `toast.dismiss(id)` call must not resurrect the toast the
|
|
304
|
+
// consumer just closed — sonner's `create()` un-dismisses a matching id
|
|
305
|
+
// otherwise (index.mjs ~181-194).
|
|
306
|
+
if (!sonnerToast.getToasts().some((t) => t.id === id))
|
|
307
|
+
return;
|
|
308
|
+
kind(titleNode, { id, duration: Infinity });
|
|
309
|
+
};
|
|
310
|
+
const resume = () => {
|
|
311
|
+
if (duration === Infinity)
|
|
312
|
+
return;
|
|
313
|
+
// A toast the user already dismissed (or that auto-closed) must not be
|
|
314
|
+
// resurrected by a stray blur — sonner's `create()` un-dismisses a matching
|
|
315
|
+
// id otherwise (index.mjs ~181-194).
|
|
316
|
+
if (!sonnerToast.getToasts().some((t) => t.id === id))
|
|
317
|
+
return;
|
|
318
|
+
kind(titleNode, { id, duration });
|
|
319
|
+
};
|
|
320
|
+
const titleNode = (_jsx(ToastTitleText, { onRevealOpen: pause, onRevealClose: resume, children: message }));
|
|
321
|
+
return kind(titleNode, {
|
|
322
|
+
...toExternalToast(options, defaultDuration, {
|
|
323
|
+
onRevealOpen: pause,
|
|
324
|
+
onRevealClose: resume,
|
|
325
|
+
}),
|
|
326
|
+
id,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
205
329
|
/**
|
|
206
330
|
* The callable used everywhere else — never sonner's own `toast` re-exported directly.
|
|
207
|
-
* `options.duration` resolution uses `??`, not `||`: sonner
|
|
208
|
-
* `duration: 0` as falsy and silently falls back to its own ~4s default
|
|
209
|
-
* source) — `??` is required so an explicit `0` isn't confused with "not
|
|
331
|
+
* `options.duration` resolution (inside `toExternalToast`) uses `??`, not `||`: sonner
|
|
332
|
+
* treats a literal `duration: 0` as falsy and silently falls back to its own ~4s default
|
|
333
|
+
* (verified in source) — `??` is required so an explicit `0` isn't confused with "not
|
|
334
|
+
* passed".
|
|
210
335
|
*/
|
|
211
|
-
const toast = Object.assign((message, options) => sonnerToast
|
|
212
|
-
success: (message, options) => sonnerToast.success
|
|
213
|
-
info: (message, options) => sonnerToast.info
|
|
214
|
-
warning: (message, options) => sonnerToast.warning
|
|
336
|
+
const toast = Object.assign((message, options) => makeToast(sonnerToast, message, options, 5000), {
|
|
337
|
+
success: (message, options) => makeToast(sonnerToast.success, message, options, 5000),
|
|
338
|
+
info: (message, options) => makeToast(sonnerToast.info, message, options, 5000),
|
|
339
|
+
warning: (message, options) => makeToast(sonnerToast.warning, message, options, 5000),
|
|
215
340
|
error: (message, options) =>
|
|
216
341
|
// Default Infinity — errors persist until manually dismissed (toast.specs.ts).
|
|
217
|
-
sonnerToast.error
|
|
342
|
+
makeToast(sonnerToast.error, message, options, Infinity),
|
|
218
343
|
promise: (promise, options) => {
|
|
219
344
|
// Discard sonner's own return value (string|number & { unwrap }) — the spec's
|
|
220
345
|
// contract is `=> void`; the loading → success/error swap is automatic and there
|
|
221
346
|
// is no toast id to combine with toast.dismiss(id).
|
|
347
|
+
//
|
|
348
|
+
// loading/success/error are wrapped in `ToastTitleText` too — they render into
|
|
349
|
+
// the same `[data-title]` slot (styled by the same `classNames.title`, clamped
|
|
350
|
+
// the same way) as every other kind, so they get the same truncation reveal.
|
|
351
|
+
// `success`/`error` accept a plain value or a function of the resolved
|
|
352
|
+
// data/caught error (PromiseToastOptions<T>'s own shape) — resolve that first,
|
|
353
|
+
// then wrap the result; sonner is what actually calls this function with the
|
|
354
|
+
// real data/error at resolve/reject time.
|
|
355
|
+
//
|
|
356
|
+
// Pause/resume: the loading phase is `type: "loading"`, which sonner's own timer
|
|
357
|
+
// effect excludes from auto-dismissal entirely (no timer ever starts for it —
|
|
358
|
+
// index.mjs ~644) — nothing to pause there. Only the resolved *success* toast can
|
|
359
|
+
// auto-dismiss, so it alone is wired below, with its own explicit 5000ms duration
|
|
360
|
+
// (sonner's own internal default for a `.promise()` resolution is 4000ms, and
|
|
361
|
+
// unlike `toExternalToast`'s call sites, `resume` here has no other way to learn
|
|
362
|
+
// what to restore). The resolved *error* toast persists until manually dismissed —
|
|
363
|
+
// `duration: Infinity` is set EXPLICITLY below, matching `toast.error`'s own
|
|
364
|
+
// default: sonner's update merge only touches keys present in the returned
|
|
365
|
+
// object, so omitting `duration` here does not inherit Infinity from anywhere,
|
|
366
|
+
// it falls back to sonner's own internal ~4000ms default instead (this was a
|
|
367
|
+
// real, Validator-caught bug — the error toast was silently auto-dismissing with
|
|
368
|
+
// no pause protection, the exact gap this whole feature exists to close). With
|
|
369
|
+
// `duration: Infinity` actually set, no timer ever starts, so no pause/resume is
|
|
370
|
+
// wired for it either.
|
|
371
|
+
const id = nextToastId();
|
|
372
|
+
const successDuration = 5000;
|
|
373
|
+
let successTitleNode;
|
|
374
|
+
const pauseSuccess = () => {
|
|
375
|
+
// Same guard as `resumeSuccess` below — see `makeToast`'s `pause` for why.
|
|
376
|
+
if (!sonnerToast.getToasts().some((t) => t.id === id))
|
|
377
|
+
return;
|
|
378
|
+
sonnerToast.success(successTitleNode, { id, duration: Infinity });
|
|
379
|
+
};
|
|
380
|
+
const resumeSuccess = () => {
|
|
381
|
+
if (!sonnerToast.getToasts().some((t) => t.id === id))
|
|
382
|
+
return;
|
|
383
|
+
sonnerToast.success(successTitleNode, {
|
|
384
|
+
id,
|
|
385
|
+
duration: successDuration,
|
|
386
|
+
});
|
|
387
|
+
};
|
|
222
388
|
sonnerToast.promise(promise, {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
389
|
+
id,
|
|
390
|
+
loading: _jsx(ToastTitleText, { children: options.loading }),
|
|
391
|
+
success: (data) => {
|
|
392
|
+
const content = typeof options.success === "function"
|
|
393
|
+
? options.success(data)
|
|
394
|
+
: options.success;
|
|
395
|
+
successTitleNode = (_jsx(ToastTitleText, { onRevealOpen: pauseSuccess, onRevealClose: resumeSuccess, children: content }));
|
|
396
|
+
return { message: successTitleNode, duration: successDuration };
|
|
397
|
+
},
|
|
398
|
+
error: (error) => {
|
|
399
|
+
const content = typeof options.error === "function"
|
|
400
|
+
? options.error(error)
|
|
401
|
+
: options.error;
|
|
402
|
+
// `duration: Infinity` must be explicit here — sonner's own merge only
|
|
403
|
+
// touches keys present in this return value, and with none supplied it
|
|
404
|
+
// falls back to its own internal ~4000ms default, not our Infinity intent.
|
|
405
|
+
// (Found by the Validator: this toast was silently auto-dismissing behind
|
|
406
|
+
// an unpaused timer, contradicting this file's own comment above.)
|
|
407
|
+
return {
|
|
408
|
+
message: _jsx(ToastTitleText, { children: content }),
|
|
409
|
+
duration: Infinity,
|
|
410
|
+
};
|
|
411
|
+
},
|
|
226
412
|
});
|
|
227
413
|
},
|
|
228
414
|
dismiss: (id) => {
|