diff --git a/webui/src/features/campaigns/components/MessagePreviewOverlay.tsx b/webui/src/features/campaigns/components/MessagePreviewOverlay.tsx index a9c7b73..6de02f5 100644 --- a/webui/src/features/campaigns/components/MessagePreviewOverlay.tsx +++ b/webui/src/features/campaigns/components/MessagePreviewOverlay.tsx @@ -88,11 +88,11 @@ export default function CampaignMessagePreviewOverlay({ }, [navigation, onClose]); return ( -
+

{title}

- +
{(recipientLabel || recipientNote || navigation) && diff --git a/webui/src/styles/campaign-workspace.css b/webui/src/styles/campaign-workspace.css index 385ca31..4db400a 100644 --- a/webui/src/styles/campaign-workspace.css +++ b/webui/src/styles/campaign-workspace.css @@ -1096,9 +1096,36 @@ } /* Shared message preview overlay --------------------------------------- */ +.message-preview-modal { + width: min(920px, 100%); + height: min(780px, calc(100dvh - 48px)); + max-height: calc(100dvh - 48px); +} + +.message-preview-modal .modal-header { + min-width: 0; +} + +.message-preview-modal .modal-header h2 { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .message-preview-modal .modal-body { + flex: 1 1 auto; + min-height: 0; display: grid; + align-content: start; gap: 1rem; + overflow: auto; + scrollbar-gutter: stable; +} + +.message-preview-modal .modal-footer { + align-items: center; + min-height: 68px; } .message-preview-modal .message-display-body, @@ -1156,6 +1183,38 @@ font-weight: 700; color: var(--text-primary); } + +@media (max-width: 720px), (max-height: 700px) { + .message-preview-backdrop { + padding: 8px; + } + + .message-preview-modal { + width: 100%; + height: calc(100dvh - 16px); + max-height: calc(100dvh - 16px); + } + + .message-preview-modal .modal-header { + min-height: 52px; + padding-inline: 14px; + } + + .message-preview-modal .modal-body { + padding: 14px; + } + + .message-preview-modal .modal-footer { + min-height: 58px; + padding: 10px 14px; + } + + .message-preview-modal .message-display-body, + .message-preview-modal .message-display-html-frame { + height: clamp(210px, 38dvh, 360px); + max-height: clamp(210px, 38dvh, 360px); + } +} .attachment-source-path-cell { display: grid; gap: 5px; diff --git a/webui/tests/message-preview-overlay-structure.test.mjs b/webui/tests/message-preview-overlay-structure.test.mjs new file mode 100644 index 0000000..cf8da30 --- /dev/null +++ b/webui/tests/message-preview-overlay-structure.test.mjs @@ -0,0 +1,43 @@ +import { readFileSync } from "node:fs"; + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const overlaySource = readFileSync( + "src/features/campaigns/components/MessagePreviewOverlay.tsx", + "utf8" +); +const styles = readFileSync("src/styles/campaign-workspace.css", "utf8"); + +assert( + overlaySource.includes("overlay-backdrop message-preview-backdrop"), + "message previews expose their responsive backdrop hook" +); +assert( + overlaySource.includes('type="button" className="modal-close" aria-label={closeLabel} title={closeLabel}'), + "the close control has stable button semantics, an accessible name, and a tooltip" +); +assert( + styles.includes("height: min(780px, calc(100dvh - 48px));"), + "desktop previews use a stable responsive height" +); +assert( + styles.includes("flex: 1 1 auto;") && + styles.includes("min-height: 0;") && + styles.includes("overflow: auto;") && + styles.includes("scrollbar-gutter: stable;"), + "the preview body owns scrolling without moving the surrounding actions" +); +assert( + styles.includes(".message-preview-modal .modal-footer") && + styles.includes("min-height: 68px;"), + "the preview footer keeps a stable desktop action area" +); +assert( + styles.includes("height: calc(100dvh - 16px);") && + styles.includes("height: clamp(210px, 38dvh, 360px);"), + "small viewports use their available dynamic height" +); + +console.log("Message preview overlay structure checks passed.");