mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-02-04 21:36:00 +00:00
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
* Adding test sidebar * Fixing mobile mobile menu and adding action menu * Adding toast * Adding toast libraries * Fix styling * More styling improvements * Improving translations * More translation improvements * More translation updates
276 lines
7.1 KiB
JavaScript
276 lines
7.1 KiB
JavaScript
/**
|
|
* Toast - Modern toast notification system
|
|
* Inspired by Toastify, Notyf, and React Hot Toast
|
|
*
|
|
* Usage:
|
|
* Toast.success('Operation completed!');
|
|
* Toast.error('Something went wrong');
|
|
* Toast.info('Here is some information');
|
|
* Toast.warning('Warning message');
|
|
* Toast.show('Custom message', { type: 'success', duration: 3000 });
|
|
*
|
|
* License: MIT
|
|
*/
|
|
|
|
(function(window) {
|
|
'use strict';
|
|
|
|
// Toast configuration
|
|
const defaultConfig = {
|
|
duration: 5000, // Auto-dismiss after 5 seconds (0 = no auto-dismiss)
|
|
position: 'top-center', // top-right, top-center, top-left, bottom-right, bottom-center, bottom-left
|
|
closeButton: true, // Show close button
|
|
progressBar: true, // Show progress bar
|
|
pauseOnHover: true, // Pause auto-dismiss on hover
|
|
maxToasts: 5, // Maximum toasts to show at once
|
|
offset: '20px', // Offset from edge
|
|
zIndex: 10000, // Z-index for toast container
|
|
};
|
|
|
|
let config = { ...defaultConfig };
|
|
let toastCount = 0;
|
|
let container = null;
|
|
|
|
/**
|
|
* Initialize toast system with custom config
|
|
*/
|
|
function init(userConfig = {}) {
|
|
config = { ...defaultConfig, ...userConfig };
|
|
createContainer();
|
|
}
|
|
|
|
/**
|
|
* Create toast container if it doesn't exist
|
|
*/
|
|
function createContainer() {
|
|
if (container) return;
|
|
|
|
container = document.createElement('div');
|
|
container.className = `toast-container toast-${config.position}`;
|
|
container.style.zIndex = config.zIndex;
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
/**
|
|
* Show a toast notification
|
|
*/
|
|
function show(message, options = {}) {
|
|
createContainer();
|
|
|
|
const toast = createToastElement(message, options);
|
|
|
|
// Limit number of toasts
|
|
const existingToasts = container.querySelectorAll('.toast');
|
|
if (existingToasts.length >= config.maxToasts) {
|
|
removeToast(existingToasts[0]);
|
|
}
|
|
|
|
// Add to container
|
|
container.appendChild(toast);
|
|
|
|
// Trigger animation
|
|
requestAnimationFrame(() => {
|
|
toast.classList.add('toast-show');
|
|
});
|
|
|
|
// Auto-dismiss
|
|
if (options.duration !== 0 && (options.duration || config.duration) > 0) {
|
|
setupAutoDismiss(toast, options.duration || config.duration);
|
|
}
|
|
|
|
return {
|
|
dismiss: () => removeToast(toast)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create toast DOM element
|
|
*/
|
|
function createToastElement(message, options) {
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${options.type || 'default'}`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.setAttribute('aria-live', 'polite');
|
|
|
|
// Icon
|
|
const icon = createIcon(options.type || 'default');
|
|
if (icon) {
|
|
toast.appendChild(icon);
|
|
}
|
|
|
|
// Message
|
|
const messageEl = document.createElement('div');
|
|
messageEl.className = 'toast-message';
|
|
messageEl.textContent = message;
|
|
toast.appendChild(messageEl);
|
|
|
|
// Close button
|
|
if (options.closeButton !== false && config.closeButton) {
|
|
const closeBtn = document.createElement('button');
|
|
closeBtn.className = 'toast-close';
|
|
closeBtn.innerHTML = '×';
|
|
closeBtn.setAttribute('aria-label', 'Close');
|
|
closeBtn.onclick = () => removeToast(toast);
|
|
toast.appendChild(closeBtn);
|
|
}
|
|
|
|
// Progress bar
|
|
if (options.progressBar !== false && config.progressBar && (options.duration || config.duration) > 0) {
|
|
const progressBar = document.createElement('div');
|
|
progressBar.className = 'toast-progress';
|
|
toast.appendChild(progressBar);
|
|
toast._progressBar = progressBar;
|
|
}
|
|
|
|
return toast;
|
|
}
|
|
|
|
/**
|
|
* Create icon based on toast type
|
|
*/
|
|
function createIcon(type) {
|
|
const iconEl = document.createElement('div');
|
|
iconEl.className = 'toast-icon';
|
|
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
svg.setAttribute('viewBox', '0 0 24 24');
|
|
svg.setAttribute('fill', 'none');
|
|
svg.setAttribute('stroke', 'currentColor');
|
|
svg.setAttribute('stroke-width', '2');
|
|
|
|
let path = '';
|
|
switch (type) {
|
|
case 'success':
|
|
path = 'M20 6L9 17l-5-5';
|
|
break;
|
|
case 'error':
|
|
path = 'M18 6L6 18M6 6l12 12';
|
|
break;
|
|
case 'warning':
|
|
path = 'M12 9v4m0 4h.01M12 2a10 10 0 100 20 10 10 0 000-20z';
|
|
svg.setAttribute('stroke-width', '1.5');
|
|
break;
|
|
case 'info':
|
|
path = 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z';
|
|
svg.setAttribute('stroke-width', '1.5');
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
pathEl.setAttribute('d', path);
|
|
pathEl.setAttribute('stroke-linecap', 'round');
|
|
pathEl.setAttribute('stroke-linejoin', 'round');
|
|
svg.appendChild(pathEl);
|
|
iconEl.appendChild(svg);
|
|
|
|
return iconEl;
|
|
}
|
|
|
|
/**
|
|
* Setup auto-dismiss with progress bar
|
|
*/
|
|
function setupAutoDismiss(toast, duration) {
|
|
let startTime = Date.now();
|
|
let remainingTime = duration;
|
|
let isPaused = false;
|
|
let animationFrame;
|
|
|
|
function updateProgress() {
|
|
if (isPaused) return;
|
|
|
|
const elapsed = Date.now() - startTime;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
|
|
if (toast._progressBar) {
|
|
toast._progressBar.style.transform = `scaleX(${1 - progress})`;
|
|
}
|
|
|
|
if (progress >= 1) {
|
|
removeToast(toast);
|
|
} else {
|
|
animationFrame = requestAnimationFrame(updateProgress);
|
|
}
|
|
}
|
|
|
|
// Pause on hover
|
|
if (config.pauseOnHover) {
|
|
toast.addEventListener('mouseenter', () => {
|
|
isPaused = true;
|
|
remainingTime = duration - (Date.now() - startTime);
|
|
cancelAnimationFrame(animationFrame);
|
|
});
|
|
|
|
toast.addEventListener('mouseleave', () => {
|
|
isPaused = false;
|
|
startTime = Date.now();
|
|
duration = remainingTime;
|
|
animationFrame = requestAnimationFrame(updateProgress);
|
|
});
|
|
}
|
|
|
|
animationFrame = requestAnimationFrame(updateProgress);
|
|
}
|
|
|
|
/**
|
|
* Remove toast with animation
|
|
*/
|
|
function removeToast(toast) {
|
|
if (!toast || !toast.parentElement) return;
|
|
|
|
toast.classList.add('toast-hide');
|
|
|
|
// Remove after animation
|
|
setTimeout(() => {
|
|
if (toast.parentElement) {
|
|
toast.parentElement.removeChild(toast);
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
// Convenience methods
|
|
function success(message, options = {}) {
|
|
return show(message, { ...options, type: 'success' });
|
|
}
|
|
|
|
function error(message, options = {}) {
|
|
return show(message, { ...options, type: 'error' });
|
|
}
|
|
|
|
function warning(message, options = {}) {
|
|
return show(message, { ...options, type: 'warning' });
|
|
}
|
|
|
|
function info(message, options = {}) {
|
|
return show(message, { ...options, type: 'info' });
|
|
}
|
|
|
|
/**
|
|
* Clear all toasts
|
|
*/
|
|
function clear() {
|
|
if (!container) return;
|
|
const toasts = container.querySelectorAll('.toast');
|
|
toasts.forEach(removeToast);
|
|
}
|
|
|
|
// Public API
|
|
window.Toast = {
|
|
init,
|
|
show,
|
|
success,
|
|
error,
|
|
warning,
|
|
info,
|
|
clear,
|
|
version: '1.0.0'
|
|
};
|
|
|
|
// Auto-initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
init();
|
|
});
|
|
|
|
})(window);
|