From 1df372764fe9a21516df83e8aeacf32da6efc236 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Sun, 23 Aug 2026 22:07:35 +0000 Subject: [PATCH] Mobile follow-ups to #7518: tool-list search, and drop the empty overflow menu (#7660) # Description of Changes Follow-up to #7518, picking up two mobile rough edges found while going over that branch. Two changes, one commit each. ## 1. Tool search back in the tool list (mobile) Tool search lives in the workbench bar's super search, which on mobile sits on the Workspace slide. So searching for a tool meant swiping off the tool list, typing, then swiping back. This puts a filter at the head of the tool panel on mobile. Reuses the existing `ToolSearch` component in `mode="filter"`, the same one the desktop fullscreen picker uses. Drives `setSearchQuery` on `ToolWorkflowContext`, so the query, filtering and grouped results are all existing paths. `ToolPanel` takes a new `showSearch` prop; `RightSidebar` passes `showSearch={isMobile}`. Desktop renders exactly as before. **To test:** - Open the editor at a phone-width viewport (under 1024px). - A "Search tools..." field should sit above Favourites / Recommended in the Tools pane. - Typing filters into grouped results. Clearing goes back to the compact list. - It hides once a tool is open, and comes back on the way out. - On desktop the field should not appear at all. ## 2. The mobile overflow menu opened with nothing in it `WorkbenchBarMobileActions` rendered its kebab trigger unconditionally. But every item inside is gated on `currentView === "viewer"` or `!isCustomView`. In a `custom:*` workbench both are false, so the dropdown was empty. `WorkbenchBarDesktopActions` renders nothing in that case, so this only showed on phones. Now returns `null` when neither group applies, with the two conditions named so the trigger and the items can't drift apart again. **To test:** - Phone-width viewport, load a PDF. - Open a tool with its own workbench view: Compare, Get Info report, Show JS, Validate Signature, Edit Table of Contents, or PDF Text Editor. - The kebab at the right of the workbench bar should be gone entirely, rather than opening an empty menu. - Back in the viewer or page editor it should still be there, with Print / Download / Save As / Close. --- .../WorkbenchBarMobileActions.tsx | 15 +++++++--- .../core/components/tools/RightSidebar.tsx | 3 ++ .../src/core/components/tools/ToolPanel.css | 11 +++++++ .../src/core/components/tools/ToolPanel.tsx | 30 +++++++++++++++++-- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/frontend/editor/src/core/components/shared/workbenchBar/WorkbenchBarMobileActions.tsx b/frontend/editor/src/core/components/shared/workbenchBar/WorkbenchBarMobileActions.tsx index 6d1cf0efdf..7675c37e33 100644 --- a/frontend/editor/src/core/components/shared/workbenchBar/WorkbenchBarMobileActions.tsx +++ b/frontend/editor/src/core/components/shared/workbenchBar/WorkbenchBarMobileActions.tsx @@ -25,6 +25,13 @@ export default function WorkbenchBarMobileActions({ }: WorkbenchBarActionsProps) { const { t } = useTranslation(); const exportDisabled = actionsDisabled || policyEnforcing; + const showPrint = currentView === "viewer"; + const showFileActions = !isCustomView; + + // Custom workbench views own their content, so none of these apply. The + // desktop cluster renders nothing at all in that case; without this the + // trigger would still be there, opening an empty dropdown. + if (!showPrint && !showFileActions) return null; return ( @@ -39,7 +46,7 @@ export default function WorkbenchBarMobileActions({ - {currentView === "viewer" && ( + {showPrint && ( } disabled={exportDisabled} @@ -48,7 +55,7 @@ export default function WorkbenchBarMobileActions({ {t("workbenchBar.print", "Print PDF")} )} - {!isCustomView && ( + {showFileActions && ( )} - {!isCustomView && saveAsIconName && ( + {showFileActions && saveAsIconName && ( @@ -74,7 +81,7 @@ export default function WorkbenchBarMobileActions({ {t("workbenchBar.saveAs", "Save As")} )} - {!isCustomView && ( + {showFileActions && ( <> diff --git a/frontend/editor/src/core/components/tools/ToolPanel.css b/frontend/editor/src/core/components/tools/ToolPanel.css index 427375db1a..3e3985103f 100644 --- a/frontend/editor/src/core/components/tools/ToolPanel.css +++ b/frontend/editor/src/core/components/tools/ToolPanel.css @@ -183,6 +183,17 @@ } } +/* In-panel tool filter. Aligned with .tool-picker__compact's inline padding so + the field lines up with the tool rows underneath it. */ +.tool-panel__search { + flex-shrink: 0; + padding: 0.5rem var(--mantine-spacing-sm) 0; +} + +.tool-panel__search .search-input-container { + margin: 0; +} + .tool-panel__compact-header-actions { display: flex; align-items: center; diff --git a/frontend/editor/src/core/components/tools/ToolPanel.tsx b/frontend/editor/src/core/components/tools/ToolPanel.tsx index 2d6006a8eb..d51ec9e0bf 100644 --- a/frontend/editor/src/core/components/tools/ToolPanel.tsx +++ b/frontend/editor/src/core/components/tools/ToolPanel.tsx @@ -4,6 +4,7 @@ import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext"; import ToolPicker from "@app/components/tools/ToolPicker"; import SearchResults from "@app/components/tools/SearchResults"; import ToolRenderer from "@app/components/tools/ToolRenderer"; +import ToolSearch from "@app/components/tools/toolPicker/ToolSearch"; import { ToolPanelViewerBar } from "@app/components/tools/ToolPanelViewerBar"; import { ToolId } from "@app/types/toolId"; @@ -20,6 +21,11 @@ interface ToolPanelProps { onToolSelect?: (id: ToolId) => void; /** Whether to render the compact (favourites + recommended only) view. */ compact?: boolean; + /** + * Render a tool filter at the head of the panel. Set where the workbench + * bar's super search is out of reach, so the list stays searchable in place. + */ + showSearch?: boolean; } /** Tool list and renderer for the right rail; rail chrome lives in RightSidebar. */ @@ -28,24 +34,44 @@ export default function ToolPanel({ onShowAllTools, onToolSelect, compact: compactProp, + showSearch = false, }: ToolPanelProps) { const { t } = useTranslation(); const { leftPanelView, searchQuery, + setSearchQuery, filteredTools, + toolRegistry, selectedToolKey, handleToolSelect, setPreviewFile, } = useToolWorkflow(); const selectTool = onToolSelect ?? handleToolSelect; + // Only offer the filter over the list itself; once a tool is open the panel + // belongs to that tool. Deriving the results branch from the same flag keeps + // the input and what it filters from drifting apart. + const panelSearch = showSearch && leftPanelView === "toolPicker"; + const searching = searchQuery.trim().length > 0; + return ( <> {/* Viewer mode tools — annotate, redact, form fill */} - {allToolsView && searchQuery.trim().length > 0 ? ( + {panelSearch && ( +
+ +
+ )} + + {searching && (allToolsView || panelSearch) ? (
selectTool(id as ToolId)} filteredTools={filteredTools} - isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)} + isSearching={searching} compact={compactProp ?? !allToolsView} onShowAllTools={onShowAllTools} />