225 lines
5.4 KiB
JavaScript
225 lines
5.4 KiB
JavaScript
|
|
/**
|
|||
|
|
* Notification System
|
|||
|
|
* Accessible toast notifications
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
(function () {
|
|||
|
|
"use strict";
|
|||
|
|
|
|||
|
|
class NotificationManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.container = null;
|
|||
|
|
this.notifications = new Map();
|
|||
|
|
this.init();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
init() {
|
|||
|
|
if (document.readyState === "loading") {
|
|||
|
|
document.addEventListener("DOMContentLoaded", () =>
|
|||
|
|
this.createContainer()
|
|||
|
|
);
|
|||
|
|
} else {
|
|||
|
|
this.createContainer();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
createContainer() {
|
|||
|
|
if (!document.body || this.container) return;
|
|||
|
|
|
|||
|
|
this.container = document.createElement("div");
|
|||
|
|
this.container.id = "notification-container";
|
|||
|
|
this.container.setAttribute("aria-live", "polite");
|
|||
|
|
this.container.setAttribute("aria-atomic", "true");
|
|||
|
|
this.container.className = "notification-container";
|
|||
|
|
|
|||
|
|
const style = document.createElement("style");
|
|||
|
|
style.textContent = `
|
|||
|
|
.notification-container {
|
|||
|
|
position: fixed;
|
|||
|
|
top: 80px;
|
|||
|
|
right: 20px;
|
|||
|
|
z-index: 10000;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 10px;
|
|||
|
|
max-width: 400px;
|
|||
|
|
pointer-events: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification {
|
|||
|
|
padding: 12px 20px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 12px;
|
|||
|
|
color: white;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
pointer-events: auto;
|
|||
|
|
animation: slideInRight 0.3s ease;
|
|||
|
|
min-width: 250px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification.removing {
|
|||
|
|
animation: slideOutRight 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-success {
|
|||
|
|
background: #10b981;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-error {
|
|||
|
|
background: #ef4444;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-info {
|
|||
|
|
background: #3b82f6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-warning {
|
|||
|
|
background: #f59e0b;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-icon {
|
|||
|
|
font-size: 18px;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-message {
|
|||
|
|
flex: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-close {
|
|||
|
|
background: transparent;
|
|||
|
|
border: none;
|
|||
|
|
color: white;
|
|||
|
|
cursor: pointer;
|
|||
|
|
padding: 4px;
|
|||
|
|
opacity: 0.8;
|
|||
|
|
transition: opacity 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification-close:hover {
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes slideInRight {
|
|||
|
|
from {
|
|||
|
|
transform: translateX(400px);
|
|||
|
|
opacity: 0;
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
transform: translateX(0);
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes slideOutRight {
|
|||
|
|
from {
|
|||
|
|
transform: translateX(0);
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
transform: translateX(400px);
|
|||
|
|
opacity: 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 640px) {
|
|||
|
|
.notification-container {
|
|||
|
|
right: 10px;
|
|||
|
|
left: 10px;
|
|||
|
|
max-width: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.notification {
|
|||
|
|
min-width: auto;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
document.head.appendChild(style);
|
|||
|
|
document.body.appendChild(this.container);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
show(message, type = "info", duration = 3000) {
|
|||
|
|
if (!this.container) this.createContainer();
|
|||
|
|
if (!this.container) return;
|
|||
|
|
|
|||
|
|
const id = Date.now() + Math.random();
|
|||
|
|
const notification = document.createElement("div");
|
|||
|
|
notification.className = `notification notification-${type}`;
|
|||
|
|
notification.setAttribute("role", "alert");
|
|||
|
|
|
|||
|
|
const icons = {
|
|||
|
|
success: "✓",
|
|||
|
|
error: "✕",
|
|||
|
|
info: "ℹ",
|
|||
|
|
warning: "⚠",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
notification.innerHTML = `
|
|||
|
|
<span class="notification-icon">${icons[type] || icons.info}</span>
|
|||
|
|
<span class="notification-message">${this.escapeHtml(message)}</span>
|
|||
|
|
<button class="notification-close" aria-label="Close notification">×</button>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
const closeBtn = notification.querySelector(".notification-close");
|
|||
|
|
closeBtn.addEventListener("click", () => this.remove(id));
|
|||
|
|
|
|||
|
|
this.container.appendChild(notification);
|
|||
|
|
this.notifications.set(id, notification);
|
|||
|
|
|
|||
|
|
if (duration > 0) {
|
|||
|
|
setTimeout(() => this.remove(id), duration);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
remove(id) {
|
|||
|
|
const notification = this.notifications.get(id);
|
|||
|
|
if (!notification) return;
|
|||
|
|
|
|||
|
|
notification.classList.add("removing");
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (notification.parentNode) {
|
|||
|
|
notification.parentNode.removeChild(notification);
|
|||
|
|
}
|
|||
|
|
this.notifications.delete(id);
|
|||
|
|
}, 300);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
escapeHtml(text) {
|
|||
|
|
const div = document.createElement("div");
|
|||
|
|
div.textContent = text;
|
|||
|
|
return div.innerHTML;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
success(message, duration) {
|
|||
|
|
return this.show(message, "success", duration);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
error(message, duration) {
|
|||
|
|
return this.show(message, "error", duration);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
info(message, duration) {
|
|||
|
|
return this.show(message, "info", duration);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
warning(message, duration) {
|
|||
|
|
return this.show(message, "warning", duration);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Create global instance
|
|||
|
|
window.Notifications = window.Notifications || new NotificationManager();
|
|||
|
|
|
|||
|
|
// Legacy compatibility
|
|||
|
|
window.showNotification = function (message, type = "info") {
|
|||
|
|
window.Notifications.show(message, type);
|
|||
|
|
};
|
|||
|
|
})();
|