Improve delivery workflows and add versioned guided tours (#1)
Reviewed-on: #1 Co-authored-by: Egor Pozharov <chedius232@gmail.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useId, useRef, type ReactNode } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { X } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -7,38 +8,38 @@ interface ModalProps {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
closeDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const Modal = ({ isOpen, onClose, title, children, footer }: ModalProps) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<div
|
||||
className="fixed inset-0 bg-black/30 transition-opacity"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all w-full max-w-lg">
|
||||
<div className="flex items-center justify-between border-b border-[#e4e2e4] px-4 py-3">
|
||||
<h3 className="text-lg font-semibold text-[#1b1b1d]">{title}</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-full p-1 hover:bg-[#f5f3f5] transition-colors"
|
||||
>
|
||||
<X size={20} className="text-[#75777d]" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="px-4 py-4">
|
||||
{children}
|
||||
</div>
|
||||
{footer && (
|
||||
<div className="border-t border-[#e4e2e4] px-4 py-3 flex justify-end gap-2">
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
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>
|
||||
</div>
|
||||
</dialog>, document.body,
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user