From 2acdc2e16dfa0418857a7cb226b96648b9ae50ab Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 1 Jul 2026 19:52:15 -0600 Subject: [PATCH] Expanding FileBrowser button widths when page is wide enough. --- app/src/components/FileBrowser.js | 25 +++++++++++++++++++++---- app/src/components/FileBrowser.test.js | 5 ++++- app/src/components/Files.js | 4 +++- app/src/hooks/customHooks.js | 24 +++++++++++++++++++++++- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/app/src/components/FileBrowser.js b/app/src/components/FileBrowser.js index a35158f6..494e1b1e 100644 --- a/app/src/components/FileBrowser.js +++ b/app/src/components/FileBrowser.js @@ -27,7 +27,7 @@ import {useDropzone} from "react-dropzone"; import {deleteFile, ignoreDirectory, makeDirectory, movePaths, renamePath, unignoreDirectory} from "../api"; import _ from 'lodash'; import {SortableTable} from "./SortableTable"; -import {useBrowseFiles, useMediaDirectory, useUploadFile, useWROLMode} from "../hooks/customHooks"; +import {useBrowseFiles, useElementWidth, useMediaDirectory, useUploadFile, useWROLMode} from "../hooks/customHooks"; import {FileRowTagIcon, FilesRefreshButton} from "./Files"; import {FilePreviewContext} from "./FilePreview"; import {DragSelectionContext, DragSelectionProvider, SettingsContext, ThemeContext} from "../contexts/contexts"; @@ -196,6 +196,10 @@ function SkeletonFileRow({depth}) { ); } +// Below this footer width (px) the action buttons are icon-only; at or above it they expand to show labels. +// The eight labeled buttons need ~1020px to fit on a single row; this leaves a small buffer. +const FOOTER_LABELS_MIN_WIDTH = 1050; + export function FileBrowser() { const [selectedPaths, setSelectedPaths] = React.useState([]); const [renameOpen, setRenameOpen] = React.useState(false); @@ -342,9 +346,15 @@ export function FileBrowser() { } } + const [footerRef, footerWidth] = useElementWidth(); + // Expand icon-only buttons to include labels once the footer is wide enough to fit them. + const showLabels = footerWidth >= FOOTER_LABELS_MIN_WIDTH; + // Semantic UI renders a labeled icon button when `content` and `labelPosition` are set, else icon-only. + const labelProps = (content) => showLabels ? {content, labelPosition: 'left'} : {}; + const footerClassName = `sticky-footer ${inverted}`; - const footer =
- + const footer =
+ 0 && !singleDirectorySelected)} onClick={() => setUploadOpen(true)} + {...labelProps('Upload')} /> setIgnoreDirectoryOpen(true)} + {...labelProps(isDirectoryIgnored ? 'Unignore' : 'Ignore')} /> setBulkTagOpen(true)} + {...labelProps('Tag')} /> ({ useWROLMode: jest.fn(() => false), useStatusFlag: jest.fn(() => false), useUploadFile: () => mockUseUploadFile, + useElementWidth: jest.fn(() => [() => {}, 0]), })); // Mock react-dropzone @@ -45,7 +46,7 @@ jest.mock('react-dropzone', () => ({ })); // Import mocked hooks for manipulation -import {useBrowseFiles, useWROLMode} from '../hooks/customHooks'; +import {useBrowseFiles, useElementWidth, useWROLMode} from '../hooks/customHooks'; // Create wrapper with all required contexts const AllContextsWrapper = ({children}) => { @@ -91,6 +92,8 @@ describe('FileBrowser', () => { fetchFiles: jest.fn(), }); useWROLMode.mockReturnValue(false); + // [callbackRef, width]; width 0 keeps footer buttons icon-only in tests. + useElementWidth.mockReturnValue([() => {}, 0]); }); describe('Directory Selection', () => { diff --git a/app/src/components/Files.js b/app/src/components/Files.js index eb820754..ddc48047 100644 --- a/app/src/components/Files.js +++ b/app/src/components/Files.js @@ -1132,10 +1132,12 @@ const useRefresh = () => { } } -export function FilesRefreshButton({paths}) { +export function FilesRefreshButton({paths, showLabel}) { const {globalRefreshing, loading, wrolModeEnabled, refreshFiles} = useRefresh(); return