From 6f6370c7f6fc099870e90e20c8aee8afcffb3142 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:19:31 +0100 Subject: [PATCH] fix: show the folder name in the Files header on a phone and keep Delete in sight --- components/files/files-header.tsx | 54 ++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/components/files/files-header.tsx b/components/files/files-header.tsx index 005bcc4ba..61e1b07ed 100644 --- a/components/files/files-header.tsx +++ b/components/files/files-header.tsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo, useState, type ReactNode } from "react"; +import { useMemo, useRef, useState, type ReactNode } from "react"; import { useTranslations } from "next-intl"; import { Menu, ChevronRight, Search, X, ArrowUpDown, Check, LayoutGrid, LayoutList, @@ -12,6 +12,7 @@ import { IconButton } from "@/components/ui/icon-button"; import { ToolbarButton, ToolbarSearch, TOOLBAR_ACTIVE } from "@/components/ui/toolbar"; import { ContextMenu, ContextMenuItem, ContextMenuSeparator } from "@/components/ui/context-menu"; import { useContextMenu } from "@/hooks/use-context-menu"; +import { useMediaQuery } from "@/hooks/use-media-query"; import { PhoneHeaderActions } from "@/components/layout/phone-header-actions"; import { cn } from "@/lib/utils"; import { anchorEvent, isInternalDrag, readDraggedNames } from "@/components/files/file-item-parts"; @@ -123,6 +124,9 @@ export function FilesHeader({ const overflowMenu = useContextMenu(); const [searchOpen, setSearchOpen] = useState(false); const [dropCrumb, setDropCrumb] = useState(null); + // A phone's row has no room for the path next to every tool: sort, view and details go in the ⋮. + const narrow = useMediaQuery("(max-width: 30rem)") && compact; + const toolsRef = useRef(null); const headerClass = "flex items-center gap-1 px-3 h-14 box-content border-b border-border min-w-0 flex-shrink-0"; // The row is px-3; the margin puts the button where the reader has it (px-4, lg:px-6). @@ -165,23 +169,30 @@ export function FilesHeader({ )} - + {!compact && } {!compact && } )} - {/* Outside the scrolling row, so the "more" button never scrolls out of sight. */} + {/* Outside the scrolling row, so Delete and the "more" button never scroll out of sight. */} + {compact && !selection.trash && } {compact && !selection.trash && }
- + {!narrow && } {toggleSlot}
); } // Deep paths keep the root and the last folders; the rest go in a menu. - const hidden = crumbs.length > MAX_VISIBLE_CRUMBS + 1 ? crumbs.slice(1, crumbs.length - MAX_VISIBLE_CRUMBS + 1) : []; - const visible = hidden.length > 0 ? [crumbs[0], ...crumbs.slice(crumbs.length - MAX_VISIBLE_CRUMBS + 1)] : crumbs; + // A phone only fits the folder you are in, with the way up in the menu. + const onlyLast = narrow ? crumbs.length > 1 : compact && crumbs.length > 2; + const hidden = onlyLast + ? crumbs.slice(0, -1) + : crumbs.length > MAX_VISIBLE_CRUMBS + 1 ? crumbs.slice(1, crumbs.length - MAX_VISIBLE_CRUMBS + 1) : []; + const visible = onlyLast + ? crumbs.slice(-1) + : hidden.length > 0 ? [crumbs[0], ...crumbs.slice(crumbs.length - MAX_VISIBLE_CRUMBS + 1)] : crumbs; const crumbDropProps = (crumb: Crumb, isLast: boolean) => isLast ? {} : { onDragOver: (e: React.DragEvent) => { @@ -269,8 +280,8 @@ export function FilesHeader({ {!toolsHidden && ( -
+
{emptyTrash && ( ))} -
+
} {!compact && ( )} - + {!narrow && } {toggleSlot} ( - // The phone header has no room for Refresh as a button. - { close(); onRefresh(); }} disabled={isLoading} /> + <> + {narrow && { close(); if (toolsRef.current) sortMenu.openContextMenu(anchorEvent(toolsRef.current, "end"), null); }} />} + {narrow && (viewMode === "list" + ? { close(); onViewModeChange("grid"); }} /> + : { close(); onViewModeChange("list"); }} />)} + {narrow && { close(); onToggleDetails(); }} />} + {/* The phone header has no room for Refresh as a button. */} + { close(); onRefresh(); }} disabled={isLoading} /> + )} />