1
1
import { ExclamationCircleIcon , XMarkIcon } from "@heroicons/react/20/solid" ;
2
2
import { CheckCircleIcon } from "@heroicons/react/24/solid" ;
3
- import { AnimatePresence , motion } from "framer-motion " ;
4
- import toast , { Toaster , resolveValue , useToasterStore } from "react-hot-toast" ;
3
+ import { Toaster , toast } from "sonner " ;
4
+
5
5
import { useTypedLoaderData } from "remix-typedjson" ;
6
6
import { loader } from "~/root" ;
7
7
import { useEffect } from "react" ;
@@ -11,79 +11,55 @@ const permanentToastDuration = 60 * 60 * 24 * 1000;
11
11
12
12
export function Toast ( ) {
13
13
const { toastMessage } = useTypedLoaderData < typeof loader > ( ) ;
14
-
15
14
useEffect ( ( ) => {
16
15
if ( ! toastMessage ) {
17
16
return ;
18
17
}
19
18
const { message, type, options } = toastMessage ;
20
19
21
- switch ( type ) {
22
- case "success" :
23
- toast . success ( message , {
24
- duration : options . ephemeral ? defaultToastDuration : permanentToastDuration ,
25
- } ) ;
26
- break ;
27
- case "error" :
28
- toast . error ( message , {
29
- duration : options . ephemeral ? defaultToastDuration : permanentToastDuration ,
30
- } ) ;
31
- break ;
32
- default :
33
- throw new Error ( `${ type } is not handled` ) ;
34
- }
20
+ toast . custom ( ( t ) => < ToastUI variant = { type } message = { message } t = { t as string } /> , {
21
+ duration : options . ephemeral ? defaultToastDuration : permanentToastDuration ,
22
+ } ) ;
35
23
} , [ toastMessage ] ) ;
36
24
25
+ return < Toaster /> ;
26
+ }
27
+
28
+ export function ToastUI ( {
29
+ variant,
30
+ message,
31
+ t,
32
+ toastWidth = 356 , // Default width, matches what sonner provides by default
33
+ } : {
34
+ variant : "error" | "success" ;
35
+ message : string ;
36
+ t : string ;
37
+ toastWidth ?: string | number ;
38
+ } ) {
37
39
return (
38
- < Toaster
39
- position = "bottom-right"
40
- toastOptions = { {
41
- success : {
42
- icon : < CheckCircleIcon className = "h-6 w-6 text-green-600" /> ,
43
- } ,
44
- error : {
45
- icon : < ExclamationCircleIcon className = "h-6 w-6 text-rose-600" /> ,
46
- } ,
40
+ < div
41
+ className = { `self-end rounded-lg border border-slate-750 bg-midnight-900 shadow-md` }
42
+ style = { {
43
+ width : toastWidth ,
47
44
} }
48
45
>
49
- { ( t ) => (
50
- < AnimatePresence >
51
- < motion . div
52
- className = "flex gap-2 rounded-lg border border-slate-750 bg-no-repeat p-4 text-bright shadow-md"
53
- style = { {
54
- opacity : t . visible ? 1 : 0 ,
55
- background :
56
- "radial-gradient(at top, hsla(271, 91%, 65%, 0.18), hsla(221, 83%, 53%, 0.18)) hsla(221, 83%, 53%, 0.18)" ,
57
- } }
58
- initial = { { opacity : 0 , y : 100 } }
59
- animate = { t . visible ? "visible" : "hidden" }
60
- variants = { {
61
- hidden : {
62
- opacity : 0 ,
63
- y : 0 ,
64
- transition : {
65
- duration : 0.15 ,
66
- ease : "easeInOut" ,
67
- } ,
68
- } ,
69
- visible : {
70
- opacity : 1 ,
71
- y : 0 ,
72
- transition : {
73
- duration : 0.3 ,
74
- ease : "easeInOut" ,
75
- } ,
76
- } ,
77
- } }
78
- >
79
- { t . icon }
80
- { resolveValue ( t . message , t ) }
81
- < button className = "p-1" onClick = { ( ) => toast . dismiss ( t . id ) } >
82
- < XMarkIcon className = "h-4 w-4 text-bright" />
83
- </ button >
84
- </ motion . div >
85
- </ AnimatePresence >
86
- ) }
87
- </ Toaster >
46
+ < div
47
+ className = "flex w-full gap-2 rounded-lg bg-no-repeat p-4 text-bright"
48
+ style = { {
49
+ background :
50
+ "radial-gradient(at top, hsla(271, 91%, 65%, 0.18), hsla(221, 83%, 53%, 0.18)) hsla(221, 83%, 53%, 0.18)" ,
51
+ } }
52
+ >
53
+ { variant === "success" ? (
54
+ < CheckCircleIcon className = "h-6 w-6 text-green-600" />
55
+ ) : (
56
+ < ExclamationCircleIcon className = "h-6 w-6 text-rose-600" />
57
+ ) }
58
+ { message }
59
+ < button className = "ms-auto p-1" onClick = { ( ) => toast . dismiss ( t ) } >
60
+ < XMarkIcon className = "h-4 w-4 text-bright" />
61
+ </ button >
62
+ </ div >
63
+ </ div >
88
64
) ;
89
65
}
0 commit comments