mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-10 23:09:46 +00:00
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:
@@ -3,7 +3,8 @@ import { ScrollArea } from './ui/scroll-area';
|
|||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from './ui/badge';
|
||||||
import { Input } from './ui/input';
|
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 { cn } from '../lib/utils';
|
||||||
import ClaudeLogo from './ClaudeLogo';
|
import ClaudeLogo from './ClaudeLogo';
|
||||||
import { api } from '../utils/api';
|
import { api } from '../utils/api';
|
||||||
@@ -66,6 +67,8 @@ function Sidebar({
|
|||||||
const [editingSession, setEditingSession] = useState(null);
|
const [editingSession, setEditingSession] = useState(null);
|
||||||
const [editingSessionName, setEditingSessionName] = useState('');
|
const [editingSessionName, setEditingSessionName] = useState('');
|
||||||
const [generatingSummary, setGeneratingSummary] = useState({});
|
const [generatingSummary, setGeneratingSummary] = useState({});
|
||||||
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
|
|
||||||
|
|
||||||
// Starred projects state - persisted in localStorage
|
// Starred projects state - persisted in localStorage
|
||||||
const [starredProjects, setStarredProjects] = useState(() => {
|
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 (
|
return (
|
||||||
<div className="h-full flex flex-col bg-card md:select-none">
|
<div className="h-full flex flex-col bg-card md:select-none">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -586,6 +601,30 @@ function Sidebar({
|
|||||||
</div>
|
</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 */}
|
{/* Projects List */}
|
||||||
<ScrollArea className="flex-1 md:px-2 md:py-3 overflow-y-auto overscroll-contain">
|
<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">
|
<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
|
Run Claude CLI in a project directory to get started
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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 isExpanded = expandedProjects.has(project.name);
|
||||||
const isSelected = selectedProject?.name === project.name;
|
const isSelected = selectedProject?.name === project.name;
|
||||||
const isStarred = isProjectStarred(project.name);
|
const isStarred = isProjectStarred(project.name);
|
||||||
|
|||||||
Reference in New Issue
Block a user