214 lines
7.2 KiB
JavaScript
214 lines
7.2 KiB
JavaScript
import React from 'react';
|
|
import { Loader2, Zap } from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
|
|
/**
|
|
* Componente de Loading Overlay Premium
|
|
* Pode ser usado como overlay de tela cheia ou dentro de containers específicos
|
|
*
|
|
* @param {Object} props
|
|
* @param {boolean} props.isLoading - Estado de carregamento
|
|
* @param {string} [props.message] - Mensagem customizada
|
|
* @param {boolean} [props.fullScreen] - Se true, ocupa tela cheia
|
|
* @param {string} [props.variant] - Variante visual: 'default' | 'minimal' | 'premium'
|
|
*/
|
|
export const LoadingOverlay = ({
|
|
isLoading = false,
|
|
message = 'Carregando dados...',
|
|
fullScreen = false,
|
|
variant = 'premium'
|
|
}) => {
|
|
if (!isLoading) return null;
|
|
|
|
const containerClasses = fullScreen
|
|
? 'fixed inset-0 z-[100]'
|
|
: 'absolute inset-0 z-40';
|
|
|
|
// Variante Minimal - Simples e discreta
|
|
if (variant === 'minimal') {
|
|
return (
|
|
<AnimatePresence>
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className={`${containerClasses} bg-white/80 dark:bg-[#0a0a0a]/80 backdrop-blur-sm flex items-center justify-center`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<Loader2 className="w-5 h-5 text-emerald-500 animate-spin" strokeWidth={2.5} />
|
|
<span className="text-sm font-medium text-slate-700 dark:text-slate-300">{message}</span>
|
|
</div>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
);
|
|
}
|
|
|
|
// Variante Default - Padrão do sistema
|
|
if (variant === 'default') {
|
|
return (
|
|
<AnimatePresence>
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className={`${containerClasses} bg-white/90 dark:bg-[#0a0a0a]/90 backdrop-blur-md flex items-center justify-center`}
|
|
>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className="relative">
|
|
<div className="w-12 h-12 border-4 border-slate-200 dark:border-[#252525] border-t-emerald-500 rounded-full animate-spin"></div>
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
|
|
</div>
|
|
</div>
|
|
<span className="text-emerald-500 font-bold uppercase tracking-widest text-[10px] animate-pulse">
|
|
{message}
|
|
</span>
|
|
</div>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
);
|
|
}
|
|
|
|
// Variante Premium - Visual sofisticado com animações
|
|
return (
|
|
<AnimatePresence>
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
className={`${containerClasses} bg-gradient-to-br from-white/95 via-slate-50/95 to-emerald-50/95 dark:from-[#0a0a0a]/95 dark:via-[#0f0f0f]/95 dark:to-[#0a1410]/95 backdrop-blur-lg flex items-center justify-center`}
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.9, y: 20 }}
|
|
animate={{ scale: 1, y: 0 }}
|
|
transition={{
|
|
type: "spring",
|
|
stiffness: 260,
|
|
damping: 20
|
|
}}
|
|
className="flex flex-col items-center gap-6"
|
|
>
|
|
{/* Animated Icon Container */}
|
|
<div className="relative">
|
|
{/* Outer rotating ring */}
|
|
<motion.div
|
|
animate={{ rotate: 360 }}
|
|
transition={{
|
|
duration: 3,
|
|
repeat: Infinity,
|
|
ease: "linear"
|
|
}}
|
|
className="absolute -inset-6 border-2 border-emerald-500/20 border-t-emerald-500 rounded-full"
|
|
/>
|
|
|
|
{/* Middle pulsing ring */}
|
|
<motion.div
|
|
animate={{
|
|
scale: [1, 1.1, 1],
|
|
opacity: [0.5, 0.8, 0.5]
|
|
}}
|
|
transition={{
|
|
duration: 2,
|
|
repeat: Infinity,
|
|
ease: "easeInOut"
|
|
}}
|
|
className="absolute -inset-4 border border-emerald-400/30 rounded-full"
|
|
/>
|
|
|
|
{/* Center icon */}
|
|
<motion.div
|
|
animate={{
|
|
y: [0, -8, 0],
|
|
}}
|
|
transition={{
|
|
duration: 1.5,
|
|
repeat: Infinity,
|
|
ease: "easeInOut"
|
|
}}
|
|
className="w-16 h-16 bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-2xl flex items-center justify-center shadow-lg shadow-emerald-500/20 relative overflow-hidden"
|
|
>
|
|
{/* Shine effect */}
|
|
<motion.div
|
|
animate={{
|
|
x: ['-100%', '200%']
|
|
}}
|
|
transition={{
|
|
duration: 2,
|
|
repeat: Infinity,
|
|
repeatDelay: 1,
|
|
ease: "easeInOut"
|
|
}}
|
|
className="absolute inset-0 w-1/2 bg-gradient-to-r from-transparent via-white/30 to-transparent skew-x-12"
|
|
/>
|
|
<Zap size={32} className="text-white relative z-10" strokeWidth={2.5} />
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Text Content */}
|
|
<div className="flex flex-col items-center gap-2">
|
|
<motion.span
|
|
animate={{ opacity: [0.5, 1, 0.5] }}
|
|
transition={{
|
|
duration: 1.5,
|
|
repeat: Infinity,
|
|
ease: "easeInOut"
|
|
}}
|
|
className="text-emerald-600 dark:text-emerald-500 text-[11px] font-black uppercase tracking-[0.3em]"
|
|
>
|
|
Pralog System
|
|
</motion.span>
|
|
<span className="text-slate-600 dark:text-slate-400 text-[10px] font-bold uppercase tracking-widest">
|
|
{message}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Loading dots */}
|
|
<div className="flex gap-1.5">
|
|
{[0, 1, 2].map((i) => (
|
|
<motion.div
|
|
key={i}
|
|
animate={{
|
|
scale: [1, 1.3, 1],
|
|
opacity: [0.3, 1, 0.3]
|
|
}}
|
|
transition={{
|
|
duration: 1,
|
|
repeat: Infinity,
|
|
delay: i * 0.2,
|
|
ease: "easeInOut"
|
|
}}
|
|
className="w-2 h-2 bg-emerald-500 rounded-full"
|
|
/>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Componente de Loading Inline - Para uso dentro de cards e seções
|
|
*/
|
|
export const LoadingInline = ({ message = 'Carregando...', size = 'md' }) => {
|
|
const sizes = {
|
|
sm: { spinner: 'w-4 h-4', text: 'text-xs' },
|
|
md: { spinner: 'w-5 h-5', text: 'text-sm' },
|
|
lg: { spinner: 'w-6 h-6', text: 'text-base' }
|
|
};
|
|
|
|
const currentSize = sizes[size] || sizes.md;
|
|
|
|
return (
|
|
<div className="flex items-center justify-center gap-3 py-8">
|
|
<Loader2 className={`${currentSize.spinner} text-emerald-500 animate-spin`} strokeWidth={2.5} />
|
|
<span className={`${currentSize.text} font-medium text-slate-600 dark:text-slate-400`}>
|
|
{message}
|
|
</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoadingOverlay;
|