Files
delivery-tracker/frontend/src/components/ui/Modal.tsx
T
chedius dee912edb8
Build and Push Docker Images / build-backend (push) Canceled after 0s
Build and Push Docker Images / build-frontend (push) Canceled after 0s
Improve delivery workflows and add versioned guided tours (#1)
Reviewed-on: #1
Co-authored-by: Egor Pozharov <chedius232@gmail.com>
2026-10-07 19:49:10 +00:00

46 lines
1.8 KiB
TypeScript

import { useEffect, useId, useRef, type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { X } from 'lucide-react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: ReactNode;
footer?: ReactNode;
closeDisabled?: boolean;
}
export const Modal = ({ isOpen, onClose, title, children, footer, closeDisabled = false }: ModalProps) => {
const ref = useRef<HTMLDialogElement>(null);
const titleId = useId();
useEffect(() => {
if (!isOpen) return;
const dialog = ref.current;
const previousFocus = document.activeElement as HTMLElement | null;
const overflow = document.body.style.overflow;
dialog?.showModal();
document.body.style.overflow = 'hidden';
return () => {
dialog?.close();
document.body.style.overflow = overflow;
if (previousFocus?.isConnected) previousFocus.focus();
};
}, [isOpen]);
return createPortal(
<dialog ref={ref} aria-labelledby={titleId} aria-modal="true" className="app-dialog"
onCancel={e => { e.preventDefault(); if (!closeDisabled) onClose(); }}
onClick={e => { if (e.target === e.currentTarget && !closeDisabled) onClose(); }}>
<div className="dialog-panel">
<div className="flex shrink-0 items-center justify-between gap-4 border-b border-[#e4e2e4] px-5 py-4">
<h2 id={titleId} className="text-xl font-semibold">{title}</h2>
<button type="button" onClick={onClose} disabled={closeDisabled} aria-label="Закрыть окно" className="icon-button"><X size={20} /></button>
</div>
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-5">{children}</div>
{footer && <div className="dialog-footer flex shrink-0 justify-end gap-3 border-t border-[#e4e2e4] bg-white px-5 py-4">{footer}</div>}
</div>
</dialog>, document.body,
);
};