Spaces:
Running
Running
| import React, { useState, useEffect } from 'react'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| import { X, Check, Info } from 'lucide-react'; | |
| const Modal = ({ | |
| isOpen, | |
| onClose, | |
| onConfirm, | |
| title, | |
| message, | |
| type = 'confirm', | |
| initialValue = '', | |
| placeholder = 'Enter something...', | |
| confirmText = 'OK', | |
| cancelText = 'Cancel' | |
| }) => { | |
| const [inputValue, setInputValue] = useState(initialValue); | |
| useEffect(() => { | |
| if (isOpen) { | |
| setInputValue(initialValue); | |
| } | |
| }, [isOpen, initialValue]); | |
| const handleSubmit = (e) => { | |
| e.preventDefault(); | |
| if (type === 'prompt') { | |
| onConfirm(inputValue); | |
| } else { | |
| onConfirm(); | |
| } | |
| onClose(); | |
| }; | |
| return ( | |
| <AnimatePresence> | |
| {isOpen && ( | |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6"> | |
| {/* Backdrop */} | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| exit={{ opacity: 0 }} | |
| onClick={onClose} | |
| className="absolute inset-0 bg-primary/20 backdrop-blur-sm" | |
| /> | |
| {/* Modal Content */} | |
| <motion.div | |
| initial={{ scale: 0.9, opacity: 0, y: 20, rotate: -1 }} | |
| animate={{ scale: 1, opacity: 1, y: 0, rotate: 0 }} | |
| exit={{ scale: 0.9, opacity: 0, y: 20, rotate: 1 }} | |
| className="relative bg-surface-container-lowest w-full max-w-md p-8 rough-border rough-shadow flex flex-col gap-6" | |
| > | |
| <button | |
| onClick={onClose} | |
| className="absolute top-4 right-4 text-on-surface-variant hover:text-primary transition-colors" | |
| > | |
| <X size={24} /> | |
| </button> | |
| <div className="flex flex-col gap-2"> | |
| <h2 className="text-4xl font-display-lg text-primary leading-tight"> | |
| {title} | |
| </h2> | |
| {message && ( | |
| <p className="font-body-md text-on-surface-variant italic"> | |
| {message} | |
| </p> | |
| )} | |
| </div> | |
| <form onSubmit={handleSubmit} className="flex flex-col gap-8"> | |
| {type === 'prompt' && ( | |
| <div className="relative group"> | |
| <input | |
| autoFocus | |
| type="text" | |
| value={inputValue} | |
| onChange={(e) => setInputValue(e.target.value)} | |
| placeholder={placeholder} | |
| className="w-full bg-surface-container-low border-b-2 border-primary/20 focus:border-primary px-2 py-3 text-xl font-body-md outline-none transition-all" | |
| /> | |
| <div className="absolute bottom-0 left-0 h-0.5 bg-primary w-0 group-focus-within:w-full transition-all duration-300" /> | |
| </div> | |
| )} | |
| <div className="flex items-center justify-end gap-4 mt-2"> | |
| <button | |
| type="button" | |
| onClick={onClose} | |
| className="px-6 py-2 font-bold text-lg text-on-surface-variant hover:text-primary transition-all" | |
| > | |
| {cancelText} | |
| </button> | |
| <motion.button | |
| whileHover={{ scale: 1.05, rotate: -1 }} | |
| whileTap={{ scale: 0.95 }} | |
| type="submit" | |
| className="bg-primary text-on-primary px-8 py-3 text-xl font-bold rough-border rough-shadow-hover flex items-center gap-2" | |
| > | |
| <Check size={20} /> | |
| <span>{confirmText}</span> | |
| </motion.button> | |
| </div> | |
| </form> | |
| {/* Decorative Sketched Element */} | |
| <div className="absolute -bottom-6 -left-6 pointer-events-none opacity-[0.03] overflow-hidden"> | |
| <Info size={160} className="-rotate-12 translate-x-4 translate-y-4" /> | |
| </div> | |
| </motion.div> | |
| </div> | |
| )} | |
| </AnimatePresence> | |
| ); | |
| }; | |
| export default Modal; | |