updateweb
This commit is contained in:
224
website/public/assets/js/notifications.js
Normal file
224
website/public/assets/js/notifications.js
Normal file
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user