Toast

일시적으로 표시되는 간결한 메시지입니다.

Example

It can only be viewed in a mobile.

Steps

Prerequisite

Add types

namespace NToast {
  type Type = 'success' | 'info' | 'warn' | 'error'
  interface Emit {
    message: string
    type: NToast.Type
  }
  interface Item {
    id: string
    message: string
    type: NToast.Type
  }
}

Add toast function

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
'use client'
 
import { useCallback, useEffect, useState } from 'react'
import {
  AlertTriangleIcon,
  BanIcon,
  CheckCircle2Icon,
  InfoIcon,
  XIcon
} from 'lucide-react'
import { createPortal } from 'react-dom'
import { EventListener } from 'utils'
 
function Toast() {
  const [list, setList] = useState<
    Array<{ id: string; message: string; type: NToast.Type }>
  >([])
 
  const onMessage = useCallback(
    ({ detail }: any) =>
      setList(
        detail.id
          ? list.filter((item) => item.id !== detail.id)
          : [
              ...list,
              {
                id: Math.random().toString(36).slice(2),
                message: detail?.message,
                type: detail.type
              }
            ]
      ),
    [list]
  )
 
  useEffect(() => {
    EventListener.once('toast', onMessage)
  }, [list, onMessage])
 
  if (!list.length) return <></>
  return createPortal(
    <div role="alertdialog">
      <div className="fixed left-1/2 top-4 z-50 -translate-x-1/2 space-y-4">
        {list.map((item) => (
          <div
            className="animate-fade-up w-72 cursor-pointer rounded bg-white px-4 py-2 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' && (
                  <CheckCircle2Icon className="h-5 w-5 text-green-500" />
                )}
                {item.type === 'info' && (
                  <InfoIcon className="h-5 w-5 text-blue-500" />
                )}
                {item.type === 'warn' && (
                  <AlertTriangleIcon className="h-5 w-5 text-amber-500" />
                )}
                {item.type === 'error' && (
                  <BanIcon className="h-5 w-5 text-red-500" />
                )}
              </span>
              <span className="flex-1 select-none text-sm">
                {item?.message}
              </span>
              <XIcon className="h-5 w-5" />
            </div>
          </div>
        ))}
      </div>
    </div>,
    document.body
  )
}
 
export default Toast

Usage

import { toast } from 'utils'
 
toast.success('Success!')
toast.info('Info!')
toast.warn('Warn!')
toast.error('Error!')

Methods

NameTypeDefault
success(message: string) => void
info(message: string) => void
warn(message: string) => void
error(message: string) => void

References