| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 'use client' | 
					
						
							|  |  |  | import type { ReactNode } from 'react' | 
					
						
							|  |  |  | import React, { useEffect, useState } from 'react' | 
					
						
							|  |  |  | import { createRoot } from 'react-dom/client' | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   CheckCircleIcon, | 
					
						
							|  |  |  |   ExclamationTriangleIcon, | 
					
						
							|  |  |  |   InformationCircleIcon, | 
					
						
							|  |  |  |   XCircleIcon, | 
					
						
							|  |  |  | } from '@heroicons/react/20/solid' | 
					
						
							| 
									
										
										
										
											2023-07-27 13:27:34 +08:00
										 |  |  | import { createContext, useContext } from 'use-context-selector' | 
					
						
							| 
									
										
										
										
											2024-07-09 15:05:40 +08:00
										 |  |  | import classNames from '@/utils/classnames' | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type IToastProps = { | 
					
						
							|  |  |  |   type?: 'success' | 'error' | 'warning' | 'info' | 
					
						
							|  |  |  |   duration?: number | 
					
						
							|  |  |  |   message: string | 
					
						
							|  |  |  |   children?: ReactNode | 
					
						
							|  |  |  |   onClose?: () => void | 
					
						
							| 
									
										
										
										
											2023-12-03 22:10:16 +08:00
										 |  |  |   className?: string | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | type IToastContext = { | 
					
						
							|  |  |  |   notify: (props: IToastProps) => void | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const ToastContext = createContext<IToastContext>({} as IToastContext) | 
					
						
							| 
									
										
										
										
											2023-07-27 13:27:34 +08:00
										 |  |  | export const useToastContext = () => useContext(ToastContext) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | const Toast = ({ | 
					
						
							|  |  |  |   type = 'info', | 
					
						
							|  |  |  |   message, | 
					
						
							|  |  |  |   children, | 
					
						
							| 
									
										
										
										
											2023-12-03 22:10:16 +08:00
										 |  |  |   className, | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | }: IToastProps) => { | 
					
						
							|  |  |  |   // sometimes message is react node array. Not handle it.
 | 
					
						
							| 
									
										
										
										
											2023-07-27 13:27:34 +08:00
										 |  |  |   if (typeof message !== 'string') | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |     return null | 
					
						
							| 
									
										
										
										
											2023-07-27 13:27:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   return <div className={classNames( | 
					
						
							| 
									
										
										
										
											2023-12-03 22:10:16 +08:00
										 |  |  |     className, | 
					
						
							| 
									
										
										
										
											2024-01-02 23:42:00 +08:00
										 |  |  |     'fixed rounded-md p-4 my-4 mx-8 z-[9999]', | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |     'top-0', | 
					
						
							|  |  |  |     'right-0', | 
					
						
							|  |  |  |     type === 'success' ? 'bg-green-50' : '', | 
					
						
							|  |  |  |     type === 'error' ? 'bg-red-50' : '', | 
					
						
							|  |  |  |     type === 'warning' ? 'bg-yellow-50' : '', | 
					
						
							|  |  |  |     type === 'info' ? 'bg-blue-50' : '', | 
					
						
							|  |  |  |   )}> | 
					
						
							|  |  |  |     <div className="flex"> | 
					
						
							|  |  |  |       <div className="flex-shrink-0"> | 
					
						
							|  |  |  |         {type === 'success' && <CheckCircleIcon className="w-5 h-5 text-green-400" aria-hidden="true" />} | 
					
						
							|  |  |  |         {type === 'error' && <XCircleIcon className="w-5 h-5 text-red-400" aria-hidden="true" />} | 
					
						
							|  |  |  |         {type === 'warning' && <ExclamationTriangleIcon className="w-5 h-5 text-yellow-400" aria-hidden="true" />} | 
					
						
							|  |  |  |         {type === 'info' && <InformationCircleIcon className="w-5 h-5 text-blue-400" aria-hidden="true" />} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |       <div className="ml-3"> | 
					
						
							|  |  |  |         <h3 className={ | 
					
						
							|  |  |  |           classNames( | 
					
						
							|  |  |  |             'text-sm font-medium', | 
					
						
							|  |  |  |             type === 'success' ? 'text-green-800' : '', | 
					
						
							|  |  |  |             type === 'error' ? 'text-red-800' : '', | 
					
						
							|  |  |  |             type === 'warning' ? 'text-yellow-800' : '', | 
					
						
							|  |  |  |             type === 'info' ? 'text-blue-800' : '', | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |         }>{message}</h3> | 
					
						
							|  |  |  |         {children && <div className={ | 
					
						
							|  |  |  |           classNames( | 
					
						
							|  |  |  |             'mt-2 text-sm', | 
					
						
							|  |  |  |             type === 'success' ? 'text-green-700' : '', | 
					
						
							|  |  |  |             type === 'error' ? 'text-red-700' : '', | 
					
						
							|  |  |  |             type === 'warning' ? 'text-yellow-700' : '', | 
					
						
							|  |  |  |             type === 'info' ? 'text-blue-700' : '', | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |         }> | 
					
						
							|  |  |  |           {children} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   </div> | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const ToastProvider = ({ | 
					
						
							|  |  |  |   children, | 
					
						
							|  |  |  | }: { | 
					
						
							|  |  |  |   children: ReactNode | 
					
						
							|  |  |  | }) => { | 
					
						
							|  |  |  |   const placeholder: IToastProps = { | 
					
						
							|  |  |  |     type: 'info', | 
					
						
							|  |  |  |     message: 'Toast message', | 
					
						
							| 
									
										
										
										
											2024-07-17 20:24:31 +08:00
										 |  |  |     duration: 6000, | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   } | 
					
						
							|  |  |  |   const [params, setParams] = React.useState<IToastProps>(placeholder) | 
					
						
							| 
									
										
										
										
											2024-07-17 21:19:04 +08:00
										 |  |  |   const defaultDuring = (params.type === 'success' || params.type === 'info') ? 3000 : 6000 | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   const [mounted, setMounted] = useState(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     if (mounted) { | 
					
						
							|  |  |  |       setTimeout(() => { | 
					
						
							|  |  |  |         setMounted(false) | 
					
						
							|  |  |  |       }, params.duration || defaultDuring) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-07-17 21:19:04 +08:00
										 |  |  |   }, [defaultDuring, mounted, params.duration]) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return <ToastContext.Provider value={{ | 
					
						
							|  |  |  |     notify: (props) => { | 
					
						
							|  |  |  |       setMounted(true) | 
					
						
							|  |  |  |       setParams(props) | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }}> | 
					
						
							|  |  |  |     {mounted && <Toast {...params} />} | 
					
						
							|  |  |  |     {children} | 
					
						
							|  |  |  |   </ToastContext.Provider> | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Toast.notify = ({ | 
					
						
							|  |  |  |   type, | 
					
						
							|  |  |  |   message, | 
					
						
							|  |  |  |   duration, | 
					
						
							| 
									
										
										
										
											2023-12-03 22:10:16 +08:00
										 |  |  |   className, | 
					
						
							|  |  |  | }: Pick<IToastProps, 'type' | 'message' | 'duration' | 'className'>) => { | 
					
						
							| 
									
										
										
										
											2024-07-17 21:19:04 +08:00
										 |  |  |   const defaultDuring = (type === 'success' || type === 'info') ? 3000 : 6000 | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |   if (typeof window === 'object') { | 
					
						
							|  |  |  |     const holder = document.createElement('div') | 
					
						
							|  |  |  |     const root = createRoot(holder) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-03 22:10:16 +08:00
										 |  |  |     root.render(<Toast type={type} message={message} duration={duration} className={className} />) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |     document.body.appendChild(holder) | 
					
						
							|  |  |  |     setTimeout(() => { | 
					
						
							|  |  |  |       if (holder) | 
					
						
							|  |  |  |         holder.remove() | 
					
						
							|  |  |  |     }, duration || defaultDuring) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default Toast |