Tips & Tricks

How to Implement Toast Notifications: Claude Code 활용 가이드

implement toast notifications: Claude Code 활용. 실용적인 코드 예시와 단계별 가이드를 포함합니다.

토스트알림の설계

토스트알림は操作結果やシステムメッセージを一時的に표시するUIパターンです。모달と違い사용자の操作を妨げないため、フィードバックに最適です。Claude Code를 활용하면 アクセシブルで美しい토스트알림システムを구축할 수 있습니다。

토스트관리システムの구현

> グローバルに使える토스트알림システムを作って。
> success, error, warning, infoの4種類に대응して。
type ToastType = 'success' | 'error' | 'warning' | 'info';

interface Toast {
  id: string;
  type: ToastType;
  message: string;
  duration: number;
  dismissible: boolean;
}

type ToastListener = (toasts: Toast[]) => void;

class ToastManager {
  private toasts: Toast[] = [];
  private listeners: Set<ToastListener> = new Set();
  private counter = 0;

  subscribe(listener: ToastListener) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  private notify() {
    this.listeners.forEach((fn) => fn([...this.toasts]));
  }

  add(type: ToastType, message: string, options?: Partial<Pick<Toast, 'duration' | 'dismissible'>>) {
    const id = `toast-${++this.counter}`;
    const toast: Toast = {
      id,
      type,
      message,
      duration: options?.duration ?? 5000,
      dismissible: options?.dismissible ?? true,
    };

    this.toasts = [...this.toasts, toast];
    this.notify();

    if (toast.duration > 0) {
      setTimeout(() => this.remove(id), toast.duration);
    }

    return id;
  }

  remove(id: string) {
    this.toasts = this.toasts.filter((t) => t.id !== id);
    this.notify();
  }

  success(message: string) { return this.add('success', message); }
  error(message: string) { return this.add('error', message, { duration: 8000 }); }
  warning(message: string) { return this.add('warning', message); }
  info(message: string) { return this.add('info', message); }
}

export const toast = new ToastManager();

React컴포넌트

import { useState, useEffect } from 'react';

const ICONS: Record<ToastType, string> = {
  success: '✓',
  error: '✕',
  warning: '⚠',
  info: 'ℹ',
};

const STYLES: Record<ToastType, string> = {
  success: 'bg-green-50 border-green-200 text-green-800',
  error: 'bg-red-50 border-red-200 text-red-800',
  warning: 'bg-yellow-50 border-yellow-200 text-yellow-800',
  info: 'bg-blue-50 border-blue-200 text-blue-800',
};

function ToastContainer() {
  const [toasts, setToasts] = useState<Toast[]>([]);

  useEffect(() => {
    return toast.subscribe(setToasts);
  }, []);

  return (
    <div
      aria-live="polite"
      aria-label="通知"
      className="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm w-full"
    >
      {toasts.map((t) => (
        <div
          key={t.id}
          role="status"
          className={`flex items-start gap-3 p-4 rounded-lg border shadow-lg
            animate-slideIn ${STYLES[t.type]}`}
        >
          <span className="text-lg font-bold flex-shrink-0">{ICONS[t.type]}</span>
          <p className="flex-1 text-sm">{t.message}</p>
          {t.dismissible && (
            <button
              onClick={() => toast.remove(t.id)}
              aria-label="通知をClose"
              className="text-gray-400 hover:text-gray-600"
            >✕</button>
          )}
        </div>
      ))}
    </div>
  );
}

애니메이션

@keyframes slideIn {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

@keyframes slideOut {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(100%);
    opacity: 0;
  }
}

.animate-slideIn {
  animation: slideIn 0.3s ease-out;
}

.animate-slideOut {
  animation: slideOut 0.3s ease-in forwards;
}

Reactフックでの활용

export function useToast() {
  return {
    success: (msg: string) => toast.success(msg),
    error: (msg: string) => toast.error(msg),
    warning: (msg: string) => toast.warning(msg),
    info: (msg: string) => toast.info(msg),
  };
}

// Usage example
function SaveButton() {
  const { success, error } = useToast();

  const handleSave = async () => {
    try {
      await saveData();
      success('保存しました');
    } catch (e) {
      error('保存に失敗しました。もう一度お試しください。');
    }
  };

  return <button onClick={handleSave}>保存</button>;
}

정리

Claude Code를 활용하면 타입安全でアクセシブルな토스트알림システムを애니메이션付きで빠르게구축할 수 있습니다。모달との使い分けは모달・다이얼로그설계を、애니메이션全般は애니메이션구현를 참고하세요.

WAI-ARIAのライブリージョン에 대해서는MDN Web Docs - aria-live를 확인하세요.

#Claude Code #toast #通知 #React #UI