11import { ExclamationCircleIcon , XMarkIcon } from "@heroicons/react/20/solid" ;
22import { 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+
55import { useTypedLoaderData } from "remix-typedjson" ;
66import { loader } from "~/root" ;
77import { useEffect } from "react" ;
@@ -11,79 +11,55 @@ const permanentToastDuration = 60 * 60 * 24 * 1000;
1111
1212export function Toast ( ) {
1313 const { toastMessage } = useTypedLoaderData < typeof loader > ( ) ;
14-
1514 useEffect ( ( ) => {
1615 if ( ! toastMessage ) {
1716 return ;
1817 }
1918 const { message, type, options } = toastMessage ;
2019
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+ } ) ;
3523 } , [ toastMessage ] ) ;
3624
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+ } ) {
3739 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 ,
4744 } }
4845 >
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 >
8864 ) ;
8965}
0 commit comments