feat(error): implement comprehensive error handling and loading states
- Add error boundary components with graceful fallbacks - Implement loading states with skeleton components - Create route-level suspense wrapper - Add error page with recovery options - Include error boundary demo for testing Error handling provides resilient user experience with clear feedback and recovery options when components fail. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
169
reactrebuild0825/src/components/loading/LoadingSpinner.tsx
Normal file
169
reactrebuild0825/src/components/loading/LoadingSpinner.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import React from 'react';
|
||||
|
||||
import { clsx } from 'clsx';
|
||||
|
||||
export interface LoadingSpinnerProps {
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
variant?: 'primary' | 'secondary' | 'accent' | 'muted';
|
||||
overlay?: boolean;
|
||||
text?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable loading spinner with glassmorphism styling
|
||||
* Provides smooth animations and multiple size/variant options
|
||||
*/
|
||||
export function LoadingSpinner({
|
||||
size = 'md',
|
||||
variant = 'primary',
|
||||
overlay = false,
|
||||
text,
|
||||
className
|
||||
}: LoadingSpinnerProps) {
|
||||
const sizeClasses = {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-6 h-6',
|
||||
lg: 'w-8 h-8',
|
||||
xl: 'w-12 h-12'
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'text-primary-500',
|
||||
secondary: 'text-secondary-500',
|
||||
accent: 'text-gold-500',
|
||||
muted: 'text-text-muted'
|
||||
};
|
||||
|
||||
const textSizeClasses = {
|
||||
sm: 'text-sm',
|
||||
md: 'text-base',
|
||||
lg: 'text-lg',
|
||||
xl: 'text-xl'
|
||||
};
|
||||
|
||||
const spinnerElement = (
|
||||
<div className="flex flex-col items-center justify-center gap-md">
|
||||
{/* Spinner SVG */}
|
||||
<svg
|
||||
className={clsx(
|
||||
'animate-spin',
|
||||
sizeClasses[size],
|
||||
variantClasses[variant],
|
||||
className
|
||||
)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* Loading Text */}
|
||||
{text && (
|
||||
<p className={clsx(
|
||||
'text-text-secondary animate-pulse',
|
||||
textSizeClasses[size]
|
||||
)}>
|
||||
{text}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
// Render as overlay if specified
|
||||
if (overlay) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center">
|
||||
<div className="bg-glass-bg border border-glass-border rounded-lg p-6xl shadow-glass-lg">
|
||||
{spinnerElement}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return spinnerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulse animation component for skeleton loading states
|
||||
*/
|
||||
export function PulseLoader({ className }: { className?: string }) {
|
||||
return (
|
||||
<div className={clsx('animate-pulse bg-glass-bg rounded', className)} />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shimmer effect component for advanced skeleton loading
|
||||
*/
|
||||
export function ShimmerLoader({
|
||||
className,
|
||||
children
|
||||
}: {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className={clsx('relative overflow-hidden', className)}>
|
||||
{children}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dots loading animation
|
||||
*/
|
||||
export function DotsLoader({
|
||||
size = 'md',
|
||||
variant = 'primary',
|
||||
className
|
||||
}: Pick<LoadingSpinnerProps, 'size' | 'variant' | 'className'>) {
|
||||
const dotSizeClasses = {
|
||||
sm: 'w-1 h-1',
|
||||
md: 'w-2 h-2',
|
||||
lg: 'w-3 h-3',
|
||||
xl: 'w-4 h-4'
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'bg-primary-500',
|
||||
secondary: 'bg-secondary-500',
|
||||
accent: 'bg-gold-500',
|
||||
muted: 'bg-text-muted'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx('flex items-center space-x-1', className)}>
|
||||
{[0, 1, 2].map((index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={clsx(
|
||||
'rounded-full animate-bounce',
|
||||
dotSizeClasses[size],
|
||||
variantClasses[variant]
|
||||
)}
|
||||
style={{
|
||||
animationDelay: `${index * 0.1}s`,
|
||||
animationDuration: '0.6s'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoadingSpinner;
|
||||
Reference in New Issue
Block a user