From 3c95bba0d25ff089b54e3e90c7ec577457ff11e9 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:42:59 +0100 Subject: [PATCH] fix: give the folder name its own line next to the docked file preview --- src/lib/__tests__/files-split.test.ts | 28 ++++++++++++++++++++++++++- src/lib/files-split.ts | 24 +++++++++++++++++++++++ src/screens/FilesScreen.tsx | 24 ++++++++++++++++++----- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/lib/__tests__/files-split.test.ts b/src/lib/__tests__/files-split.test.ts index c6eb3eb3..20cb4d0a 100644 --- a/src/lib/__tests__/files-split.test.ts +++ b/src/lib/__tests__/files-split.test.ts @@ -5,7 +5,10 @@ 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'; +import { + FILES_LIST_MIN, FILES_TITLE_MIN, PREVIEW_HEADER_CHROME, PREVIEW_TITLE_MIN, + filesHeaderTitleRoom, filesListWidth, filesTitleOnOwnRow, +} 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) @@ -28,6 +31,29 @@ describe('filesListWidth', () => { }); }); +describe('the folder name in the Files header next to the docked preview', () => { + it('had about 50 dp beside four buttons and Back in a 320 dp list column', () => { + expect(filesHeaderTitleRoom(320, 4, true)).toBe(52); + expect(filesHeaderTitleRoom(320, 5, false)).toBe(52); + }); + + it('moves under the buttons when that is too little, and stays beside them when there is room', () => { + expect(filesTitleOnOwnRow(filesListWidth(720), 4, true)).toBe(true); + expect(filesTitleOnOwnRow(FILES_LIST_MIN, 5, false)).toBe(true); + expect(filesTitleOnOwnRow(800, 4, true)).toBe(false); + expect(filesHeaderTitleRoom(800, 4, true)).toBeGreaterThanOrEqual(FILES_TITLE_MIN); + // Not measured yet: beside the buttons, as before. + expect(filesTitleOnOwnRow(0, 5, true)).toBe(false); + }); + + it('is how the header lays out its title', () => { + const source = fs.readFileSync(path.resolve(__dirname, '../../screens/FilesScreen.tsx'), 'utf8'); + expect(source).toMatch(/const titleOwnRow = isSplit && previewTarget != null && filesTitleOnOwnRow\(headerWidth, headerButtons, canBack\);/); + expect(source).toMatch(/onLayout=\{\(e\) => setHeaderWidth\(e\.nativeEvent\.layout\.width\)\}/); + expect(source).toMatch(/\{titleOwnRow \? \(\s*/); + }); +}); + 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'); diff --git a/src/lib/files-split.ts b/src/lib/files-split.ts index 5ba424fb..b8600bcb 100644 --- a/src/lib/files-split.ts +++ b/src/lib/files-split.ts @@ -22,3 +22,27 @@ 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)); } + +/** Room the folder name keeps beside the Files header buttons before it moves to a line of its own. */ +export const FILES_TITLE_MIN = 120; +const HEADER_PADDING = 12; // spacing.md, each side +const HEADER_BUTTON = 44; // componentSizes.buttonLg +const HEADER_GAP = 4; // spacing.xs, between buttons +const HEADER_ROW_GAP = 8; // spacing.sm, title side to buttons + +/** + * Width left for the folder name in the Files header, `width` wide, with + * `buttons` buttons on the right and a back button or not. Next to the docked + * preview (280 to 420 dp) the four or five buttons left a folder name about + * 50 dp: "…". + */ +export function filesHeaderTitleRoom(width: number, buttons: number, back: boolean): number { + const actions = buttons * HEADER_BUTTON + Math.max(0, buttons - 1) * HEADER_GAP; + const left = back ? HEADER_BUTTON + HEADER_GAP : 0; + return width - 2 * HEADER_PADDING - actions - HEADER_ROW_GAP - left; +} + +/** Whether the folder name goes on its own line under the header buttons. A width not measured yet keeps it beside them. */ +export function filesTitleOnOwnRow(width: number, buttons: number, back: boolean): boolean { + return width > 0 && filesHeaderTitleRoom(width, buttons, back) < FILES_TITLE_MIN; +} diff --git a/src/screens/FilesScreen.tsx b/src/screens/FilesScreen.tsx index 3b19ef67..d43e951f 100644 --- a/src/screens/FilesScreen.tsx +++ b/src/screens/FilesScreen.tsx @@ -71,7 +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 { filesListWidth, filesTitleOnOwnRow } from '../lib/files-split'; import { radioOptionA11y } from '../components/radio-a11y'; import { useBackWhileFocused } from '../lib/use-back-while-focused'; import { filesBackStep } from '../lib/files-back'; @@ -232,6 +232,9 @@ export default function FilesScreen() { // whole screen keeps clear of the side insets in landscape. const insets = useSafeAreaInsets(); const headerInsets = { paddingTop: insets.top + spacing.sm, paddingHorizontal: spacing.md }; + // The header's width: next to the docked preview the folder name moves + // under the buttons when they leave it too little room. + const [headerWidth, setHeaderWidth] = useState(0); // Folder navigation stack. Hierarchy is real parentId nesting (#379) — each // entry is the folder's node id (namespaced "accountId:nodeId" inside a @@ -1015,8 +1018,12 @@ export default function FilesScreen() { ); } + // Search and view toggle; New folder and Upload; Trash at the top level. + // Only the list column next to the docked preview is that narrow. + const headerButtons = 2 + (canCreateHere ? 2 : 0) + (path.length === 0 && !inTrash ? 1 : 0); + const titleOwnRow = isSplit && previewTarget != null && filesTitleOnOwnRow(headerWidth, headerButtons, canBack); return ( - + setHeaderWidth(e.nativeEvent.layout.width)}> {canBack ? ( @@ -1030,9 +1037,11 @@ export default function FilesScreen() { ) : null} - - {headerName} - + {!titleOwnRow ? ( + + {headerName} + + ) : null} + {titleOwnRow ? ( + + {headerName} + + ) : null} {canBack ? ( {[rootLabel, ...(inTrash ? [trashLabel] : path.map((p) => p.name))].join(' / ')}