UNPKG

@divetocode/toast

Version:

A lightweight toast notification system for React/Next.js

1 lines 5.13 kB
{"version":3,"sources":["../src/Toast.tsx","../src/ToastRoot.tsx"],"sourcesContent":["type ToastListener = (message: string, options?: { type?: \"success\" | \"error\" }) => void;\r\nlet listener: ToastListener | null = null;\r\n\r\nexport const showToast = (message: string, options?: { type?: \"success\" | \"error\" }) => {\r\n listener?.(message, options);\r\n};\r\n\r\nexport const _internalSetToastListener = (fn: ToastListener) => {\r\n listener = fn;\r\n};","\"use client\";\r\n\r\nimport { useEffect, useState } from \"react\";\r\nimport { createPortal } from \"react-dom\";\r\nimport { _internalSetToastListener } from \"./Toast\";\r\n\r\nlet toastId = 0;\r\n\r\ntype ToastType = \"success\" | \"error\";\r\n\r\ninterface ToastItem {\r\n id: number;\r\n message: string;\r\n type: ToastType;\r\n}\r\n\r\nconst ToastRoot = () => {\r\n const [toasts, setToasts] = useState<ToastItem[]>([]);\r\n const [mounted, setMounted] = useState(false); // ✅ document 접근 안전 체크\r\n\r\n useEffect(() => {\r\n setMounted(true); // ✅ 브라우저에서만 true\r\n\r\n _internalSetToastListener((message, options) => {\r\n const id = toastId++;\r\n const type: ToastType = options?.type || \"success\";\r\n\r\n setToasts((prev) => [...prev, { id, message, type }]);\r\n\r\n setTimeout(() => {\r\n setToasts((prev) => prev.filter((t) => t.id !== id));\r\n }, 5000);\r\n });\r\n }, []);\r\n\r\n if (!mounted) return null; // ✅ SSR 중에는 렌더링하지 않음\r\n\r\n return createPortal(\r\n <>\r\n <style>\r\n {`\r\n @keyframes slide-in {\r\n 0% {\r\n opacity: 0;\r\n transform: translateX(100%);\r\n }\r\n 100% {\r\n opacity: 1;\r\n transform: translateX(0);\r\n }\r\n }\r\n\r\n .toast-container {\r\n position: fixed;\r\n top: 1.5rem; /* top-6 */\r\n right: 1.5rem; /* right-6 */\r\n z-index: 50;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: flex-end;\r\n gap: 0.5rem; /* space-y-2 */\r\n pointer-events: none;\r\n }\r\n\r\n .toast-base {\r\n position: relative;\r\n width: 100%;\r\n max-width: 20rem; /* max-w-xs */\r\n padding: 0.5rem 1rem; /* px-4 py-2 */\r\n margin-top: 0.5rem; /* mt-2 */\r\n border-radius: 0.375rem; /* rounded-md */\r\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),\r\n 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-lg */\r\n font-size: 0.875rem; /* text-sm */\r\n font-weight: 500; /* font-medium */\r\n transition: all 0.5s; /* transition-all duration-500 */\r\n transform: translateX(0); /* transform */\r\n animation: slide-in 0.3s ease-out; /* animate-slide-in */\r\n }\r\n\r\n .toast-success {\r\n background-color: #d1fae5; /* bg-green-100 */\r\n color: #065f46; /* text-green-900 */\r\n border: 1px solid #a7f3d0; /* border-green-300 */\r\n }\r\n\r\n .toast-error {\r\n background-color: #fee2e2; /* bg-red-100 */\r\n color: #991b1b; /* text-red-900 */\r\n border: 1px solid #fca5a5; /* border-red-300 */\r\n }\r\n `}\r\n </style>\r\n\r\n <div className=\"toast-container\">\r\n {toasts.map((t) => {\r\n const typeClass = t.type === \"error\" ? \"toast-error\" : \"toast-success\";\r\n return (\r\n <div key={t.id} className={`toast-base ${typeClass}`}>\r\n {t.message}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </>,\r\n document.body\r\n );\r\n};\r\n\r\nexport default ToastRoot;"],"mappings":";AACA,IAAI,WAAiC;AAE9B,IAAM,YAAY,CAAC,SAAiB,YAA6C;AACtF,aAAW,SAAS,OAAO;AAC7B;AAEO,IAAM,4BAA4B,CAAC,OAAsB;AAC9D,aAAW;AACb;;;ACPA,SAAS,WAAW,gBAAgB;AACpC,SAAS,oBAAoB;AAmCzB,mBACE,KADF;AAhCJ,IAAI,UAAU;AAUd,IAAM,YAAY,MAAM;AACtB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsB,CAAC,CAAC;AACpD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,eAAW,IAAI;AAEf,8BAA0B,CAAC,SAAS,YAAY;AAC9C,YAAM,KAAK;AACX,YAAM,OAAkB,SAAS,QAAQ;AAEzC,gBAAU,CAAC,SAAS,CAAC,GAAG,MAAM,EAAE,IAAI,SAAS,KAAK,CAAC,CAAC;AAEpD,iBAAW,MAAM;AACf,kBAAU,CAAC,SAAS,KAAK,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;AAAA,MACrD,GAAG,GAAI;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,MAAI,CAAC,QAAS,QAAO;AAErB,SAAO;AAAA,IACL,iCACE;AAAA,0BAAC,WACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAoDH;AAAA,MAEA,oBAAC,SAAI,WAAU,mBACZ,iBAAO,IAAI,CAAC,MAAM;AACjB,cAAM,YAAY,EAAE,SAAS,UAAU,gBAAgB;AACvD,eACE,oBAAC,SAAe,WAAW,cAAc,SAAS,IAC/C,YAAE,WADK,EAAE,EAEZ;AAAA,MAEJ,CAAC,GACH;AAAA,OACF;AAAA,IACA,SAAS;AAAA,EACX;AACF;AAEA,IAAO,oBAAQ;","names":[]}