React'ta Yeniden Kullanılabilir Modal Bileşeni Oluşturma
Modal bileşenleri, modern web uygulamalarının vazgeçilmez bir parçasıdır. Bu yazıda, React ve TypeScript kullanarak yeniden kullanılabilir, erişilebilir ve animasyonlu bir modal bileşeni oluşturmayı öğreneceğiz.
Modal Bileşeninin Temel Yapısı
İlk olarak, modal bileşenimizin temel yapısını ve prop tiplerini oluşturalım.
import React, { useEffect, useRef } from 'react'; import { createPortal } from 'react-dom'; import { motion, AnimatePresence } from 'framer-motion'; interface ModalProps { isOpen: boolean; onClose: () => void; title?: string; children: React.ReactNode; size?: 'sm' | 'md' | 'lg'; closeOnOutsideClick?: boolean; closeOnEsc?: boolean; } const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children, size = 'md', closeOnOutsideClick = true, closeOnEsc = true, }) => { const modalRef = useRef<HTMLDivElement>(null); // ESC tuşu ile kapatma useEffect(() => { const handleEscKey = (event: KeyboardEvent) => { if (closeOnEsc && event.key === 'Escape') { onClose(); } }; if (isOpen) { document.addEventListener('keydown', handleEscKey); document.body.style.overflow = 'hidden'; // Scroll'u engelle } return () => { document.removeEventListener('keydown', handleEscKey); document.body.style.overflow = 'unset'; }; }, [isOpen, onClose, closeOnEsc]); // Dışarı tıklama ile kapatma const handleOutsideClick = (event: React.MouseEvent) => { if (closeOnOutsideClick && modalRef.current && !modalRef.current.contains(event.target as Node)) { onClose(); } }; // Modal içeriği const modalContent = ( <AnimatePresence> {isOpen && ( <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50" onClick={handleOutsideClick} > <motion.div ref={modalRef} className={` bg-white dark:bg-gray-800 rounded-lg shadow-xl ${size === 'sm' ? 'max-w-sm' : size === 'lg' ? 'max-w-2xl' : 'max-w-lg'} w-full `} initial={{ opacity: 0, scale: 0.9, y: 20 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9, y: 20 }} transition={{ type: 'spring', duration: 0.3 }} > {/* Modal Header */} {title && ( <div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700"> <h2 className="text-xl font-semibold text-gray-900 dark:text-white"> {title} </h2> </div> )} {/* Modal Body */} <div className="px-6 py-4">{children}</div> {/* Modal Footer - İsteğe bağlı */} <div className="px-6 py-4 border-t border-gray-200 dark:border-gray-700"> <div className="flex justify-end space-x-3"> <button onClick={onClose} className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600" > Kapat </button> </div> </div> </motion.div> </div> )} </AnimatePresence> ); // Portal ile body'e render etme return createPortal(modalContent, document.body); }; export default Modal;
Modal Hook'u ile State Yönetimi
Modal'ın state yönetimini kolaylaştırmak için özel bir hook oluşturalım.
import { useState, useCallback } from 'react'; interface UseModalReturn { isOpen: boolean; open: () => void; close: () => void; toggle: () => void; } export function useModal(initialState = false): UseModalReturn { const [isOpen, setIsOpen] = useState(initialState); const open = useCallback(() => setIsOpen(true), []); const close = useCallback(() => setIsOpen(false), []); const toggle = useCallback(() => setIsOpen(prev => !prev), []); return { isOpen, open, close, toggle }; }
Erişilebilirlik (A11y) Özellikleri
Modal'ımızı ARIA standartlarına uygun hale getirelim.
interface ModalProps { // ... diğer prop'lar ... ariaLabel?: string; ariaDescribedby?: string; } const Modal: React.FC<ModalProps> = ({ // ... diğer prop'lar ... ariaLabel, ariaDescribedby, }) => { // ... önceki kodlar ... const modalContent = ( <div role="dialog" aria-modal="true" aria-label={ariaLabel} aria-describedby={ariaDescribedby} className="modal-container" > {/* ... modal içeriği ... */} </div> ); // ... kalan kodlar ... };
Özelleştirilebilir Animasyonlar
Framer Motion ile farklı animasyon varyantları ekleyelim.
const animationVariants = { fade: { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, }, scale: { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9 }, }, slideUp: { initial: { opacity: 0, y: 50 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 50 }, }, }; interface ModalProps { // ... diğer prop'lar ... animation?: keyof typeof animationVariants; } const Modal: React.FC<ModalProps> = ({ // ... diğer prop'lar ... animation = 'scale', }) => { const selectedAnimation = animationVariants[animation]; return ( <motion.div initial={selectedAnimation.initial} animate={selectedAnimation.animate} exit={selectedAnimation.exit} transition={{ type: 'spring', duration: 0.3 }} > {/* ... modal içeriği ... */} </motion.div> ); };
Kullanım Örnekleri
Modal bileşenimizi farklı senaryolarda nasıl kullanabileceğimizi görelim.
1. Basit Kullanım
function App() { const { isOpen, open, close } = useModal(); return ( <div> <button onClick={open}>Modal'ı Aç</button> <Modal isOpen={isOpen} onClose={close} title="Örnek Modal"> <p>Modal içeriği burada yer alır.</p> </Modal> </div> ); }
2. Form Modal'ı
function UserFormModal() { const { isOpen, close } = useModal(); const [formData, setFormData] = useState({ name: '', email: '' }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await saveUser(formData); close(); } catch (error) { console.error('Form gönderimi başarısız:', error); } }; return ( <Modal isOpen={isOpen} onClose={close} title="Kullanıcı Ekle" size="lg" closeOnOutsideClick={false} > <form onSubmit={handleSubmit} className="space-y-4"> <div> <label className="block text-sm font-medium text-gray-700"> Ad Soyad </label> <input type="text" value={formData.name} onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm" /> </div> <div> <label className="block text-sm font-medium text-gray-700"> E-posta </label> <input type="email" value={formData.email} onChange={e => setFormData(prev => ({ ...prev, email: e.target.value }))} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm" /> </div> <div className="flex justify-end space-x-3"> <button type="button" onClick={close} className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md" > İptal </button> <button type="submit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md" > Kaydet </button> </div> </form> </Modal> ); }
3. Onay Modal'ı
function ConfirmationModal({ isOpen, onClose, onConfirm, message }: { isOpen: boolean; onClose: () => void; onConfirm: () => void; message: string; }) { return ( <Modal isOpen={isOpen} onClose={onClose} title="Onay" size="sm" animation="slideUp" > <div className="text-center"> <p className="text-gray-700 dark:text-gray-300">{message}</p> <div className="mt-6 flex justify-center space-x-3"> <button onClick={onClose} className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md" > İptal </button> <button onClick={() => { onConfirm(); onClose(); }} className="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md" > Onayla </button> </div> </div> </Modal> ); }
Best Practices ve İpuçları
Performans Optimizasyonu
- Modal içeriğini lazy loading ile yükleyin
- Gereksiz render'ları önlemek için React.memo kullanın
- Animasyonlar için CSS transforms kullanın
Erişilebilirlik
- Focus trap kullanın
- Uygun ARIA attribute'larını ekleyin
- Klavye navigasyonunu destekleyin
Responsive Tasarım
- Mobile-first yaklaşımı benimseyin
- Farklı ekran boyutları için uygun padding ve margin değerleri kullanın
- Touch cihazlar için uygun interaction'ları ekleyin
State Yönetimi
- Complex state için useReducer kullanın
- Form state'i için React Hook Form gibi kütüphaneler tercih edin
- Global state gerekiyorsa Context API veya state management kütüphanesi kullanın
Sonuç
Modern bir modal bileşeni oluştururken dikkat edilmesi gereken birçok nokta vardır:
- Erişilebilirlik standartlarına uygunluk
- Performans optimizasyonu
- Responsive tasarım
- Kullanıcı deneyimi
- Yeniden kullanılabilirlik
- Type safety
Bu yazıda oluşturduğumuz modal bileşeni, tüm bu gereksinimleri karşılayan, production-ready bir çözüm sunmaktadır.
İlgili Etiketler: #React #Components #TypeScript #Accessibility #Animation #UIUX #WebDevelopment