90 lines
2.4 KiB
TypeScript
90 lines
2.4 KiB
TypeScript
import { useEffect, useRef, useState } from "preact/hooks";
|
|
import { useFocusTrap } from "../utils/useFocusTrap.ts";
|
|
|
|
/**
|
|
* Listens for submit on forms with data-confirm-message and shows a themed
|
|
* confirm dialog instead of the browser's native confirm().
|
|
* Add to any page and add data-confirm-message="..." (and optionally
|
|
* data-confirm-label="Confirm") to forms that should use the themed dialog.
|
|
*/
|
|
export default function ConfirmSubmitGuard() {
|
|
const [state, setState] = useState<
|
|
{
|
|
message: string;
|
|
confirmLabel: string;
|
|
form: HTMLFormElement | null;
|
|
} | null
|
|
>(null);
|
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
useFocusTrap(state !== null, modalRef);
|
|
|
|
useEffect(() => {
|
|
const handleSubmit = (e: Event) => {
|
|
const form = e.target as HTMLFormElement;
|
|
const message = form?.getAttribute?.("data-confirm-message");
|
|
if (!message || !form) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const confirmLabel = form.getAttribute("data-confirm-label") || "Confirm";
|
|
setState({ message, confirmLabel, form });
|
|
};
|
|
|
|
document.addEventListener("submit", handleSubmit, true);
|
|
return () => document.removeEventListener("submit", handleSubmit, true);
|
|
}, []);
|
|
|
|
const handleConfirm = () => {
|
|
if (state?.form) {
|
|
const form = state.form;
|
|
setState(null);
|
|
form.submit();
|
|
} else {
|
|
setState(null);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setState(null);
|
|
};
|
|
|
|
if (!state) return null;
|
|
|
|
return (
|
|
<div
|
|
ref={modalRef}
|
|
class="theme-dialog-overlay"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="confirm-dialog-title"
|
|
>
|
|
<div
|
|
class="theme-dialog-container flex flex-col gap-4"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<h2 id="confirm-dialog-title" class="theme-dialog-title">
|
|
Confirm
|
|
</h2>
|
|
<p class="text-sm opacity-90" style={{ color: "var(--theme-muted)" }}>
|
|
{state.message}
|
|
</p>
|
|
<div class="flex gap-3 justify-end mt-2">
|
|
<button
|
|
type="button"
|
|
class="theme-dialog-cancel-button"
|
|
onClick={handleCancel}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="theme-dialog-submit-button"
|
|
onClick={handleConfirm}
|
|
>
|
|
{state.confirmLabel}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|