UNPKG

aura-glass

Version:

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

470 lines (467 loc) 15.5 kB
import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; import React, { Component, useState, useEffect } 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 '../primitives/motion/MotionFramer.js'; import { getSafeWindow, isBrowser, getSafeNavigator } from './env.js'; /** * Production-ready error boundary with glassmorphism styling */ class GlassErrorBoundary extends Component { constructor(props) { super(props); this.resetTimeoutId = null; this.resetErrorBoundary = () => { const { onReset } = this.props; this.setState({ hasError: false, error: null, errorInfo: null, errorId: "", retryCount: 0 }); if (this.resetTimeoutId) { clearTimeout(this.resetTimeoutId); this.resetTimeoutId = null; } onReset?.(); }; this.handleRetry = () => { const { maxRetries = 3 } = this.props; const { retryCount } = this.state; if (retryCount >= maxRetries) { return; } this.setState(prevState => ({ ...prevState, hasError: false, error: null, errorInfo: null, retryCount: prevState.retryCount + 1 })); this.props.onReset?.(); }; this.state = { hasError: false, error: null, errorInfo: null, errorId: "", retryCount: 0 }; } static getDerivedStateFromError(error) { const errorId = `error-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; return { hasError: true, error, errorId }; } componentDidCatch(error, errorInfo) { const { onError, componentName = "Unknown" } = this.props; const { errorId } = this.state; // Enhanced error info const nav = getSafeNavigator(); const win = getSafeWindow(); const enhancedErrorInfo = { ...errorInfo, componentName, timestamp: new Date().toISOString(), userAgent: nav?.userAgent, url: win?.location.href }; this.setState({ errorInfo }); // Report error onError?.(error, errorInfo, errorId); // Development logging if (process.env.NODE_ENV === "development") { // Use structured logging in development const errorLog = { type: "ERROR_BOUNDARY", componentName, errorId, error: error.message, stack: error.stack, componentStack: errorInfo.componentStack, timestamp: enhancedErrorInfo.timestamp }; console.group(`🚨 Error Boundary: ${componentName} (${errorId})`); console.table(errorLog); console.groupEnd(); } // Production error reporting (you would integrate with your error service) if (process.env.NODE_ENV === "production") { this.reportErrorToService(error, enhancedErrorInfo, errorId); } } componentDidUpdate(prevProps) { const { resetOnPropsChange, children } = this.props; const { hasError } = this.state; // Reset error state if props changed and resetOnPropsChange is enabled if (hasError && resetOnPropsChange && prevProps.children !== children) { this.resetErrorBoundary(); } } componentWillUnmount() { if (this.resetTimeoutId) { clearTimeout(this.resetTimeoutId); } } async reportErrorToService(error, errorInfo, errorId) { try { // Example error reporting - replace with your actual service const errorReport = { errorId, message: error.message, stack: error.stack, componentStack: errorInfo.componentStack, timestamp: errorInfo.timestamp, userAgent: errorInfo.userAgent, url: errorInfo.url, componentName: errorInfo.componentName }; // Production error reporting would be handled here // Example: await errorTrackingService.report(errorReport); // Example: Send to error tracking service // await fetch('/api/errors', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify(errorReport), // }); } catch (reportingError) { // Silent fail in production, log in development if (process.env.NODE_ENV === "development") { console.warn("Failed to report error:", reportingError); } } } render() { const { hasError, error, errorInfo, errorId, retryCount } = this.state; const { children, fallback: CustomFallback, maxRetries = 3 } = this.props; if (hasError && error && errorInfo) { // Use custom fallback if provided if (CustomFallback) { return jsx(CustomFallback, { error: error, errorInfo: errorInfo, retry: this.handleRetry, errorId: errorId }); } // Default glassmorphism error UI return jsx(OptimizedGlassCore, { className: 'glass-p-8 glass-m-4 max-w-md glass-mx-auto text-center', intensity: "medium", elevation: "level2", children: jsxs("div", { className: "glass-auto-gap glass-auto-gap-lg", children: [jsx("div", { className: 'w-16 h-16 glass-mx-auto glass-surface-danger/20 glass-radius-full glass-flex glass-items-center glass-justify-center', children: jsx("svg", { className: 'w-8 h-8 text-red-400', fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" }) }) }), jsxs("div", { children: [jsx("h3", { className: 'glass-text-lg font-semibold glass-text-primary mb-2', children: "Oops! Something went wrong" }), jsx("p", { className: "glass-text-primary/70 glass-text-sm", children: error.message || "An unexpected error occurred" })] }), jsxs("p", { className: 'glass-text-xs glass-text-primary/50 font-mono', children: ["Error ID: ", errorId] }), jsxs("div", { className: "glass-flex glass-flex-col glass-auto-gap glass-auto-gap-sm", children: [retryCount < maxRetries && jsxs("button", { onClick: this.handleRetry, className: 'glass-px-4 glass-py-2 glass-surface-primary/20 text-blue-300 glass-radius hover:bg-blue-500/30 transition-colors glass-focus glass-touch-target glass-contrast-guard', children: ["Try Again (", maxRetries - retryCount, " attempts left)"] }), jsx("button", { onClick: () => getSafeWindow()?.location.reload?.(), className: 'glass-px-4 glass-py-2 glass-surface-subtle/10 glass-text-primary/70 glass-radius hover:bg-white/20 transition-colors glass-focus glass-touch-target glass-contrast-guard', children: "Reload Page" }), process.env.NODE_ENV === "development" && jsxs("details", { className: 'mt-4 text-left', children: [jsx("summary", { className: 'cursor-pointer glass-text-primary/50 glass-text-xs', children: "Show Error Details" }), jsx("pre", { className: 'mt-2 glass-p-3 glass-surface-dark/20 glass-radius glass-text-xs glass-text-primary/70 overflow-auto max-h-32', children: error.stack }), jsx("pre", { className: 'mt-2 glass-p-3 glass-surface-dark/20 glass-radius glass-text-xs glass-text-primary/70 overflow-auto max-h-32', children: errorInfo.componentStack })] })] })] }) }); } return children; } } /** * HOC for wrapping components with error boundary */ function withGlassErrorBoundary(Component, errorBoundaryProps) { const WrappedComponent = /*#__PURE__*/React.forwardRef((props, ref) => jsx(GlassErrorBoundary, { ...errorBoundaryProps, children: jsx(Component, { ...props, ref: ref }) })); WrappedComponent.displayName = `withGlassErrorBoundary(${Component.displayName || Component.name})`; return WrappedComponent; } /** * Error boundary specifically for async operations */ class GlassAsyncErrorBoundary extends Component { constructor(props) { super(props); this.timeoutId = null; this.state = { hasError: false, error: null, errorInfo: null, errorId: "", retryCount: 0, isTimeout: false }; } componentDidMount() { const { timeout = 30000 } = this.props; if (timeout > 0) { this.timeoutId = setTimeout(() => { this.setState({ hasError: true, error: new Error("Operation timed out"), errorInfo: { componentStack: "Async operation timeout" }, errorId: `timeout-${Date.now()}`, isTimeout: true }); }, timeout); } } componentWillUnmount() { if (this.timeoutId) { clearTimeout(this.timeoutId); } } static getDerivedStateFromError(error) { return { hasError: true, error, errorId: `async-error-${Date.now()}` }; } componentDidCatch(error, errorInfo) { const { onError } = this.props; const { errorId } = this.state; if (this.timeoutId) { clearTimeout(this.timeoutId); } this.setState({ errorInfo }); onError?.(error, errorInfo, errorId); } render() { const { hasError, error, isTimeout } = this.state; const { children } = this.props; if (hasError && error) { return jsx(OptimizedGlassCore, { className: 'glass-p-6 glass-m-4 text-center', intensity: "medium", elevation: "level1", children: jsxs("div", { className: "glass-auto-gap glass-auto-gap-lg", children: [jsx("div", { className: 'w-12 h-12 glass-mx-auto glass-surface-warning/20 glass-radius-full glass-flex glass-items-center glass-justify-center', children: isTimeout ? jsx("svg", { className: 'w-6 h-6 text-yellow-400', fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) }) : jsx("svg", { className: 'w-6 h-6 text-yellow-400', fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" }) }) }), jsxs("div", { children: [jsx("h3", { className: 'glass-text-lg font-semibold glass-text-primary mb-2', children: isTimeout ? "Request Timed Out" : "Loading Failed" }), jsx("p", { className: "glass-text-primary/70 glass-text-sm", children: isTimeout ? "The operation took too long to complete" : error.message || "Failed to load content" })] }), jsx("button", { onClick: () => getSafeWindow()?.location.reload?.(), className: 'glass-px-4 glass-py-2 glass-surface-warning/20 text-yellow-300 glass-radius hover:bg-yellow-500/30 transition-colors glass-focus glass-touch-target glass-contrast-guard', children: "Reload Page" })] }) }); } return children; } } /** * Lightweight error boundary for non-critical components */ const GlassLightErrorBoundary = ({ children, fallback, onError }) => { const [hasError, setHasError] = useState(false); useEffect(() => { const handleError = event => { setHasError(true); onError?.(event.error); }; const handleUnhandledRejection = event => { setHasError(true); onError?.(new Error(event.reason)); }; if (!isBrowser()) return; const win = getSafeWindow(); if (!win) return; win.addEventListener("error", handleError); win.addEventListener("unhandledrejection", handleUnhandledRejection); return () => { win.removeEventListener("error", handleError); win.removeEventListener("unhandledrejection", handleUnhandledRejection); }; }, [onError]); if (hasError) { return jsx("div", { className: 'glass-p-4 glass-surface-danger/10 glass-border glass-border-red-500/20 glass-radius text-center', children: fallback || jsx("p", { className: 'text-red-400 glass-text-sm', children: "This component failed to load" }) }); } return jsx(Fragment, { children: children }); }; /** * Error boundary specifically for glass components */ const GlassComponentErrorBoundary = ({ children, componentName = "GlassComponent" }) => { return jsx(GlassErrorBoundary, { componentName: componentName, fallback: ({ error, retry, errorId }) => jsx(OptimizedGlassCore, { className: 'glass-p-4 text-center glass-border glass-border-red-500/20', intensity: "subtle", elevation: "level1", children: jsxs("div", { className: "glass-auto-gap glass-auto-gap-md", children: [jsx("div", { className: 'w-10 h-10 glass-mx-auto glass-surface-danger/20 glass-radius-full glass-flex glass-items-center glass-justify-center', children: jsx("span", { className: 'text-red-400 glass-text-sm', children: "\u26A0" }) }), jsxs("div", { children: [jsxs("h4", { className: 'text-red-400 font-medium glass-text-sm', children: [componentName, " Error"] }), jsx("p", { className: 'text-red-300/70 glass-text-xs mt-1', children: error.message })] }), jsx("button", { onClick: retry, className: 'glass-px-3 glass-py-1 glass-surface-danger/20 text-red-300 glass-radius glass-text-xs hover:bg-red-500/30 transition-colors glass-focus glass-touch-target glass-contrast-guard', children: "Retry" }), process.env.NODE_ENV === "development" && jsxs("p", { className: 'text-red-400/50 glass-text-xs font-mono', children: ["ID: ", errorId] })] }) }), children: children }); }; export { GlassAsyncErrorBoundary, GlassComponentErrorBoundary, GlassErrorBoundary, GlassLightErrorBoundary, withGlassErrorBoundary }; //# sourceMappingURL=errorBoundary.js.map