UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

319 lines (316 loc) 9.45 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { GlassButton } from '../button/GlassButton.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { X, Info, AlertTriangle, AlertCircle, CheckCircle } from 'lucide-react'; import { useContext, useState, useEffect, createContext } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; // Toast context const ToastContext = /*#__PURE__*/createContext(null); const useToast = () => { const context = useContext(ToastContext); if (!context) { throw new Error("useToast must be used within a ToastProvider"); } return context; }; /** * GlassToast component * Individual toast notification */ const GlassToast = ({ // TODO: Integrate ContrastGuard for table cells, list items, badges, card titles, and other text content for WCAG AA compliance id, title, description, type = "info", duration = 5000, action, onClose, onDismiss, className }) => { const [isVisible, setIsVisible] = useState(true); const [progress, setProgress] = useState(100); const [paused, setPaused] = useState(false); // Auto-dismiss timer useEffect(() => { if (duration <= 0) return; const interval = setInterval(() => { if (paused) return; setProgress(prev => { if (prev <= 0) { handleDismiss(); return 0; } return prev - 100 / (duration / 100); }); }, 100); return () => clearInterval(interval); }, [duration, paused]); const handleDismiss = () => { setIsVisible(false); setTimeout(() => { onDismiss?.(id); onClose?.(); }, 300); // Wait for exit animation }; const getTypeStyles = () => { switch (type) { case "success": return { icon: jsx(CheckCircle, { className: 'w-5 h-5 text-primary' }), borderColor: "border-green-400/30", bgColor: "bg-green-500/10" }; case "error": return { icon: jsx(AlertCircle, { className: 'w-5 h-5 text-primary' }), borderColor: "border-red-400/30", bgColor: "bg-red-500/10" }; case "warning": return { icon: jsx(AlertTriangle, { className: 'w-5 h-5 text-primary' }), borderColor: "border-yellow-400/30", bgColor: "bg-yellow-500/10" }; default: return { icon: jsx(Info, { className: 'w-5 h-5 text-primary' }), borderColor: "border-blue-400/30", bgColor: "bg-blue-500/10" }; } }; const { icon, borderColor, bgColor } = getTypeStyles(); if (!isVisible) return null; return jsx(MotionFramer, { "data-glass-component": true, preset: "slideRight", duration: 300, children: jsxs(OptimizedGlassCore, { elevation: "level4", intensity: "strong", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", liftOnHover: true, className: cn("relative min-w-80 max-w-md glass-p-4 glass-backdrop-blur-md", "border border-white/20 shadow-2xl", bgColor, className), onMouseEnter: () => setPaused(true), onMouseLeave: () => setPaused(false), children: [duration > 0 && jsx("div", { className: 'absolute top-0 left-0 right-0 h-1 glass-surface-subtle/20 glass-radius-t-lg overflow-hidden', children: jsx("div", { className: cn("h-full transition-all duration-100 ease-linear", borderColor.replace("border-", "bg-")), style: { width: `${progress}%` } }) }), jsxs("div", { className: "glass-flex glass-items-start glass-gap-3", children: [jsx("div", { className: "glass-flex-shrink-0 glass-mt-0-5", children: icon }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [title && jsx("h4", { className: 'text-primary font-medium glass-text-sm leading-tight mb-1', children: title }), description && jsx("p", { className: 'text-primary/80 glass-text-sm leading-relaxed', children: description }), action && jsx(GlassToastAction, { onClick: action.onClick, className: 'mt-3', children: action.label })] }), jsx(GlassButton, { onClick: handleDismiss, className: 'glass-flex-shrink-0 glass-p-1 glass-radius-md hover:glass-surface-subtle/10 transition-colors duration-200', "aria-label": "Close toast", children: jsx(X, { className: 'w-4 h-4 text-primary/60 hover:text-primary' }) })] })] }) }); }; /** * GlassToastProvider component * Provides toast context and manages toast state */ const GlassToastProvider = ({ children, duration = 5000, maxToasts = 5, position = "bottom-right" }) => { const [toasts, setToasts] = useState([]); const addToast = toast => { const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const newToast = { ...toast, id, duration: toast.duration ?? duration }; setToasts(prev => { const updated = [newToast, ...prev]; return updated.slice(0, maxToasts); }); // Auto-remove after duration if (newToast.duration && newToast.duration > 0) { setTimeout(() => { removeToast(id); }, newToast.duration); } return id; }; const removeToast = id => { setToasts(prev => prev.filter(toast => toast.id !== id)); }; const updateToast = (id, updates) => { setToasts(prev => prev.map(toast => toast.id === id ? { ...toast, ...updates } : toast)); }; const contextValue = { toasts, addToast, removeToast, updateToast }; return jsxs(ToastContext.Provider, { value: contextValue, children: [children, jsx(GlassToastViewport, { position: position })] }); }; /** * GlassToastViewport component * Container for displaying toasts */ const GlassToastViewport = ({ className, hotkey = ["altKey", "KeyT"], position = "bottom-right" }) => { const { toasts, removeToast } = useToast(); // Keyboard shortcut to focus viewport useEffect(() => { const handleKeyDown = e => { const shouldFocus = hotkey.every(key => { if (key === "altKey") return e.altKey; if (key === "ctrlKey") return e.ctrlKey; if (key === "shiftKey") return e.shiftKey; if (key === "metaKey") return e.metaKey; return e.key === key; }); if (shouldFocus) { // Focus first toast const firstToast = document.querySelector("[data-toast-id]"); if (firstToast instanceof HTMLElement) { firstToast.focus(); } } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [hotkey]); const positionClasses = { "top-left": "top-4 left-4", "top-right": "top-4 right-4", "bottom-left": "bottom-4 left-4", "bottom-right": "bottom-4 right-4", "top-center": "top-4 left-1/2 -translate-x-1/2", "bottom-center": "bottom-4 left-1/2 -translate-x-1/2" }; return jsx("div", { className: cn("fixed z-[9999] flex flex-col glass-gap-3 pointer-events-none", positionClasses[position], className), children: toasts.map(toast => jsx("div", { "data-toast-id": toast.id, className: 'pointer-events-auto', children: jsx(GlassToast, { ...toast, onDismiss: removeToast }) }, toast.id)) }); }; /** * GlassToastAction component * Action button for toast */ const GlassToastAction = ({ children, className, onClick }) => { return jsx(GlassButton, { onClick: onClick, className: cn("inline-flex items-center justify-center glass-px-3 glass-py-1.5", "glass-text-xs font-medium glass-text-primary/90", "bg-black/30 hover:bg-black/40 border border-white/30 hover:border-white/40", "glass-radius-md transition-all duration-200", "focus:outline-none focus:ring-2 focus:ring-white/30", className), children: children }); }; /** * Hook for creating different types of toasts */ const useToastActions = () => { const { addToast } = useToast(); return { success: (title, description, options) => addToast({ ...options, title, description, type: "success" }), error: (title, description, options) => addToast({ ...options, title, description, type: "error" }), warning: (title, description, options) => addToast({ ...options, title, description, type: "warning" }), info: (title, description, options) => addToast({ ...options, title, description, type: "info" }), custom: toast => addToast(toast) }; }; export { GlassToast, GlassToastAction, GlassToastProvider, GlassToastViewport, useToast, useToastActions }; //# sourceMappingURL=GlassToast.js.map