Skip to content

Commit

Permalink
Fix events inside popups by using a portal
Browse files Browse the repository at this point in the history
  • Loading branch information
davidje13 committed Nov 30, 2024
1 parent 3bba1ac commit 009ba78
Showing 1 changed file with 43 additions and 23 deletions.
66 changes: 43 additions & 23 deletions frontend/src/components/common/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {
type FC,
type PropsWithChildren,
useRef,
useEffect,
type KeyboardEvent,
useId,
useLayoutEffect,
useState,
} from 'react';
import { createPortal } from 'react-dom';
import { useEvent } from '../../hooks/useEvent';
import './Popup.less';

Expand All @@ -26,7 +25,15 @@ export const Popup: FC<PropsWithChildren<PropsT>> = ({
onClose,
children,
}) => {
const titleID = useId();
const [dialog] = useState(() => document.createElement('dialog'));
const [lagDisplay, setLagDisplay] = useState(false);
const showContent = isOpen || lagDisplay;

const handleCancel = useEvent((e: Event) => {
e.preventDefault();
onClose();
});

const handleKeyDown = useEvent((e: KeyboardEvent) => {
e.stopPropagation();
Expand All @@ -47,34 +54,47 @@ export const Popup: FC<PropsWithChildren<PropsT>> = ({
}
});

const id = useId();
const dialog = useRef<HTMLDialogElement>(null);
useEffect(() => {
useLayoutEffect(() => {
dialog.className = 'popup-content';
dialog.setAttribute('aria-labelledby', titleID);
dialog.addEventListener('cancel', handleCancel);
dialog.addEventListener('keydown', handleKeyDown);
return () => {
dialog.removeEventListener('cancel', handleCancel);
dialog.removeEventListener('keydown', handleKeyDown);
};
}, [dialog, titleID, handleCancel, handleKeyDown]);

useLayoutEffect(() => {
if (!showContent) {
return;
}
document.body.append(dialog);
return () => dialog.remove();
}, [dialog, showContent]);

useLayoutEffect(() => {
if (isOpen) {
setLagDisplay(true);
dialog.current?.showModal();
return () => dialog.current?.close();
dialog.showModal();
return () => dialog.close();
} else {
const tm = setTimeout(() => setLagDisplay(false), 500);
return () => clearTimeout(tm);
}
}, [isOpen]);
}, [dialog, isOpen]);

if (!showContent) {
return null;
}

return (
<dialog
ref={dialog}
className="popup-content"
onCancel={(e) => {
e.preventDefault();
onClose();
}}
onKeyDown={handleKeyDown}
aria-labelledby={id}
>
<h1 id={id} className={hideTitle ? 'hidden' : ''}>
return createPortal(
<>
<h1 id={titleID} className={hideTitle ? 'hidden' : ''}>
{title}
</h1>
{isOpen || lagDisplay ? children : null}
</dialog>
{children}
</>,
dialog,
);
};

0 comments on commit 009ba78

Please sign in to comment.