diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index c69d7c2c7..a4f6e764f 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, SIDEBAR_MIN, SIDEBAR_MAX, EMAIL_LIST_MIN, EMAIL_LIST_MAX, EMAIL_LIST_HEIGHT_MIN, EMAIL_LIST_HEIGHT_MAX } 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 } 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"; @@ -283,6 +283,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { // Column resize state (disable transitions during drag) const [isResizing, setIsResizing] = useState(false); const dragStartWidth = useRef(0); + const listPaneRef = useRef(null); // Mobile conversation view state const [conversationBlobSource, setConversationBlobSource] = useState(); const conversationOpenSeqRef = useRef(0); @@ -5086,6 +5087,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
{/* Email List - full width on mobile, fixed width/height on tablet/desktop */}

{t('shell.message_list')}

@@ -5498,7 +5500,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { value={emailListWidth} min={EMAIL_LIST_MIN} max={EMAIL_LIST_MAX} - onResizeStart={() => { dragStartWidth.current = emailListWidth; setIsResizing(true); }} + onResizeStart={() => { dragStartWidth.current = listPaneRef.current?.getBoundingClientRect().width ?? emailListWidth; setIsResizing(true); }} onResize={(delta) => setEmailListWidth(dragStartWidth.current + delta)} onResizeEnd={() => { setIsResizing(false); persistColumnWidths(); }} onDoubleClick={resetEmailListWidth} @@ -5511,7 +5513,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { value={emailListHeight} min={EMAIL_LIST_HEIGHT_MIN} max={EMAIL_LIST_HEIGHT_MAX} - onResizeStart={() => { dragStartWidth.current = emailListHeight; setIsResizing(true); }} + onResizeStart={() => { dragStartWidth.current = listPaneRef.current?.getBoundingClientRect().height ?? emailListHeight; setIsResizing(true); }} onResize={(delta) => setEmailListHeight(dragStartWidth.current + delta)} onResizeEnd={() => { setIsResizing(false); persistColumnWidths(); }} onDoubleClick={resetEmailListHeight} diff --git a/stores/__tests__/ui-store.test.ts b/stores/__tests__/ui-store.test.ts index 76eef2c2f..612c73a85 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 } from '../ui-store'; +import { useUIStore, listPaneCssSize } from '../ui-store'; describe('ui-store', () => { beforeEach(() => { @@ -17,6 +17,16 @@ describe('ui-store', () => { localStorage.clear(); }); + describe('listPaneCssSize', () => { + it('keeps the saved size only while the reading pane keeps its minimum', () => { + expect(listPaneCssSize(600, 240, 320)).toBe('clamp(240px, calc(100% - 320px), 600px)'); + }); + + 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)'); + }); + }); + describe('setSidebarWidth', () => { it('should clamp to minimum', () => { useUIStore.getState().setSidebarWidth(50); diff --git a/stores/ui-store.ts b/stores/ui-store.ts index 275474aff..2f0b5a2eb 100644 --- a/stores/ui-store.ts +++ b/stores/ui-store.ts @@ -16,6 +16,18 @@ const EMAIL_LIST_DEFAULT = 384; export const EMAIL_LIST_HEIGHT_MIN = 160; export const EMAIL_LIST_HEIGHT_MAX = 800; 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; + +/** + * 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)`; +} interface UIState { // Mobile view state