Skip to content

Commit 715ea0e

Browse files
authored
Add collapsible file filter controls to workspace search (#100)
- Hide include/exclude inputs behind a toggle - Keep filters visible when patterns are set - Reset filter toggle state when clearing search
1 parent 0d9aef2 commit 715ea0e

1 file changed

Lines changed: 50 additions & 21 deletions

File tree

apps/web/src/components/WorkspaceFileTree.tsx

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
FolderClosedIcon,
66
FolderIcon,
77
SearchIcon,
8+
SlidersHorizontalIcon,
89
TriangleAlertIcon,
910
} from "lucide-react";
1011
import { memo, useCallback, useDeferredValue, useState } from "react";
@@ -18,6 +19,7 @@ import { cn } from "~/lib/utils";
1819
import { readNativeApi } from "~/nativeApi";
1920
import { resolvePathLinkTarget } from "~/terminal-links";
2021
import { VscodeEntryIcon } from "./chat/VscodeEntryIcon";
22+
import { Collapsible, CollapsibleContent } from "./ui/collapsible";
2123
import { Input } from "./ui/input";
2224
import { InputGroup, InputGroupAddon, InputGroupInput } from "./ui/input-group";
2325
import { toastManager } from "./ui/toast";
@@ -34,6 +36,7 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: {
3436
const [searchQuery, setSearchQuery] = useState("");
3537
const [includePattern, setIncludePattern] = useState("");
3638
const [excludePattern, setExcludePattern] = useState("");
39+
const [filtersOpen, setFiltersOpen] = useState(false);
3740
const deferredSearchQuery = useDeferredValue(searchQuery);
3841
const deferredIncludePattern = useDeferredValue(includePattern);
3942
const deferredExcludePattern = useDeferredValue(excludePattern);
@@ -46,6 +49,9 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: {
4649
}, []);
4750

4851
const openFileInViewer = useCodeViewerStore((state) => state.openFile);
52+
const filtersHaveContent = includePattern.trim().length > 0 || excludePattern.trim().length > 0;
53+
const filtersVisible = filtersOpen || filtersHaveContent;
54+
4955
const searchActive =
5056
deferredSearchQuery.trim().length > 0 ||
5157
deferredIncludePattern.trim().length > 0 ||
@@ -104,6 +110,7 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: {
104110
setSearchQuery("");
105111
setIncludePattern("");
106112
setExcludePattern("");
113+
setFiltersOpen(false);
107114
}, []);
108115

109116
return (
@@ -121,28 +128,50 @@ export const WorkspaceFileTree = memo(function WorkspaceFileTree(props: {
121128
spellCheck={false}
122129
aria-label="Search files"
123130
/>
131+
<InputGroupAddon align="inline-end">
132+
<button
133+
type="button"
134+
onClick={() => setFiltersOpen((prev) => !prev)}
135+
aria-label="Toggle file filters"
136+
aria-expanded={filtersVisible}
137+
className={cn(
138+
"flex size-6 items-center justify-center rounded-md transition-colors hover:bg-accent/60",
139+
filtersHaveContent && "text-foreground",
140+
)}
141+
>
142+
<SlidersHorizontalIcon
143+
className={cn(
144+
"size-3.5",
145+
filtersHaveContent
146+
? "text-foreground"
147+
: "text-muted-foreground/65",
148+
)}
149+
/>
150+
</button>
151+
</InputGroupAddon>
124152
</InputGroup>
125-
<div className="grid gap-1.5">
126-
<Input
127-
size="sm"
128-
value={includePattern}
129-
onChange={(event) => setIncludePattern(event.target.value)}
130-
placeholder="Include: src/**, *.{ts,tsx}"
131-
spellCheck={false}
132-
aria-label="Files to include"
133-
/>
134-
<Input
135-
size="sm"
136-
value={excludePattern}
137-
onChange={(event) => setExcludePattern(event.target.value)}
138-
placeholder="Exclude: dist/**, *.snap"
139-
spellCheck={false}
140-
aria-label="Files to exclude"
141-
/>
142-
</div>
143-
<p className="text-[10px] text-muted-foreground/55">
144-
CamelCase, path-ordered, and glob-restricted search.
145-
</p>
153+
<Collapsible open={filtersVisible}>
154+
<CollapsibleContent>
155+
<div className="grid gap-1.5 pt-1.5">
156+
<Input
157+
size="sm"
158+
value={includePattern}
159+
onChange={(event) => setIncludePattern(event.target.value)}
160+
placeholder="Include: src/**, *.{ts,tsx}"
161+
spellCheck={false}
162+
aria-label="Files to include"
163+
/>
164+
<Input
165+
size="sm"
166+
value={excludePattern}
167+
onChange={(event) => setExcludePattern(event.target.value)}
168+
placeholder="Exclude: dist/**, *.snap"
169+
spellCheck={false}
170+
aria-label="Files to exclude"
171+
/>
172+
</div>
173+
</CollapsibleContent>
174+
</Collapsible>
146175
</div>
147176

148177
{searchActive ? (

0 commit comments

Comments
 (0)