ba36f67371
* fix: reliable Escape key close for all modals via useEscapeKey hook - Add useEscapeKey hook (document-level keydown listener) - Retrofit all 12 modal/overlay components to use it - Remove redundant overlay onKeyDown Escape handlers - Simplify modal-content onKeyDown to plain stopPropagation - Replace MedDetailModal's capture-phase useEffect with 3 useEscapeKey calls - Replace SharedSchedule's inline useEffect with useEscapeKey - Add mandatory modal rules to UI Consistency skill - All 777 frontend + 569 backend tests pass * fix: smooth mobile edit transition and align modal validation behavior * fix: keep overlay keydown non-closing for Enter key * fix: show mobile name error when validation already exists * fix: restore app-level escape priority handling * fix: prioritize schedule lightbox on Escape
66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
// =============================================================================
|
||
// ConfirmModal Component - Simple confirmation dialog
|
||
// =============================================================================
|
||
|
||
import type { ReactNode } from "react";
|
||
import { useEscapeKey } from "../hooks/useEscapeKey";
|
||
|
||
export interface ConfirmModalProps {
|
||
title: string;
|
||
message: string | ReactNode;
|
||
confirmLabel: string;
|
||
cancelLabel: string;
|
||
onConfirm: () => void;
|
||
onCancel: () => void;
|
||
isLoading?: boolean;
|
||
confirmVariant?: "primary" | "danger" | "success" | "warning";
|
||
overlayClassName?: string;
|
||
}
|
||
|
||
export function ConfirmModal({
|
||
title,
|
||
message,
|
||
confirmLabel,
|
||
cancelLabel,
|
||
onConfirm,
|
||
onCancel,
|
||
isLoading = false,
|
||
confirmVariant = "primary",
|
||
overlayClassName,
|
||
}: ConfirmModalProps) {
|
||
useEscapeKey(true, onCancel);
|
||
|
||
return (
|
||
<div
|
||
className={`modal-overlay${overlayClassName ? ` ${overlayClassName}` : ""}`}
|
||
onClick={onCancel}
|
||
onKeyDown={(e) => {
|
||
if (e.key !== "Escape") e.stopPropagation();
|
||
}}
|
||
>
|
||
<div
|
||
className="modal-content confirm-modal"
|
||
onClick={(e) => e.stopPropagation()}
|
||
onKeyDown={(e) => {
|
||
if (e.key !== "Escape") e.stopPropagation();
|
||
}}
|
||
style={{ maxWidth: "450px" }}
|
||
>
|
||
<button className="modal-close" onClick={onCancel}>
|
||
×
|
||
</button>
|
||
<h2 style={{ marginBottom: "16px", paddingRight: "2rem" }}>{title}</h2>
|
||
<div style={{ marginBottom: "24px" }}>{typeof message === "string" ? <p>{message}</p> : message}</div>
|
||
<div className="modal-footer" style={{ padding: "1rem 0 0 0", borderTop: "none", justifyContent: "flex-end" }}>
|
||
<button type="button" className="ghost" onClick={onCancel} disabled={isLoading}>
|
||
{cancelLabel}
|
||
</button>
|
||
<button type="button" className={confirmVariant} onClick={onConfirm} disabled={isLoading}>
|
||
{confirmLabel}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|