- 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>
169 lines
3.8 KiB
TypeScript
169 lines
3.8 KiB
TypeScript
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; |