diff --git a/components/email/__tests__/viewer-quick-reply-words.test.tsx b/components/email/__tests__/viewer-quick-reply-words.test.tsx
index f980a7c93..2262a4ba1 100644
--- a/components/email/__tests__/viewer-quick-reply-words.test.tsx
+++ b/components/email/__tests__/viewer-quick-reply-words.test.tsx
@@ -85,3 +85,18 @@ describe('quick reply wording', () => {
expect(onReply).toHaveBeenCalledWith('Thanks, will do');
});
});
+
+// compose-p2-17: the toasts lift over the box by its height (toast.tsx).
+describe('quick reply box and the toasts', () => {
+ it('marks the box and gives the page its height, and takes both back when it goes', async () => {
+ const height = vi.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(96);
+ const { unmount } = render();
+ await act(async () => { await new Promise((r) => setTimeout(r, 10)); });
+
+ expect(document.querySelector('[data-quick-reply-box]')).not.toBeNull();
+ expect(document.documentElement.style.getPropertyValue('--quick-reply-height')).toBe('96px');
+ unmount();
+ expect(document.documentElement.style.getPropertyValue('--quick-reply-height')).toBe('');
+ height.mockRestore();
+ });
+});
diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx
index 80ffc1c4c..7cdfca9a8 100644
--- a/components/email/email-viewer.tsx
+++ b/components/email/email-viewer.tsx
@@ -3516,6 +3516,21 @@ export function EmailViewer({
// Not on spam: a reply confirms the address to the sender.
const showQuickReply = !!email && quickReplyEnabled && !mvReplyRow && !replyComposerOpen && !isDraft && !isScheduled && !isBodyLoading && !isInJunkFolder
&& (!effectiveEmailContent.isHtml || iframeReady);
+ // Toasts sit above the quick reply box, as above a composer's footer
+ // (compose-p2-17): its height for toast.tsx, as it grows when focused.
+ useEffect(() => {
+ const box = quickReplyBoxRef.current;
+ if (!showQuickReply || !box || typeof ResizeObserver === 'undefined') return;
+ const root = document.documentElement;
+ const update = () => root.style.setProperty('--quick-reply-height', `${box.offsetHeight}px`);
+ update();
+ const observer = new ResizeObserver(update);
+ observer.observe(box);
+ return () => {
+ observer.disconnect();
+ root.style.removeProperty('--quick-reply-height');
+ };
+ }, [showQuickReply]);
// The plain-text body, where reader-above-quote mounts between text and quote.
const [plainBodyEl, setPlainBodyEl] = useState(null);
// Read aloud (browser speech): the body as shown, HTML frame or plain text.
@@ -6191,7 +6206,7 @@ ${printWatermark ? printWatermark.html : ''}
)}
{/* Quick Reply Section - hidden for drafts and while loading a new email */}
- {showQuickReply && (
+ {showQuickReply && (
{
expect(container).toHaveClass('[html[data-composer-open]_&]:bottom-20');
});
+ // compose-p2-17: "Draft saved" covered the quick reply box.
+ it('sits over the quick reply box, by its height', () => {
+ render();
+ const container = toastContainer();
+ expect(container).toHaveClass('[html:has([data-quick-reply-box])_&]:bottom-[calc(var(--quick-reply-height,5rem)+0.75rem)]');
+ expect(container).toHaveClass('max-sm:[html:has([data-quick-reply-box])_&]:bottom-[calc(var(--quick-reply-height,5rem)+0.75rem)]');
+ });
+
it('sits above a registered bottom bar on the desktop too, such as a form footer', () => {
render();
const container = toastContainer();
diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx
index 33ebeab9b..8de8acca0 100644
--- a/components/ui/toast.tsx
+++ b/components/ui/toast.tsx
@@ -475,10 +475,13 @@ export function ToastContainer({ toasts, onClose }: { toasts: Toast[]; onClose:
// lifted over a registered bottom bar (a form's Save/Cancel footer)
// and over an open composer's footer.
"fixed bottom-[max(1.25rem,calc(var(--bottom-controls-height,0px)+0.75rem))] end-5 z-[99999] flex flex-col gap-2.5 [html[data-composer-open]_&]:bottom-20",
+ // And over the reader's quick reply box (compose-p2-17).
+ "[html:has([data-quick-reply-box])_&]:bottom-[calc(var(--quick-reply-height,5rem)+0.75rem)]",
// Phone: full width with 12px margins, above the bottom controls
// (the tab bar, or whichever bar registered with useBottomControls).
"max-sm:inset-x-3 max-sm:bottom-[calc(var(--bottom-controls-height,3.75rem)+0.75rem)]",
"max-sm:[html[data-composer-open]_&]:bottom-[max(5rem,calc(var(--bottom-controls-height,3.75rem)+0.75rem))]",
+ "max-sm:[html:has([data-quick-reply-box])_&]:bottom-[calc(var(--quick-reply-height,5rem)+0.75rem)]",
// Out of the way of the focused element: the top end corner.
dodge && "top-5 bottom-auto!",
held && "invisible"