Archive
Toast

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!!')

References


© 2023 kidow. All rights reserved.
안녕하세요?