Files
imager/src/components/modals/Modal.tsx
SuperKali 8e99d25c8d feat: add settings panel with theme, language, and developer options
Implement comprehensive settings modal with:
- Theme switching (light/dark/auto) with system preference detection
- Language selection for 17 languages with native name sorting
- Developer mode with detailed logging and log viewer
- About section with app info and external links
- Update notification improvements with reduced log spam

Technical improvements:
- Added ThemeContext with persistent state management
- Implemented memory-safe log file reading (5MB limit)
- Fixed all ESLint, TypeScript, and Clippy warnings
- Added JSDoc documentation for public APIs
- Updated README.md and DEVELOPMENT.md with new features
2025-12-30 09:59:24 +01:00

81 lines
2.3 KiB
TypeScript

import { type ReactNode, useEffect, useCallback, useState, useRef } from 'react';
import { X, ChevronLeft } from 'lucide-react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: ReactNode;
searchBar?: ReactNode;
showBack?: boolean;
onBack?: () => void;
}
export function Modal({ isOpen, onClose, title, children, searchBar, showBack, onBack }: ModalProps) {
const [isExiting, setIsExiting] = useState(false);
const isExitingRef = useRef(false);
const handleClose = useCallback(() => {
if (isExitingRef.current) return;
isExitingRef.current = true;
setIsExiting(true);
setTimeout(() => {
setIsExiting(false);
isExitingRef.current = false;
onClose();
}, 200); // Match the CSS exit animation duration
}, [onClose]);
const handleEscape = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
if (showBack && onBack) {
onBack();
} else {
handleClose();
}
}
}, [handleClose, showBack, onBack]);
useEffect(() => {
if (isOpen) {
document.addEventListener('keydown', handleEscape);
document.body.style.overflow = 'hidden';
} else {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = '';
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = '';
};
}, [isOpen, handleEscape]);
if (!isOpen && !isExiting) return null;
const animationClass = isExiting ? 'modal-exiting' : 'modal-entering';
return (
<div className={`modal-overlay ${animationClass}`} onClick={handleClose}>
<div className={`modal ${animationClass}`} onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<div className="modal-header-left">
{showBack && onBack && (
<button className="modal-back" onClick={onBack}>
<ChevronLeft size={20} />
</button>
)}
<h2 className="modal-title">{title}</h2>
</div>
<button className="modal-close" onClick={handleClose}>
<X size={20} />
</button>
</div>
{searchBar}
<div className="modal-body">
{children}
</div>
</div>
</div>
);
}