UNPKG

react-cool-toast

Version:

A lightweight, customizable toast notification library for React

508 lines (493 loc) 23.6 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('react'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; // Sound frequencies for different toast types var soundFrequencies = { success: [523.25, 659.25, 783.99], // C5, E5, G5 - Major chord error: [220, 174.61], // A3, F3 - Dissonant warning: [440, 554.37], // A4, C#5 - Attention grabbing info: [523.25, 698.46], // C5, F5 - Neutral loading: [523.25, 659.25], // C5, E5 - Gentle progression notification: [659.25, 523.25], // E5, C5 - Pleasant none: [] }; // Audio context for Web Audio API var audioContext = null; var getAudioContext = function () { if (typeof window === 'undefined') return null; if (!audioContext) { try { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } catch (error) { console.warn('Web Audio API not supported'); return null; } } return audioContext; }; // Play a tone with given frequency and duration var playTone = function (frequency, duration, volume) { var context = getAudioContext(); if (!context) return; var oscillator = context.createOscillator(); var gainNode = context.createGain(); oscillator.connect(gainNode); gainNode.connect(context.destination); oscillator.frequency.setValueAtTime(frequency, context.currentTime); oscillator.type = 'sine'; gainNode.gain.setValueAtTime(0, context.currentTime); gainNode.gain.linearRampToValueAtTime(volume, context.currentTime + 0.01); gainNode.gain.linearRampToValueAtTime(0, context.currentTime + duration); oscillator.start(context.currentTime); oscillator.stop(context.currentTime + duration); }; // Play sound sequence for different toast types var playToastSound = function (sound) { if (sound === false || sound === 'none') return; var soundType = 'notification'; if (typeof sound === 'string') { soundType = sound; } var frequencies = soundFrequencies[soundType]; if (!frequencies || frequencies.length === 0) return; frequencies.forEach(function (frequency, index) { setTimeout(function () { playTone(frequency, 0.15, 0.05); }, index * 100); }); }; // Check if user has enabled sounds in their preferences var shouldPlaySound = function (enableSounds) { if (enableSounds === void 0) { enableSounds = true; } if (!enableSounds) return false; // Check if user has reduced motion preference (often correlates with sound preference) if (typeof window !== 'undefined' && window.matchMedia) { try { var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReducedMotion) return false; } catch (error) { // Ignore errors in test environment console.warn('matchMedia not supported'); } } return true; }; // Global toast functions that will be set by the provider var addToast = null; var removeToast = null; var updateToast = null; var clearToasts = null; var enableSounds = true; // Set the toast functions (called by ToastProvider) var setToastFunctions = function (functions) { var _a; addToast = functions.addToast; removeToast = functions.removeToast; updateToast = functions.updateToast; clearToasts = functions.clearToasts; enableSounds = (_a = functions.enableSounds) !== null && _a !== void 0 ? _a : true; }; // Main toast function var toast = function (message, options) { var _a, _b, _c, _d, _e; if (options === void 0) { options = {}; } if (!addToast) { console.warn('Toast functions not initialized. Make sure to wrap your app with ToastProvider.'); return ''; } // Play sound if enabled if (options.sound !== false && shouldPlaySound(enableSounds)) { var soundType = options.sound === true || options.sound === undefined ? options.type || 'info' : options.sound; playToastSound(soundType); } var id = addToast({ message: message, type: options.type || 'info', duration: (_a = options.duration) !== null && _a !== void 0 ? _a : 4000, position: options.position || 'top-right', theme: options.theme, icon: options.icon, className: options.className, style: options.style, onClose: options.onClose, onOpen: options.onOpen, sound: options.sound, dismissible: (_b = options.dismissible) !== null && _b !== void 0 ? _b : true, swipeable: (_c = options.swipeable) !== null && _c !== void 0 ? _c : true, showProgress: (_d = options.showProgress) !== null && _d !== void 0 ? _d : false, actions: options.actions, richContent: (_e = options.richContent) !== null && _e !== void 0 ? _e : false, }); return id; }; // Convenience methods toast.success = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { type: 'success' })); }; toast.error = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { type: 'error' })); }; toast.warning = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { type: 'warning' })); }; toast.info = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { type: 'info' })); }; toast.loading = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { type: 'loading', duration: 0 })); // Loading toasts don't auto-dismiss }; // Promise-based toast toast.promise = function (promise, messages, options) { if (options === void 0) { options = {}; } var loadingId = toast.loading(messages.loading, options); return promise .then(function (data) { var successMessage = typeof messages.success === 'function' ? messages.success(data) : messages.success; if (updateToast) { updateToast(loadingId, { type: 'success', message: successMessage, duration: 4000, }); } return data; }) .catch(function (error) { var errorMessage = typeof messages.error === 'function' ? messages.error(error) : messages.error; if (updateToast) { updateToast(loadingId, { type: 'error', message: errorMessage, duration: 4000, }); } throw error; }); }; // Utility functions toast.dismiss = function (id) { if (removeToast) { removeToast(id); } }; toast.dismissAll = function () { if (clearToasts) { clearToasts(); } }; // Custom toast with full control toast.custom = function (message, options) { if (options === void 0) { options = {}; } return toast(message, options); }; // Toast with action buttons toast.action = function (message, actions, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { actions: actions })); }; // Rich content toast (HTML, images, etc.) toast.rich = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { richContent: true })); }; // Silent toast (no sound) toast.silent = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { sound: false })); }; // Toast with progress bar toast.progress = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { showProgress: true })); }; // Themed toasts toast.glass = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { theme: 'glass' })); }; toast.neon = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { theme: 'neon' })); }; toast.minimal = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { theme: 'minimal' })); }; toast.colorful = function (message, options) { if (options === void 0) { options = {}; } return toast(message, __assign(__assign({}, options), { theme: 'colorful' })); }; var ToastContext = react.createContext(undefined); var ToastProvider = function (_a) { var children = _a.children, _b = _a.defaultPosition, defaultPosition = _b === void 0 ? 'top-right' : _b, _c = _a.maxToasts, maxToasts = _c === void 0 ? 5 : _c, _d = _a.enableSounds, enableSounds = _d === void 0 ? true : _d, defaultTheme = _a.defaultTheme; var _e = react.useState([]), toasts = _e[0], setToasts = _e[1]; var removeToast = react.useCallback(function (id) { setToasts(function (prev) { return prev.filter(function (toast) { return toast.id !== id; }); }); }, []); var addToast = react.useCallback(function (toast) { var _a, _b, _c, _d; var id = Math.random().toString(36).substr(2, 9); var newToast = __assign(__assign({}, toast), { id: id, createdAt: Date.now(), position: toast.position || defaultPosition, theme: toast.theme || defaultTheme, dismissible: (_a = toast.dismissible) !== null && _a !== void 0 ? _a : true, swipeable: (_b = toast.swipeable) !== null && _b !== void 0 ? _b : true, showProgress: (_c = toast.showProgress) !== null && _c !== void 0 ? _c : false, richContent: (_d = toast.richContent) !== null && _d !== void 0 ? _d : false }); setToasts(function (prev) { // Limit number of toasts var filtered = prev.filter(function (t) { return t.position === newToast.position; }); if (filtered.length >= maxToasts) { // Remove oldest toast var oldestId_1 = filtered[0].id; setTimeout(function () { return removeToast(oldestId_1); }, 100); } return __spreadArray(__spreadArray([], prev, true), [newToast], false); }); // Don't auto-remove if duration is 0 or if showProgress is true (handled by component) if (newToast.duration > 0 && !newToast.showProgress) { setTimeout(function () { removeToast(id); }, newToast.duration); } return id; }, [defaultPosition, defaultTheme, maxToasts, removeToast]); var updateToast = react.useCallback(function (id, updates) { setToasts(function (prev) { return prev.map(function (toast) { return toast.id === id ? __assign(__assign({}, toast), updates) : toast; }); }); }, []); var clearToasts = react.useCallback(function () { setToasts([]); }, []); var value = { toasts: toasts, addToast: addToast, removeToast: removeToast, updateToast: updateToast, clearToasts: clearToasts, }; // Set global toast functions react.useEffect(function () { setToastFunctions({ addToast: addToast, removeToast: removeToast, updateToast: updateToast, clearToasts: clearToasts, enableSounds: enableSounds, }); }, [addToast, removeToast, updateToast, clearToasts, enableSounds]); return (jsxRuntime.jsx(ToastContext.Provider, { value: value, children: children })); }; var useToast = function () { var context = react.useContext(ToastContext); if (context === undefined) { throw new Error('useToast must be used within a ToastProvider'); } return context; }; var getIcon = function (type) { switch (type) { case 'success': return (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsxRuntime.jsx("path", { d: "M9 12l2 2 4-4" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" })] })); case 'error': return (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }), jsxRuntime.jsx("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), jsxRuntime.jsx("line", { x1: "9", y1: "9", x2: "15", y2: "15" })] })); case 'warning': return (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsxRuntime.jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }), jsxRuntime.jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }), jsxRuntime.jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })] })); case 'loading': return (jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntime.jsx("path", { d: "M21 12a9 9 0 11-6.219-8.56" }) })); case 'info': default: return (jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }), jsxRuntime.jsx("line", { x1: "12", y1: "16", x2: "12", y2: "12" }), jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })] })); } }; var ToastComponent = function (_a) { var toast = _a.toast, onRemove = _a.onRemove; _a.onUpdate; var _b = react.useState(false), isVisible = _b[0], setIsVisible = _b[1]; var _c = react.useState(false), isLeaving = _c[0], setIsLeaving = _c[1]; var _d = react.useState(100), progress = _d[0], setProgress = _d[1]; var _e = react.useState(false), isSwiping = _e[0], setIsSwiping = _e[1]; var _f = react.useState(0), swipeX = _f[0], setSwipeX = _f[1]; var toastRef = react.useRef(null); var progressIntervalRef = react.useRef(null); var startXRef = react.useRef(0); var startTimeRef = react.useRef(0); react.useEffect(function () { // Trigger enter animation var timer = setTimeout(function () { return setIsVisible(true); }, 10); return function () { return clearTimeout(timer); }; }, []); react.useEffect(function () { if (toast.onOpen) { toast.onOpen(); } }, [toast]); // Progress bar animation react.useEffect(function () { if (toast.showProgress && toast.duration > 0) { var interval = 50; // Update every 50ms var decrement_1 = (interval / toast.duration) * 100; progressIntervalRef.current = setInterval(function () { setProgress(function (prev) { var newProgress = prev - decrement_1; if (newProgress <= 0) { handleClose(); return 0; } return newProgress; }); }, interval); return function () { if (progressIntervalRef.current) { clearInterval(progressIntervalRef.current); } }; } }, [toast.showProgress, toast.duration]); // Auto-dismiss timer react.useEffect(function () { if (toast.duration > 0 && !toast.showProgress) { var timer_1 = setTimeout(function () { handleClose(); }, toast.duration); return function () { return clearTimeout(timer_1); }; } }, [toast.duration, toast.showProgress]); var handleClose = react.useCallback(function () { setIsLeaving(true); if (progressIntervalRef.current) { clearInterval(progressIntervalRef.current); } setTimeout(function () { onRemove(toast.id); if (toast.onClose) { toast.onClose(); } }, 300); // Match animation duration }, [toast.id, toast.onClose, onRemove]); // Swipe gesture handlers var handleTouchStart = function (e) { if (!toast.swipeable) return; startXRef.current = e.touches[0].clientX; startTimeRef.current = Date.now(); setIsSwiping(true); }; var handleTouchMove = function (e) { if (!toast.swipeable || !isSwiping) return; var currentX = e.touches[0].clientX; var diffX = currentX - startXRef.current; setSwipeX(diffX); }; var handleTouchEnd = function () { if (!toast.swipeable || !isSwiping) return; var swipeThreshold = 100; var timeThreshold = 300; var swipeTime = Date.now() - startTimeRef.current; if (Math.abs(swipeX) > swipeThreshold || (Math.abs(swipeX) > 50 && swipeTime < timeThreshold)) { handleClose(); } else { setSwipeX(0); } setIsSwiping(false); }; var handleClick = function () { if (toast.type !== 'loading' && toast.dismissible !== false) { handleClose(); } }; var handleActionClick = function (action) { action.onClick(); handleClose(); }; var icon = toast.icon || getIcon(toast.type); // Build class names var classNames = [ 'cool-toast', toast.type, isVisible ? 'visible' : '', isLeaving ? 'leaving' : '', isSwiping ? 'swiping' : '', Math.abs(swipeX) > 50 ? 'swipe-threshold' : '', toast.theme ? "theme-".concat(toast.theme) : '', toast.richContent ? 'rich-content' : '', toast.className || '' ].filter(Boolean).join(' '); var toastStyle = __assign(__assign({}, toast.style), { transform: isSwiping ? "translateX(".concat(swipeX, "px)") : undefined }); return (jsxRuntime.jsxs("div", { ref: toastRef, className: classNames, style: toastStyle, onClick: handleClick, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, role: "alert", "aria-live": "polite", "aria-atomic": "true", "aria-describedby": "toast-".concat(toast.id, "-message"), children: [jsxRuntime.jsxs("div", { className: "cool-toast-content", children: [jsxRuntime.jsx("div", { className: "cool-toast-icon", children: icon }), jsxRuntime.jsx("div", { id: "toast-".concat(toast.id, "-message"), className: "cool-toast-message", dangerouslySetInnerHTML: toast.richContent ? { __html: toast.message } : undefined, children: !toast.richContent && toast.message }), toast.dismissible !== false && toast.type !== 'loading' && (jsxRuntime.jsx("button", { className: "cool-toast-close", onClick: function (e) { e.stopPropagation(); handleClose(); }, "aria-label": "Close notification", children: jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) }))] }), toast.actions && toast.actions.length > 0 && (jsxRuntime.jsx("div", { className: "cool-toast-actions", children: toast.actions.map(function (action, index) { return (jsxRuntime.jsx("button", { className: "cool-toast-action ".concat(action.style || 'secondary'), onClick: function (e) { e.stopPropagation(); handleActionClick(action); }, children: action.label }, index)); }) })), toast.showProgress && toast.duration > 0 && (jsxRuntime.jsx("div", { className: "cool-toast-progress", style: { width: "".concat(progress, "%") }, role: "progressbar", "aria-valuenow": progress, "aria-valuemin": 0, "aria-valuemax": 100 }))] })); }; var Toaster = function (_a) { var _b = _a.position, position = _b === void 0 ? 'top-right' : _b, _c = _a.reverseOrder, reverseOrder = _c === void 0 ? false : _c, _d = _a.gutter, gutter = _d === void 0 ? 8 : _d, _e = _a.containerClassName, containerClassName = _e === void 0 ? '' : _e, _f = _a.containerStyle, containerStyle = _f === void 0 ? {} : _f, _g = _a.toastOptions, toastOptions = _g === void 0 ? {} : _g; var _h = useToast(), toasts = _h.toasts, removeToast = _h.removeToast, updateToast = _h.updateToast; var filteredToasts = toasts.filter(function (toast) { return toast.position === position; }); var sortedToasts = reverseOrder ? __spreadArray([], filteredToasts, true).reverse() : filteredToasts; return (jsxRuntime.jsx("div", { className: "cool-toaster ".concat(containerClassName), "data-position": position, style: __assign({ '--gutter': "".concat(gutter, "px") }, containerStyle), children: sortedToasts.map(function (toast) { return (jsxRuntime.jsx(ToastComponent, { toast: __assign(__assign({}, toast), toastOptions), onRemove: removeToast, onUpdate: updateToast }, toast.id)); }) })); }; exports.Toast = ToastComponent; exports.ToastProvider = ToastProvider; exports.Toaster = Toaster; exports.playToastSound = playToastSound; exports.shouldPlaySound = shouldPlaySound; exports.toast = toast; exports.useToast = useToast; //# sourceMappingURL=index.js.map