aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
319 lines (316 loc) • 9.45 kB
JavaScript
'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