integratedhub/islands/ConfirmSubmitGuard.tsx

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>
);
}