Files
clean-architecture-frontend…/src/presentation/components/ui/dialog.jsx
T

89 lines
2.4 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useId, useRef } from "react";
/**
* @param {{
* open: boolean,
* onClose: () => void,
* title: string,
* description?: string,
* children?: React.ReactNode,
* actions?: React.ReactNode
* }} props
*/
export function Dialog({
open,
onClose,
title,
description,
children,
actions,
}) {
const dialogRef = useRef(/** @type {HTMLDialogElement | null} */ (null));
const previousFocusRef = useRef(/** @type {HTMLElement | null} */ (null));
const titleId = useId();
const descriptionId = useId();
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (open) {
previousFocusRef.current =
document.activeElement instanceof HTMLElement
? document.activeElement
: null;
if (!dialog.open) {
if (typeof dialog.showModal === "function") dialog.showModal();
else dialog.setAttribute("open", "");
}
const firstFocusable = dialog.querySelector(
"[autofocus], button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])",
);
if (firstFocusable instanceof HTMLElement) firstFocusable.focus();
return;
}
if (dialog.open) {
if (typeof dialog.close === "function") dialog.close();
else dialog.removeAttribute("open");
}
previousFocusRef.current?.focus();
previousFocusRef.current = null;
}, [open]);
return (
<dialog
className="ui-dialog"
ref={dialogRef}
aria-labelledby={titleId}
aria-describedby={description ? descriptionId : undefined}
onCancel={(event) => {
event.preventDefault();
onClose();
}}
onClick={(event) => {
if (event.target === event.currentTarget) onClose();
}}
>
<div className="ui-dialog__surface">
<header className="ui-dialog__header">
<div>
<h2 id={titleId}>{title}</h2>
{description ? <p id={descriptionId}>{description}</p> : null}
</div>
<button
className="ui-dialog__close"
type="button"
aria-label={`${title} 닫기`}
onClick={onClose}
>
×
</button>
</header>
{children ? <div className="ui-dialog__content">{children}</div> : null}
{actions ? <footer className="ui-dialog__actions">{actions}</footer> : null}
</div>
</dialog>
);
}