import { Loader2, CheckCircle, AlertCircle, X } from 'lucide-react';
import { useState, useEffect } from 'react';
// Animated Loading Spinner
export function LoadingSpinner({ size = 'md', className = '' }) {
const sizeClasses = {
sm: 'h-5 w-5',
md: 'h-8 w-8',
lg: 'h-12 w-12',
xl: 'h-16 w-16'
};
return (
);
}
// Full page loading
export function PageLoader() {
return (
);
}
// Skeleton loader for cards
export function CardSkeleton({ count = 1 }) {
return (
<>
{Array.from({ length: count }).map((_, i) => (
))}
>
);
}
// Skeleton loader for list items
export function ListSkeleton({ count = 5 }) {
return (
{Array.from({ length: count }).map((_, i) => (
))}
);
}
// Toast notification component
export function Toast({ message, type = 'success', onClose, duration = 3000 }) {
const [isVisible, setIsVisible] = useState(true);
const [isExiting, setIsExiting] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
handleClose();
}, duration);
return () => clearTimeout(timer);
}, [duration]);
const handleClose = () => {
setIsExiting(true);
setTimeout(() => {
setIsVisible(false);
onClose?.();
}, 200);
};
if (!isVisible) return null;
const typeStyles = {
success: 'bg-green-50 border-green-200 text-green-800',
error: 'bg-red-50 border-red-200 text-red-800',
warning: 'bg-amber-50 border-amber-200 text-amber-800',
info: 'bg-blue-50 border-blue-200 text-blue-800'
};
const icons = {
success: ,
error: ,
warning: ,
info:
};
return (
);
}
// Animated counter component
export function AnimatedCounter({ value, duration = 1000 }) {
const [displayValue, setDisplayValue] = useState(0);
useEffect(() => {
if (typeof value !== 'number') {
setDisplayValue(value);
return;
}
let startTime;
const startValue = displayValue;
const endValue = value;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / duration, 1);
// Ease out
const easeOut = 1 - Math.pow(1 - progress, 3);
const currentValue = Math.round(startValue + (endValue - startValue) * easeOut);
setDisplayValue(currentValue);
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}, [value, duration]);
return {displayValue};
}
// Animated presence wrapper
export function AnimatedPresence({ children, show, className = '' }) {
const [shouldRender, setShouldRender] = useState(show);
useEffect(() => {
if (show) setShouldRender(true);
}, [show]);
const handleAnimationEnd = () => {
if (!show) setShouldRender(false);
};
if (!shouldRender) return null;
return (
{children}
);
}
// Ripple button wrapper
export function RippleButton({ children, onClick, className = '', ...props }) {
const handleClick = (e) => {
const button = e.currentTarget;
const ripple = document.createElement('span');
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.width = ripple.style.height = `${size}px`;
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
ripple.className = 'absolute bg-white/30 rounded-full animate-ping pointer-events-none';
button.appendChild(ripple);
setTimeout(() => ripple.remove(), 500);
onClick?.(e);
};
return (
);
}
// Pulsing dot indicator
export function PulsingDot({ color = 'green', size = 'sm' }) {
const sizeClasses = {
sm: 'w-2 h-2',
md: 'w-3 h-3',
lg: 'w-4 h-4'
};
const colorClasses = {
green: 'bg-green-500',
red: 'bg-red-500',
amber: 'bg-amber-500',
blue: 'bg-blue-500'
};
return (
);
}
// Progress bar
export function ProgressBar({ value, max = 100, showLabel = false, className = '' }) {
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
return (
{showLabel && (
{Math.round(percentage)}%
)}
);
}