Toast
Example
Steps
Prerequisite
Install Package
npm install classnames @heroicons/react
Define Types
types/index.d.ts
namespace NToast {
type Type = 'success' | 'info' | 'warn' | 'error'
interface Emit {
message: string
type: NToast.Type
}
}
Add Animation in tailwind.config.js
tailwind.config.js
module.exports = {
theme: {
extend: {
keyframes: {
'fade-up': {
from: {
opacity: 0,
transform: 'translate3d(0, -16px, 0)'
},
'60%': {
opacity: 1
},
to: {
transform: 'none'
}
}
},
animation: {
'fade-up': 'fade-up 0.2s linear'
}
}
}
}
Add Util Function
services/utils/index.ts
class Toast {
private emit(message: string, type: NToast.Type) {
EventListener.emit<NToast.Emit>('toast', { message, type })
}
success(message: string) {
this.emit(message, 'success')
}
info(message: string) {
this.emit(message, 'info')
}
warn(message: string) {
this.emit(message, 'warn')
}
error(message: string) {
this.emit(message, 'error')
}
}
export const toast = new Toast()
Copy Code
containers/Toast/index.tsx
import { useCallback, useEffect } from 'react'
import type { FC } from 'react'
import { EventListener, useObjectState } from 'services'
import { createPortal } from 'react-dom'
import {
CheckCircleIcon,
ExclamationCircleIcon,
ExclamationTriangleIcon,
InformationCircleIcon
} from '@heroicons/react/24/outline'
import { XMarkIcon } from '@heroicons/react/20/solid'
export interface Props {}
interface State {
list: Array<{
id: string
message: string
type: NToast.Type
}>
}
const Toast: FC<Props> = () => {
const [{ list }, setState] = useObjectState<State>({ list: [] })
const onMessage = useCallback(
({ detail }: any) =>
setState({
list: !!detail.id
? list.filter((item) => item.id !== detail.id)
: [
...list,
{
id: Math.random().toString(36).slice(2),
message: detail?.message,
type: detail.type
}
]
}),
[list.length]
)
useEffect(() => {
EventListener.once('toast', onMessage)
}, [list.length])
if (!list.length) return null
return createPortal(
<div role="alertdialog">
<div className="fixed top-4 left-1/2 z-50 -translate-x-1/2 space-y-4">
{list.map((item) => (
<div
className="animate-fade-up w-72 cursor-pointer rounded bg-white py-2 px-4 dark:bg-black"
id={item.id}
key={item.id}
onClick={() => EventListener.emit('toast', { id: item.id })}
role="alert"
style={{
boxShadow:
'rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px'
}}
>
<div className="flex items-center gap-2">
<span>
{item.type === 'success' && (
<CheckCircleIcon className="h-5 w-5 text-green-500" />
)}
{item.type === 'info' && (
<InformationCircleIcon className="h-5 w-5 text-blue-500" />
)}
{item.type === 'warn' && (
<ExclamationTriangleIcon className="h-5 w-5 text-amber-500" />
)}
{item.type === 'error' && (
<ExclamationCircleIcon className="h-5 w-5 text-red-500" />
)}
</span>
<span className="flex-1 select-none text-sm">
{item?.message}
</span>
<XMarkIcon className="h-5 w-5" />
</div>
</div>
))}
</div>
</div>,
document.body
)
}
export default Toast
Usage
toast.success('Success!!')
toast.info('Info!!')
toast.warn('Warn!!')
toast.error('Error!!')