'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' import { createContext, useContext } from 'use-context-selector' import classNames from '@/utils/classnames' export type IToastProps = { type?: 'success' | 'error' | 'warning' | 'info' duration?: number message: string children?: ReactNode onClose?: () => void className?: string } type IToastContext = { notify: (props: IToastProps) => void } export const ToastContext = createContext({} as IToastContext) export const useToastContext = () => useContext(ToastContext) const Toast = ({ type = 'info', message, children, className, }: IToastProps) => { // sometimes message is react node array. Not handle it. if (typeof message !== 'string') return null return
{type === 'success' &&

{message}

{children &&
{children}
}
} export const ToastProvider = ({ children, }: { children: ReactNode }) => { const placeholder: IToastProps = { type: 'info', message: 'Toast message', duration: 6000, } const [params, setParams] = React.useState(placeholder) const defaultDuring = (params.type === 'success' || params.type === 'info') ? 3000 : 6000 const [mounted, setMounted] = useState(false) useEffect(() => { if (mounted) { setTimeout(() => { setMounted(false) }, params.duration || defaultDuring) } }, [defaultDuring, mounted, params.duration]) return { setMounted(true) setParams(props) }, }}> {mounted && } {children} } Toast.notify = ({ type, message, duration, className, }: Pick) => { const defaultDuring = (type === 'success' || type === 'info') ? 3000 : 6000 if (typeof window === 'object') { const holder = document.createElement('div') const root = createRoot(holder) root.render() document.body.appendChild(holder) setTimeout(() => { if (holder) holder.remove() }, duration || defaultDuring) } } export default Toast