mirror of
https://github.com/armbian/imager.git
synced 2026-01-06 12:31:28 -08:00
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
81 lines
2.3 KiB
TypeScript
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>
|
|
);
|
|
}
|