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