45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
import { motion, AnimatePresence } from 'framer-motion'
|
|
|
|
interface LoadingOverlayProps {
|
|
isLoading: boolean
|
|
message?: string
|
|
}
|
|
|
|
/**
|
|
* Full-screen loading overlay with spinner and message
|
|
* Prevents user interaction during payment code generation
|
|
*/
|
|
export function LoadingOverlay({ isLoading, message = 'Memproses...' }: LoadingOverlayProps) {
|
|
return (
|
|
<AnimatePresence>
|
|
{isLoading && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
|
|
role="status"
|
|
aria-live="polite"
|
|
aria-busy="true"
|
|
>
|
|
<div className="bg-white rounded-lg p-6 shadow-xl max-w-sm mx-4">
|
|
<div className="flex flex-col items-center gap-4">
|
|
{/* Spinner */}
|
|
<div
|
|
className="h-12 w-12 animate-spin rounded-full border-4 border-black/20 border-t-black"
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
{/* Message */}
|
|
<p className="text-center text-black font-medium">
|
|
{message}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
)
|
|
}
|