feat: add Reading Pane at Bottom mail layout #262

This commit is contained in:
Linus Rath
2026-05-11 15:35:43 +02:00
parent b3dc2e32b8
commit 2c513129f2
7 changed files with 120 additions and 39 deletions
+35 -11
View File
@@ -203,7 +203,7 @@ export default function Home() {
// Mobile/tablet responsive hooks // Mobile/tablet responsive hooks
const { isMobile, isTablet } = useDeviceDetection(); const { isMobile, isTablet } = useDeviceDetection();
const { activeView, sidebarOpen, setSidebarOpen, setActiveView, tabletListVisible, setTabletListVisible, sidebarWidth, emailListWidth, setSidebarWidth, setEmailListWidth, persistColumnWidths, sidebarCollapsed, resetSidebarWidth, resetEmailListWidth } = useUIStore(); const { activeView, sidebarOpen, setSidebarOpen, setActiveView, tabletListVisible, setTabletListVisible, sidebarWidth, emailListWidth, emailListHeight, setSidebarWidth, setEmailListWidth, setEmailListHeight, persistColumnWidths, sidebarCollapsed, resetSidebarWidth, resetEmailListWidth, resetEmailListHeight } = useUIStore();
const { const {
emails, emails,
mailboxes, mailboxes,
@@ -637,6 +637,7 @@ export default function Home() {
const parsed = JSON.parse(stored); const parsed = JSON.parse(stored);
if (parsed.sidebarWidth) setSidebarWidth(parsed.sidebarWidth); if (parsed.sidebarWidth) setSidebarWidth(parsed.sidebarWidth);
if (parsed.emailListWidth) setEmailListWidth(parsed.emailListWidth); if (parsed.emailListWidth) setEmailListWidth(parsed.emailListWidth);
if (parsed.emailListHeight) setEmailListHeight(parsed.emailListHeight);
} }
} catch { /* ignore parse errors */ } } catch { /* ignore parse errors */ }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -1713,9 +1714,11 @@ export default function Home() {
// Get current mailbox name for mobile header // Get current mailbox name for mobile header
const currentMailboxName = mailboxes.find(m => m.id === selectedMailbox)?.name || "Inbox"; const currentMailboxName = mailboxes.find(m => m.id === selectedMailbox)?.name || "Inbox";
const isFocusedMailLayout = mailLayout === 'focus'; const isFocusedMailLayout = mailLayout === 'focus';
const isHorizontalMailLayout = mailLayout === 'horizontal' && !isMobile && !isTablet;
const hasViewerContent = showComposer || Boolean(conversationThread) || Boolean(selectedEmail); const hasViewerContent = showComposer || Boolean(conversationThread) || Boolean(selectedEmail);
const shouldCollapseListPane = (isTablet && !tabletListVisible) || (!isMobile && isFocusedMailLayout && hasViewerContent); const shouldCollapseListPane = (isTablet && !tabletListVisible) || (!isMobile && isFocusedMailLayout && hasViewerContent);
const shouldHideViewerPane = !isMobile && isFocusedMailLayout && !hasViewerContent; const shouldHideViewerPane = !isMobile && isFocusedMailLayout && !hasViewerContent;
const shouldHideHorizontalViewerPane = isHorizontalMailLayout && !hasViewerContent;
// Handle email selection with mobile view switching // Handle email selection with mobile view switching
const handleEmailSelect = async (email: { id: string }) => { const handleEmailSelect = async (email: { id: string }) => {
@@ -1974,21 +1977,30 @@ export default function Home() {
{/* Main Content Area */} {/* Main Content Area */}
<div className={cn("flex flex-col flex-1 min-w-0 h-full", inlineApp && "hidden")}> <div className={cn("flex flex-col flex-1 min-w-0 h-full", inlineApp && "hidden")}>
<div className="flex flex-1 min-h-0"> <div className={cn("flex flex-1 min-h-0", isHorizontalMailLayout && "md:flex-col")}>
{/* Email List - full width on mobile, fixed width on tablet/desktop */} {/* Email List - full width on mobile, fixed width/height on tablet/desktop */}
<div <div
className={cn( className={cn(
"relative flex flex-col h-full bg-background border-r border-border", "relative flex flex-col bg-background",
isHorizontalMailLayout ? "md:w-full md:h-auto" : "h-full border-r border-border",
// Mobile: full width, hidden when viewing email // Mobile: full width, hidden when viewing email
"max-md:flex-1 max-md:border-r-0", "max-md:flex-1 max-md:border-r-0 max-md:border-b-0",
isMobile && activeView !== "list" && "max-md:hidden", isMobile && activeView !== "list" && "max-md:hidden",
// Tablet/Desktop: fixed width with collapse animation // Tablet/Desktop: fixed width with collapse animation
shouldHideViewerPane ? "md:flex-1 md:border-r-0" : "md:flex-shrink-0", !isHorizontalMailLayout && (shouldHideViewerPane ? "md:flex-1 md:border-r-0" : "md:flex-shrink-0"),
"md:shadow-sm", isHorizontalMailLayout && (shouldHideHorizontalViewerPane ? "md:flex-1" : "md:flex-shrink-0"),
isHorizontalMailLayout && !shouldHideHorizontalViewerPane && "md:shadow-[0_8px_12px_-6px_rgba(0,0,0,0.18)] dark:md:shadow-[0_8px_14px_-6px_rgba(0,0,0,0.55)]",
!isHorizontalMailLayout && "md:shadow-sm",
!isResizing && "transition-all duration-200 ease-out", !isResizing && "transition-all duration-200 ease-out",
shouldCollapseListPane && "md:w-0 md:opacity-0 md:overflow-hidden md:border-r-0" shouldCollapseListPane && "md:w-0 md:opacity-0 md:overflow-hidden md:border-r-0"
)} )}
style={!isMobile && !shouldCollapseListPane && !shouldHideViewerPane ? { width: emailListWidth } : undefined} style={
isMobile
? undefined
: isHorizontalMailLayout
? (!shouldHideHorizontalViewerPane ? { height: emailListHeight } : undefined)
: (!shouldCollapseListPane && !shouldHideViewerPane ? { width: emailListWidth } : undefined)
}
> >
{/* Mobile Header for List View */} {/* Mobile Header for List View */}
<MobileHeader <MobileHeader
@@ -2297,7 +2309,7 @@ export default function Home() {
</div> </div>
{/* Email list resize handle (desktop only) */} {/* Email list resize handle (desktop only) */}
{!isMobile && !isTablet && !isFocusedMailLayout && ( {!isMobile && !isTablet && !isFocusedMailLayout && !isHorizontalMailLayout && (
<ResizeHandle <ResizeHandle
onResizeStart={() => { dragStartWidth.current = emailListWidth; setIsResizing(true); }} onResizeStart={() => { dragStartWidth.current = emailListWidth; setIsResizing(true); }}
onResize={(delta) => setEmailListWidth(dragStartWidth.current + delta)} onResize={(delta) => setEmailListWidth(dragStartWidth.current + delta)}
@@ -2305,17 +2317,29 @@ export default function Home() {
onDoubleClick={resetEmailListWidth} onDoubleClick={resetEmailListWidth}
/> />
)} )}
{!isMobile && !isTablet && isHorizontalMailLayout && !shouldHideHorizontalViewerPane && (
<ResizeHandle
orientation="horizontal"
onResizeStart={() => { dragStartWidth.current = emailListHeight; setIsResizing(true); }}
onResize={(delta) => setEmailListHeight(dragStartWidth.current + delta)}
onResizeEnd={() => { setIsResizing(false); persistColumnWidths(); }}
onDoubleClick={resetEmailListHeight}
/>
)}
{/* Email Viewer / Composer - full screen on mobile, flex on tablet/desktop */} {/* Email Viewer / Composer - full screen on mobile, flex on tablet/desktop */}
<div <div
className={cn( className={cn(
"flex flex-col h-full bg-background flex-1 min-w-0", "flex flex-col bg-background flex-1 min-w-0",
isHorizontalMailLayout ? "min-h-0" : "h-full",
// Mobile: full screen overlay when active // Mobile: full screen overlay when active
"max-md:fixed max-md:inset-0 max-md:z-30", "max-md:fixed max-md:inset-0 max-md:z-30",
"max-md:h-full",
isMobile && activeView !== "viewer" && "max-md:hidden", isMobile && activeView !== "viewer" && "max-md:hidden",
// Tablet/Desktop: relative // Tablet/Desktop: relative
"md:relative", "md:relative",
shouldHideViewerPane && "md:hidden" shouldHideViewerPane && "md:hidden",
shouldHideHorizontalViewerPane && "md:hidden"
)} )}
> >
{/* Inline Composer - shown in viewer pane */} {/* Inline Composer - shown in viewer pane */}
+1 -1
View File
@@ -30,7 +30,7 @@ const RESTRICTABLE_SETTINGS = [
{ key: 'markAsReadDelay', label: 'Mark as Read Delay', category: 'Email', type: 'number' }, { key: 'markAsReadDelay', label: 'Mark as Read Delay', category: 'Email', type: 'number' },
{ key: 'deleteAction', label: 'Delete Action', category: 'Email', type: 'enum', allowedValues: ['trash', 'permanent'] }, { key: 'deleteAction', label: 'Delete Action', category: 'Email', type: 'enum', allowedValues: ['trash', 'permanent'] },
{ key: 'showPreview', label: 'Show Preview', category: 'Email', type: 'boolean' }, { key: 'showPreview', label: 'Show Preview', category: 'Email', type: 'boolean' },
{ key: 'mailLayout', label: 'Mail Layout', category: 'Email', type: 'enum', allowedValues: ['split', 'focus'] }, { key: 'mailLayout', label: 'Mail Layout', category: 'Email', type: 'enum', allowedValues: ['split', 'focus', 'horizontal'] },
{ key: 'emailsPerPage', label: 'Emails Per Page', category: 'Email', type: 'number' }, { key: 'emailsPerPage', label: 'Emails Per Page', category: 'Email', type: 'number' },
{ key: 'externalContentPolicy', label: 'External Content Policy', category: 'Email', type: 'enum', allowedValues: ['allow', 'block', 'ask'] }, { key: 'externalContentPolicy', label: 'External Content Policy', category: 'Email', type: 'enum', allowedValues: ['allow', 'block', 'ask'] },
{ key: 'sendConfirmation', label: 'Send Confirmation', category: 'Composer', type: 'boolean' }, { key: 'sendConfirmation', label: 'Send Confirmation', category: 'Composer', type: 'boolean' },
+20 -11
View File
@@ -8,38 +8,46 @@ interface ResizeHandleProps {
onResize: (delta: number) => void; onResize: (delta: number) => void;
onResizeEnd?: () => void; onResizeEnd?: () => void;
onDoubleClick?: () => void; onDoubleClick?: () => void;
orientation?: "vertical" | "horizontal";
className?: string; className?: string;
} }
const KEYBOARD_STEP = 10; const KEYBOARD_STEP = 10;
export function ResizeHandle({ onResizeStart, onResize, onResizeEnd, onDoubleClick, className }: ResizeHandleProps) { export function ResizeHandle({ onResizeStart, onResize, onResizeEnd, onDoubleClick, orientation = "vertical", className }: ResizeHandleProps) {
const isDragging = useRef(false); const isDragging = useRef(false);
const startX = useRef(0); const startPos = useRef(0);
const isHorizontal = orientation === "horizontal";
const handleMouseDown = useCallback((e: React.MouseEvent) => { const handleMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault(); e.preventDefault();
isDragging.current = true; isDragging.current = true;
startX.current = e.clientX; startPos.current = isHorizontal ? e.clientY : e.clientX;
document.body.style.cursor = "col-resize"; document.body.style.cursor = isHorizontal ? "row-resize" : "col-resize";
document.body.style.userSelect = "none"; document.body.style.userSelect = "none";
onResizeStart?.(); onResizeStart?.();
}, [onResizeStart]); }, [onResizeStart, isHorizontal]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => { const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
let delta = 0; let delta = 0;
if (isHorizontal) {
if (e.key === "ArrowUp") delta = -KEYBOARD_STEP;
else if (e.key === "ArrowDown") delta = KEYBOARD_STEP;
else return;
} else {
if (e.key === "ArrowLeft") delta = -KEYBOARD_STEP; if (e.key === "ArrowLeft") delta = -KEYBOARD_STEP;
else if (e.key === "ArrowRight") delta = KEYBOARD_STEP; else if (e.key === "ArrowRight") delta = KEYBOARD_STEP;
else return; else return;
}
e.preventDefault(); e.preventDefault();
onResize(delta); onResize(delta);
onResizeEnd?.(); onResizeEnd?.();
}, [onResize, onResizeEnd]); }, [onResize, onResizeEnd, isHorizontal]);
useEffect(() => { useEffect(() => {
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
if (!isDragging.current) return; if (!isDragging.current) return;
const delta = e.clientX - startX.current; const delta = (isHorizontal ? e.clientY : e.clientX) - startPos.current;
onResize(delta); onResize(delta);
}; };
@@ -57,24 +65,25 @@ export function ResizeHandle({ onResizeStart, onResize, onResizeEnd, onDoubleCli
document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp); document.removeEventListener("mouseup", handleMouseUp);
}; };
}, [onResize, onResizeEnd]); }, [onResize, onResizeEnd, isHorizontal]);
return ( return (
<div <div
role="separator" role="separator"
aria-orientation="vertical" aria-orientation={isHorizontal ? "horizontal" : "vertical"}
aria-label="Resize" aria-label="Resize"
tabIndex={0} tabIndex={0}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
className={cn( className={cn(
"w-1 flex-shrink-0 cursor-col-resize hover:bg-primary/30 active:bg-primary/50 transition-colors relative group", "flex-shrink-0 hover:bg-primary/30 active:bg-primary/50 transition-colors relative group",
"focus-visible:outline-none focus-visible:bg-primary/40 focus-visible:ring-2 focus-visible:ring-primary/50", "focus-visible:outline-none focus-visible:bg-primary/40 focus-visible:ring-2 focus-visible:ring-primary/50",
isHorizontal ? "h-1 cursor-row-resize bg-border" : "w-1 cursor-col-resize",
className className
)} )}
> >
<div className="absolute inset-y-0 -left-1 -right-1" /> <div className={cn("absolute", isHorizontal ? "inset-x-0 -top-1 -bottom-1" : "inset-y-0 -left-1 -right-1")} />
</div> </div>
); );
} }
+31 -4
View File
@@ -20,8 +20,6 @@ function MailLayoutPreview({
value: MailLayout; value: MailLayout;
t: (key: string) => string; t: (key: string) => string;
}) { }) {
const isSplit = value === 'split';
return ( return (
<div className="mt-3 rounded-xl border border-border bg-background p-3"> <div className="mt-3 rounded-xl border border-border bg-background p-3">
<div> <div>
@@ -33,7 +31,7 @@ function MailLayoutPreview({
<div className="flex h-28"> <div className="flex h-28">
<div className="w-11 border-r border-border bg-muted/40" /> <div className="w-11 border-r border-border bg-muted/40" />
{isSplit ? ( {value === 'split' && (
<> <>
<div className="w-28 border-r border-border bg-background"> <div className="w-28 border-r border-border bg-background">
{MAIL_LAYOUT_PREVIEW_ROWS.map((row) => ( {MAIL_LAYOUT_PREVIEW_ROWS.map((row) => (
@@ -56,7 +54,9 @@ function MailLayoutPreview({
<div className="mt-1.5 h-2 w-2/3 rounded bg-foreground/10" /> <div className="mt-1.5 h-2 w-2/3 rounded bg-foreground/10" />
</div> </div>
</> </>
) : ( )}
{value === 'focus' && (
<div className="flex-1 bg-background px-2 py-2"> <div className="flex-1 bg-background px-2 py-2">
<div className="space-y-1.5"> <div className="space-y-1.5">
{MAIL_LAYOUT_PREVIEW_ROWS.map((row) => ( {MAIL_LAYOUT_PREVIEW_ROWS.map((row) => (
@@ -76,6 +76,32 @@ function MailLayoutPreview({
</div> </div>
</div> </div>
)} )}
{value === 'horizontal' && (
<div className="flex-1 flex flex-col bg-background">
<div className="border-b border-border bg-background">
{MAIL_LAYOUT_PREVIEW_ROWS.map((row) => (
<div
key={row.subject}
className={cn(
'border-b border-border px-2 py-1 text-[10px] last:border-b-0',
row.selected && 'bg-primary/10'
)}
>
<div className="truncate text-foreground">
<span className="font-medium">{row.sender}</span>
<span className="mx-1.5 text-muted-foreground">{row.subject}</span>
</div>
</div>
))}
</div>
<div className="flex-1 bg-background px-3 py-2">
<div className="h-2 w-20 rounded bg-foreground/10" />
<div className="mt-1.5 h-1.5 w-full rounded bg-foreground/10" />
<div className="mt-1 h-1.5 w-5/6 rounded bg-foreground/10" />
</div>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -100,6 +126,7 @@ export function LayoutSettings() {
options={[ options={[
{ value: 'split', label: tEmail('mail_layout.split') }, { value: 'split', label: tEmail('mail_layout.split') },
{ value: 'focus', label: tEmail('mail_layout.focus') }, { value: 'focus', label: tEmail('mail_layout.focus') },
{ value: 'horizontal', label: tEmail('mail_layout.horizontal') },
]} ]}
/> />
<MailLayoutPreview value={mailLayout} t={tEmail} /> <MailLayoutPreview value={mailLayout} t={tEmail} />
+4 -2
View File
@@ -949,11 +949,13 @@
}, },
"mail_layout": { "mail_layout": {
"label": "Mail Layout", "label": "Mail Layout",
"description": "Choose between the classic split reading pane and a Gmail-style focused reading flow.", "description": "Choose between the classic split reading pane, a Gmail-style focused reading flow, or a Zimbra-style bottom reading pane.",
"split": "Split pane", "split": "Split pane",
"split_description": "Keep the message list and reading pane visible side by side.", "split_description": "Keep the message list and reading pane visible side by side.",
"focus": "Focused list", "focus": "Focused list",
"focus_description": "Show one line per message and open mail full-width while keeping the folder sidebar visible." "focus_description": "Show one line per message and open mail full-width while keeping the folder sidebar visible.",
"horizontal": "Reading pane at bottom",
"horizontal_description": "Show the message list on top and open the selected message in a reading pane below it."
}, },
"show_preview": { "show_preview": {
"label": "Show Preview Text", "label": "Show Preview Text",
+1 -1
View File
@@ -38,7 +38,7 @@ export type MailAttachmentAction = 'preview' | 'download';
export type AttachmentPosition = 'beside-sender' | 'below-header'; export type AttachmentPosition = 'beside-sender' | 'below-header';
export type ToolbarPosition = 'top' | 'below-subject'; export type ToolbarPosition = 'top' | 'below-subject';
export type ArchiveMode = 'single' | 'year' | 'month'; export type ArchiveMode = 'single' | 'year' | 'month';
export type MailLayout = 'split' | 'focus'; export type MailLayout = 'split' | 'focus' | 'horizontal';
export type CalendarHoverPreview = 'off' | 'instant' | 'delay-500ms' | 'delay-1s' | 'delay-2s'; export type CalendarHoverPreview = 'off' | 'instant' | 'delay-500ms' | 'delay-1s' | 'delay-2s';
export type HoverAction = 'delete' | 'star' | 'markRead' | 'archive' | 'tag' | 'spam'; export type HoverAction = 'delete' | 'star' | 'markRead' | 'archive' | 'tag' | 'spam';
+25 -6
View File
@@ -11,6 +11,10 @@ const SIDEBAR_DEFAULT = 256;
const EMAIL_LIST_MIN = 240; const EMAIL_LIST_MIN = 240;
const EMAIL_LIST_MAX = 600; const EMAIL_LIST_MAX = 600;
const EMAIL_LIST_DEFAULT = 384; const EMAIL_LIST_DEFAULT = 384;
// Email list height (in pixels) for horizontal "Reading Pane at Bottom" layout
const EMAIL_LIST_HEIGHT_MIN = 160;
const EMAIL_LIST_HEIGHT_MAX = 800;
const EMAIL_LIST_HEIGHT_DEFAULT = 320;
interface UIState { interface UIState {
// Mobile view state // Mobile view state
@@ -28,6 +32,7 @@ interface UIState {
// Resizable column widths (desktop only) // Resizable column widths (desktop only)
sidebarWidth: number; sidebarWidth: number;
emailListWidth: number; emailListWidth: number;
emailListHeight: number;
// Sidebar collapsed state (desktop) // Sidebar collapsed state (desktop)
sidebarCollapsed: boolean; sidebarCollapsed: boolean;
@@ -40,8 +45,10 @@ interface UIState {
setDeviceType: (isMobile: boolean, isTablet: boolean, isDesktop: boolean) => void; setDeviceType: (isMobile: boolean, isTablet: boolean, isDesktop: boolean) => void;
setSidebarWidth: (width: number) => void; setSidebarWidth: (width: number) => void;
setEmailListWidth: (width: number) => void; setEmailListWidth: (width: number) => void;
setEmailListHeight: (height: number) => void;
resetSidebarWidth: () => void; resetSidebarWidth: () => void;
resetEmailListWidth: () => void; resetEmailListWidth: () => void;
resetEmailListHeight: () => void;
persistColumnWidths: () => void; persistColumnWidths: () => void;
setSidebarCollapsed: (collapsed: boolean) => void; setSidebarCollapsed: (collapsed: boolean) => void;
toggleSidebarCollapsed: () => void; toggleSidebarCollapsed: () => void;
@@ -64,6 +71,7 @@ export const useUIStore = create<UIState>((set, get) => ({
isDesktop: true, isDesktop: true,
sidebarWidth: SIDEBAR_DEFAULT, sidebarWidth: SIDEBAR_DEFAULT,
emailListWidth: EMAIL_LIST_DEFAULT, emailListWidth: EMAIL_LIST_DEFAULT,
emailListHeight: EMAIL_LIST_HEIGHT_DEFAULT,
sidebarCollapsed: false, sidebarCollapsed: false,
// Actions // Actions
@@ -84,26 +92,37 @@ export const useUIStore = create<UIState>((set, get) => ({
setEmailListWidth: (width) => setEmailListWidth: (width) =>
set({ emailListWidth: Math.min(EMAIL_LIST_MAX, Math.max(EMAIL_LIST_MIN, width)) }), set({ emailListWidth: Math.min(EMAIL_LIST_MAX, Math.max(EMAIL_LIST_MIN, width)) }),
setEmailListHeight: (height) =>
set({ emailListHeight: Math.min(EMAIL_LIST_HEIGHT_MAX, Math.max(EMAIL_LIST_HEIGHT_MIN, height)) }),
resetSidebarWidth: () => { resetSidebarWidth: () => {
set({ sidebarWidth: SIDEBAR_DEFAULT }); set({ sidebarWidth: SIDEBAR_DEFAULT });
const { emailListWidth } = get(); const { emailListWidth, emailListHeight } = get();
try { try {
localStorage.setItem("column-widths", JSON.stringify({ sidebarWidth: SIDEBAR_DEFAULT, emailListWidth })); localStorage.setItem("column-widths", JSON.stringify({ sidebarWidth: SIDEBAR_DEFAULT, emailListWidth, emailListHeight }));
} catch { /* localStorage may be unavailable */ } } catch { /* localStorage may be unavailable */ }
}, },
resetEmailListWidth: () => { resetEmailListWidth: () => {
set({ emailListWidth: EMAIL_LIST_DEFAULT }); set({ emailListWidth: EMAIL_LIST_DEFAULT });
const { sidebarWidth } = get(); const { sidebarWidth, emailListHeight } = get();
try { try {
localStorage.setItem("column-widths", JSON.stringify({ sidebarWidth, emailListWidth: EMAIL_LIST_DEFAULT })); localStorage.setItem("column-widths", JSON.stringify({ sidebarWidth, emailListWidth: EMAIL_LIST_DEFAULT, emailListHeight }));
} catch { /* localStorage may be unavailable */ }
},
resetEmailListHeight: () => {
set({ emailListHeight: EMAIL_LIST_HEIGHT_DEFAULT });
const { sidebarWidth, emailListWidth } = get();
try {
localStorage.setItem("column-widths", JSON.stringify({ sidebarWidth, emailListWidth, emailListHeight: EMAIL_LIST_HEIGHT_DEFAULT }));
} catch { /* localStorage may be unavailable */ } } catch { /* localStorage may be unavailable */ }
}, },
persistColumnWidths: () => { persistColumnWidths: () => {
const { sidebarWidth, emailListWidth } = get(); const { sidebarWidth, emailListWidth, emailListHeight } = get();
try { try {
localStorage.setItem("column-widths", JSON.stringify({ sidebarWidth, emailListWidth })); localStorage.setItem("column-widths", JSON.stringify({ sidebarWidth, emailListWidth, emailListHeight }));
} catch { /* localStorage may be unavailable */ } } catch { /* localStorage may be unavailable */ }
}, },