feat: Add project search filter to sidebar

- Added search input below header to filter projects by name
- Search works on both display name and project path
- Clear button (X) appears when search is active
- Shows "No matching projects" message when filter returns no results
- Search box only visible when projects exist
- Case-insensitive search implementation
This commit is contained in:
lvalics
2025-07-12 21:57:09 +03:00
committed by GitHub
parent 5ec51dacc3
commit a56e06385d

View File

@@ -3,7 +3,8 @@ import { ScrollArea } from './ui/scroll-area';
import { Button } from './ui/button';
import { Badge } from './ui/badge';
import { Input } from './ui/input';
import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRight, Edit3, Check, X, Trash2, Settings, FolderPlus, RefreshCw, Sparkles, Edit2, Star } from 'lucide-react';
import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRight, Edit3, Check, X, Trash2, Settings, FolderPlus, RefreshCw, Sparkles, Edit2, Star, Search } from 'lucide-react';
import { cn } from '../lib/utils';
import ClaudeLogo from './ClaudeLogo';
import { api } from '../utils/api';
@@ -66,6 +67,8 @@ function Sidebar({
const [editingSession, setEditingSession] = useState(null);
const [editingSessionName, setEditingSessionName] = useState('');
const [generatingSummary, setGeneratingSummary] = useState({});
const [searchFilter, setSearchFilter] = useState('');
// Starred projects state - persisted in localStorage
const [starredProjects, setStarredProjects] = useState(() => {
@@ -402,6 +405,18 @@ function Sidebar({
}
});
// Filter projects based on search input
const filteredProjects = sortedProjects.filter(project => {
if (!searchFilter.trim()) return true;
const searchLower = searchFilter.toLowerCase();
const displayName = (project.displayName || project.name).toLowerCase();
const projectName = project.name.toLowerCase();
// Search in both display name and actual project name/path
return displayName.includes(searchLower) || projectName.includes(searchLower);
});
return (
<div className="h-full flex flex-col bg-card md:select-none">
{/* Header */}
@@ -586,6 +601,30 @@ function Sidebar({
</div>
)}
{/* Search Filter */}
{projects.length > 0 && !isLoading && (
<div className="px-3 md:px-4 py-2 border-b border-border">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search projects..."
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
className="pl-9 h-9 text-sm bg-muted/50 border-0 focus:bg-background focus:ring-1 focus:ring-primary/20"
/>
{searchFilter && (
<button
onClick={() => setSearchFilter('')}
className="absolute right-2 top-1/2 transform -translate-y-1/2 p-1 hover:bg-accent rounded"
>
<X className="w-3 h-3 text-muted-foreground" />
</button>
)}
</div>
</div>
)}
{/* Projects List */}
<ScrollArea className="flex-1 md:px-2 md:py-3 overflow-y-auto overscroll-contain">
<div className="md:space-y-1 pb-safe-area-inset-bottom">
@@ -609,8 +648,18 @@ function Sidebar({
Run Claude CLI in a project directory to get started
</p>
</div>
) : filteredProjects.length === 0 ? (
<div className="text-center py-12 md:py-8 px-4">
<div className="w-12 h-12 bg-muted rounded-lg flex items-center justify-center mx-auto mb-4 md:mb-3">
<Search className="w-6 h-6 text-muted-foreground" />
</div>
<h3 className="text-base font-medium text-foreground mb-2 md:mb-1">No matching projects</h3>
<p className="text-sm text-muted-foreground">
Try adjusting your search term
</p>
</div>
) : (
sortedProjects.map((project) => {
filteredProjects.map((project) => {
const isExpanded = expandedProjects.has(project.name);
const isSelected = selectedProject?.name === project.name;
const isStarred = isProjectStarred(project.name);