react-toast-master 2.1.1 → 2.1.3

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,667 @@
1
+ import { CheckCheck, CircleDashed, Info, PinIcon, X, XCircle } from "lucide-react";
2
+ import React, { createContext, useContext, useEffect, useRef, useState } from "react";
3
+
4
+ import "./components/styles/background.css";
5
+ import "./components/styles/border.css";
6
+ import "./components/styles/button.css";
7
+ import "./components/styles/shadows.css";
8
+ import "./components/styles/textAlign.css";
9
+ import "./components/styles/textColor.css";
10
+ import "./components/styles/toastPosition.css";
11
+ import "./components/styles/animation.css";
12
+ import "./components/styles/breakPoints.css";
13
+ import "./components/styles/skew.css";
14
+
15
+ import Footer from "./components/footer/Footer";
16
+ import InnerFooter from "./components/footer/InnerFooter";
17
+ import LoadFooter from "./components/footer/LoadFooter";
18
+ import { getAnimation } from "./components/functions/animation";
19
+
20
+ const ToastContext = createContext();
21
+
22
+ export const useToast = () => useContext(ToastContext);
23
+
24
+ export const ToastProvider = ({ children }) => {
25
+ const [toastType, setToastType] = useState("");
26
+ const [toastMessage, setToastMessage] = useState("");
27
+ const [confirmResolve, setConfirmResolve] = useState(null);
28
+ const [toastTimeout, setToastTimeout] = useState(null);
29
+ const [showCloseButton, setShowCloseButton] = useState(true);
30
+ const [showButton, setShowButton] = useState(null);
31
+
32
+ const [toastBackground, setToastBackground] = useState("");
33
+ const [toastPosition, setToastPosition] = useState("");
34
+ const [toastSkew, setToastSkew] = useState("");
35
+ const [toastAnimation, setToastAnimation] = useState("");
36
+ const [toastShadow, setToastShadow] = useState("");
37
+ const [toastRadius, setToastRadius] = useState("");
38
+ const [toastAlignment, setToastAlignment] = useState("");
39
+ const [showOverlay, setShowOverlay] = useState(false);
40
+
41
+ const [footer, setFooter] = useState(null);
42
+ const [showLoadFooter, setShowLoadFooter] = useState(false);
43
+ const [loadFooter, setLoadFooter] = useState(null);
44
+
45
+ const confirmButtonRef = useRef(null);
46
+
47
+ const toastBackgroundClasses = {
48
+ success: "bg_success",
49
+ error: "bg_error",
50
+ warning: "bg_warning",
51
+ info: "bg_info",
52
+ glass: "bg_glass",
53
+ white: "bg_white",
54
+ dark: "bg_dark",
55
+ gray: "bg_gray",
56
+ transparent: "bg_transparent",
57
+ };
58
+
59
+ const toastPositionClasses = {
60
+ top: "top",
61
+ topFull: "topFull",
62
+ topLeft: "topLeft",
63
+ topRight: "topRight",
64
+
65
+ bottom: "bottom",
66
+ bottomFull: "bottomFull",
67
+ bottomLeft: "bottomLeft",
68
+ bottomRight: "bottomRight",
69
+
70
+ center: "middle",
71
+ };
72
+ const toastRevealAnimation = {
73
+ down: "ani_down",
74
+ top: "ani_top",
75
+ left: "ani_left",
76
+ right: "ani_right",
77
+ fade: "ani_fade",
78
+ zoom: "ani_zoom",
79
+ jelly: "ani_jelly",
80
+ };
81
+
82
+ const toastAwesomeAlignment = {
83
+ left: "text_start",
84
+ right: "text_end",
85
+ center: "text_center",
86
+ };
87
+ const toastAwesomeSkew = {
88
+ right3: "three",
89
+ right6: "six",
90
+ right12: "twelve",
91
+ };
92
+ const toastAwesomeShadow = {
93
+ none: "",
94
+ gray: "box_shadow",
95
+ block: "box_shadow_block",
96
+ error: "shadow_error",
97
+ white: "shadow_white",
98
+ dark: "shadow_dark",
99
+ success: "shadow_success",
100
+ info: "shadow_info",
101
+ warning: "shadow_warning",
102
+ around: "shadow_around",
103
+ };
104
+ const toastAwesomeRadius = {
105
+ none: "rounded_none",
106
+ sm: "rounded_sm",
107
+ md: "rounded_md",
108
+ lg: "rounded_lg",
109
+ xl: "rounded_xl",
110
+ twoXl: "rounded_2xl",
111
+ full: "rounded_full",
112
+ };
113
+
114
+ /**
115
+ * Find the position class key from the toastPositionClasses object
116
+ * that matches the toastPosition prop value.~
117
+ * @param {Object} toastPositionClasses - the object containing all the
118
+ * position classes as keys and their corresponding values
119
+ * @param {string} toastPosition - the position to be matched
120
+ * @returns {string} the key of the matching position class
121
+ */
122
+ const toastPG = Object.keys(toastPositionClasses).find(
123
+ (key) => toastPositionClasses[key] === toastPosition
124
+ );
125
+
126
+ /**
127
+ * Find the background class key from the toastBackgroundClasses object
128
+ * that matches the toastBackground prop value.
129
+ * @param {Object} toastBackgroundClasses - the object containing all the
130
+ * background classes as keys and their corresponding values
131
+ * @param {string} toastBackground - the background color to be matched
132
+ * @returns {string} the key of the matching background class
133
+ */
134
+ const toastBG = Object.keys(toastBackgroundClasses).find(
135
+ (key) => toastBackgroundClasses[key] === toastBackground
136
+ );
137
+
138
+ const animation = getAnimation(toastAnimation, toastPG);
139
+
140
+ const hideToast = () => {
141
+ setToastAnimation(animation);
142
+
143
+ const timeoutId = setTimeout(() => {
144
+ setToastType("");
145
+ setToastMessage("");
146
+ clearTimeout(toastTimeout);
147
+ setToastAnimation("");
148
+ }, 300);
149
+
150
+ return () => clearTimeout(timeoutId);
151
+ };
152
+
153
+ /**
154
+ * Displays a toast message.
155
+ *
156
+ * @param {Object} toast The toast object with the following properties:
157
+ * @param {string} type The type of the toast (e.g. success, error, etc.)
158
+ * @param {string} message The message to be displayed in the toast
159
+ * @param {string} bg The background color of the toast
160
+ * @param {string} position The position of the toast
161
+ * @param {string} transition The transition animation of the toast
162
+ * @param {*} loadFooter The footer will display after 7 seconds
163
+ * @param {*} footer The footer of the toast when it's not a "loading" and "confirm" toast
164
+ * @param {string} skew The skew of the toast
165
+ * @param {boolean} cancelButton Whether or not to display a cancel button
166
+ * @param {string} shadow The shadow of the toast
167
+ * @param {string} radius The border radius of the toast
168
+ * @param {string} align The alignment of the toasts text
169
+ * @returns {Promise} A promise that resolves when the toast is confirmed/rejected
170
+ */
171
+ const toastMaster = (toast) => {
172
+ if (toastTimeout) {
173
+ clearTimeout(toastTimeout);
174
+ }
175
+ return new Promise((resolve) => {
176
+ setToastAnimation(animation);
177
+
178
+ const openToast = () => {
179
+ openNewToast(toast, resolve);
180
+ };
181
+
182
+ if (toastType) {
183
+ setTimeout(openToast, 300);
184
+ } else {
185
+ openToast();
186
+ }
187
+ });
188
+ };
189
+
190
+ const openNewToast = (toast, resolve) => {
191
+ const {
192
+ type = "success",
193
+ message = "",
194
+ bg = "white",
195
+ position = "top",
196
+ transition = "zoom",
197
+ loadFooter = null,
198
+ footer = null,
199
+ skew = "",
200
+ cancelButton = false,
201
+ shadow = "gray",
202
+ radius = "lg",
203
+ align = "center",
204
+ } = toast;
205
+
206
+ setToastType(type);
207
+ setToastMessage(message);
208
+ setLoadFooter(loadFooter);
209
+ setFooter(footer);
210
+ setShowButton(cancelButton);
211
+
212
+ const toastBackground = toastBackgroundClasses[bg] || "";
213
+ const toastPosition = toastPositionClasses[position] || "";
214
+ const revealAnimation = toastRevealAnimation[transition] || "";
215
+ const toastSkew = toastAwesomeSkew[skew] || "";
216
+ const toastShadow = toastAwesomeShadow[shadow] || "";
217
+ const toastRadius = toastAwesomeRadius[radius] || "";
218
+ const toastAlignment = toastAwesomeAlignment[align] || "";
219
+
220
+ setToastBackground(toastBackground);
221
+ setToastPosition(toastPosition);
222
+ setToastAnimation(revealAnimation);
223
+ setToastSkew(toastSkew);
224
+ setToastShadow(toastShadow);
225
+ setToastRadius(toastRadius);
226
+ setToastAlignment(toastAlignment);
227
+
228
+ if (
229
+ type === "success" ||
230
+ type === "successWhite" ||
231
+ type === "successDark" ||
232
+ type === "error" ||
233
+ type === "errorWhite" ||
234
+ type === "errorDark" ||
235
+ type === "warning" ||
236
+ type === "warningWhite" ||
237
+ type === "warningDark" ||
238
+ type === "info" ||
239
+ type === "infoWhite" ||
240
+ type === "infoDark" ||
241
+ type === "basic" ||
242
+ type === "basicDark"
243
+ ) {
244
+ setToastTimeout(setTimeout(() => hideToast(), 4500));
245
+ }
246
+
247
+ if (type === "confirm" || type === "confirmDark") {
248
+ setConfirmResolve(() => resolve);
249
+ }
250
+
251
+ if (type === "loading" || type === "loadingWhite" || type === "loadingDark") {
252
+ setShowLoadFooter(false);
253
+ setTimeout(() => setShowLoadFooter(true), 7330);
254
+ }
255
+ if (type === "loading" || type === "loadingWhite" || type === "loadingDark") {
256
+ setToastType("");
257
+ setToastMessage("");
258
+ setLoadFooter("");
259
+ setFooter("");
260
+ setShowButton("");
261
+
262
+ setTimeout(() => {
263
+ setToastType(type);
264
+ setToastMessage(message);
265
+ setLoadFooter(loadFooter);
266
+ setFooter(footer);
267
+ setShowButton(cancelButton);
268
+ }, 330);
269
+
270
+ return;
271
+ }
272
+ };
273
+
274
+ /**
275
+ * Handles the show/hide of the close button of the toast.
276
+ *
277
+ * The close button is hidden when the loading toast initially appears,
278
+ * after 7 seconds the close button is shown.
279
+ */
280
+ useEffect(() => {
281
+ if (toastType === "loading" || toastType === "loadingWhite" || toastType === "loadingDark") {
282
+ setShowCloseButton(false);
283
+ setTimeout(() => setShowCloseButton(true), 7000);
284
+ } else {
285
+ setShowCloseButton(true);
286
+ }
287
+ }, [toastType]); // eslint-disable-line react-hooks/exhaustive-deps
288
+
289
+ useEffect(() => {
290
+ /**
291
+ * Handles the key down event and hides the toast if the escape key is pressed.
292
+ *
293
+ * @param {Object} event - the key down event object
294
+ */
295
+ const handleKeyDown = (event) => {
296
+ if (event.key === "Escape") {
297
+ hideToast();
298
+ }
299
+ };
300
+ document.addEventListener("keydown", handleKeyDown);
301
+ return () => {
302
+ document.removeEventListener("keydown", handleKeyDown);
303
+ };
304
+ }, []);
305
+
306
+ /**
307
+ * Handles the confirmation of "confirm" tost type.
308
+ *
309
+ * @returns {Function} a function to clear the timeout
310
+ */
311
+ const handleConfirm = () => {
312
+ setToastAnimation(animation);
313
+
314
+ const timeoutId = setTimeout(() => {
315
+ if (confirmResolve) {
316
+ confirmResolve(true);
317
+ }
318
+ setToastAnimation("");
319
+ hideToast();
320
+ setShowOverlay(false);
321
+ }, 330);
322
+
323
+ return () => clearTimeout(timeoutId);
324
+ };
325
+ /**
326
+ * Sets focus to the confirm button when toastType is "confirm" or "confirmDark"
327
+ */
328
+ useEffect(() => {
329
+ if (toastType === "confirm" || toastType === "confirmDark") {
330
+ confirmButtonRef.current.focus();
331
+ }
332
+ }, [toastType]);
333
+
334
+ /**
335
+ * Handles the cancellation of "confirm" and "confirmDark" tost types.
336
+ */
337
+ const handleCancel = () => {
338
+ if (confirmResolve) {
339
+ confirmResolve(false);
340
+ }
341
+ hideToast();
342
+ setShowOverlay(false);
343
+ };
344
+
345
+ const toastClasses = {
346
+ success: "text_success",
347
+ error: "text_error",
348
+ loading: "text_warning",
349
+ warning: "text_warning",
350
+ warningStay: "text_warning",
351
+ info: "text_info",
352
+ infoStay: "text_info",
353
+ confirm: "text_white",
354
+ basic: "text_white",
355
+
356
+ successWhite: "text_white",
357
+ errorWhite: "text_white",
358
+ loadingWhite: "text_white",
359
+ warningWhite: "text_white",
360
+ warningStayWhite: "text_white",
361
+ infoWhite: "text_white",
362
+ infoStayWhite: "text_white",
363
+
364
+ successDark: "text_dark",
365
+ errorDark: "text_dark",
366
+ loadingDark: "text_dark",
367
+ warningDark: "text_dark",
368
+ warningStayDark: "text_dark",
369
+ infoDark: "text_dark",
370
+ infoStayDark: "text_dark ",
371
+ confirmDark: "text_dark ",
372
+ basicDark: "text_dark ",
373
+ };
374
+
375
+ /**
376
+ * Create a proxy that will return the appropriate icon component based on
377
+ * the prop name passed to it.
378
+ * @returns {React.Component} Icon component
379
+ */
380
+ const iconComponent = new Proxy(
381
+ {},
382
+ {
383
+ /**
384
+ * Get the icon component based on the prop name.
385
+ * @param {Object} target - The target object
386
+ * @param {string} prop - The prop name
387
+ * @returns {React.Component} Icon component
388
+ */
389
+ get(target, prop) {
390
+ switch (prop) {
391
+ case "success":
392
+ case "successWhite":
393
+ case "successDark":
394
+ return <CheckCheck />;
395
+
396
+ case "error":
397
+ case "errorWhite":
398
+ case "errorDark":
399
+ return <XCircle />;
400
+
401
+ case "loading":
402
+ case "loadingWhite":
403
+ case "loadingDark":
404
+ return <CircleDashed className="animate_spin" />;
405
+
406
+ case "warning":
407
+ case "warningWhite":
408
+ case "warningDark":
409
+ case "warningStay":
410
+ case "warningStayWhite":
411
+ case "warningStayDark":
412
+ return <Info />;
413
+
414
+ case "info":
415
+ case "infoWhite":
416
+ case "infoDark":
417
+ case "infoStay":
418
+ case "infoStayWhite":
419
+ case "infoStayDark":
420
+ return <PinIcon className="rotate_deg" />;
421
+
422
+ default:
423
+ return undefined;
424
+ }
425
+ },
426
+ }
427
+ );
428
+
429
+ /**
430
+ * Set of toast types that are not auto-hidden
431
+ *
432
+ * These are toast types that do not hide automatically, instead they require
433
+ * manual dismissal, hence we clear the timeout when the user hovers over the
434
+ * toast and set it again when the user leaves the toast.
435
+ */
436
+ const nonAutoHideTypesSet = new Set([
437
+ "confirm",
438
+ "confirmWhite",
439
+ "confirmDark",
440
+ "infoStay",
441
+ "infoStayWhite",
442
+ "infoStayDark",
443
+ "loading",
444
+ "loadingWhite",
445
+ "loadingDark",
446
+ "warningStay",
447
+ "warningStayWhite",
448
+ "warningStayDark",
449
+ ]);
450
+
451
+ /**
452
+ * Handle the mouseenter event on the toast
453
+ *
454
+ * If the toast type is not in the nonAutoHideTypesSet, then we clear the
455
+ * toast timeout, so that the toast does not get hidden automatically.
456
+ */
457
+ const handleMouseEnter = () => {
458
+ if (!nonAutoHideTypesSet.has(toastType)) {
459
+ clearTimeout(toastTimeout);
460
+ }
461
+ };
462
+
463
+ /**
464
+ * Handle the mouseleave event on the toast
465
+ *
466
+ * If the toast type is not in the nonAutoHideTypesSet, then we set the
467
+ * toast timeout again, so that the toast gets hidden after the timeout
468
+ * duration.
469
+ */
470
+ const handleMouseLeave = () => {
471
+ if (!nonAutoHideTypesSet.has(toastType)) {
472
+ setToastTimeout(setTimeout(hideToast, 1500));
473
+ }
474
+ };
475
+
476
+ /**
477
+ * Sets the showOverlay state based on the type of the toast
478
+ *
479
+ * ShowOverlay is using class "overlay" to show a semi transparent overlay
480
+ * and blur on beneath of "confirm" and "confirmDark" toast
481
+ *
482
+ * If the toast type is either confirm or confirmDark, then set
483
+ * showOverlay to true, else set it to false
484
+ */
485
+ useEffect(() => {
486
+ setShowOverlay(toastType === "confirm" || toastType === "confirmDark");
487
+ }, [toastType]);
488
+
489
+ return (
490
+ <ToastContext.Provider value={{ toastMaster, hideToast }}>
491
+ <>
492
+ {children}
493
+ {toastType && (
494
+ <>
495
+ {(toastType === "confirm" || toastType === "confirmDark") && (
496
+ <div className={`overlay ${showOverlay ? "show" : ""}`} />
497
+ )}
498
+ <div
499
+ className={`outer_container ${toastPosition} ${toastAnimation}`}
500
+ style={{ zIndex: "9999" }}
501
+ >
502
+ <div
503
+ className={`inner_container ${
504
+ toastPG === "bottomFull" || toastPG === "topFull"
505
+ ? "toast_width_full"
506
+ : "max_width"
507
+ } ${
508
+ toastClasses[toastType]
509
+ } ${toastBackground} ${toastSkew} ${toastShadow} ${toastRadius}`}
510
+ onMouseEnter={handleMouseEnter}
511
+ onMouseLeave={handleMouseLeave}
512
+ >
513
+ <div
514
+ className={`${
515
+ toastType === "confirm" || toastType === "confirmDark"
516
+ ? "toast_width_confirm"
517
+ : "toast_width"
518
+ }`}
519
+ >
520
+ <div className={`toast_message ${toastAlignment}`}>
521
+ {toastType !== "confirm" &&
522
+ toastType !== "confirmDark" &&
523
+ toastType !== "basic" &&
524
+ toastType !== "basicDark" && (
525
+ <div>
526
+ <span className="sr_only">toast icon</span>
527
+ <span aria-hidden="true">{iconComponent[toastType]}</span>
528
+ </div>
529
+ )}
530
+ <>{toastMessage}</>
531
+
532
+ {/* close button below */}
533
+ <div
534
+ className={`closeDiv ${
535
+ showCloseButton &&
536
+ showButton &&
537
+ toastType !== "confirm" &&
538
+ toastType !== "confirmDark" &&
539
+ toastPG !== "bottomFull" &&
540
+ toastPG !== "topFull"
541
+ ? `div_flex`
542
+ : "div_hidden"
543
+ }`}
544
+ >
545
+ <div>
546
+ <button
547
+ onClick={hideToast}
548
+ id="close"
549
+ className={`closeButton
550
+ ${
551
+ toastBG === "white"
552
+ ? "bg_whiter"
553
+ : toastBG === "glass" || toastBG === "transparent"
554
+ ? "bg_glass_close"
555
+ : "bg_darker"
556
+ }`}
557
+ >
558
+ {/* This span is just for accessibility purposes so that screen
559
+ readers will know what the close button does. */}
560
+ <span className="sr_only">close toast</span>
561
+ {/* This is the close icon. */}
562
+ <X
563
+ aria-hidden="true"
564
+ size={18}
565
+ />
566
+ </button>
567
+ </div>
568
+ </div>
569
+ </div>
570
+
571
+ {["confirm", "confirmDark"].includes(toastType) && footer && (
572
+ <InnerFooter
573
+ footer={footer}
574
+ toastBG={toastBG}
575
+ toastAlignment={toastAlignment}
576
+ toastType={toastType}
577
+ />
578
+ )}
579
+
580
+ <>
581
+ {(toastType === "confirm" || toastType === "confirmDark") && (
582
+ <div
583
+ className={`confirm_div
584
+ ${
585
+ toastAlignment === "text_start"
586
+ ? "justify_end"
587
+ : toastAlignment === "text_end"
588
+ ? "justify_start"
589
+ : toastAlignment === "text_center"
590
+ ? "justify_center"
591
+ : null
592
+ }
593
+ `}
594
+ >
595
+ <button
596
+ className={`cancel_button ${
597
+ toastBG === "white"
598
+ ? "cancel_button_dark"
599
+ : toastBG === "success" ||
600
+ toastBG === "warning" ||
601
+ toastBG === "error" ||
602
+ toastBG === "info" ||
603
+ toastBG === "dark"
604
+ ? "cancel_button_all"
605
+ : "cancel_button_glass"
606
+ }`}
607
+ onClick={handleCancel}
608
+ >
609
+ <span className="sr_only">close toast</span>
610
+ Cancel
611
+ </button>
612
+
613
+ <button
614
+ ref={confirmButtonRef}
615
+ className={`confirm_button
616
+ ${toastBG === "dark" ? "confirm_button_dark" : "confirm_button_white"}`}
617
+ onClick={handleConfirm}
618
+ >
619
+ <span className="sr_only">confirm action</span>
620
+ Confirm
621
+ </button>
622
+ </div>
623
+ )}
624
+ </>
625
+ </div>
626
+
627
+ <>
628
+ {!(
629
+ toastType === "confirmDark" ||
630
+ toastType === "confirm" ||
631
+ toastPG === "bottomFull" ||
632
+ toastPG === "topFull"
633
+ ) && (
634
+ <div>
635
+ {footer && (
636
+ <Footer
637
+ footer={footer}
638
+ toastBG={toastBG}
639
+ toastAlignment={toastAlignment}
640
+ />
641
+ )}
642
+ </div>
643
+ )}
644
+
645
+ {!(
646
+ toastPG === "bottomFull" ||
647
+ toastPG === "topFull" ||
648
+ toastType === "confirmDark" ||
649
+ toastType === "confirm"
650
+ ) &&
651
+ showLoadFooter &&
652
+ loadFooter && (
653
+ <LoadFooter
654
+ toastAlignment={toastAlignment}
655
+ toastBG={toastBG}
656
+ loadFooter={loadFooter}
657
+ />
658
+ )}
659
+ </>
660
+ </div>
661
+ </div>
662
+ </>
663
+ )}
664
+ </>
665
+ </ToastContext.Provider>
666
+ );
667
+ };
@@ -0,0 +1,20 @@
1
+ import "../styles/textColor.css";
2
+ import "../styles/breakPoints.css";
3
+
4
+ const Footer = ({ footer, toastAlignment, toastBG }) => (
5
+ <div className={`_footer toast_width ${toastAlignment} ${toastBGClasses[toastBG]}`}>{footer}</div>
6
+ );
7
+
8
+ const toastBGClasses = {
9
+ dark: "footer_dark",
10
+ warning: "footer_dark",
11
+ white: "footer_white",
12
+ info: "footer_info",
13
+ error: "footer_error",
14
+ success: "footer_success",
15
+ gray: "footer_success",
16
+ glass: "footer_success",
17
+ transparent: "footer_success",
18
+ };
19
+
20
+ export default Footer;
@@ -0,0 +1,20 @@
1
+ import "../styles/textColor.css";
2
+ import "../styles/breakPoints.css";
3
+
4
+ const InnerFooter = ({ footer, toastAlignment, toastBG }) => (
5
+ <div className={`innerFooter ${toastAlignment} ${toastBGClasses[toastBG]}`}>{footer}</div>
6
+ );
7
+
8
+ const toastBGClasses = {
9
+ dark: "footer_dark",
10
+ warning: "footer_dark",
11
+ white: "footer_white",
12
+ info: "footer_info",
13
+ error: "footer_error",
14
+ success: "footer_success",
15
+ gray: "footer_success",
16
+ glass: "footer_success",
17
+ transparent: "footer_success",
18
+ };
19
+
20
+ export default InnerFooter;