| // src/components/common/LoadingSpinner.tsx | |
| import { motion } from "framer-motion"; | |
| import { useState, useEffect } from "react"; | |
| import { Brain, Sparkles } from "lucide-react"; | |
| interface LoadingSpinnerProps { | |
| /** Size of the spinner */ | |
| size?: "sm" | "md" | "lg" | "xl"; | |
| /** Display full-screen overlay */ | |
| fullScreen?: boolean; | |
| /** Loading text to display (set to empty string to hide) */ | |
| text?: string; | |
| /** Additional CSS classes */ | |
| className?: string; | |
| } | |
| const sizeClasses = { | |
| sm: "w-6 h-6", | |
| md: "w-12 h-12", | |
| lg: "w-20 h-20", | |
| xl: "w-32 h-32", | |
| }; | |
| const textSizeClasses = { | |
| sm: "text-[10px] tracking-wider", | |
| md: "text-xs tracking-widest uppercase", | |
| lg: "text-sm tracking-widest uppercase font-semibold", | |
| xl: "text-base tracking-widest uppercase font-bold", | |
| }; | |
| export function LoadingSpinner({ | |
| size = "md", | |
| fullScreen = false, | |
| text = "Loading...", | |
| className = "", | |
| }: LoadingSpinnerProps) { | |
| const [statusIdx, setStatusIdx] = useState(0); | |
| const statuses = [ | |
| "Establishing secure network...", | |
| "Retrieving peer workspace elements...", | |
| "Syncing collaborative editors...", | |
| "Bootstrapping AI Co-Scientist models...", | |
| "Initializing workspace canvas..." | |
| ]; | |
| useEffect(() => { | |
| if (!fullScreen) return; | |
| const interval = setInterval(() => { | |
| setStatusIdx(prev => (prev + 1) % statuses.length); | |
| }, 1800); | |
| return () => clearInterval(interval); | |
| }, [fullScreen]); | |
| const spinnerContent = ( | |
| <div className={`flex flex-col items-center justify-center gap-6 ${className}`}> | |
| <div className={`relative ${sizeClasses[size]} flex items-center justify-center`}> | |
| {/* Outer Ring - Slow Clockwise */} | |
| <motion.div | |
| animate={{ rotate: 360 }} | |
| transition={{ | |
| duration: 2.2, | |
| repeat: Infinity, | |
| ease: "linear", | |
| }} | |
| className="absolute inset-0 rounded-full border border-transparent border-t-teal-500 border-r-indigo-500 opacity-80" | |
| style={{ filter: "drop-shadow(0 0 8px rgba(99, 102, 241, 0.4))" }} | |
| /> | |
| {/* Inner Ring - Fast Counter-Clockwise */} | |
| <motion.div | |
| animate={{ rotate: -360 }} | |
| transition={{ | |
| duration: 1.4, | |
| repeat: Infinity, | |
| ease: "linear", | |
| }} | |
| className="absolute inset-2 rounded-full border border-transparent border-t-pink-500 border-l-purple-500 opacity-90" | |
| style={{ filter: "drop-shadow(0 0 6px rgba(236, 72, 153, 0.4))" }} | |
| /> | |
| {/* Glowing Orb Aura */} | |
| <motion.div | |
| animate={{ scale: [0.9, 1.15, 0.9] }} | |
| transition={{ | |
| duration: 1.8, | |
| repeat: Infinity, | |
| ease: "easeInOut", | |
| }} | |
| className="absolute inset-4 rounded-full bg-gradient-to-tr from-teal-500/25 to-indigo-500/25 blur-sm" | |
| /> | |
| {/* Core Pulsing Icon */} | |
| <motion.div | |
| animate={{ scale: [0.85, 1.1, 0.85] }} | |
| transition={{ | |
| duration: 1.8, | |
| repeat: Infinity, | |
| ease: "easeInOut", | |
| }} | |
| className="relative z-10 text-white flex items-center justify-center bg-slate-950 rounded-full p-2 border border-white/10" | |
| > | |
| <Brain className="w-5 h-5 text-teal-400" /> | |
| </motion.div> | |
| </div> | |
| <div className="text-center space-y-2"> | |
| {text && ( | |
| <p className={`${textSizeClasses[size]} text-indigo-900/60 dark:text-indigo-200/60 font-mono tracking-widest animate-pulse`}> | |
| {text} | |
| </p> | |
| )} | |
| {fullScreen && ( | |
| <motion.p | |
| key={statusIdx} | |
| initial={{ opacity: 0, y: 5 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -5 }} | |
| className="text-[10px] text-slate-400 dark:text-slate-500 font-mono tracking-wide" | |
| > | |
| {statuses[statusIdx]} | |
| </motion.p> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| if (fullScreen) { | |
| return ( | |
| <div className="fixed inset-0 z-[100] flex items-center justify-center bg-slate-950/85 backdrop-blur-xl"> | |
| {/* Background Decorative Mesh grid */} | |
| <div className="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:14px_24px] pointer-events-none" /> | |
| {/* Spotlight aura */} | |
| <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[350px] h-[350px] rounded-full bg-indigo-500/10 blur-[80px] pointer-events-none" /> | |
| <div className="relative z-10 flex flex-col items-center"> | |
| <div className="mb-6 flex items-center gap-1.5 px-3 py-1 rounded-full bg-white/5 border border-white/10 text-white text-[9px] font-bold tracking-widest uppercase"> | |
| <Sparkles className="w-3 h-3 text-teal-400" /> IE Research Network | |
| </div> | |
| {spinnerContent} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return spinnerContent; | |
| } | |
Xet Storage Details
- Size:
- 5.02 kB
- Xet hash:
- 6e87be5d69b0ddd47d9148cfe400164dbdc7086085cbe091b256699b33485f87
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.