// FloatToastContent.tsx — Toast 컴포넌트 카탈로그 import { useState, useEffect } from 'react'; import type { DesignTheme } from '../designTheme'; interface FloatToastContentProps { theme: DesignTheme; } type ToastType = 'success' | 'error' | 'info' | 'warning'; interface ToastItem { id: number; type: ToastType; message: string; progress: number; } const TOAST_CONFIG: Record = { success: { color: '#22c55e', bg: 'rgba(34,197,94,0.12)', icon: '✓', label: 'Success' }, error: { color: '#ef4444', bg: 'rgba(239,68,68,0.12)', icon: '✕', label: 'Error' }, info: { color: '#06b6d4', bg: 'rgba(6,182,212,0.12)', icon: 'ℹ', label: 'Info' }, warning: { color: '#eab308', bg: 'rgba(234,179,8,0.12)', icon: '⚠', label: 'Warning' }, }; const DEMO_MESSAGES: Record = { success: '저장이 완료되었습니다.', error: '요청 처리 중 오류가 발생했습니다.', info: '시뮬레이션이 시작되었습니다.', warning: '미저장 변경사항이 있습니다.', }; const TOAST_DURATION = 3000; let toastIdCounter = 0; export const FloatToastContent = ({ theme }: FloatToastContentProps) => { const t = theme; const isDark = t.mode === 'dark'; const [toasts, setToasts] = useState([]); const addToast = (type: ToastType) => { const id = ++toastIdCounter; setToasts((prev) => [...prev, { id, type, message: DEMO_MESSAGES[type], progress: 100 }]); }; const removeToast = (id: number) => { setToasts((prev) => prev.filter((t) => t.id !== id)); }; useEffect(() => { if (toasts.length === 0) return; const interval = setInterval(() => { setToasts((prev) => prev .map((toast) => ({ ...toast, progress: toast.progress - 100 / (TOAST_DURATION / 100) })) .filter((toast) => toast.progress > 0), ); }, 100); return () => clearInterval(interval); }, [toasts.length]); const toastBg = isDark ? '#1b1f2c' : '#ffffff'; const toastBorder = isDark ? 'rgba(66,71,84,0.30)' : '#e2e8f0'; return (
{/* ── 개요 ── */}

Toast

미구현 — 설계 사양

화면을 차단하지 않는 비파괴적 알림. fixed bottom-right 에 위치하며 일정 시간 후 자동으로 사라진다. 현재 프로젝트에서는{' '} window.alert 또는 console.log로 대체하고 있으며 커스텀 구현이 필요하다.

{/* ── Live Preview ── */}

Live Preview

interactive

버튼 클릭 시 화면 우하단에 Toast가 표시됩니다. 3초 후 자동으로 사라집니다.

{(Object.keys(TOAST_CONFIG) as ToastType[]).map((type) => { const cfg = TOAST_CONFIG[type]; return ( ); })}
{toasts.length > 0 && (

활성 Toast: {toasts.length}개 — 우측 하단을 확인하세요

)}
{/* ── Anatomy ── */}

Anatomy

{/* 구조 목업 */}
Structure
{/* 성공 Toast 목업 */}
{(['success', 'info', 'error'] as ToastType[]).map((type, i) => { const cfg = TOAST_CONFIG[type]; return (
{cfg.icon} {DEMO_MESSAGES[type].slice(0, 14)}…
); })}
{/* 위치 규칙 */}
Position Rules
{[ { label: 'position', value: 'fixed', desc: '뷰포트 기준 고정' }, { label: 'bottom', value: '24px', desc: '화면 하단에서 24px' }, { label: 'right', value: '24px', desc: '화면 우측에서 24px' }, { label: 'z-index', value: 'z-60', desc: '콘텐츠 위, Modal(9999) 아래' }, { label: 'width', value: '320px (고정)', desc: '일정한 너비 유지' }, { label: 'gap', value: '8px (스택)', desc: '복수 Toast 간격' }, ].map((item) => (
{item.label} {item.value} {item.desc}
))}
{/* ── 타입별 색상 ── */}

타입별 색상

{(Object.entries(TOAST_CONFIG) as [ToastType, (typeof TOAST_CONFIG)[ToastType]][]).map( ([type, cfg]) => (
{cfg.icon} {cfg.label}
{cfg.color} {type === 'success' && '저장 완료, 복사 완료, 전송 성공'} {type === 'error' && 'API 오류, 저장 실패, 권한 없음'} {type === 'info' && '작업 시작, 업데이트 알림'} {type === 'warning' && '미저장 변경, 만료 임박'}
), )}
{/* ── 구현 패턴 제안 ── */}

구현 패턴 제안 — useToast Hook

Toast는 앱 어디서든 호출해야 하므로 Zustand store + useToast hook{' '} 패턴을 권장한다. ToastContainer는 App.tsx 최상위에 한 번만 렌더링한다.

{[ { title: 'toastStore.ts', code: 'const useToastStore = create()\naddToast(type, message, duration?)\nremoveToast(id)', desc: 'Zustand store — Toast 큐 관리', }, { title: 'useToast.ts', code: 'const { success, error, info, warning } = useToast()\nsuccess("저장 완료") // duration 기본값 3000ms', desc: '컴포넌트에서 호출하는 hook', }, { title: 'ToastContainer.tsx', code: '
\n {toasts.map(t => )}\n
', desc: 'App.tsx 최상위에 배치', }, ].map((item) => (
{item.title} {item.desc}
                  {item.code}
                
))}
{/* ── 실제 Toast 렌더링 (fixed 위치) ── */} {toasts.length > 0 && (
{toasts.map((toast) => { const cfg = TOAST_CONFIG[toast.type]; return (
{cfg.icon} {toast.message}
{/* Progress bar */}
); })}
)}
); }; export default FloatToastContent;