From 573a04d2e5dbe53d279533902d380d5df9b7b79d Mon Sep 17 00:00:00 2001 From: simos Date: Wed, 17 Sep 2025 08:00:24 +0000 Subject: [PATCH] Feat: Add search bar on file tree --- src/components/FileTree.jsx | 155 +++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 36 deletions(-) diff --git a/src/components/FileTree.jsx b/src/components/FileTree.jsx index 1048bdb..725ae7c 100644 --- a/src/components/FileTree.jsx +++ b/src/components/FileTree.jsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from 'react'; import { ScrollArea } from './ui/scroll-area'; import { Button } from './ui/button'; -import { Folder, FolderOpen, File, FileText, FileCode, List, TableProperties, Eye } from 'lucide-react'; +import { Input } from './ui/input'; +import { Folder, FolderOpen, File, FileText, FileCode, List, TableProperties, Eye, Search, X } from 'lucide-react'; import { cn } from '../lib/utils'; import CodeEditor from './CodeEditor'; import ImageViewer from './ImageViewer'; @@ -14,6 +15,8 @@ function FileTree({ selectedProject }) { const [selectedFile, setSelectedFile] = useState(null); const [selectedImage, setSelectedImage] = useState(null); const [viewMode, setViewMode] = useState('detailed'); // 'simple', 'detailed', 'compact' + const [searchQuery, setSearchQuery] = useState(''); + const [filteredFiles, setFilteredFiles] = useState([]); useEffect(() => { if (selectedProject) { @@ -29,6 +32,51 @@ function FileTree({ selectedProject }) { } }, []); + // Filter files based on search query + useEffect(() => { + if (!searchQuery.trim()) { + setFilteredFiles(files); + } else { + const filtered = filterFiles(files, searchQuery.toLowerCase()); + setFilteredFiles(filtered); + + // Auto-expand directories that contain matches + const expandMatches = (items) => { + items.forEach(item => { + if (item.type === 'directory' && item.children && item.children.length > 0) { + setExpandedDirs(prev => new Set(prev.add(item.path))); + expandMatches(item.children); + } + }); + }; + expandMatches(filtered); + } + }, [files, searchQuery]); + + // Recursively filter files and directories based on search query + const filterFiles = (items, query) => { + return items.reduce((filtered, item) => { + const matchesName = item.name.toLowerCase().includes(query); + let filteredChildren = []; + + if (item.type === 'directory' && item.children) { + filteredChildren = filterFiles(item.children, query); + } + + // Include item if: + // 1. It matches the search query, or + // 2. It's a directory with matching children + if (matchesName || filteredChildren.length > 0) { + filtered.push({ + ...item, + children: filteredChildren + }); + } + + return filtered; + }, []); + }; + const fetchFiles = async () => { setLoading(true); try { @@ -308,42 +356,67 @@ function FileTree({ selectedProject }) { return (
- {/* View Mode Toggle */} -
-

Files

-
- - - + {/* Header with Search and View Mode Toggle */} +
+
+

Files

+
+ + + +
+
+ + {/* Search Bar */} +
+ + setSearchQuery(e.target.value)} + className="pl-8 pr-8 h-8 text-sm" + /> + {searchQuery && ( + + )}
{/* Column Headers for Detailed View */} - {viewMode === 'detailed' && files.length > 0 && ( + {viewMode === 'detailed' && filteredFiles.length > 0 && (
Name
@@ -365,11 +438,21 @@ function FileTree({ selectedProject }) { Check if the project path is accessible

+ ) : filteredFiles.length === 0 && searchQuery ? ( +
+
+ +
+

No matches found

+

+ Try a different search term or clear the search +

+
) : (
- {viewMode === 'simple' && renderFileTree(files)} - {viewMode === 'compact' && renderCompactView(files)} - {viewMode === 'detailed' && renderDetailedView(files)} + {viewMode === 'simple' && renderFileTree(filteredFiles)} + {viewMode === 'compact' && renderCompactView(filteredFiles)} + {viewMode === 'detailed' && renderDetailedView(filteredFiles)}
)}