Files
dgtlmoon afc64ea96a
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
Layout css (#3702)
* 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
2026-01-03 14:47:26 +01:00

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