UNPKG

react-toastly

Version:

A beautiful React toast notification library with animations, gradients, sounds, and premium features for free.

142 lines (125 loc) 4.22 kB
import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from "react"; const ToastlyContext = createContext(); const soundMap = { success: "/sounds/success.mp3", error: "/sounds/error.mp3", warning: "/sounds/warning.mp3", info: "/sounds/info.mp3", }; export function ToastlyProvider({ children }) { const [toasts, setToasts] = useState([]); const timers = useRef(new Map()); const playSound = (type) => { const soundPath = soundMap[type]; if (soundPath) { const audio = new Audio(soundPath); audio.play().catch((e) => console.warn("Sound play failed:", e)); } }; const removeToast = useCallback((id) => { setToasts((prev) => prev.filter((t) => t.id !== id)); timers.current.delete(id); }, []); const addToast = useCallback(({ type, message, content, duration = 3000 }) => { const id = Date.now() + Math.random(); setToasts((prev) => [...prev, { id, type, message, content }]); playSound(type); const timerId = setTimeout(() => { removeToast(id); }, duration); timers.current.set(id, timerId); }, [removeToast]); // Clear timers on unmount to avoid leaks useEffect(() => { return () => { timers.current.forEach((timerId) => clearTimeout(timerId)); timers.current.clear(); }; }, []); const success = (msg, opts = {}) => addToast({ type: "success", message: msg, ...opts }); const error = (msg, opts = {}) => addToast({ type: "error", message: msg, ...opts }); const warning = (msg, opts = {}) => addToast({ type: "warning", message: msg, ...opts }); const info = (msg, opts = {}) => addToast({ type: "info", message: msg, ...opts }); const custom = (content, opts = {}) => addToast({ type: "custom", content, ...opts }); // Promise-based toast helper const promise = (promise, { loading, success: successMsg, error: errorMsg }) => { const id = Date.now() + Math.random(); setToasts((prev) => [...prev, { id, type: "info", message: loading }]); playSound("info"); return promise .then((res) => { removeToast(id); addToast({ type: "success", message: successMsg }); return res; }) .catch((err) => { removeToast(id); addToast({ type: "error", message: errorMsg }); throw err; }); }; return ( <ToastlyContext.Provider value={{ success, error, warning, info, custom, promise }}> {children} <div style={{ position: "fixed", top: 10, right: 10, display: "flex", flexDirection: "column", gap: 10, zIndex: 9999, maxWidth: 320, }} > {toasts.map(({ id, type, message, content }) => ( <div key={id} role="alert" aria-live="assertive" aria-atomic="true" style={{ padding: "12px 20px", borderRadius: 8, color: "white", fontWeight: "bold", backgroundColor: type === "success" ? "#4caf50" : type === "error" ? "#f44336" : type === "warning" ? "#ff9800" : type === "info" ? "#2196f3" : "#555", boxShadow: "0 2px 6px rgba(0,0,0,0.3)", opacity: 0.9, animation: "slideIn 0.3s ease forwards", }} > {content || message} </div> ))} </div> <style>{` @keyframes slideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 0.9; transform: translateX(0); } } `}</style> </ToastlyContext.Provider> ); } export function useToastly() { const context = useContext(ToastlyContext); if (!context) throw new Error("useToastly must be used within ToastlyProvider"); return context; }