@@ -2,6 +2,7 @@ import * as React from "react"
22
33const TOAST_LIMIT = 1
44const TOAST_REMOVE_DELAY = 1000000
5+ const DEFAULT_TOAST_DURATION = 4200
56
67const actionTypes = {
78 ADD_TOAST : "ADD_TOAST" ,
@@ -18,6 +19,34 @@ function genId() {
1819}
1920
2021const toastTimeouts = new Map ( )
22+ const autoDismissTimeouts = new Map ( )
23+
24+ const clearAutoDismissTimeout = ( toastId ) => {
25+ if ( ! autoDismissTimeouts . has ( toastId ) ) {
26+ return
27+ }
28+
29+ window . clearTimeout ( autoDismissTimeouts . get ( toastId ) )
30+ autoDismissTimeouts . delete ( toastId )
31+ }
32+
33+ const scheduleAutoDismiss = ( toastId , duration = DEFAULT_TOAST_DURATION ) => {
34+ clearAutoDismissTimeout ( toastId )
35+
36+ if ( ! Number . isFinite ( duration ) || duration <= 0 ) {
37+ return
38+ }
39+
40+ const timeout = window . setTimeout ( ( ) => {
41+ autoDismissTimeouts . delete ( toastId )
42+ dispatch ( {
43+ type : "DISMISS_TOAST" ,
44+ toastId,
45+ } )
46+ } , duration )
47+
48+ autoDismissTimeouts . set ( toastId , timeout )
49+ }
2150
2251const addToRemoveQueue = ( toastId ) => {
2352 if ( toastTimeouts . has ( toastId ) ) {
@@ -38,6 +67,15 @@ const addToRemoveQueue = (toastId) => {
3867export const reducer = ( state , action ) => {
3968 switch ( action . type ) {
4069 case "ADD_TOAST" :
70+ const existingToastIndex = state . toasts . findIndex ( ( t ) => t . id === action . toast . id )
71+ if ( existingToastIndex !== - 1 ) {
72+ const nextToasts = [ ...state . toasts ]
73+ nextToasts . splice ( existingToastIndex , 1 )
74+ return {
75+ ...state ,
76+ toasts : [ action . toast , ...nextToasts ] . slice ( 0 , TOAST_LIMIT ) ,
77+ } ;
78+ }
4179 return {
4280 ...state ,
4381 toasts : [ action . toast , ...state . toasts ] . slice ( 0 , TOAST_LIMIT ) ,
@@ -73,6 +111,11 @@ export const reducer = (state, action) => {
73111 } ;
74112 }
75113 case "REMOVE_TOAST" :
114+ if ( action . toastId !== undefined ) {
115+ clearAutoDismissTimeout ( action . toastId )
116+ } else {
117+ state . toasts . forEach ( ( toast ) => clearAutoDismissTimeout ( toast . id ) )
118+ }
76119 if ( action . toastId === undefined ) {
77120 return {
78121 ...state ,
@@ -100,26 +143,45 @@ function dispatch(action) {
100143function toast ( {
101144 ...props
102145} ) {
103- const id = genId ( )
146+ const id = props . id ?? genId ( )
147+ const duration = typeof props . duration === "number" ? props . duration : DEFAULT_TOAST_DURATION
148+ const buildToastPayload = ( nextProps = { } ) => ( {
149+ ...props ,
150+ ...nextProps ,
151+ id,
152+ duration : typeof nextProps . duration === "number" ? nextProps . duration : duration ,
153+ open : true ,
154+ onOpenChange : ( open ) => {
155+ if ( ! open ) dismiss ( )
156+ } ,
157+ } )
158+
159+ const update = ( nextProps ) => {
160+ const nextDuration =
161+ typeof nextProps ?. duration === "number"
162+ ? nextProps . duration
163+ : memoryState . toasts . find ( ( toast ) => toast . id === id ) ?. duration ?? duration
104164
105- const update = ( props ) =>
106165 dispatch ( {
107166 type : "UPDATE_TOAST" ,
108- toast : { ...props , id } ,
167+ toast : buildToastPayload ( { ...nextProps , duration : nextDuration } ) ,
109168 } )
110- const dismiss = ( ) => dispatch ( { type : "DISMISS_TOAST" , toastId : id } )
169+ scheduleAutoDismiss ( id , nextDuration )
170+ }
171+
172+ const dismiss = ( ) => {
173+ clearAutoDismissTimeout ( id )
174+ dispatch ( { type : "DISMISS_TOAST" , toastId : id } )
175+ }
176+
177+ const payload = buildToastPayload ( )
178+ const hasExistingToast = memoryState . toasts . some ( ( toast ) => toast . id === id )
111179
112180 dispatch ( {
113- type : "ADD_TOAST" ,
114- toast : {
115- ...props ,
116- id,
117- open : true ,
118- onOpenChange : ( open ) => {
119- if ( ! open ) dismiss ( )
120- } ,
121- } ,
181+ type : hasExistingToast ? "UPDATE_TOAST" : "ADD_TOAST" ,
182+ toast : payload ,
122183 } )
184+ scheduleAutoDismiss ( id , payload . duration )
123185
124186 return {
125187 id : id ,
0 commit comments