From 65a8ac34698bbffd77a7669ab9204e262b14742b Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:28:30 +0100 Subject: [PATCH] fix: show the file name in the tablet split view's preview header --- src/lib/__tests__/files-split.test.ts | 37 +++++++++++++++++++++++++++ src/lib/files-split.ts | 24 +++++++++++++++++ src/screens/FilesScreen.tsx | 13 +++++++--- 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/lib/__tests__/files-split.test.ts create mode 100644 src/lib/files-split.ts diff --git a/src/lib/__tests__/files-split.test.ts b/src/lib/__tests__/files-split.test.ts new file mode 100644 index 00000000..c6eb3eb3 --- /dev/null +++ b/src/lib/__tests__/files-split.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it, vi } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; + +vi.mock('react-native', () => ({ useWindowDimensions: () => ({ width: 800, height: 1280 }) })); +vi.mock('react-native-safe-area-context', () => ({ useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }) })); + +import { FILES_LIST_MIN, PREVIEW_HEADER_CHROME, PREVIEW_TITLE_MIN, filesListWidth } from '../files-split'; + +// In the tablet split view the docked file preview had a fixed 45 % of the +// row. On a Pixel Tablet in portrait (about 720 dp next to the tab rail) +// that left the header's file name 2 px between its six buttons. +const titleRoom = (contentWidth: number) => contentWidth - filesListWidth(contentWidth) - PREVIEW_HEADER_CHROME; + +describe('filesListWidth', () => { + it('leaves the file name room in the preview header on a tablet in portrait', () => { + expect(titleRoom(720)).toBeGreaterThanOrEqual(PREVIEW_TITLE_MIN); + expect(filesListWidth(720)).toBeGreaterThanOrEqual(FILES_LIST_MIN); + }); + + it('gives the list its usual pane width on a wide window', () => { + expect(filesListWidth(1200)).toBe(408); + expect(titleRoom(1200)).toBeGreaterThan(400); + }); + + it('keeps the list usable on a narrow split', () => { + expect(filesListWidth(600)).toBe(FILES_LIST_MIN); + }); +}); + +describe('the Files split view', () => { + it('sizes the list column with filesListWidth and lets the preview take the rest', () => { + const source = fs.readFileSync(path.resolve(__dirname, '../../screens/FilesScreen.tsx'), 'utf8'); + expect(source).toContain('width: filesListWidth(contentWidth)'); + expect(source).not.toMatch(/previewPane: \{[^}]*width: '45%'/); + }); +}); diff --git a/src/lib/files-split.ts b/src/lib/files-split.ts new file mode 100644 index 00000000..5ba424fb --- /dev/null +++ b/src/lib/files-split.ts @@ -0,0 +1,24 @@ +import { listPaneWidth } from './use-layout'; + +/** + * Fixed chrome of the docked file preview's header: 16 dp padding on each + * side and six 40 dp buttons (close, previous, next, open, download, share) + * with 8 dp gaps, the title's gap included. + */ +export const PREVIEW_HEADER_CHROME = 2 * 16 + 6 * 40 + 6 * 8; +/** Room the file name keeps in the docked preview's header. */ +export const PREVIEW_TITLE_MIN = 120; +/** The file list never gets narrower than this next to a preview. */ +export const FILES_LIST_MIN = 280; + +/** + * Width of the file list next to the docked preview. The preview had a fixed + * 45 %: on a tablet in portrait (about 720 dp next to the tab rail) that was + * 324 dp, and the six header buttons left the file name 2 px. The list takes + * the usual list-pane width (listPaneWidth, as in Mail and Contacts) and + * gives way, down to FILES_LIST_MIN, until the name has PREVIEW_TITLE_MIN. + */ +export function filesListWidth(contentWidth: number): number { + const previewMin = PREVIEW_HEADER_CHROME + PREVIEW_TITLE_MIN; + return Math.max(FILES_LIST_MIN, Math.min(listPaneWidth(contentWidth), contentWidth - previewMin)); +} diff --git a/src/screens/FilesScreen.tsx b/src/screens/FilesScreen.tsx index b62cab75..d9c040d0 100644 --- a/src/screens/FilesScreen.tsx +++ b/src/screens/FilesScreen.tsx @@ -71,6 +71,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { formatListDate } from '../lib/date-format'; import { useLayout, useSheetInsets } from '../lib/use-layout'; import { useSheetBottomInset } from '../lib/sheet-bottom-inset'; +import { filesListWidth } from '../lib/files-split'; import { useBackWhileFocused } from '../lib/use-back-while-focused'; import { filesBackStep } from '../lib/files-back'; import { driveChange, type DriveScope } from '../lib/files-drive'; @@ -258,6 +259,8 @@ export default function FilesScreen() { const [actionsTarget, setActionsTarget] = useState(null); // The menu of a starred folder or a recent file. const [placeMenu, setPlaceMenu] = useState(null); + // Width inside the side insets, for the list next to a docked preview. + const [contentWidth, setContentWidth] = useState(0); const [shareTarget, setShareTarget] = useState(null); const [moveTarget, setMoveTarget] = useState(null); const [previewTarget, setPreviewTarget] = useState(null); @@ -1563,8 +1566,11 @@ export default function FilesScreen() { // On a wide window an open file shows beside the list instead of covering it. const previewDocked = isSplit && previewTarget != null; return ( - - + setContentWidth(e.nativeEvent.layout.width - insets.left - insets.right)} + > + {renderHeader()} {body} @@ -2177,7 +2183,8 @@ function makeStyles(c: ThemePalette) { }, splitRow: { flexDirection: 'row' }, listColumn: { flex: 1 }, - previewPane: { width: '45%', borderLeftWidth: 1, borderLeftColor: c.border }, + // The rest of the row; the list column gets filesListWidth. + previewPane: { flex: 1, borderLeftWidth: 1, borderLeftColor: c.border }, header: { paddingBottom: spacing.sm, borderBottomWidth: 1,