goey-toast 0.1.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/index.cjs ADDED
@@ -0,0 +1,983 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var sonner = require('sonner');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var framerMotion = require('framer-motion');
7
+
8
+ // src/components/GoeyToaster.tsx
9
+
10
+ // src/context.ts
11
+ var _position = "bottom-right";
12
+ function setGoeyPosition(position) {
13
+ _position = position;
14
+ }
15
+ function getGoeyPosition() {
16
+ return _position;
17
+ }
18
+ function GoeyToaster({
19
+ position = "bottom-right",
20
+ duration,
21
+ gap = 14,
22
+ offset = "24px",
23
+ theme = "light",
24
+ toastOptions,
25
+ expand,
26
+ closeButton,
27
+ richColors,
28
+ visibleToasts,
29
+ dir
30
+ }) {
31
+ react.useEffect(() => {
32
+ setGoeyPosition(position);
33
+ }, [position]);
34
+ react.useEffect(() => {
35
+ if (process.env.NODE_ENV !== "development") return;
36
+ const el = document.createElement("div");
37
+ el.setAttribute("data-goey-toast-css", "");
38
+ el.style.position = "absolute";
39
+ el.style.width = "0";
40
+ el.style.height = "0";
41
+ el.style.overflow = "hidden";
42
+ el.style.pointerEvents = "none";
43
+ document.body.appendChild(el);
44
+ const value = getComputedStyle(el).getPropertyValue("--goey-toast");
45
+ document.body.removeChild(el);
46
+ if (!value) {
47
+ console.warn(
48
+ '[goey-toast] Styles not found. Make sure to import the CSS:\n\n import "goey-toast/styles.css";\n'
49
+ );
50
+ }
51
+ }, []);
52
+ return /* @__PURE__ */ jsxRuntime.jsx(
53
+ sonner.Toaster,
54
+ {
55
+ position,
56
+ duration,
57
+ gap,
58
+ offset,
59
+ theme,
60
+ toastOptions: { unstyled: true, ...toastOptions },
61
+ expand,
62
+ closeButton,
63
+ richColors,
64
+ visibleToasts,
65
+ dir
66
+ }
67
+ );
68
+ }
69
+ var DefaultIcon = ({ size = 20, className }) => /* @__PURE__ */ jsxRuntime.jsxs(
70
+ "svg",
71
+ {
72
+ xmlns: "http://www.w3.org/2000/svg",
73
+ width: size,
74
+ height: size,
75
+ viewBox: "0 0 24 24",
76
+ fill: "none",
77
+ stroke: "currentColor",
78
+ strokeWidth: 2,
79
+ strokeLinecap: "round",
80
+ strokeLinejoin: "round",
81
+ className,
82
+ children: [
83
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
84
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
85
+ ]
86
+ }
87
+ );
88
+ var SuccessIcon = ({ size = 20, className }) => /* @__PURE__ */ jsxRuntime.jsxs(
89
+ "svg",
90
+ {
91
+ xmlns: "http://www.w3.org/2000/svg",
92
+ width: size,
93
+ height: size,
94
+ viewBox: "0 0 24 24",
95
+ fill: "none",
96
+ stroke: "currentColor",
97
+ strokeWidth: 2,
98
+ strokeLinecap: "round",
99
+ strokeLinejoin: "round",
100
+ className,
101
+ children: [
102
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
103
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 12l2 2 4-4" })
104
+ ]
105
+ }
106
+ );
107
+ var ErrorIcon = ({ size = 20, className }) => /* @__PURE__ */ jsxRuntime.jsxs(
108
+ "svg",
109
+ {
110
+ xmlns: "http://www.w3.org/2000/svg",
111
+ width: size,
112
+ height: size,
113
+ viewBox: "0 0 24 24",
114
+ fill: "none",
115
+ stroke: "currentColor",
116
+ strokeWidth: 2,
117
+ strokeLinecap: "round",
118
+ strokeLinejoin: "round",
119
+ className,
120
+ children: [
121
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
122
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 9l-6 6" }),
123
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 9l6 6" })
124
+ ]
125
+ }
126
+ );
127
+ var WarningIcon = ({ size = 20, className }) => /* @__PURE__ */ jsxRuntime.jsxs(
128
+ "svg",
129
+ {
130
+ xmlns: "http://www.w3.org/2000/svg",
131
+ width: size,
132
+ height: size,
133
+ viewBox: "0 0 24 24",
134
+ fill: "none",
135
+ stroke: "currentColor",
136
+ strokeWidth: 2,
137
+ strokeLinecap: "round",
138
+ strokeLinejoin: "round",
139
+ className,
140
+ children: [
141
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
142
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
143
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
144
+ ]
145
+ }
146
+ );
147
+ var InfoIcon = ({ size = 20, className }) => /* @__PURE__ */ jsxRuntime.jsxs(
148
+ "svg",
149
+ {
150
+ xmlns: "http://www.w3.org/2000/svg",
151
+ width: size,
152
+ height: size,
153
+ viewBox: "0 0 24 24",
154
+ fill: "none",
155
+ stroke: "currentColor",
156
+ strokeWidth: 2,
157
+ strokeLinecap: "round",
158
+ strokeLinejoin: "round",
159
+ className,
160
+ children: [
161
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
162
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
163
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
164
+ ]
165
+ }
166
+ );
167
+ var SpinnerIcon = ({ size = 20, className }) => /* @__PURE__ */ jsxRuntime.jsx(
168
+ "svg",
169
+ {
170
+ xmlns: "http://www.w3.org/2000/svg",
171
+ width: size,
172
+ height: size,
173
+ viewBox: "0 0 24 24",
174
+ fill: "none",
175
+ stroke: "currentColor",
176
+ strokeWidth: 2,
177
+ strokeLinecap: "round",
178
+ strokeLinejoin: "round",
179
+ className,
180
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
181
+ }
182
+ );
183
+ var QUERY = "(prefers-reduced-motion: reduce)";
184
+ function getInitialState() {
185
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
186
+ return false;
187
+ }
188
+ return window.matchMedia(QUERY).matches;
189
+ }
190
+ function usePrefersReducedMotion() {
191
+ const [prefersReducedMotion, setPrefersReducedMotion] = react.useState(getInitialState);
192
+ react.useEffect(() => {
193
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
194
+ return;
195
+ }
196
+ const mql = window.matchMedia(QUERY);
197
+ const handler = (event) => {
198
+ setPrefersReducedMotion(event.matches);
199
+ };
200
+ mql.addEventListener("change", handler);
201
+ return () => mql.removeEventListener("change", handler);
202
+ }, []);
203
+ return prefersReducedMotion;
204
+ }
205
+
206
+ // src/components/GoeyToast.module.css
207
+ var GoeyToast_default = {};
208
+ var phaseIconMap = {
209
+ default: DefaultIcon,
210
+ success: SuccessIcon,
211
+ error: ErrorIcon,
212
+ warning: WarningIcon,
213
+ info: InfoIcon
214
+ };
215
+ var titleColorMap = {
216
+ loading: GoeyToast_default.titleLoading,
217
+ default: GoeyToast_default.titleDefault,
218
+ success: GoeyToast_default.titleSuccess,
219
+ error: GoeyToast_default.titleError,
220
+ warning: GoeyToast_default.titleWarning,
221
+ info: GoeyToast_default.titleInfo
222
+ };
223
+ var actionColorMap = {
224
+ loading: GoeyToast_default.actionInfo,
225
+ default: GoeyToast_default.actionDefault,
226
+ success: GoeyToast_default.actionSuccess,
227
+ error: GoeyToast_default.actionError,
228
+ warning: GoeyToast_default.actionWarning,
229
+ info: GoeyToast_default.actionInfo
230
+ };
231
+ var PH = 34;
232
+ var DEFAULT_DISPLAY_DURATION = 4e3;
233
+ var BASE_SQUISH = { type: "spring", stiffness: 380, damping: 16, mass: 0.7 };
234
+ var DEFAULT_EXPAND_DUR = 0.6;
235
+ var DEFAULT_COLLAPSE_DUR = 0.9;
236
+ function squishSpring(durationSec, defaultDur) {
237
+ const scale = durationSec / defaultDur;
238
+ return { ...BASE_SQUISH, mass: BASE_SQUISH.mass * scale };
239
+ }
240
+ var observerRegistry = /* @__PURE__ */ new Map();
241
+ function registerSonnerObserver(ol, callback) {
242
+ let entry = observerRegistry.get(ol);
243
+ if (!entry) {
244
+ const callbacks = /* @__PURE__ */ new Set();
245
+ let applying = false;
246
+ const observer = new MutationObserver(() => {
247
+ if (applying) return;
248
+ applying = true;
249
+ requestAnimationFrame(() => {
250
+ callbacks.forEach((cb) => cb());
251
+ requestAnimationFrame(() => {
252
+ applying = false;
253
+ });
254
+ });
255
+ });
256
+ observer.observe(ol, {
257
+ attributes: true,
258
+ attributeFilter: ["style"],
259
+ subtree: true,
260
+ childList: true
261
+ });
262
+ entry = { observer, callbacks };
263
+ observerRegistry.set(ol, entry);
264
+ }
265
+ entry.callbacks.add(callback);
266
+ return () => {
267
+ entry.callbacks.delete(callback);
268
+ if (entry.callbacks.size === 0) {
269
+ entry.observer.disconnect();
270
+ observerRegistry.delete(ol);
271
+ }
272
+ };
273
+ }
274
+ function syncSonnerHeights(wrapperEl) {
275
+ if (!wrapperEl) return;
276
+ const li = wrapperEl.closest("[data-sonner-toast]");
277
+ if (!li?.parentElement) return;
278
+ const ol = li.parentElement;
279
+ const toasts = Array.from(
280
+ ol.querySelectorAll(":scope > [data-sonner-toast]")
281
+ );
282
+ for (const t of toasts) {
283
+ const content = t.firstElementChild;
284
+ const height = content ? content.getBoundingClientRect().height : 0;
285
+ if (height > 0) {
286
+ t.style.setProperty("--initial-height", `${height}px`);
287
+ }
288
+ }
289
+ }
290
+ function morphPath(pw, bw, th, t) {
291
+ const pr = PH / 2;
292
+ const pillW = Math.min(pw, bw);
293
+ const bodyH = PH + (th - PH) * t;
294
+ if (t <= 0 || bodyH - PH < 8) {
295
+ return [
296
+ `M 0,${pr}`,
297
+ `A ${pr},${pr} 0 0 1 ${pr},0`,
298
+ `H ${pillW - pr}`,
299
+ `A ${pr},${pr} 0 0 1 ${pillW},${pr}`,
300
+ `A ${pr},${pr} 0 0 1 ${pillW - pr},${PH}`,
301
+ `H ${pr}`,
302
+ `A ${pr},${pr} 0 0 1 0,${pr}`,
303
+ `Z`
304
+ ].join(" ");
305
+ }
306
+ const curve = 14 * t;
307
+ const cr = Math.min(16, (bodyH - PH) * 0.45);
308
+ const bodyW = pillW + (bw - pillW) * t;
309
+ const bodyTop = PH - curve;
310
+ const qEndX = Math.min(pillW + curve, bodyW - cr);
311
+ return [
312
+ `M 0,${pr}`,
313
+ `A ${pr},${pr} 0 0 1 ${pr},0`,
314
+ `H ${pillW - pr}`,
315
+ `A ${pr},${pr} 0 0 1 ${pillW},${pr}`,
316
+ `L ${pillW},${bodyTop}`,
317
+ `Q ${pillW},${bodyTop + curve} ${qEndX},${bodyTop + curve}`,
318
+ `H ${bodyW - cr}`,
319
+ `A ${cr},${cr} 0 0 1 ${bodyW},${bodyTop + curve + cr}`,
320
+ `L ${bodyW},${bodyH - cr}`,
321
+ `A ${cr},${cr} 0 0 1 ${bodyW - cr},${bodyH}`,
322
+ `H ${cr}`,
323
+ `A ${cr},${cr} 0 0 1 0,${bodyH - cr}`,
324
+ `Z`
325
+ ].join(" ");
326
+ }
327
+ var GoeyToast = ({
328
+ title,
329
+ description,
330
+ action,
331
+ icon,
332
+ phase,
333
+ classNames,
334
+ fillColor = "#ffffff",
335
+ borderColor,
336
+ borderWidth,
337
+ timing
338
+ }) => {
339
+ const position = getGoeyPosition();
340
+ const isRight = position?.includes("right") ?? false;
341
+ const prefersReducedMotion = usePrefersReducedMotion();
342
+ const [actionSuccess, setActionSuccess] = react.useState(null);
343
+ const [dismissing, setDismissing] = react.useState(false);
344
+ const collapsingRef = react.useRef(false);
345
+ const preDismissRef = react.useRef(false);
346
+ const collapseEndTime = react.useRef(0);
347
+ const expandedDimsRef = react.useRef({ pw: 0, bw: 0, th: 0 });
348
+ const effectiveTitle = actionSuccess ?? title;
349
+ const effectivePhase = actionSuccess ? "success" : phase;
350
+ const effectiveDescription = actionSuccess ? void 0 : description;
351
+ const effectiveAction = actionSuccess ? void 0 : action;
352
+ const isLoading = effectivePhase === "loading";
353
+ const hasDescription = Boolean(effectiveDescription);
354
+ const hasAction = Boolean(effectiveAction);
355
+ const isExpanded = (hasDescription || hasAction) && !dismissing;
356
+ const [showBody, setShowBody] = react.useState(false);
357
+ const wrapperRef = react.useRef(null);
358
+ const pathRef = react.useRef(null);
359
+ const headerRef = react.useRef(null);
360
+ const contentRef = react.useRef(null);
361
+ const morphCtrl = react.useRef(null);
362
+ const pillResizeCtrl = react.useRef(null);
363
+ const headerSquishCtrl = react.useRef(null);
364
+ const morphTRef = react.useRef(0);
365
+ const aDims = react.useRef({ pw: 0, bw: 0, th: 0 });
366
+ const dimsRef = react.useRef({ pw: 0, bw: 0, th: 0 });
367
+ const [dims, setDims] = react.useState({ pw: 0, bw: 0, th: 0 });
368
+ react.useEffect(() => {
369
+ dimsRef.current = dims;
370
+ }, [dims]);
371
+ const flush = react.useCallback(() => {
372
+ const { pw: p, bw: b, th: h } = aDims.current;
373
+ if (p <= 0 || b <= 0 || h <= 0) return;
374
+ const t = Math.max(0, Math.min(1, morphTRef.current));
375
+ const pos = getGoeyPosition();
376
+ const rightSide = pos?.includes("right") ?? false;
377
+ pathRef.current?.setAttribute("d", morphPath(p, b, h, t));
378
+ if (t >= 1) {
379
+ if (wrapperRef.current) {
380
+ wrapperRef.current.style.width = "";
381
+ }
382
+ if (contentRef.current) {
383
+ contentRef.current.style.width = "";
384
+ contentRef.current.style.overflow = "";
385
+ contentRef.current.style.maxHeight = "";
386
+ contentRef.current.style.clipPath = "";
387
+ }
388
+ } else if (t > 0) {
389
+ const targetBw = dimsRef.current.bw;
390
+ const targetTh = dimsRef.current.th;
391
+ const pillW = Math.min(p, b);
392
+ const currentW = pillW + (b - pillW) * t;
393
+ const currentH = PH + (targetTh - PH) * t;
394
+ if (wrapperRef.current) {
395
+ wrapperRef.current.style.width = currentW + "px";
396
+ }
397
+ if (contentRef.current) {
398
+ contentRef.current.style.width = targetBw + "px";
399
+ contentRef.current.style.overflow = "hidden";
400
+ contentRef.current.style.maxHeight = currentH + "px";
401
+ const clip = targetBw - currentW;
402
+ contentRef.current.style.clipPath = rightSide ? `inset(0 0 0 ${clip}px)` : `inset(0 ${clip}px 0 0)`;
403
+ }
404
+ } else {
405
+ const pillW = Math.min(p, b);
406
+ if (wrapperRef.current) {
407
+ wrapperRef.current.style.width = pillW + "px";
408
+ }
409
+ if (contentRef.current) {
410
+ contentRef.current.style.width = "";
411
+ contentRef.current.style.overflow = "hidden";
412
+ contentRef.current.style.maxHeight = PH + "px";
413
+ contentRef.current.style.clipPath = "";
414
+ }
415
+ }
416
+ }, []);
417
+ const measure = react.useCallback(() => {
418
+ if (!headerRef.current || !contentRef.current) return;
419
+ const wr = wrapperRef.current;
420
+ const savedW = wr?.style.width ?? "";
421
+ const savedOv = contentRef.current.style.overflow;
422
+ const savedMH = contentRef.current.style.maxHeight;
423
+ const savedCW = contentRef.current.style.width;
424
+ if (wr) {
425
+ wr.style.width = "";
426
+ }
427
+ contentRef.current.style.overflow = "";
428
+ contentRef.current.style.maxHeight = "";
429
+ contentRef.current.style.width = "";
430
+ const cs = getComputedStyle(contentRef.current);
431
+ const paddingX = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
432
+ const pw2 = headerRef.current.offsetWidth + paddingX;
433
+ const bw2 = contentRef.current.offsetWidth;
434
+ const th2 = contentRef.current.offsetHeight;
435
+ if (wr) {
436
+ wr.style.width = savedW;
437
+ }
438
+ contentRef.current.style.overflow = savedOv;
439
+ contentRef.current.style.maxHeight = savedMH;
440
+ contentRef.current.style.width = savedCW;
441
+ setDims({ pw: pw2, bw: bw2, th: th2 });
442
+ }, []);
443
+ react.useLayoutEffect(() => {
444
+ measure();
445
+ const t = setTimeout(measure, 100);
446
+ return () => clearTimeout(t);
447
+ }, [effectiveTitle, effectivePhase, isExpanded, showBody, effectiveDescription, effectiveAction, measure]);
448
+ react.useEffect(() => {
449
+ if (!contentRef.current) return;
450
+ const ro = new ResizeObserver(measure);
451
+ ro.observe(contentRef.current);
452
+ return () => ro.disconnect();
453
+ }, [measure]);
454
+ const { pw, bw, th } = dims;
455
+ const hasDims = pw > 0 && bw > 0 && th > 0;
456
+ const blobSquishCtrl = react.useRef(null);
457
+ const expandDur = timing?.expandDuration ?? DEFAULT_EXPAND_DUR;
458
+ const collapseDur = timing?.collapseDuration ?? DEFAULT_COLLAPSE_DUR;
459
+ const lastSquishTime = react.useRef(0);
460
+ const triggerLandingSquish = react.useCallback((phase2 = "mount") => {
461
+ if (!wrapperRef.current || prefersReducedMotion) return;
462
+ const now = Date.now();
463
+ if (now - lastSquishTime.current < 300) return;
464
+ lastSquishTime.current = now;
465
+ blobSquishCtrl.current?.stop();
466
+ const el = wrapperRef.current;
467
+ const spring = phase2 === "collapse" ? squishSpring(collapseDur, DEFAULT_COLLAPSE_DUR) : squishSpring(expandDur, DEFAULT_EXPAND_DUR);
468
+ const compressY = phase2 === "collapse" ? 0.07 : 0.12;
469
+ const expandX = phase2 === "collapse" ? 0.035 : 0.06;
470
+ blobSquishCtrl.current = framerMotion.animate(0, 1, {
471
+ ...spring,
472
+ onUpdate: (v) => {
473
+ const intensity = Math.sin(v * Math.PI);
474
+ const sy = 1 - compressY * intensity;
475
+ const sx = 1 + expandX * intensity;
476
+ const mirror = el.style.transform?.includes("scaleX(-1)") ? "scaleX(-1) " : "";
477
+ el.style.transformOrigin = "center bottom";
478
+ el.style.transform = mirror + `scaleX(${sx}) scaleY(${sy})`;
479
+ },
480
+ onComplete: () => {
481
+ const right = el.style.transform?.includes("scaleX(-1)");
482
+ el.style.transform = right ? "scaleX(-1)" : "";
483
+ el.style.transformOrigin = "";
484
+ }
485
+ });
486
+ }, [prefersReducedMotion, expandDur, collapseDur]);
487
+ react.useLayoutEffect(() => {
488
+ if (!hasDims || collapsingRef.current) return;
489
+ const prev = { ...aDims.current };
490
+ const target = { pw, bw, th };
491
+ if (prev.bw <= 0) {
492
+ aDims.current = target;
493
+ flush();
494
+ return;
495
+ }
496
+ if (morphTRef.current > 0 && morphTRef.current < 1) {
497
+ aDims.current = target;
498
+ flush();
499
+ return;
500
+ }
501
+ if (showBody) {
502
+ aDims.current = target;
503
+ flush();
504
+ return;
505
+ }
506
+ if (prev.bw === target.bw && prev.pw === target.pw && prev.th === target.th) return;
507
+ if (prefersReducedMotion) {
508
+ aDims.current = target;
509
+ flush();
510
+ return;
511
+ }
512
+ pillResizeCtrl.current?.stop();
513
+ if (Date.now() - collapseEndTime.current > 500 && !isExpanded) {
514
+ triggerLandingSquish("expand");
515
+ }
516
+ pillResizeCtrl.current = framerMotion.animate(0, 1, {
517
+ type: "spring",
518
+ duration: 0.5,
519
+ bounce: 0.35,
520
+ onUpdate: (t) => {
521
+ aDims.current = {
522
+ pw: prev.pw + (target.pw - prev.pw) * t,
523
+ bw: prev.bw + (target.bw - prev.bw) * t,
524
+ th: prev.th + (target.th - prev.th) * t
525
+ };
526
+ flush();
527
+ }
528
+ });
529
+ }, [pw, bw, th, hasDims, showBody, flush, prefersReducedMotion, triggerLandingSquish]);
530
+ const expandDelayMs = prefersReducedMotion ? 0 : timing?.expandDelay ?? 330;
531
+ const mountSquished = react.useRef(false);
532
+ react.useEffect(() => {
533
+ if (hasDims && !mountSquished.current && !isExpanded) {
534
+ mountSquished.current = true;
535
+ const t = setTimeout(triggerLandingSquish, expandDelayMs);
536
+ return () => clearTimeout(t);
537
+ }
538
+ }, [hasDims, expandDelayMs, triggerLandingSquish]);
539
+ const prevShowBody = react.useRef(false);
540
+ react.useLayoutEffect(() => {
541
+ if (!prevShowBody.current && showBody) {
542
+ triggerLandingSquish("expand");
543
+ }
544
+ prevShowBody.current = showBody;
545
+ }, [showBody, triggerLandingSquish]);
546
+ const shakeCtrl = react.useRef(null);
547
+ const prevPhase = react.useRef(phase);
548
+ react.useEffect(() => {
549
+ if (phase === "error" && prevPhase.current !== "error" && !dismissing && wrapperRef.current && !prefersReducedMotion) {
550
+ shakeCtrl.current?.stop();
551
+ const el = wrapperRef.current;
552
+ const mirror = el.style.transform?.includes("scaleX(-1)") ? "scaleX(-1) " : "";
553
+ shakeCtrl.current = framerMotion.animate(0, 1, {
554
+ duration: 0.4,
555
+ ease: "easeOut",
556
+ onUpdate: (v) => {
557
+ const decay = 1 - v;
558
+ const shake = Math.sin(v * Math.PI * 6) * decay * 3;
559
+ el.style.transform = mirror + `translateX(${shake}px)`;
560
+ },
561
+ onComplete: () => {
562
+ el.style.transform = mirror.trim() || "";
563
+ }
564
+ });
565
+ }
566
+ prevPhase.current = phase;
567
+ return () => {
568
+ shakeCtrl.current?.stop();
569
+ };
570
+ }, [phase, dismissing, prefersReducedMotion]);
571
+ react.useEffect(() => {
572
+ if (isExpanded) {
573
+ const delay = prefersReducedMotion ? 0 : timing?.expandDelay ?? 330;
574
+ const t1 = setTimeout(() => setShowBody(true), delay);
575
+ return () => clearTimeout(t1);
576
+ }
577
+ morphCtrl.current?.stop();
578
+ pillResizeCtrl.current?.stop();
579
+ if (morphTRef.current > 0) {
580
+ const csPad = contentRef.current ? getComputedStyle(contentRef.current) : null;
581
+ const padX = csPad ? parseFloat(csPad.paddingLeft) + parseFloat(csPad.paddingRight) : 20;
582
+ const targetPw = headerRef.current ? headerRef.current.offsetWidth + padX : aDims.current.pw;
583
+ const targetDims = { pw: targetPw, bw: targetPw, th: PH };
584
+ if (prefersReducedMotion) {
585
+ morphTRef.current = 0;
586
+ collapsingRef.current = false;
587
+ preDismissRef.current = false;
588
+ setShowBody(false);
589
+ aDims.current = { ...targetDims };
590
+ flush();
591
+ return;
592
+ }
593
+ const savedDims = expandedDimsRef.current.bw > 0 ? { ...expandedDimsRef.current } : { ...aDims.current };
594
+ const isPreDismiss = preDismissRef.current;
595
+ const collapseDur2 = timing?.collapseDuration ?? 0.9;
596
+ const collapseTransition = isPreDismiss ? { duration: collapseDur2, ease: [0.4, 0, 0.2, 1] } : { type: "spring", duration: collapseDur2, bounce: 0.2 };
597
+ triggerLandingSquish("collapse");
598
+ morphCtrl.current = framerMotion.animate(morphTRef.current, 0, {
599
+ ...collapseTransition,
600
+ onUpdate: (t) => {
601
+ morphTRef.current = t;
602
+ aDims.current = {
603
+ pw: targetDims.pw + (savedDims.pw - targetDims.pw) * t,
604
+ bw: targetDims.bw + (savedDims.bw - targetDims.bw) * t,
605
+ th: targetDims.th + (savedDims.th - targetDims.th) * t
606
+ };
607
+ flush();
608
+ },
609
+ onComplete: () => {
610
+ morphTRef.current = 0;
611
+ collapsingRef.current = false;
612
+ preDismissRef.current = false;
613
+ collapseEndTime.current = Date.now();
614
+ aDims.current = { ...targetDims };
615
+ flush();
616
+ setShowBody(false);
617
+ }
618
+ });
619
+ return () => {
620
+ morphCtrl.current?.stop();
621
+ };
622
+ }
623
+ setShowBody(false);
624
+ morphTRef.current = 0;
625
+ flush();
626
+ }, [isExpanded, flush, prefersReducedMotion]);
627
+ react.useEffect(() => {
628
+ if (!showBody || actionSuccess || dismissing) return;
629
+ const expandDelayMs2 = prefersReducedMotion ? 0 : timing?.expandDelay ?? 330;
630
+ const collapseMs = prefersReducedMotion ? 10 : (timing?.collapseDuration ?? 0.9) * 1e3;
631
+ const displayMs = timing?.displayDuration ?? DEFAULT_DISPLAY_DURATION;
632
+ const delay = displayMs - expandDelayMs2 - collapseMs;
633
+ if (delay <= 0) return;
634
+ const timer = setTimeout(() => {
635
+ expandedDimsRef.current = { ...aDims.current };
636
+ collapsingRef.current = true;
637
+ preDismissRef.current = true;
638
+ setDismissing(true);
639
+ }, delay);
640
+ return () => clearTimeout(timer);
641
+ }, [showBody, actionSuccess, dismissing, prefersReducedMotion]);
642
+ react.useEffect(() => {
643
+ if (!showBody) {
644
+ morphTRef.current = 0;
645
+ morphCtrl.current?.stop();
646
+ flush();
647
+ return;
648
+ }
649
+ if (prefersReducedMotion) {
650
+ pillResizeCtrl.current?.stop();
651
+ morphCtrl.current?.stop();
652
+ morphTRef.current = 1;
653
+ aDims.current = { ...dimsRef.current };
654
+ flush();
655
+ syncSonnerHeights(wrapperRef.current);
656
+ return;
657
+ }
658
+ const raf = requestAnimationFrame(() => {
659
+ pillResizeCtrl.current?.stop();
660
+ morphCtrl.current?.stop();
661
+ const startDims = { ...aDims.current };
662
+ morphCtrl.current = framerMotion.animate(0, 1, {
663
+ type: "spring",
664
+ duration: timing?.expandDuration ?? 0.9,
665
+ bounce: 0.2,
666
+ onUpdate: (t) => {
667
+ morphTRef.current = t;
668
+ const target = dimsRef.current;
669
+ aDims.current = {
670
+ pw: startDims.pw + (target.pw - startDims.pw) * t,
671
+ bw: startDims.bw + (target.bw - startDims.bw) * t,
672
+ th: startDims.th + (target.th - startDims.th) * t
673
+ };
674
+ flush();
675
+ },
676
+ onComplete: () => {
677
+ morphTRef.current = 1;
678
+ aDims.current = { ...dimsRef.current };
679
+ flush();
680
+ syncSonnerHeights(wrapperRef.current);
681
+ }
682
+ });
683
+ });
684
+ return () => {
685
+ cancelAnimationFrame(raf);
686
+ morphCtrl.current?.stop();
687
+ };
688
+ }, [showBody, flush, prefersReducedMotion]);
689
+ const headerSquished = react.useRef(false);
690
+ react.useEffect(() => {
691
+ if (!headerRef.current || prefersReducedMotion) return;
692
+ headerSquishCtrl.current?.stop();
693
+ const el = headerRef.current;
694
+ if (showBody && !dismissing && !actionSuccess) {
695
+ headerSquished.current = true;
696
+ headerSquishCtrl.current = framerMotion.animate(0, 1, {
697
+ ...squishSpring(expandDur, DEFAULT_EXPAND_DUR),
698
+ onUpdate: (v) => {
699
+ const scale = 1 - 0.05 * v;
700
+ const pushY = v * 1;
701
+ el.style.transform = `scale(${scale}) translateY(${pushY}px)`;
702
+ }
703
+ });
704
+ } else if (headerSquished.current) {
705
+ headerSquished.current = false;
706
+ const isSpringCollapse = !preDismissRef.current;
707
+ const transition = isSpringCollapse ? squishSpring(collapseDur, DEFAULT_COLLAPSE_DUR) : { duration: collapseDur * 0.5, ease: [0.4, 0, 0.2, 1] };
708
+ headerSquishCtrl.current = framerMotion.animate(1, 0, {
709
+ ...transition,
710
+ onUpdate: (v) => {
711
+ const scale = 1 - 0.05 * v;
712
+ const pushY = v * 1;
713
+ el.style.transform = `scale(${scale}) translateY(${pushY}px)`;
714
+ },
715
+ onComplete: () => {
716
+ el.style.transform = "";
717
+ }
718
+ });
719
+ }
720
+ return () => {
721
+ headerSquishCtrl.current?.stop();
722
+ };
723
+ }, [showBody, dismissing, actionSuccess, prefersReducedMotion, expandDur, collapseDur]);
724
+ react.useEffect(() => {
725
+ const wrapper = wrapperRef.current;
726
+ if (!wrapper) return;
727
+ const ol = wrapper.closest("[data-sonner-toast]")?.parentElement;
728
+ if (!ol) return;
729
+ return registerSonnerObserver(ol, () => {
730
+ syncSonnerHeights(wrapper);
731
+ });
732
+ }, []);
733
+ const handleActionClick = react.useCallback(() => {
734
+ if (!effectiveAction) return;
735
+ effectiveAction.onClick();
736
+ if (effectiveAction.successLabel) {
737
+ expandedDimsRef.current = { ...aDims.current };
738
+ collapsingRef.current = true;
739
+ setActionSuccess(effectiveAction.successLabel);
740
+ }
741
+ }, [effectiveAction]);
742
+ const renderIcon = () => {
743
+ if (!actionSuccess && icon) return icon;
744
+ if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, { size: 18, className: GoeyToast_default.spinnerSpin });
745
+ const IconComponent = phaseIconMap[effectivePhase];
746
+ return /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { size: 18 });
747
+ };
748
+ const iconTransition = prefersReducedMotion ? { duration: 0.01 } : { duration: 0.2 };
749
+ const iconEl = /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${GoeyToast_default.iconWrapper}${classNames?.icon ? ` ${classNames.icon}` : ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxRuntime.jsx(
750
+ framerMotion.motion.div,
751
+ {
752
+ initial: prefersReducedMotion ? false : { opacity: 0, scale: 0.5 },
753
+ animate: { opacity: 1, scale: 1 },
754
+ exit: { opacity: 0, scale: 0.5 },
755
+ transition: iconTransition,
756
+ children: renderIcon()
757
+ },
758
+ isLoading ? "spinner" : effectivePhase
759
+ ) }) });
760
+ const titleEl = /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${GoeyToast_default.title}${classNames?.title ? ` ${classNames.title}` : ""}`, children: effectiveTitle });
761
+ const iconAndTitle = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
762
+ iconEl,
763
+ titleEl
764
+ ] });
765
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className: `${GoeyToast_default.wrapper}${classNames?.wrapper ? ` ${classNames.wrapper}` : ""}`, style: isRight ? { marginLeft: "auto", transform: "scaleX(-1)" } : void 0, role: effectivePhase === "error" ? "alert" : "status", "aria-live": effectivePhase === "error" ? "assertive" : "polite", "aria-atomic": "true", children: [
766
+ /* @__PURE__ */ jsxRuntime.jsx(
767
+ "svg",
768
+ {
769
+ className: GoeyToast_default.blobSvg,
770
+ "aria-hidden": true,
771
+ children: /* @__PURE__ */ jsxRuntime.jsx(
772
+ "path",
773
+ {
774
+ ref: pathRef,
775
+ fill: fillColor,
776
+ stroke: borderColor || "none",
777
+ strokeWidth: borderColor ? borderWidth ?? 1.5 : 0
778
+ }
779
+ )
780
+ }
781
+ ),
782
+ /* @__PURE__ */ jsxRuntime.jsxs(
783
+ "div",
784
+ {
785
+ ref: contentRef,
786
+ className: `${GoeyToast_default.content} ${showBody ? GoeyToast_default.contentExpanded : GoeyToast_default.contentCompact}${classNames?.content ? ` ${classNames.content}` : ""}`,
787
+ style: isRight ? { transform: "scaleX(-1)", textAlign: "right" } : { textAlign: "left" },
788
+ children: [
789
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: headerRef, className: `${GoeyToast_default.header} ${titleColorMap[effectivePhase]}${classNames?.header ? ` ${classNames.header}` : ""}`, children: iconAndTitle }),
790
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showBody && hasDescription && !dismissing && /* @__PURE__ */ jsxRuntime.jsx(
791
+ framerMotion.motion.div,
792
+ {
793
+ className: `${GoeyToast_default.description}${classNames?.description ? ` ${classNames.description}` : ""}`,
794
+ style: { textAlign: "left" },
795
+ initial: prefersReducedMotion ? false : { opacity: 0 },
796
+ animate: { opacity: 1 },
797
+ exit: { opacity: 0 },
798
+ transition: prefersReducedMotion ? { duration: 0.01 } : { duration: 0.35, ease: [0.4, 0, 0.2, 1] },
799
+ children: effectiveDescription
800
+ },
801
+ "description"
802
+ ) }),
803
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showBody && hasAction && effectiveAction && !dismissing && /* @__PURE__ */ jsxRuntime.jsx(
804
+ framerMotion.motion.div,
805
+ {
806
+ className: `${GoeyToast_default.actionWrapper}${classNames?.actionWrapper ? ` ${classNames.actionWrapper}` : ""}`,
807
+ initial: prefersReducedMotion ? false : { opacity: 0 },
808
+ animate: { opacity: 1 },
809
+ exit: { opacity: 0 },
810
+ transition: prefersReducedMotion ? { duration: 0.01 } : { duration: 0.35, ease: [0.4, 0, 0.2, 1], delay: 0.1 },
811
+ children: /* @__PURE__ */ jsxRuntime.jsx(
812
+ "button",
813
+ {
814
+ className: `${GoeyToast_default.actionButton} ${actionColorMap[effectivePhase]}${classNames?.actionButton ? ` ${classNames.actionButton}` : ""}`,
815
+ onClick: handleActionClick,
816
+ type: "button",
817
+ "aria-label": effectiveAction.label,
818
+ children: effectiveAction.label
819
+ }
820
+ )
821
+ },
822
+ "action"
823
+ ) })
824
+ ]
825
+ }
826
+ )
827
+ ] });
828
+ };
829
+ var ToastErrorBoundary = class extends react.Component {
830
+ constructor() {
831
+ super(...arguments);
832
+ this.state = { hasError: false };
833
+ }
834
+ static getDerivedStateFromError() {
835
+ return { hasError: true };
836
+ }
837
+ componentDidCatch(error, errorInfo) {
838
+ if (process.env.NODE_ENV !== "production") {
839
+ console.error("[GoeyToast] Rendering error:", error, errorInfo);
840
+ }
841
+ }
842
+ render() {
843
+ if (this.state.hasError) {
844
+ return null;
845
+ }
846
+ return this.props.children;
847
+ }
848
+ };
849
+ var DEFAULT_EXPANDED_DURATION = 4e3;
850
+ function GoeyToastWrapper({
851
+ initialPhase,
852
+ title,
853
+ type,
854
+ description,
855
+ action,
856
+ icon,
857
+ classNames,
858
+ fillColor,
859
+ borderColor,
860
+ borderWidth,
861
+ timing
862
+ }) {
863
+ return /* @__PURE__ */ jsxRuntime.jsx(ToastErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(
864
+ GoeyToast,
865
+ {
866
+ title,
867
+ description,
868
+ type,
869
+ action,
870
+ icon,
871
+ phase: initialPhase,
872
+ classNames,
873
+ fillColor,
874
+ borderColor,
875
+ borderWidth,
876
+ timing
877
+ }
878
+ ) });
879
+ }
880
+ function PromiseToastWrapper({
881
+ promise,
882
+ data,
883
+ toastId
884
+ }) {
885
+ const [phase, setPhase] = react.useState("loading");
886
+ const [title, setTitle] = react.useState(data.loading);
887
+ const [description, setDescription] = react.useState(data.description?.loading);
888
+ const [action, setAction] = react.useState(void 0);
889
+ react.useEffect(() => {
890
+ const resetDuration = (hasExpandedContent) => {
891
+ const baseDuration = data.timing?.displayDuration ?? (hasExpandedContent ? DEFAULT_EXPANDED_DURATION : void 0);
892
+ const collapseDurMs = (data.timing?.collapseDuration ?? 0.9) * 1e3;
893
+ const duration = baseDuration != null && hasExpandedContent ? baseDuration + collapseDurMs : baseDuration;
894
+ if (duration != null) {
895
+ sonner.toast.custom(() => /* @__PURE__ */ jsxRuntime.jsx(PromiseToastWrapper, { promise, data, toastId }), { id: toastId, duration });
896
+ }
897
+ };
898
+ promise.then((result) => {
899
+ const desc = typeof data.description?.success === "function" ? data.description.success(result) : data.description?.success;
900
+ setTitle(
901
+ typeof data.success === "function" ? data.success(result) : data.success
902
+ );
903
+ setDescription(desc);
904
+ setAction(data.action?.success);
905
+ setPhase("success");
906
+ resetDuration(Boolean(desc || data.action?.success));
907
+ }).catch((err) => {
908
+ const desc = typeof data.description?.error === "function" ? data.description.error(err) : data.description?.error;
909
+ setTitle(
910
+ typeof data.error === "function" ? data.error(err) : data.error
911
+ );
912
+ setDescription(desc);
913
+ setAction(data.action?.error);
914
+ setPhase("error");
915
+ resetDuration(Boolean(desc || data.action?.error));
916
+ });
917
+ }, []);
918
+ return /* @__PURE__ */ jsxRuntime.jsx(ToastErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(
919
+ GoeyToast,
920
+ {
921
+ title,
922
+ description,
923
+ type: phase === "loading" ? "info" : phase,
924
+ action,
925
+ phase,
926
+ classNames: data.classNames,
927
+ fillColor: data.fillColor,
928
+ borderColor: data.borderColor,
929
+ borderWidth: data.borderWidth,
930
+ timing: data.timing
931
+ }
932
+ ) });
933
+ }
934
+ function createGoeyToast(title, type, options) {
935
+ const baseDuration = options?.timing?.displayDuration ?? options?.duration ?? (options?.description ? DEFAULT_EXPANDED_DURATION : void 0);
936
+ const hasExpandedContent = Boolean(options?.description || options?.action);
937
+ const collapseDurMs = (options?.timing?.collapseDuration ?? 0.9) * 1e3;
938
+ const duration = baseDuration != null && hasExpandedContent ? baseDuration + collapseDurMs : baseDuration;
939
+ return sonner.toast.custom(
940
+ () => /* @__PURE__ */ jsxRuntime.jsx(
941
+ GoeyToastWrapper,
942
+ {
943
+ initialPhase: type,
944
+ title,
945
+ type,
946
+ description: options?.description,
947
+ action: options?.action,
948
+ icon: options?.icon,
949
+ classNames: options?.classNames,
950
+ fillColor: options?.fillColor,
951
+ borderColor: options?.borderColor,
952
+ borderWidth: options?.borderWidth,
953
+ timing: options?.timing
954
+ }
955
+ ),
956
+ {
957
+ duration,
958
+ id: options?.id
959
+ }
960
+ );
961
+ }
962
+ var goeyToast = Object.assign(
963
+ (title, options) => createGoeyToast(title, "default", options),
964
+ {
965
+ success: (title, options) => createGoeyToast(title, "success", options),
966
+ error: (title, options) => createGoeyToast(title, "error", options),
967
+ warning: (title, options) => createGoeyToast(title, "warning", options),
968
+ info: (title, options) => createGoeyToast(title, "info", options),
969
+ promise: (promise, data) => {
970
+ const id = Math.random().toString(36).slice(2);
971
+ return sonner.toast.custom(() => /* @__PURE__ */ jsxRuntime.jsx(PromiseToastWrapper, { promise, data, toastId: id }), {
972
+ id,
973
+ duration: data.timing?.displayDuration != null || data.description ? Infinity : void 0
974
+ });
975
+ },
976
+ dismiss: sonner.toast.dismiss
977
+ }
978
+ );
979
+
980
+ exports.GoeyToaster = GoeyToaster;
981
+ exports.goeyToast = goeyToast;
982
+ //# sourceMappingURL=index.cjs.map
983
+ //# sourceMappingURL=index.cjs.map