mirror of
https://github.com/tiennm99/HugoBlox-kit.git
synced 2026-10-14 02:15:09 +00:00
198 lines
6.7 KiB
JavaScript
198 lines
6.7 KiB
JavaScript
/**
|
|
* Notification/Toast System for Hugo Blox Kit
|
|
* Provides reusable notification functionality
|
|
*/
|
|
|
|
import {hugoEnvironment} from "@params";
|
|
|
|
// Debug mode based on environment
|
|
const isDebugMode = hugoEnvironment === "development";
|
|
|
|
/**
|
|
* Show a notification message using Tailwind styles
|
|
* @param {string} message - The message to display
|
|
* @param {string} type - The type of notification ('success', 'error', 'info', 'warning')
|
|
* @param {number} duration - Duration in milliseconds (default 3000)
|
|
* @returns {HTMLElement} - The notification element
|
|
*/
|
|
export function showNotification(message, type = "success", duration = 3000) {
|
|
// Get or create the notification container
|
|
const container = getOrCreateContainer();
|
|
|
|
// Remove any existing notifications with same message
|
|
const existingNotification = container.querySelector(".hb-notification");
|
|
if (existingNotification?.textContent?.includes(message)) {
|
|
existingNotification.remove();
|
|
}
|
|
|
|
// Create notification element
|
|
const notification = createNotificationElement(message, type);
|
|
|
|
// Add to container
|
|
container.appendChild(notification);
|
|
|
|
// Handle close button
|
|
const closeBtn = notification.querySelector(".hb-notification-close");
|
|
if (closeBtn) {
|
|
closeBtn.addEventListener("click", () => {
|
|
removeNotification(notification);
|
|
});
|
|
}
|
|
|
|
// Auto-remove after duration
|
|
if (duration > 0) {
|
|
setTimeout(() => {
|
|
removeNotification(notification);
|
|
}, duration);
|
|
}
|
|
|
|
return notification;
|
|
}
|
|
|
|
/**
|
|
* Get or create the notification container
|
|
* @returns {HTMLElement} - The container element
|
|
*/
|
|
function getOrCreateContainer() {
|
|
let container = document.getElementById("hb-notification-container");
|
|
|
|
if (!container) {
|
|
if (isDebugMode) {
|
|
console.warn("Notification container not found, creating fallback");
|
|
}
|
|
container = document.createElement("div");
|
|
container.id = "hb-notification-container";
|
|
container.className = "fixed top-20 right-4 z-[9999] pointer-events-none";
|
|
container.setAttribute("aria-live", "polite");
|
|
container.setAttribute("aria-atomic", "true");
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
/**
|
|
* Create a notification element
|
|
* @param {string} message - The message to display
|
|
* @param {string} type - The type of notification
|
|
* @returns {HTMLElement} - The notification element
|
|
*/
|
|
function createNotificationElement(message, type) {
|
|
const notification = document.createElement("div");
|
|
notification.setAttribute("role", "alert");
|
|
|
|
// Define colors for different types
|
|
const colors = {
|
|
success: {bg: "#10b981", bgClass: "bg-green-500"},
|
|
error: {bg: "#ef4444", bgClass: "bg-red-500"},
|
|
info: {bg: "#3b82f6", bgClass: "bg-blue-500"},
|
|
warning: {bg: "#f59e0b", bgClass: "bg-amber-500"},
|
|
};
|
|
|
|
const color = colors[type] || colors.info;
|
|
|
|
// Set classes
|
|
notification.className = `hb-notification pointer-events-auto flex items-center gap-2 px-4 py-3 text-white rounded-lg shadow-lg ${color.bgClass} animate-slide-in`;
|
|
|
|
// Add inline styles as fallback
|
|
notification.style.cssText = `
|
|
background-color: ${color.bg};
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
margin-bottom: 0.5rem;
|
|
pointer-events: auto;
|
|
`;
|
|
|
|
// Add icon based on type
|
|
const icons = {
|
|
success:
|
|
'<svg class="w-5 h-5 flex-shrink-0" 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"></path></svg>',
|
|
error:
|
|
'<svg class="w-5 h-5 flex-shrink-0" 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"></path></svg>',
|
|
info: '<svg class="w-5 h-5 flex-shrink-0" 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"></path></svg>',
|
|
warning:
|
|
'<svg class="w-5 h-5 flex-shrink-0" 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"></path></svg>',
|
|
};
|
|
|
|
const icon = icons[type] || icons.info;
|
|
|
|
notification.innerHTML = `
|
|
${icon}
|
|
<span class="text-sm font-medium">${message}</span>
|
|
<button class="hb-notification-close ml-2 text-white/80 hover:text-white transition-colors" aria-label="Close">
|
|
<svg class="w-4 h-4" 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"></path>
|
|
</svg>
|
|
</button>
|
|
`;
|
|
|
|
// Ensure animation styles exist
|
|
ensureAnimationStyles();
|
|
|
|
return notification;
|
|
}
|
|
|
|
/**
|
|
* Remove notification with animation
|
|
* @param {HTMLElement} notification - The notification element to remove
|
|
*/
|
|
export function removeNotification(notification) {
|
|
if (!notification || !notification.parentNode) return;
|
|
|
|
notification.classList.remove("animate-slide-in");
|
|
notification.classList.add("animate-slide-out");
|
|
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.remove();
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
/**
|
|
* Ensure animation styles are in the document
|
|
*/
|
|
function ensureAnimationStyles() {
|
|
if (!document.querySelector("#hb-notification-styles")) {
|
|
const style = document.createElement("style");
|
|
style.id = "hb-notification-styles";
|
|
style.textContent = `
|
|
@keyframes slide-in {
|
|
from {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
@keyframes slide-out {
|
|
from {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
.animate-slide-in {
|
|
animation: slide-in 0.3s ease-out;
|
|
}
|
|
.animate-slide-out {
|
|
animation: slide-out 0.3s ease-out;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
}
|
|
|
|
// Export default for convenience
|
|
export default showNotification;
|