Use the central Dialog for Campaign previews
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, type ReactNode } from "react";
|
import { useEffect, useRef, type ReactNode } from "react";
|
||||||
import { ArrowBigLeft, ArrowBigLeftDash, ArrowBigRight, ArrowBigRightDash } from "lucide-react";
|
import { ArrowBigLeft, ArrowBigLeftDash, ArrowBigRight, ArrowBigRightDash } from "lucide-react";
|
||||||
import { Button, MessageDisplayPanel, type MessageDisplayAttachment } from "@govoplan/core-webui";
|
import { Button, Dialog, MessageDisplayPanel, type MessageDisplayAttachment } from "@govoplan/core-webui";
|
||||||
|
|
||||||
// Campaign review/template/mock previews need recipient navigation, review notes,
|
// Campaign review/template/mock previews need recipient navigation, review notes,
|
||||||
// raw MIME inspection and attachment grouping. Generic mailbox reading uses
|
// raw MIME inspection and attachment grouping. Generic mailbox reading uses
|
||||||
@@ -60,15 +60,13 @@ export default function CampaignMessagePreviewOverlay({
|
|||||||
}: CampaignMessagePreviewOverlayProps) {
|
}: CampaignMessagePreviewOverlayProps) {
|
||||||
const shownSubject = subject?.trim() || "i18n:govoplan-campaign.no_subject.7b4e8035";
|
const shownSubject = subject?.trim() || "i18n:govoplan-campaign.no_subject.7b4e8035";
|
||||||
const fields = metaItems.map((item) => ({ label: item.label, value: item.value }));
|
const fields = metaItems.map((item) => ({ label: item.label, value: item.value }));
|
||||||
|
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
const dialogPanel = contentRef.current?.closest<HTMLElement>("[data-dialog-stack-state]");
|
||||||
|
if (dialogPanel?.dataset.dialogStackState !== "topmost") return;
|
||||||
if (isEditableTarget(event.target)) return;
|
if (isEditableTarget(event.target)) return;
|
||||||
if (event.key === "Escape") {
|
|
||||||
event.preventDefault();
|
|
||||||
onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!navigation) return;
|
if (!navigation) return;
|
||||||
if (event.key === "ArrowLeft") {
|
if (event.key === "ArrowLeft") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -87,16 +85,26 @@ export default function CampaignMessagePreviewOverlay({
|
|||||||
|
|
||||||
window.addEventListener("keydown", handleKeyDown);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [navigation, onClose]);
|
}, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overlay-backdrop message-preview-backdrop" role="dialog" aria-modal="true" aria-labelledby="message-preview-title">
|
<Dialog
|
||||||
<div className="modal-panel template-preview-modal message-preview-modal">
|
open
|
||||||
<header className="modal-header">
|
title={title}
|
||||||
<h2 id="message-preview-title">{title}</h2>
|
onClose={onClose}
|
||||||
<button type="button" className="modal-close" aria-label={closeLabel} title={closeLabel} onClick={onClose}>×</button>
|
closeLabel={closeLabel}
|
||||||
</header>
|
closeOnBackdrop={false}
|
||||||
<div className="modal-body">
|
backdropClassName="overlay-backdrop message-preview-backdrop"
|
||||||
|
className="modal-panel template-preview-modal message-preview-modal"
|
||||||
|
headerClassName="modal-header"
|
||||||
|
bodyClassName="modal-body"
|
||||||
|
footerClassName="modal-footer"
|
||||||
|
footer={<>
|
||||||
|
{actions && <div className="button-row compact-actions">{actions}</div>}
|
||||||
|
<Button variant="primary" onClick={onClose}>{closeLabel}</Button>
|
||||||
|
</>}
|
||||||
|
>
|
||||||
|
<div ref={contentRef} className="message-preview-content">
|
||||||
{(recipientLabel || recipientNote || navigation) &&
|
{(recipientLabel || recipientNote || navigation) &&
|
||||||
<div className="template-preview-toolbar">
|
<div className="template-preview-toolbar">
|
||||||
<div>
|
<div>
|
||||||
@@ -133,12 +141,7 @@ export default function CampaignMessagePreviewOverlay({
|
|||||||
</details>
|
</details>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<footer className="modal-footer">
|
</Dialog>);
|
||||||
{actions && <div className="button-row compact-actions">{actions}</div>}
|
|
||||||
<Button variant="primary" onClick={onClose}>{closeLabel}</Button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1335,6 +1335,10 @@
|
|||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-preview-content {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
.message-preview-modal .modal-footer {
|
.message-preview-modal .modal-footer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 68px;
|
min-height: 68px;
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ assert(
|
|||||||
"message previews expose their responsive backdrop hook"
|
"message previews expose their responsive backdrop hook"
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
overlaySource.includes('type="button" className="modal-close" aria-label={closeLabel} title={closeLabel}'),
|
overlaySource.includes("<Dialog") &&
|
||||||
"the close control has stable button semantics, an accessible name, and a tooltip"
|
overlaySource.includes("closeLabel={closeLabel}") &&
|
||||||
|
overlaySource.includes('dataset.dialogStackState !== "topmost"'),
|
||||||
|
"message previews use the central stacked Dialog with an accessible close label"
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
styles.includes("height: min(780px, calc(100dvh - 48px));"),
|
styles.includes("height: min(780px, calc(100dvh - 48px));"),
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ for (const [key, english, german] of newTranslations) {
|
|||||||
const overlaySource = readFileSync("src/features/campaigns/components/MessagePreviewOverlay.tsx", "utf8");
|
const overlaySource = readFileSync("src/features/campaigns/components/MessagePreviewOverlay.tsx", "utf8");
|
||||||
assert(overlaySource.includes("message-preview-backdrop"), "message previews expose a layout-specific responsive backdrop hook");
|
assert(overlaySource.includes("message-preview-backdrop"), "message previews expose a layout-specific responsive backdrop hook");
|
||||||
assert(overlaySource.includes("{actions && <div"), "built-message footer actions remain in the stable shared preview footer");
|
assert(overlaySource.includes("{actions && <div"), "built-message footer actions remain in the stable shared preview footer");
|
||||||
assert(overlaySource.includes('aria-label={closeLabel}'), "the preview close control has an accessible label");
|
assert(overlaySource.includes("<Dialog") && overlaySource.includes("closeLabel={closeLabel}"), "the preview uses the central Dialog and its accessible close label");
|
||||||
|
|
||||||
const styles = readFileSync("src/styles/campaign-workspace.css", "utf8");
|
const styles = readFileSync("src/styles/campaign-workspace.css", "utf8");
|
||||||
assert(styles.includes("height: min(780px, calc(100dvh - 48px));"), "desktop previews use a stable responsive height");
|
assert(styles.includes("height: min(780px, calc(100dvh - 48px));"), "desktop previews use a stable responsive height");
|
||||||
|
|||||||
Reference in New Issue
Block a user