flashpop 1.0.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.
@@ -0,0 +1,795 @@
1
+ import React, { useState, useRef, useCallback, useEffect, createContext, useMemo, useContext } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ const SuccessIcon = ({
6
+ size = 20,
7
+ className = '',
8
+ ...props
9
+ }) => /*#__PURE__*/jsxs("svg", {
10
+ width: size,
11
+ height: size,
12
+ viewBox: "0 0 24 24",
13
+ fill: "none",
14
+ stroke: "currentColor",
15
+ strokeWidth: "2",
16
+ strokeLinecap: "round",
17
+ strokeLinejoin: "round",
18
+ className: `easy-toast-icon easy-toast-icon--success ${className}`,
19
+ "aria-hidden": "true",
20
+ ...props,
21
+ children: [/*#__PURE__*/jsx("path", {
22
+ d: "M22 11.08V12a10 10 0 1 1-5.93-9.14"
23
+ }), /*#__PURE__*/jsx("polyline", {
24
+ points: "22 4 12 14.01 9 11.01"
25
+ })]
26
+ });
27
+
28
+ /**
29
+ * Error Icon SVG
30
+ */
31
+ const ErrorIcon = ({
32
+ size = 20,
33
+ className = '',
34
+ ...props
35
+ }) => /*#__PURE__*/jsxs("svg", {
36
+ width: size,
37
+ height: size,
38
+ viewBox: "0 0 24 24",
39
+ fill: "none",
40
+ stroke: "currentColor",
41
+ strokeWidth: "2",
42
+ strokeLinecap: "round",
43
+ strokeLinejoin: "round",
44
+ className: `easy-toast-icon easy-toast-icon--error ${className}`,
45
+ "aria-hidden": "true",
46
+ ...props,
47
+ children: [/*#__PURE__*/jsx("circle", {
48
+ cx: "12",
49
+ cy: "12",
50
+ r: "10"
51
+ }), /*#__PURE__*/jsx("line", {
52
+ x1: "15",
53
+ y1: "9",
54
+ x2: "9",
55
+ y2: "15"
56
+ }), /*#__PURE__*/jsx("line", {
57
+ x1: "9",
58
+ y1: "9",
59
+ x2: "15",
60
+ y2: "15"
61
+ })]
62
+ });
63
+
64
+ /**
65
+ * Warning Icon SVG
66
+ */
67
+ const WarningIcon = ({
68
+ size = 20,
69
+ className = '',
70
+ ...props
71
+ }) => /*#__PURE__*/jsxs("svg", {
72
+ width: size,
73
+ height: size,
74
+ viewBox: "0 0 24 24",
75
+ fill: "none",
76
+ stroke: "currentColor",
77
+ strokeWidth: "2",
78
+ strokeLinecap: "round",
79
+ strokeLinejoin: "round",
80
+ className: `easy-toast-icon easy-toast-icon--warning ${className}`,
81
+ "aria-hidden": "true",
82
+ ...props,
83
+ children: [/*#__PURE__*/jsx("path", {
84
+ d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
85
+ }), /*#__PURE__*/jsx("line", {
86
+ x1: "12",
87
+ y1: "9",
88
+ x2: "12",
89
+ y2: "13"
90
+ }), /*#__PURE__*/jsx("line", {
91
+ x1: "12",
92
+ y1: "17",
93
+ x2: "12.01",
94
+ y2: "17"
95
+ })]
96
+ });
97
+
98
+ /**
99
+ * Info Icon SVG
100
+ */
101
+ const InfoIcon = ({
102
+ size = 20,
103
+ className = '',
104
+ ...props
105
+ }) => /*#__PURE__*/jsxs("svg", {
106
+ width: size,
107
+ height: size,
108
+ viewBox: "0 0 24 24",
109
+ fill: "none",
110
+ stroke: "currentColor",
111
+ strokeWidth: "2",
112
+ strokeLinecap: "round",
113
+ strokeLinejoin: "round",
114
+ className: `easy-toast-icon easy-toast-icon--info ${className}`,
115
+ "aria-hidden": "true",
116
+ ...props,
117
+ children: [/*#__PURE__*/jsx("circle", {
118
+ cx: "12",
119
+ cy: "12",
120
+ r: "10"
121
+ }), /*#__PURE__*/jsx("line", {
122
+ x1: "12",
123
+ y1: "16",
124
+ x2: "12",
125
+ y2: "12"
126
+ }), /*#__PURE__*/jsx("line", {
127
+ x1: "12",
128
+ y1: "8",
129
+ x2: "12.01",
130
+ y2: "8"
131
+ })]
132
+ });
133
+
134
+ /**
135
+ * Loading Spinner SVG
136
+ */
137
+ const LoadingIcon = ({
138
+ size = 20,
139
+ className = '',
140
+ ...props
141
+ }) => /*#__PURE__*/jsx("svg", {
142
+ width: size,
143
+ height: size,
144
+ viewBox: "0 0 24 24",
145
+ fill: "none",
146
+ stroke: "currentColor",
147
+ strokeWidth: "2.5",
148
+ strokeLinecap: "round",
149
+ strokeLinejoin: "round",
150
+ className: `easy-toast-icon easy-toast-icon--loading ${className}`,
151
+ "aria-hidden": "true",
152
+ ...props,
153
+ children: /*#__PURE__*/jsx("path", {
154
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
155
+ })
156
+ });
157
+
158
+ /**
159
+ * Close (Dismiss) Button Icon SVG
160
+ */
161
+ const CloseIcon = ({
162
+ size = 16,
163
+ className = '',
164
+ ...props
165
+ }) => /*#__PURE__*/jsxs("svg", {
166
+ width: size,
167
+ height: size,
168
+ viewBox: "0 0 24 24",
169
+ fill: "none",
170
+ stroke: "currentColor",
171
+ strokeWidth: "2",
172
+ strokeLinecap: "round",
173
+ strokeLinejoin: "round",
174
+ className: `easy-toast-close-icon ${className}`,
175
+ "aria-hidden": "true",
176
+ ...props,
177
+ children: [/*#__PURE__*/jsx("line", {
178
+ x1: "18",
179
+ y1: "6",
180
+ x2: "6",
181
+ y2: "18"
182
+ }), /*#__PURE__*/jsx("line", {
183
+ x1: "6",
184
+ y1: "6",
185
+ x2: "18",
186
+ y2: "18"
187
+ })]
188
+ });
189
+
190
+ const ToastItem = ({
191
+ id,
192
+ type = 'default',
193
+ title,
194
+ message,
195
+ duration = 3000,
196
+ showProgressBar = true,
197
+ pauseOnHover = true,
198
+ closeButton = true,
199
+ icon,
200
+ action,
201
+ onClose,
202
+ onAccept,
203
+ onCancel,
204
+ acceptLabel,
205
+ cancelLabel = 'Dismiss',
206
+ swipeable = true,
207
+ swipeThreshold = 70,
208
+ theme = 'light',
209
+ className = '',
210
+ style = {},
211
+ onDismiss
212
+ }) => {
213
+ const [isExiting, setIsExiting] = useState(false);
214
+ const [exitDirection, setExitDirection] = useState(null); // 'left' | 'right' | null
215
+ const [isPaused, setIsPaused] = useState(false);
216
+
217
+ // Swipe state
218
+ const [isDragging, setIsDragging] = useState(false);
219
+ const [dragOffset, setDragOffset] = useState(0);
220
+ const startXRef = useRef(0);
221
+ const isPointerDownRef = useRef(false);
222
+
223
+ // Ref to track remaining duration when paused
224
+ const remainingTimeRef = useRef(duration);
225
+ const startTimeRef = useRef(null);
226
+ const timerRef = useRef(null);
227
+
228
+ // Check if this toast supports an "accept" action
229
+ const hasAcceptAction = Boolean(onAccept || action && action.onClick);
230
+ const resolvedAcceptLabel = acceptLabel || action && action.label || 'Accept';
231
+
232
+ // Trigger graceful exit animation before removal
233
+ const handleDismiss = useCallback((direction = null) => {
234
+ if (isExiting) return;
235
+ setIsExiting(true);
236
+ if (direction) {
237
+ setExitDirection(direction);
238
+ }
239
+ if (onClose) {
240
+ onClose(id);
241
+ }
242
+ // Match the CSS exit animation duration
243
+ setTimeout(() => {
244
+ onDismiss(id);
245
+ }, 280);
246
+ }, [id, isExiting, onClose, onDismiss]);
247
+
248
+ // Trigger accept action
249
+ const handleAccept = useCallback(() => {
250
+ if (isExiting) return;
251
+ if (onAccept) {
252
+ onAccept(id);
253
+ } else if (action && action.onClick) {
254
+ action.onClick(id);
255
+ }
256
+ handleDismiss('right');
257
+ }, [action, handleDismiss, id, isExiting, onAccept]);
258
+
259
+ // Handle auto-dismiss timer
260
+ useEffect(() => {
261
+ if (duration === false || duration === Infinity || duration <= 0) {
262
+ return;
263
+ }
264
+ const startTimer = time => {
265
+ startTimeRef.current = Date.now();
266
+ timerRef.current = setTimeout(() => {
267
+ handleDismiss();
268
+ }, time);
269
+ };
270
+ if (!isPaused && !isDragging) {
271
+ startTimer(remainingTimeRef.current);
272
+ }
273
+ return () => {
274
+ if (timerRef.current) {
275
+ clearTimeout(timerRef.current);
276
+ }
277
+ };
278
+ }, [duration, isPaused, isDragging, handleDismiss]);
279
+
280
+ // Pause on mouse enter
281
+ const handleMouseEnter = () => {
282
+ if (!pauseOnHover || duration === false || duration === Infinity || duration <= 0) {
283
+ return;
284
+ }
285
+ if (timerRef.current) {
286
+ clearTimeout(timerRef.current);
287
+ const elapsed = Date.now() - startTimeRef.current;
288
+ remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsed);
289
+ }
290
+ setIsPaused(true);
291
+ };
292
+
293
+ // Resume on mouse leave
294
+ const handleMouseLeave = () => {
295
+ if (!pauseOnHover || duration === false || duration === Infinity || duration <= 0 || isDragging) {
296
+ return;
297
+ }
298
+ setIsPaused(false);
299
+ };
300
+
301
+ // --- Pointer Swipe Handlers ---
302
+ const handlePointerDown = e => {
303
+ if (!swipeable || isExiting) return;
304
+ // Don't drag if clicking buttons or interactive elements
305
+ if (e.target.closest('button') || e.target.closest('a')) return;
306
+ if (e.button !== undefined && e.button !== 0) return; // Only primary mouse button
307
+
308
+ isPointerDownRef.current = true;
309
+ startXRef.current = e.clientX;
310
+ setIsDragging(true);
311
+
312
+ // Pause timer while swiping
313
+ if (timerRef.current) {
314
+ clearTimeout(timerRef.current);
315
+ const elapsed = Date.now() - (startTimeRef.current || Date.now());
316
+ remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsed);
317
+ }
318
+ try {
319
+ e.currentTarget.setPointerCapture(e.pointerId);
320
+ } catch (err) {
321
+ // Ignore if setPointerCapture not supported
322
+ }
323
+ };
324
+ const handlePointerMove = e => {
325
+ if (!isPointerDownRef.current || !isDragging) return;
326
+ const currentX = e.clientX;
327
+ const diff = currentX - startXRef.current;
328
+ setDragOffset(diff);
329
+ };
330
+ const handlePointerUp = e => {
331
+ if (!isPointerDownRef.current) return;
332
+ isPointerDownRef.current = false;
333
+ setIsDragging(false);
334
+ try {
335
+ e.currentTarget.releasePointerCapture(e.pointerId);
336
+ } catch (err) {
337
+ // Ignore
338
+ }
339
+
340
+ // Check if swipe distance exceeded threshold
341
+ if (dragOffset >= swipeThreshold) {
342
+ // Swiped Right
343
+ if (hasAcceptAction) {
344
+ handleAccept();
345
+ } else {
346
+ if (onCancel) onCancel(id);
347
+ handleDismiss('right');
348
+ }
349
+ } else if (dragOffset <= -swipeThreshold) {
350
+ // Swiped Left
351
+ if (onCancel) onCancel(id);
352
+ handleDismiss('left');
353
+ } else {
354
+ // Reset back to center
355
+ setDragOffset(0);
356
+ setIsPaused(false);
357
+ }
358
+ };
359
+ const handlePointerCancel = () => {
360
+ isPointerDownRef.current = false;
361
+ setIsDragging(false);
362
+ setDragOffset(0);
363
+ setIsPaused(false);
364
+ };
365
+
366
+ // Resolve appropriate icon
367
+ const renderIcon = () => {
368
+ if (icon === false) return null;
369
+ if (icon && /*#__PURE__*/React.isValidElement(icon)) {
370
+ return /*#__PURE__*/jsx("span", {
371
+ className: "easy-toast-custom-icon",
372
+ children: icon
373
+ });
374
+ }
375
+ switch (type) {
376
+ case 'success':
377
+ return /*#__PURE__*/jsx(SuccessIcon, {});
378
+ case 'error':
379
+ return /*#__PURE__*/jsx(ErrorIcon, {});
380
+ case 'warning':
381
+ return /*#__PURE__*/jsx(WarningIcon, {});
382
+ case 'info':
383
+ return /*#__PURE__*/jsx(InfoIcon, {});
384
+ case 'loading':
385
+ return /*#__PURE__*/jsx(LoadingIcon, {});
386
+ default:
387
+ return null;
388
+ }
389
+ };
390
+ const hasIcon = icon !== false && (icon || type !== 'default');
391
+ const hasProgressBar = showProgressBar && duration && duration !== Infinity && duration > 0;
392
+
393
+ // Compute inline drag transform
394
+ const getDragStyle = () => {
395
+ if (isDragging) {
396
+ return {
397
+ transform: `translateX(${dragOffset}px)`,
398
+ transition: 'none',
399
+ opacity: Math.max(0.45, 1 - Math.abs(dragOffset) / 280)
400
+ };
401
+ }
402
+ if (exitDirection === 'left') {
403
+ return {
404
+ transform: 'translateX(-115%) scale(0.9)',
405
+ opacity: 0,
406
+ transition: 'transform 0.28s cubic-bezier(0.4, 0, 1, 1), opacity 0.28s ease'
407
+ };
408
+ }
409
+ if (exitDirection === 'right') {
410
+ return {
411
+ transform: 'translateX(115%) scale(0.9)',
412
+ opacity: 0,
413
+ transition: 'transform 0.28s cubic-bezier(0.4, 0, 1, 1), opacity 0.28s ease'
414
+ };
415
+ }
416
+ return {
417
+ transition: 'transform 0.24s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.24s ease'
418
+ };
419
+ };
420
+
421
+ // Visual cues during swipe
422
+ const isSwipingAccept = dragOffset > 25 && hasAcceptAction;
423
+ const isSwipingDismissRight = dragOffset > 25 && !hasAcceptAction;
424
+ const isSwipingCancel = dragOffset < -25;
425
+ return /*#__PURE__*/jsxs("div", {
426
+ role: type === 'error' ? 'alert' : 'status',
427
+ "aria-live": type === 'error' ? 'assertive' : 'polite',
428
+ className: `easy-toast-item easy-toast--${type} easy-toast--theme-${theme} ${isExiting ? 'easy-toast--exit' : 'easy-toast--enter'} ${swipeable ? 'easy-toast--swipeable' : ''} ${isDragging ? 'easy-toast--dragging' : ''} ${isSwipingAccept ? 'easy-toast--swipe-accept' : ''} ${isSwipingCancel || isSwipingDismissRight ? 'easy-toast--swipe-cancel' : ''} ${className}`,
429
+ style: {
430
+ ...style,
431
+ ...getDragStyle()
432
+ },
433
+ onMouseEnter: handleMouseEnter,
434
+ onMouseLeave: handleMouseLeave,
435
+ onPointerDown: handlePointerDown,
436
+ onPointerMove: handlePointerMove,
437
+ onPointerUp: handlePointerUp,
438
+ onPointerCancel: handlePointerCancel,
439
+ children: [isSwipingAccept && /*#__PURE__*/jsxs("div", {
440
+ className: "easy-toast-swipe-badge easy-toast-swipe-badge--accept",
441
+ children: ["\u2713 ", resolvedAcceptLabel]
442
+ }), (isSwipingCancel || isSwipingDismissRight) && /*#__PURE__*/jsxs("div", {
443
+ className: "easy-toast-swipe-badge easy-toast-swipe-badge--cancel",
444
+ children: ["\u2715 ", cancelLabel]
445
+ }), /*#__PURE__*/jsxs("div", {
446
+ className: "easy-toast-content-wrapper",
447
+ children: [hasIcon && /*#__PURE__*/jsx("div", {
448
+ className: "easy-toast-icon-container",
449
+ children: renderIcon()
450
+ }), /*#__PURE__*/jsxs("div", {
451
+ className: "easy-toast-text-container",
452
+ children: [title && /*#__PURE__*/jsx("div", {
453
+ className: "easy-toast-title",
454
+ children: title
455
+ }), /*#__PURE__*/jsx("div", {
456
+ className: "easy-toast-message",
457
+ children: message
458
+ })]
459
+ }), action && /*#__PURE__*/jsx("div", {
460
+ className: "easy-toast-action-container",
461
+ children: /*#__PURE__*/jsx("button", {
462
+ type: "button",
463
+ className: "easy-toast-action-button",
464
+ onClick: () => {
465
+ action.onClick?.(id);
466
+ if (action.dismissOnClick !== false) {
467
+ handleDismiss('right');
468
+ }
469
+ },
470
+ children: action.label
471
+ })
472
+ }), closeButton && /*#__PURE__*/jsx("button", {
473
+ type: "button",
474
+ className: "easy-toast-close-button",
475
+ "aria-label": "Close notification",
476
+ onClick: () => handleDismiss(),
477
+ children: /*#__PURE__*/jsx(CloseIcon, {})
478
+ })]
479
+ }), hasProgressBar && /*#__PURE__*/jsx("div", {
480
+ className: "easy-toast-progress-bar-track",
481
+ children: /*#__PURE__*/jsx("div", {
482
+ className: "easy-toast-progress-bar",
483
+ style: {
484
+ animationDuration: `${duration}ms`,
485
+ animationPlayState: isPaused || isDragging ? 'paused' : 'running'
486
+ }
487
+ })
488
+ })]
489
+ });
490
+ };
491
+
492
+ const VALID_POSITIONS = ['top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'];
493
+
494
+ /**
495
+ * ToastContainer Component
496
+ * Renders toast groups in their respective screen positions via a React Portal.
497
+ */
498
+ const ToastContainer = ({
499
+ toasts = [],
500
+ onDismiss,
501
+ position: defaultPosition = 'top-right',
502
+ theme: defaultTheme = 'light',
503
+ newestOnTop = false,
504
+ pauseOnHover = true,
505
+ showProgressBar = true,
506
+ swipeable = true,
507
+ swipeThreshold = 70,
508
+ limit,
509
+ className = '',
510
+ style = {}
511
+ }) => {
512
+ // Guard for SSR (Next.js, Gatsby, Remix)
513
+ const isMounted = typeof window !== 'undefined' && typeof document !== 'undefined';
514
+ if (!isMounted) {
515
+ return null;
516
+ }
517
+
518
+ // Filter toasts if limit is set
519
+ const visibleToasts = limit && limit > 0 ? toasts.slice(-limit) : toasts;
520
+
521
+ // Group toasts by position
522
+ const groupedToasts = visibleToasts.reduce((acc, toast) => {
523
+ const pos = toast.position || defaultPosition;
524
+ const validPos = VALID_POSITIONS.includes(pos) ? pos : 'top-right';
525
+ if (!acc[validPos]) {
526
+ acc[validPos] = [];
527
+ }
528
+ acc[validPos].push(toast);
529
+ return acc;
530
+ }, {});
531
+ const portalContent = /*#__PURE__*/jsx("div", {
532
+ className: `easy-toast-portal ${className}`,
533
+ style: style,
534
+ children: Object.entries(groupedToasts).map(([pos, items]) => {
535
+ const sortedItems = newestOnTop ? [...items].reverse() : items;
536
+ return /*#__PURE__*/jsx("div", {
537
+ className: `easy-toast-container easy-toast-container--${pos}`,
538
+ "aria-live": "polite",
539
+ children: sortedItems.map(toast => /*#__PURE__*/jsx(ToastItem, {
540
+ ...toast,
541
+ pauseOnHover: toast.pauseOnHover ?? pauseOnHover,
542
+ showProgressBar: toast.showProgressBar ?? showProgressBar,
543
+ swipeable: toast.swipeable ?? swipeable,
544
+ swipeThreshold: toast.swipeThreshold ?? swipeThreshold,
545
+ theme: toast.theme || defaultTheme,
546
+ onDismiss: onDismiss
547
+ }, toast.id))
548
+ }, pos);
549
+ })
550
+ });
551
+ return /*#__PURE__*/createPortal(portalContent, document.body);
552
+ };
553
+
554
+ const ToastContext = /*#__PURE__*/createContext(null);
555
+ let toastCount = 0;
556
+ const generateId = () => {
557
+ toastCount += 1;
558
+ return `easy-toast-${Date.now()}-${toastCount}`;
559
+ };
560
+
561
+ /**
562
+ * ToastProvider Component
563
+ * Wraps your application to provide toast functionality anywhere in the component tree.
564
+ */
565
+ const ToastProvider = ({
566
+ children,
567
+ position = 'top-right',
568
+ autoClose = 3000,
569
+ pauseOnHover = true,
570
+ showProgressBar = true,
571
+ swipeable = true,
572
+ swipeThreshold = 70,
573
+ theme = 'light',
574
+ limit,
575
+ newestOnTop = false,
576
+ containerClassName = '',
577
+ containerStyle = {}
578
+ }) => {
579
+ const [toasts, setToasts] = useState([]);
580
+ const toastsRef = useRef(toasts);
581
+ toastsRef.current = toasts;
582
+
583
+ // Add a new toast
584
+ const addToast = useCallback((messageOrOptions, maybeOptions = {}) => {
585
+ let options = {};
586
+ if (typeof messageOrOptions === 'object' && ! /*#__PURE__*/React.isValidElement(messageOrOptions)) {
587
+ options = {
588
+ ...messageOrOptions
589
+ };
590
+ } else {
591
+ options = {
592
+ ...maybeOptions,
593
+ message: messageOrOptions
594
+ };
595
+ }
596
+ const id = options.id || generateId();
597
+ const newToast = {
598
+ id,
599
+ type: options.type || 'default',
600
+ message: options.message,
601
+ title: options.title,
602
+ duration: options.duration !== undefined ? options.duration : autoClose,
603
+ showProgressBar: options.showProgressBar !== undefined ? options.showProgressBar : showProgressBar,
604
+ pauseOnHover: options.pauseOnHover !== undefined ? options.pauseOnHover : pauseOnHover,
605
+ swipeable: options.swipeable !== undefined ? options.swipeable : swipeable,
606
+ swipeThreshold: options.swipeThreshold !== undefined ? options.swipeThreshold : swipeThreshold,
607
+ closeButton: options.closeButton !== undefined ? options.closeButton : true,
608
+ icon: options.icon,
609
+ action: options.action,
610
+ onClose: options.onClose,
611
+ onAccept: options.onAccept,
612
+ onCancel: options.onCancel,
613
+ acceptLabel: options.acceptLabel,
614
+ cancelLabel: options.cancelLabel,
615
+ position: options.position || position,
616
+ theme: options.theme || theme,
617
+ className: options.className || '',
618
+ style: options.style || {}
619
+ };
620
+ setToasts(prev => {
621
+ // If updating an existing toast with same id
622
+ const existingIndex = prev.findIndex(t => t.id === id);
623
+ if (existingIndex > -1) {
624
+ const updated = [...prev];
625
+ updated[existingIndex] = {
626
+ ...updated[existingIndex],
627
+ ...newToast
628
+ };
629
+ return updated;
630
+ }
631
+
632
+ // Apply limit if specified
633
+ if (limit && limit > 0 && prev.length >= limit) {
634
+ return [...prev.slice(prev.length - limit + 1), newToast];
635
+ }
636
+ return [...prev, newToast];
637
+ });
638
+ return id;
639
+ }, [autoClose, showProgressBar, pauseOnHover, position, theme, limit]);
640
+
641
+ // Update an existing toast (e.g. for promises)
642
+ const updateToast = useCallback((id, updatedOptions) => {
643
+ setToasts(prev => prev.map(t => {
644
+ if (t.id === id) {
645
+ return {
646
+ ...t,
647
+ ...updatedOptions,
648
+ id // preserve id
649
+ };
650
+ }
651
+ return t;
652
+ }));
653
+ }, []);
654
+
655
+ // Dismiss a specific toast
656
+ const dismissToast = useCallback(id => {
657
+ setToasts(prev => prev.filter(t => t.id !== id));
658
+ }, []);
659
+
660
+ // Dismiss all toasts
661
+ const dismissAll = useCallback(() => {
662
+ setToasts([]);
663
+ }, []);
664
+
665
+ // Context value with core functions
666
+ const contextValue = useMemo(() => ({
667
+ addToast,
668
+ updateToast,
669
+ dismissToast,
670
+ dismissAll,
671
+ toasts
672
+ }), [addToast, updateToast, dismissToast, dismissAll, toasts]);
673
+ return /*#__PURE__*/jsxs(ToastContext.Provider, {
674
+ value: contextValue,
675
+ children: [children, /*#__PURE__*/jsx(ToastContainer, {
676
+ toasts: toasts,
677
+ onDismiss: dismissToast,
678
+ position: position,
679
+ theme: theme,
680
+ limit: limit,
681
+ newestOnTop: newestOnTop,
682
+ pauseOnHover: pauseOnHover,
683
+ showProgressBar: showProgressBar,
684
+ swipeable: swipeable,
685
+ swipeThreshold: swipeThreshold,
686
+ className: containerClassName,
687
+ style: containerStyle
688
+ })]
689
+ });
690
+ };
691
+
692
+ /**
693
+ * Custom Hook: useToast
694
+ * Exposes an intuitive API to trigger and manage toast notifications.
695
+ *
696
+ * Example:
697
+ * const toast = useToast();
698
+ * toast.success('Profile updated!');
699
+ * toast.error('Something went wrong');
700
+ */
701
+ const useToast = () => {
702
+ const context = useContext(ToastContext);
703
+ if (!context) {
704
+ throw new Error('useToast must be used within a <ToastProvider>. Please wrap your root component in <ToastProvider>.');
705
+ }
706
+ const {
707
+ addToast,
708
+ updateToast,
709
+ dismissToast,
710
+ dismissAll
711
+ } = context;
712
+
713
+ // Base callable toast function
714
+ const toast = useCallback((message, options) => {
715
+ return addToast(message, options);
716
+ }, [addToast]);
717
+
718
+ // Helper: toast.success
719
+ toast.success = useCallback((message, options = {}) => {
720
+ return addToast(message, {
721
+ ...options,
722
+ type: 'success'
723
+ });
724
+ }, [addToast]);
725
+
726
+ // Helper: toast.error
727
+ toast.error = useCallback((message, options = {}) => {
728
+ return addToast(message, {
729
+ ...options,
730
+ type: 'error'
731
+ });
732
+ }, [addToast]);
733
+
734
+ // Helper: toast.warning
735
+ toast.warning = useCallback((message, options = {}) => {
736
+ return addToast(message, {
737
+ ...options,
738
+ type: 'warning'
739
+ });
740
+ }, [addToast]);
741
+
742
+ // Helper: toast.info
743
+ toast.info = useCallback((message, options = {}) => {
744
+ return addToast(message, {
745
+ ...options,
746
+ type: 'info'
747
+ });
748
+ }, [addToast]);
749
+
750
+ // Helper: toast.loading
751
+ toast.loading = useCallback((message, options = {}) => {
752
+ return addToast(message, {
753
+ ...options,
754
+ type: 'loading',
755
+ duration: false,
756
+ // does not auto-dismiss
757
+ showProgressBar: false
758
+ });
759
+ }, [addToast]);
760
+
761
+ // Helper: toast.promise
762
+ toast.promise = useCallback((promise, messages, options = {}) => {
763
+ const id = toast.loading(messages.loading || 'Loading...', options);
764
+ const p = promise instanceof Promise ? promise : Promise.resolve(promise);
765
+ p.then(result => {
766
+ const successMsg = typeof messages.success === 'function' ? messages.success(result) : messages.success || 'Success!';
767
+ updateToast(id, {
768
+ type: 'success',
769
+ message: successMsg,
770
+ duration: options.duration !== undefined ? options.duration : 3000,
771
+ showProgressBar: options.showProgressBar !== undefined ? options.showProgressBar : true,
772
+ ...options.successOptions
773
+ });
774
+ }).catch(err => {
775
+ const errorMsg = typeof messages.error === 'function' ? messages.error(err) : messages.error || 'Something went wrong';
776
+ updateToast(id, {
777
+ type: 'error',
778
+ message: errorMsg,
779
+ duration: options.duration !== undefined ? options.duration : 4000,
780
+ showProgressBar: options.showProgressBar !== undefined ? options.showProgressBar : true,
781
+ ...options.errorOptions
782
+ });
783
+ });
784
+ return p;
785
+ }, [toast, updateToast]);
786
+
787
+ // Programmatic dismiss
788
+ toast.dismiss = dismissToast;
789
+ toast.dismissAll = dismissAll;
790
+ toast.update = updateToast;
791
+ return toast;
792
+ };
793
+
794
+ export { CloseIcon, ErrorIcon, InfoIcon, LoadingIcon, SuccessIcon, ToastContainer, ToastContext, ToastItem, ToastProvider, WarningIcon, useToast };
795
+ //# sourceMappingURL=index.esm.js.map