Комментарии 4
По-моему, было бы гораздо проще реализовать модальное окно на основе элемента dialog
, потому что он поддерживает модальность (захват фокуса) и закрытие по Эскейпу "из коробки". Достаточно добавить кнопку X и немного стилей.
Для того, чтобы фокус работал правильно вне зависимости от наличия или отсуствия элементов ввода в теле диалога, кнопку X можно расположить "внизу", переместив её "наверх" с помощю стилей.
Например:
import type { PropsWithChildren, ReactNode } from 'react';
import { forwardRef, useRef, useImperativeHandle } from 'react';
export interface IDialog {
get isOpen(): boolean;
show(): void;
hide(): void;
}
export type DialogProps = PropsWithChildren<{
label?: string;
footer?: ReactNode;
}>;
const Dialog = forwardRef<IDialog, DialogProps>(function Dialog(
{ label, footer, children },
ref
) {
const dialogRef = useRef<HTMLDialogElement>(null);
useImperativeHandle(
ref,
() => {
return {
get isOpen() {
return dialogRef?.current?.open;
},
show() {
dialogRef?.current?.showModal();
},
hide() {
dialogRef?.current?.close();
},
} as IDialog;
},
[]
);
const handleCloseButtonClick = () => {
dialogRef?.current?.close();
};
return (
<dialog
ref={dialogRef}
className="overflow-visible bg-transparent p-0 outline-none backdrop:animate-reveal-100 backdrop:bg-slate-100 backdrop:bg-opacity-50 backdrop:backdrop-blur-xs"
>
<div className="flex flex-col-reverse">
<article className="relative flex h-screen-50 max-h-100 min-h-60 w-screen-40 min-w-xl max-w-2xl flex-col overflow-hidden rounded-md border border-slate-300 bg-white drop-shadow-md">
{label && (
<header className="flex items-center justify-between border-b border-b-slate-300 bg-slate-100 p-4">
<h2 className="text-lg font-semibold">{label}</h2>
</header>
)}
<div className="flex-1 overflow-hidden overflow-y-auto">
{children}
</div>
{footer && (
<footer className="flex justify-end border-t border-t-slate-300 p-4">
{footer}
</footer>
)}
</article>
<div className="flex w-full justify-end py-2 align-middle">
<button
onClick={handleCloseButtonClick}
className="h-8 w-8 rounded-md bg-transparent hover:bg-slate-200 focus:bg-slate-200"
>
<span className="leading-none text-slate-800">✕</span>
</button>
</div>
</div>
</dialog>
);
});
export default Dialog;
А как правильно обработать ситуацию, когда одно модальное окно порождает другое? В данной реализации вроде это никак не обрабатывается?
Для такого сценария достаточно будет менять контент внутри модалки по каким-то условиям. Т.е. не модалка пораждает другую модалку, а просто идёт смена контента внутри модального окна.
Например можно попробовать прикинуть на примере пошаговой формы (скажем 3 шага), с сабмитом на последнем шаге.
Контент внутри modal под условным рендерингом будет находится. Каждый "шаг" отображается по набору условий (допустим стейт в компоненте с номером шага). Далее схема будет следующая:
- заполнили 1й шаг, нажали кнопку "дальше"
- по клику на кнопку стейт поменялся ->
- затем сработали условия для скрытия 1го шага и показа 2го шага
- заполнили 2й шаг -> клик-> стейт -> смена условий и показ 3го шага
- сделали сабмит, перестали рендерить модалку
Делаем кастомное модальное окно для React