From ea8b27689aa06bbba30cf0b9dd3eae69bf5ee71b Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 3 Nov 2026 07:55:39 +0100 Subject: [PATCH] fix: give a composer in the bottom pane room for the message body --- components/mail/mail-app.tsx | 4 ++-- stores/__tests__/ui-store.test.ts | 6 +++++- stores/ui-store.ts | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 13381f0e1..d68c18c98 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -57,7 +57,7 @@ import { useSettingsStore } from "@/stores/settings-store"; import { useEffectiveMailLayout } from "@/hooks/use-effective-mail-layout"; import { useContactStore } from "@/stores/contact-store"; import { useIdentityStore } from "@/stores/identity-store"; -import { useUIStore, listPaneCssSize, SIDEBAR_MIN, SIDEBAR_MAX, EMAIL_LIST_MIN, EMAIL_LIST_MAX, EMAIL_LIST_HEIGHT_MIN, EMAIL_LIST_HEIGHT_MAX, READING_PANE_MIN_WIDTH, READING_PANE_MIN_HEIGHT } from "@/stores/ui-store"; +import { useUIStore, listPaneCssSize, SIDEBAR_MIN, SIDEBAR_MAX, EMAIL_LIST_MIN, EMAIL_LIST_MAX, EMAIL_LIST_HEIGHT_MIN, EMAIL_LIST_HEIGHT_MAX, READING_PANE_MIN_WIDTH, READING_PANE_MIN_HEIGHT, COMPOSER_PANE_MIN_HEIGHT } from "@/stores/ui-store"; import { useDeviceDetection } from "@/hooks/use-media-query"; import { usePaneSize } from "@/hooks/use-pane-size"; import { usePaneId } from "@/hooks/use-pane-context"; @@ -5139,7 +5139,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { isMobile ? undefined : isHorizontalMailLayout - ? (!shouldHideHorizontalViewerPane ? { height: listPaneCssSize(emailListHeight, EMAIL_LIST_HEIGHT_MIN, READING_PANE_MIN_HEIGHT) } : undefined) + ? (!shouldHideHorizontalViewerPane ? { height: listPaneCssSize(emailListHeight, EMAIL_LIST_HEIGHT_MIN, showComposer ? COMPOSER_PANE_MIN_HEIGHT : READING_PANE_MIN_HEIGHT) } : undefined) : (!shouldCollapseListPane && !shouldHideViewerPane ? { width: listPaneCssSize(clampEmailListWidth(emailListWidth), EMAIL_LIST_MIN, READING_PANE_MIN_WIDTH) } : undefined) } > diff --git a/stores/__tests__/ui-store.test.ts b/stores/__tests__/ui-store.test.ts index 612c73a85..65d2d265a 100644 --- a/stores/__tests__/ui-store.test.ts +++ b/stores/__tests__/ui-store.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { useUIStore, listPaneCssSize } from '../ui-store'; +import { useUIStore, listPaneCssSize, COMPOSER_PANE_MIN_HEIGHT } from '../ui-store'; describe('ui-store', () => { beforeEach(() => { @@ -25,6 +25,10 @@ describe('ui-store', () => { it('never puts the lower limit above a saved size smaller than it', () => { expect(listPaneCssSize(220, 240, 320)).toBe('clamp(220px, calc(100% - 320px), 220px)'); }); + + it('reserves a text-relative height for a composer below the list', () => { + expect(listPaneCssSize(320, 160, COMPOSER_PANE_MIN_HEIGHT)).toBe('clamp(160px, calc(100% - 30rem), 320px)'); + }); }); describe('setSidebarWidth', () => { diff --git a/stores/ui-store.ts b/stores/ui-store.ts index 2f0b5a2eb..6d4799d52 100644 --- a/stores/ui-store.ts +++ b/stores/ui-store.ts @@ -19,14 +19,18 @@ const EMAIL_LIST_HEIGHT_DEFAULT = 320; // What the reading pane keeps of the window, whatever size the list was dragged to export const READING_PANE_MIN_WIDTH = 320; export const READING_PANE_MIN_HEIGHT = 240; +// A composer below the list needs room for its fields and some of the body. In +// rem because those fields grow with the text size. +export const COMPOSER_PANE_MIN_HEIGHT = '30rem'; /** * The list pane's CSS size: the size the user dragged it to, as long as that * leaves the reading pane its minimum. Saved sizes outlive the window they were * set in, and a smaller window would squeeze the reading pane down to nothing. */ -export function listPaneCssSize(saved: number, min: number, readingPaneMin: number): string { - return `clamp(${Math.min(saved, min)}px, calc(100% - ${readingPaneMin}px), ${saved}px)`; +export function listPaneCssSize(saved: number, min: number, readingPaneMin: number | string): string { + const reserved = typeof readingPaneMin === 'number' ? `${readingPaneMin}px` : readingPaneMin; + return `clamp(${Math.min(saved, min)}px, calc(100% - ${reserved}), ${saved}px)`; } interface UIState {