import { useEffect , useState } from 'react' ; import { DefaultToastOptions , Toast , ToastType } from './types' ; const TOAST_LIMIT = 20 ; export enum ActionType { ADD_TOAST , UPDATE_TOAST , UPSERT_TOAST , DISMISS_TOAST , REMOVE_TOAST , START_PAUSE , END_PAUSE } type Action = { type : ActionType . ADD_TOAST ; toast : Toast ; } | { type : ActionType . UPSERT_TOAST ; toast : Toast ; } | { type : ActionType . UPDATE_TOAST ; toast : Partial < Toast > ; } | { type : ActionType . DISMISS_TOAST ; toastId ? : string ; } | { type : ActionType . REMOVE_TOAST ; toastId ? : string ; } | { type : ActionType . START_PAUSE ; time : number ; } | { type : ActionType . END_PAUSE ; time : number ; } ; interface State { toasts : Toast [ ] ; pausedAt : number | undefined ; } const toastTimeouts = new Map < Toast [ 'id' ] , ReturnType < typeof setTimeout > > ( ) ; const addToRemoveQueue = ( toastId ) => { if ( toastTimeouts . has ( toastId ) ) { return ; } const timeout = setTimeout ( ( ) => { toastTimeouts . delete ( toastId ) ; dispatch ( { type : ActionType . REMOVE_TOAST , toastId : toastId , } ) ; } , 1000 ) ; toastTimeouts . set ( toastId , timeout ) ; } ; const clearFromRemoveQueue = ( toastId ) => { const timeout = toastTimeouts . get ( toastId ) ; if ( timeout ) { clearTimeout ( timeout ) ; } } ; export const reducer = ( state , action ) : State => { switch ( action . type ) { case ActionType . ADD_TOAST : return { ... state , toasts : [ action . toast , ... state . toasts ] . slice ( 0 , TOAST_LIMIT ) , } ; case ActionType . UPDATE_TOAST : if ( action . toast . id ) { clearFromRemoveQueue ( action . toast . id ) ; } return { ... state , toasts : state . toasts . map ( ( t ) => t . id === action . toast . id ? { ... t , ... action . toast } : t ) , } ; case ActionType . UPSERT_TOAST : const { toast } = action ; return state . toasts . find ( ( t ) => t . id === toast . id ) ? reducer ( state , { type : ActionType . UPDATE_TOAST , toast } ) : reducer ( state , { type : ActionType . ADD_TOAST , toast } ) ; case ActionType . DISMISS_TOAST : const { toastId } = action ; if ( toastId ) { addToRemoveQueue ( toastId ) ; } else { state . toasts . forEach ( ( toast ) => { addToRemoveQueue ( toast . id ) ; } ) ; } return { ... state , toasts : state . toasts . map ( ( t ) => t . id === toastId || toastId === undefined ? { ... t , visible : false , } : t ) , } ; case ActionType . REMOVE_TOAST : if ( action . toastId === undefined ) { return { ... state , toasts : [ ] , } ; } return { ... state , toasts : state . toasts . filter ( ( t ) => t . id !== action . toastId ) , } ; case ActionType . START_PAUSE : return { ... state , pausedAt : action . time , } ; case ActionType . END_PAUSE : const diff = action . time - ( state . pausedAt || 0 ) ; return { ... state , pausedAt : undefined , toasts : state . toasts . map ( ( t ) => ( { ... t , pauseDuration : t . pauseDuration + diff , } ) ) , } ; } } ; const listeners = [ ] ; let memoryState = { toasts : [ ] , pausedAt : undefined } ; export const dispatch = ( action ) => { memoryState = reducer ( memoryState , action ) ; listeners . forEach ( ( listener ) => { listener ( memoryState ) ; } ) ; } ; const defaultTimeouts = { blank : 4000 , error : 4000 , success : 2000 , loading : Infinity , custom : 4000 , } ; export const useStore = ( toastOptions = { } ) : State => { const [ state , setState ] = useState < State > ( memoryState ) ; useEffect ( ( ) => { listeners . push ( setState ) ; return ( ) => { const index = listeners . indexOf ( setState ) ; if ( index > - 1 ) { listeners . splice ( index , 1 ) ; } } ; } , [ state ] ) ; const mergedToasts = state . toasts . map ( ( t ) => ( { ... toastOptions , ... toastOptions [ t . type ] , ... t , duration : t . duration || toastOptions [ t . type ] ?. duration || toastOptions ?. duration || defaultTimeouts [ t . type ] , style : { ... toastOptions . style , ... toastOptions [ t . type ] ?. style , ... t . style , } , } ) ) ; return { ... state , toasts : mergedToasts , } ; } ;