cosmo/frontend/src/components/Loading.tsx

112 lines
4.1 KiB
TypeScript

/**
* Loading component with green theme
*/
import { useEffect, useState } from 'react';
interface LoadingProps {
message?: string;
showProgress?: boolean;
}
export function Loading({ message = "正在加载深空探索器...", showProgress = true }: LoadingProps) {
const [dots, setDots] = useState('');
const [progress, setProgress] = useState(0);
// Animate dots
useEffect(() => {
const interval = setInterval(() => {
setDots(prev => prev.length >= 3 ? '' : prev + '.');
}, 500);
return () => clearInterval(interval);
}, []);
// Simulate progress
useEffect(() => {
if (!showProgress) return;
const interval = setInterval(() => {
setProgress(prev => {
if (prev >= 90) return prev; // Stop at 90% until real data loads
return prev + Math.random() * 10;
});
}, 300);
return () => clearInterval(interval);
}, [showProgress]);
return (
<div className="fixed inset-0 w-full h-full flex items-center justify-center bg-black z-50">
{/* Starfield background effect */}
<div className="absolute inset-0 overflow-hidden">
{[...Array(50)].map((_, i) => (
<div
key={i}
className="absolute w-1 h-1 bg-white rounded-full opacity-50"
style={{
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animation: `twinkle ${2 + Math.random() * 3}s infinite`,
animationDelay: `${Math.random() * 2}s`
}}
/>
))}
</div>
{/* Loading content */}
<div className="relative z-10 text-center">
{/* Spinning loader with green theme */}
<div className="mb-6 flex justify-center">
<div className="relative">
{/* Outer ring */}
<div className="w-24 h-24 rounded-full border-4 border-[#238636]/20"></div>
{/* Spinning ring */}
<div className="absolute inset-0 w-24 h-24 rounded-full border-4 border-transparent border-t-[#238636] border-r-[#238636] animate-spin"></div>
{/* Inner glow */}
<div className="absolute inset-0 w-24 h-24 rounded-full shadow-[0_0_30px_rgba(35,134,54,0.3)]"></div>
{/* Center icon */}
<div className="absolute inset-0 flex items-center justify-center">
<svg className="w-10 h-10 text-[#238636]" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z" />
</svg>
</div>
</div>
</div>
{/* Loading text with green theme */}
<div className="text-[#238636] text-xl font-medium mb-2">
{message}{dots}
</div>
{/* Subtitle */}
<div className="text-gray-400 text-sm mb-6">
</div>
{/* Progress bar */}
{showProgress && (
<div className="w-64 mx-auto">
<div className="h-1 bg-white/10 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-[#238636] to-[#2ea043] rounded-full transition-all duration-300 shadow-[0_0_10px_rgba(35,134,54,0.5)]"
style={{ width: `${progress}%` }}
/>
</div>
<div className="text-[#238636] text-xs mt-2">
{Math.round(progress)}%
</div>
</div>
)}
</div>
{/* Add twinkle animation */}
<style>{`
@keyframes twinkle {
0%, 100% { opacity: 0.2; }
50% { opacity: 0.8; }
}
`}</style>
</div>
);
}