testes/src_2/components/shared/LoadingOverlay.jsx

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;