77 lines
2.1 KiB
TypeScript
77 lines
2.1 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { X, Loader2 } from 'lucide-react';
|
|
import './ProgressModal.css';
|
|
|
|
interface ProgressModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
title: string;
|
|
message?: string;
|
|
progress?: {
|
|
current: number;
|
|
total: number;
|
|
};
|
|
taskId?: string;
|
|
}
|
|
|
|
export function ProgressModal({ isOpen, onClose, title, message, progress, taskId }: ProgressModalProps) {
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
document.body.style.overflow = 'hidden';
|
|
} else {
|
|
document.body.style.overflow = '';
|
|
}
|
|
return () => {
|
|
document.body.style.overflow = '';
|
|
};
|
|
}, [isOpen]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const progressPercent = progress ? Math.round((progress.current / progress.total) * 100) : 0;
|
|
|
|
return (
|
|
<div className="progress-modal-overlay" onClick={onClose}>
|
|
<div className="progress-modal" onClick={(e) => e.stopPropagation()}>
|
|
<div className="progress-modal-header">
|
|
<h3>{title}</h3>
|
|
<button type="button" className="progress-modal-close" onClick={onClose} aria-label="Close">
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="progress-modal-content">
|
|
{message && <p className="progress-modal-message">{message}</p>}
|
|
|
|
{progress && (
|
|
<div className="progress-modal-bar">
|
|
<div className="progress-modal-bar-track">
|
|
<div
|
|
className="progress-modal-bar-fill"
|
|
style={{ width: `${progressPercent}%` }}
|
|
/>
|
|
</div>
|
|
<span className="progress-modal-bar-text">
|
|
{progress.current} of {progress.total} pages
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{!progress && (
|
|
<div className="progress-modal-spinner">
|
|
<Loader2 className="spin" size={24} />
|
|
</div>
|
|
)}
|
|
|
|
{taskId && (
|
|
<p className="progress-modal-task-id">
|
|
Task ID: <code>{taskId}</code>
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|