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
JavaScript
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
;