From d295b05decab2f67dbd066f63666e7910283e16b Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Sun, 29 Mar 2026 01:59:19 -0500 Subject: [PATCH] Add collapsible file filter controls to workspace search - Hide include/exclude inputs behind a toggle - Keep filters visible when patterns are set - Reset filter toggle state when clearing search --- apps/web/src/components/WorkspaceFileTree.tsx | 71 +++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/apps/web/src/components/WorkspaceFileTree.tsx b/apps/web/src/components/WorkspaceFileTree.tsx index 6c820ff0..9d2ab57c 100644 --- a/apps/web/src/components/WorkspaceFileTree.tsx +++ b/apps/web/src/components/WorkspaceFileTree.tsx @@ -5,6 +5,7 @@ import { FolderClosedIcon, FolderIcon, SearchIcon, + SlidersHorizontalIcon, TriangleAlertIcon, } from "lucide-react"; import { memo, useCallback, useDeferredValue, useState } from "react"; @@ -18,6 +19,7 @@ import { cn } from "~/lib/utils"; import { readNativeApi } from "~/nativeApi"; import { resolvePathLinkTarget } from "~/terminal-links"; import { VscodeEntryIcon } from "./chat/VscodeEntryIcon"; +import { Collapsible, CollapsibleContent } from "./ui/collapsible"; import { Input } from "./ui/input"; import { InputGroup, InputGroupAddon, InputGroupInput } from "./ui/input-group"; import { toastManager } from "./ui/toast"; @@ -34,6 +36,7 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: { const [searchQuery, setSearchQuery] = useState(""); const [includePattern, setIncludePattern] = useState(""); const [excludePattern, setExcludePattern] = useState(""); + const [filtersOpen, setFiltersOpen] = useState(false); const deferredSearchQuery = useDeferredValue(searchQuery); const deferredIncludePattern = useDeferredValue(includePattern); const deferredExcludePattern = useDeferredValue(excludePattern); @@ -46,6 +49,9 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: { }, []); const openFileInViewer = useCodeViewerStore((state) => state.openFile); + const filtersHaveContent = includePattern.trim().length > 0 || excludePattern.trim().length > 0; + const filtersVisible = filtersOpen || filtersHaveContent; + const searchActive = deferredSearchQuery.trim().length > 0 || deferredIncludePattern.trim().length > 0 || @@ -104,6 +110,7 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: { setSearchQuery(""); setIncludePattern(""); setExcludePattern(""); + setFiltersOpen(false); }, []); return ( @@ -121,28 +128,50 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: { spellCheck={false} aria-label="Search files" /> + + + -
- setIncludePattern(event.target.value)} - placeholder="Include: src/**, *.{ts,tsx}" - spellCheck={false} - aria-label="Files to include" - /> - setExcludePattern(event.target.value)} - placeholder="Exclude: dist/**, *.snap" - spellCheck={false} - aria-label="Files to exclude" - /> -
-

- CamelCase, path-ordered, and glob-restricted search. -

+ + +
+ setIncludePattern(event.target.value)} + placeholder="Include: src/**, *.{ts,tsx}" + spellCheck={false} + aria-label="Files to include" + /> + setExcludePattern(event.target.value)} + placeholder="Exclude: dist/**, *.snap" + spellCheck={false} + aria-label="Files to exclude" + /> +
+
+
{searchActive ? (