UNPKG

react-quick-notify

Version:

react-quick-notify: A beautiful, customizable toast notification system for React applications with zero CSS dependencies

425 lines (395 loc) 14.5 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); const ToastContext = react.createContext(undefined); const useToastContext = () => { const context = react.useContext(ToastContext); if (!context) { throw new Error('useToastContext must be used within a ToastProvider'); } return context; }; const ToastProvider = ({ children, config = {} }) => { const [toasts, setToasts] = react.useState([]); const defaultConfig = { position: 'top-right', duration: 5000, maxToasts: 0, // 0 = unlimited toasts reverseOrder: false, // default: old toasts first (new at bottom) ...config, }; // Extract config values for useCallback dependencies const configDuration = defaultConfig.duration; const configMaxToasts = defaultConfig.maxToasts; const removeToast = react.useCallback((id) => { setToasts(prev => prev.filter(toast => toast.id !== id)); }, []); const addToast = react.useCallback((toastData) => { const id = Math.random().toString(36).substr(2, 9); const newToast = { id, ...toastData, duration: toastData.duration ?? configDuration, // Use provided duration or fall back to config }; setToasts(prev => { const newToasts = [...prev, newToast]; // Limit number of toasts if maxToasts is set and greater than 0 if (configMaxToasts && configMaxToasts > 0 && newToasts.length > configMaxToasts) { return newToasts.slice(-configMaxToasts); } return newToasts; }); // Auto remove toast after duration if (newToast.duration && newToast.duration > 0) { setTimeout(() => { removeToast(id); }, newToast.duration); } }, [configDuration, configMaxToasts, removeToast]); const clearAllToasts = react.useCallback(() => { setToasts([]); }, []); const updatePromiseToast = react.useCallback((promiseId, type, message) => { setToasts(prev => prev.map(toast => toast.promiseId === promiseId ? { ...toast, type, message } : toast)); }, []); const value = { toasts, config: defaultConfig, addToast, removeToast, clearAllToasts, updatePromiseToast, }; return (jsxRuntime.jsx(ToastContext.Provider, { value: value, children: children })); }; const useToast = () => { const { addToast, removeToast, clearAllToasts, toasts, updatePromiseToast } = useToastContext(); const toast = { success: (message, duration) => { addToast({ type: 'success', message, duration, }); }, error: (message, duration) => { addToast({ type: 'error', message, duration, }); }, warning: (message, duration) => { addToast({ type: 'warning', message, duration, }); }, info: (message, duration) => { addToast({ type: 'info', message, duration, }); }, custom: (type, message, duration) => { addToast({ type, message, duration, }); }, promise: (promise, messages, duration) => { const promiseId = Math.random().toString(36).substr(2, 9); // Add loading toast addToast({ type: 'loading', message: messages.loading, duration: 0, // Don't auto-dismiss loading toasts isPromise: true, promiseId, }); // Handle promise resolution promise .then(() => { updatePromiseToast(promiseId, 'success', messages.success); // Auto-dismiss success toast after duration if (duration && duration > 0) { setTimeout(() => { removeToast(promiseId); }, duration); } }) .catch(() => { updatePromiseToast(promiseId, 'error', messages.error); // Auto-dismiss error toast after duration if (duration && duration > 0) { setTimeout(() => { removeToast(promiseId); }, duration); } }); return promise; }, dismiss: removeToast, clear: clearAllToasts, }; return { toast, toasts, }; }; /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ var defaultAttributes = { xmlns: "http://www.w3.org/2000/svg", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }; /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().trim(); const createLucideIcon = (iconName, iconNode) => { const Component = react.forwardRef( ({ color = "currentColor", size = 24, strokeWidth = 2, absoluteStrokeWidth, className = "", children, ...rest }, ref) => { return react.createElement( "svg", { ref, ...defaultAttributes, width: size, height: size, stroke: color, strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth, className: ["lucide", `lucide-${toKebabCase(iconName)}`, className].join(" "), ...rest }, [ ...iconNode.map(([tag, attrs]) => react.createElement(tag, attrs)), ...Array.isArray(children) ? children : [children] ] ); } ); Component.displayName = `${iconName}`; return Component; }; /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const AlertTriangle = createLucideIcon("AlertTriangle", [ [ "path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z", key: "c3ski4" } ], ["path", { d: "M12 9v4", key: "juzpu7" }], ["path", { d: "M12 17h.01", key: "p32p05" }] ]); /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const CheckCircle = createLucideIcon("CheckCircle", [ ["path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14", key: "g774vq" }], ["path", { d: "m9 11 3 3L22 4", key: "1pflzl" }] ]); /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Info = createLucideIcon("Info", [ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], ["path", { d: "M12 16v-4", key: "1dtifu" }], ["path", { d: "M12 8h.01", key: "e9boi3" }] ]); /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const Loader2 = createLucideIcon("Loader2", [ ["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }] ]); /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const XCircle = createLucideIcon("XCircle", [ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], ["path", { d: "m15 9-6 6", key: "1uzhvr" }], ["path", { d: "m9 9 6 6", key: "z0biqf" }] ]); /** * @license lucide-react v0.344.0 - ISC * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. */ const X = createLucideIcon("X", [ ["path", { d: "M18 6 6 18", key: "1bl5f8" }], ["path", { d: "m6 6 12 12", key: "d8bk6v" }] ]); const ToastItem = ({ toast }) => { const { removeToast } = useToastContext(); const [isVisible, setIsVisible] = react.useState(false); const [isLeaving, setIsLeaving] = react.useState(false); const [iconAnimated, setIconAnimated] = react.useState(false); react.useEffect(() => { const timer = setTimeout(() => setIsVisible(true), 10); const iconTimer = setTimeout(() => setIconAnimated(true), 200); return () => { clearTimeout(timer); clearTimeout(iconTimer); }; }, []); const handleDismiss = () => { setIsLeaving(true); setTimeout(() => { removeToast(toast.id); }, 300); }; const getToastTypeClass = () => { switch (toast.type) { case 'success': return 'rqn-toast-item--success'; case 'error': return 'rqn-toast-item--error'; case 'warning': return 'rqn-toast-item--warning'; case 'info': return 'rqn-toast-item--info'; case 'loading': return 'rqn-toast-item--loading'; default: return 'rqn-toast-item--default'; } }; const getIcon = () => { const iconClass = `rqn-toast-icon ${iconAnimated ? 'rqn-toast-icon--animated' : ''}`; switch (toast.type) { case 'success': return jsxRuntime.jsx(CheckCircle, { className: `${iconClass} rqn-toast-icon--success` }); case 'error': return jsxRuntime.jsx(XCircle, { className: `${iconClass} rqn-toast-icon--error` }); case 'warning': return jsxRuntime.jsx(AlertTriangle, { className: `${iconClass} rqn-toast-icon--warning` }); case 'info': return jsxRuntime.jsx(Info, { className: `${iconClass} rqn-toast-icon--info` }); case 'loading': return jsxRuntime.jsx(Loader2, { className: `${iconClass} rqn-toast-icon--loading rqn-toast-icon--spinning` }); default: return jsxRuntime.jsx(Info, { className: `${iconClass} rqn-toast-icon--default` }); } }; const getMessageClass = () => { switch (toast.type) { case 'success': return 'rqn-toast-message--success'; case 'error': return 'rqn-toast-message--error'; case 'warning': return 'rqn-toast-message--warning'; case 'info': return 'rqn-toast-message--info'; case 'loading': return 'rqn-toast-message--loading'; default: return 'rqn-toast-message--default'; } }; const getCloseButtonClass = () => { switch (toast.type) { case 'success': return 'rqn-toast-close--success'; case 'error': return 'rqn-toast-close--error'; case 'warning': return 'rqn-toast-close--warning'; case 'info': return 'rqn-toast-close--info'; case 'loading': return 'rqn-toast-close--loading'; default: return 'rqn-toast-close--default'; } }; return (jsxRuntime.jsx("div", { className: `rqn-toast-item ${getToastTypeClass()} ${isVisible && !isLeaving ? 'rqn-toast-item--visible' : ''} ${isLeaving ? 'rqn-toast-item--leaving' : ''}`, children: jsxRuntime.jsxs("div", { className: "rqn-toast-content", children: [getIcon(), jsxRuntime.jsx("p", { className: `rqn-toast-message ${getMessageClass()}`, children: toast.message }), jsxRuntime.jsx("button", { onClick: handleDismiss, className: `rqn-toast-close ${getCloseButtonClass()}`, children: jsxRuntime.jsx(X, { className: "rqn-toast-close-icon" }) })] }) })); }; const ToastContainer = ({ position }) => { const { toasts, config } = useToastContext(); // Use position prop or fall back to global config const finalPosition = position || config.position || 'top-right'; if (toasts.length === 0) { return null; } const getPositionClass = () => { switch (finalPosition) { case 'top-left': return 'rqn-toast-container--top-left'; case 'top-center': return 'rqn-toast-container--top-center'; case 'top-right': return 'rqn-toast-container--top-right'; case 'bottom-left': return 'rqn-toast-container--bottom-left'; case 'bottom-center': return 'rqn-toast-container--bottom-center'; case 'bottom-right': return 'rqn-toast-container--bottom-right'; default: return 'rqn-toast-container--top-right'; } }; const getContainerDirection = () => { // Only apply CSS reverse for bottom positioned containers (visual stacking from bottom) return finalPosition.includes('bottom') ? 'rqn-toast-container--reverse' : ''; }; // Apply toast order based on reverseOrder configuration // For top positions: reverseOrder=true means newest first (at top visually) // For bottom positions: reverseOrder=true means newest first (but CSS reverse handles visual stacking) const orderedToasts = config.reverseOrder ? [...toasts].reverse() : toasts; return (jsxRuntime.jsx("div", { "aria-live": "assertive", className: `rqn-toast-container ${getPositionClass()} ${getContainerDirection()}`, children: orderedToasts.map(toast => (jsxRuntime.jsx(ToastItem, { toast: toast }, toast.id))) })); }; exports.ToastContainer = ToastContainer; exports.ToastItem = ToastItem; exports.ToastProvider = ToastProvider; exports.useToast = useToast; //# sourceMappingURL=index.js.map