297 lines
11 KiB
JavaScript
297 lines
11 KiB
JavaScript
/**
|
|
* NOTIFICATION SYSTEM - Composant unifié pour toasts et alertes
|
|
* Fait partie du design system Notytex
|
|
*/
|
|
|
|
class NotificationSystem {
|
|
constructor(core, config = {}) {
|
|
this.core = core;
|
|
this.config = {
|
|
position: 'top-right',
|
|
duration: 3000,
|
|
maxVisible: 5,
|
|
animation: 'slide',
|
|
...config
|
|
};
|
|
|
|
this.notifications = new Map();
|
|
this.container = null;
|
|
this.nextId = 1;
|
|
}
|
|
|
|
init() {
|
|
this.createContainer();
|
|
this.core.components.set('notifications', this);
|
|
|
|
// Enregistrer dans le core pour accès global
|
|
this.core.on('notification:show', (event) => {
|
|
this.show(event.detail);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Créer le conteneur pour les notifications
|
|
*/
|
|
createContainer() {
|
|
if (this.container) return;
|
|
|
|
const positions = {
|
|
'top-right': 'fixed top-4 right-4 z-50',
|
|
'top-left': 'fixed top-4 left-4 z-50',
|
|
'bottom-right': 'fixed bottom-4 right-4 z-50',
|
|
'bottom-left': 'fixed bottom-4 left-4 z-50',
|
|
'top-center': 'fixed top-4 left-1/2 transform -translate-x-1/2 z-50',
|
|
'bottom-center': 'fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50'
|
|
};
|
|
|
|
this.container = this.core.utils.createElement('div', {
|
|
id: 'notytex-notifications',
|
|
className: `${positions[this.config.position]} space-y-2 pointer-events-none`
|
|
});
|
|
|
|
document.body.appendChild(this.container);
|
|
}
|
|
|
|
/**
|
|
* Afficher une notification
|
|
*/
|
|
show(options = {}) {
|
|
const notification = {
|
|
id: this.nextId++,
|
|
type: options.type || 'info',
|
|
title: options.title || '',
|
|
message: options.message || '',
|
|
duration: options.duration || this.config.duration,
|
|
actions: options.actions || [],
|
|
persistent: options.persistent || false,
|
|
...options
|
|
};
|
|
|
|
// Limiter le nombre de notifications visibles
|
|
if (this.notifications.size >= this.config.maxVisible) {
|
|
const oldestId = Array.from(this.notifications.keys())[0];
|
|
this.hide(oldestId);
|
|
}
|
|
|
|
this.notifications.set(notification.id, notification);
|
|
this.render(notification);
|
|
|
|
// Auto-masquage si non persistant
|
|
if (!notification.persistent && notification.duration > 0) {
|
|
setTimeout(() => {
|
|
this.hide(notification.id);
|
|
}, notification.duration);
|
|
}
|
|
|
|
return notification.id;
|
|
}
|
|
|
|
/**
|
|
* Afficher types spécifiques (méthodes de convenance)
|
|
*/
|
|
success(message, title = 'Succès', options = {}) {
|
|
return this.show({ type: 'success', message, title, ...options });
|
|
}
|
|
|
|
error(message, title = 'Erreur', options = {}) {
|
|
return this.show({ type: 'error', message, title, ...options });
|
|
}
|
|
|
|
warning(message, title = 'Attention', options = {}) {
|
|
return this.show({ type: 'warning', message, title, ...options });
|
|
}
|
|
|
|
info(message, title = 'Information', options = {}) {
|
|
return this.show({ type: 'info', message, title, ...options });
|
|
}
|
|
|
|
/**
|
|
* Masquer une notification
|
|
*/
|
|
hide(notificationId) {
|
|
const notification = this.notifications.get(notificationId);
|
|
if (!notification) return;
|
|
|
|
const element = document.getElementById(`notification-${notificationId}`);
|
|
if (element) {
|
|
// Animation de sortie
|
|
element.style.transition = 'all 300ms cubic-bezier(0.4, 0, 0.2, 1)';
|
|
element.style.transform = 'translateX(100%)';
|
|
element.style.opacity = '0';
|
|
|
|
setTimeout(() => {
|
|
if (element.parentNode) {
|
|
element.parentNode.removeChild(element);
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
this.notifications.delete(notificationId);
|
|
}
|
|
|
|
/**
|
|
* Masquer toutes les notifications
|
|
*/
|
|
hideAll() {
|
|
Array.from(this.notifications.keys()).forEach(id => {
|
|
this.hide(id);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Rendu d'une notification
|
|
*/
|
|
render(notification) {
|
|
const typeConfig = this.getTypeConfig(notification.type);
|
|
|
|
const element = this.core.utils.createElement('div', {
|
|
id: `notification-${notification.id}`,
|
|
className: `pointer-events-auto max-w-sm w-full ${typeConfig.bgColor} border ${typeConfig.borderColor} shadow-lg rounded-lg overflow-hidden transform transition-all duration-300 translate-x-full opacity-0`,
|
|
role: 'alert',
|
|
'aria-live': 'polite'
|
|
});
|
|
|
|
const content = this.core.utils.createElement('div', {
|
|
className: 'p-4'
|
|
});
|
|
|
|
const header = this.core.utils.createElement('div', {
|
|
className: 'flex items-start'
|
|
});
|
|
|
|
// Icône
|
|
const iconContainer = this.core.utils.createElement('div', {
|
|
className: 'flex-shrink-0'
|
|
});
|
|
iconContainer.innerHTML = typeConfig.icon;
|
|
|
|
// Contenu
|
|
const textContent = this.core.utils.createElement('div', {
|
|
className: 'ml-3 w-0 flex-1'
|
|
});
|
|
|
|
if (notification.title) {
|
|
const title = this.core.utils.createElement('p', {
|
|
className: `text-sm font-medium ${typeConfig.textColor}`
|
|
});
|
|
title.textContent = notification.title;
|
|
textContent.appendChild(title);
|
|
}
|
|
|
|
if (notification.message) {
|
|
const message = this.core.utils.createElement('p', {
|
|
className: `text-sm ${typeConfig.textColor} ${notification.title ? 'mt-1' : ''}`
|
|
});
|
|
message.textContent = notification.message;
|
|
textContent.appendChild(message);
|
|
}
|
|
|
|
// Actions
|
|
if (notification.actions && notification.actions.length > 0) {
|
|
const actionsContainer = this.core.utils.createElement('div', {
|
|
className: 'mt-3 flex space-x-2'
|
|
});
|
|
|
|
notification.actions.forEach(action => {
|
|
const button = this.core.utils.createElement('button', {
|
|
className: `text-sm font-medium ${typeConfig.actionColor} hover:${typeConfig.actionHoverColor} transition-colors`,
|
|
onclick: action.handler
|
|
});
|
|
button.textContent = action.text;
|
|
actionsContainer.appendChild(button);
|
|
});
|
|
|
|
textContent.appendChild(actionsContainer);
|
|
}
|
|
|
|
// Bouton de fermeture
|
|
const closeButton = this.core.utils.createElement('div', {
|
|
className: 'ml-4 flex-shrink-0 flex'
|
|
});
|
|
|
|
const closeBtn = this.core.utils.createElement('button', {
|
|
className: `rounded-md inline-flex ${typeConfig.textColor} hover:${typeConfig.closeHoverColor} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500`,
|
|
onclick: () => this.hide(notification.id)
|
|
});
|
|
|
|
closeBtn.innerHTML = `
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
</svg>
|
|
`;
|
|
|
|
closeButton.appendChild(closeBtn);
|
|
|
|
// Assemblage
|
|
header.appendChild(iconContainer);
|
|
header.appendChild(textContent);
|
|
header.appendChild(closeButton);
|
|
content.appendChild(header);
|
|
element.appendChild(content);
|
|
|
|
this.container.appendChild(element);
|
|
|
|
// Animation d'entrée
|
|
requestAnimationFrame(() => {
|
|
element.classList.remove('translate-x-full', 'opacity-0');
|
|
});
|
|
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* Configuration des types de notification
|
|
*/
|
|
getTypeConfig(type) {
|
|
const configs = {
|
|
success: {
|
|
bgColor: 'bg-green-50',
|
|
borderColor: 'border-green-200',
|
|
textColor: 'text-green-800',
|
|
actionColor: 'text-green-600',
|
|
actionHoverColor: 'text-green-500',
|
|
closeHoverColor: 'text-green-500',
|
|
icon: `<svg class="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
</svg>`
|
|
},
|
|
error: {
|
|
bgColor: 'bg-red-50',
|
|
borderColor: 'border-red-200',
|
|
textColor: 'text-red-800',
|
|
actionColor: 'text-red-600',
|
|
actionHoverColor: 'text-red-500',
|
|
closeHoverColor: 'text-red-500',
|
|
icon: `<svg class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>`
|
|
},
|
|
warning: {
|
|
bgColor: 'bg-orange-50',
|
|
borderColor: 'border-orange-200',
|
|
textColor: 'text-orange-800',
|
|
actionColor: 'text-orange-600',
|
|
actionHoverColor: 'text-orange-500',
|
|
closeHoverColor: 'text-orange-500',
|
|
icon: `<svg class="w-5 h-5 text-orange-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
|
</svg>`
|
|
},
|
|
info: {
|
|
bgColor: 'bg-blue-50',
|
|
borderColor: 'border-blue-200',
|
|
textColor: 'text-blue-800',
|
|
actionColor: 'text-blue-600',
|
|
actionHoverColor: 'text-blue-500',
|
|
closeHoverColor: 'text-blue-500',
|
|
icon: `<svg class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
|
</svg>`
|
|
}
|
|
};
|
|
|
|
return configs[type] || configs.info;
|
|
}
|
|
}
|
|
|
|
export default NotificationSystem; |