← back to Letsbegin
components/ProjectSelector.tsx
124 lines
'use client'
import { useState, useRef, useEffect } from 'react'
interface Project {
name: string
path: string
hasClaudeMd: boolean
}
interface Props {
projects: Project[]
selectedProject: Project | null
onSelect: (project: Project) => void
onCreateNew: () => void
}
export default function ProjectSelector({ projects, selectedProject, onSelect, onCreateNew }: Props) {
const [isOpen, setIsOpen] = useState(false)
const [search, setSearch] = useState('')
const dropdownRef = useRef<HTMLDivElement>(null)
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
const filteredProjects = projects.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
)
return (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-[var(--surface-alt)] border border-[var(--border)] hover:border-[var(--primary)] transition-colors min-w-[200px]"
>
<span className="text-[var(--foreground-secondary)]">Project:</span>
<span className="font-medium truncate max-w-[150px]">
{selectedProject?.name || 'Select...'}
</span>
<svg
className={`w-4 h-4 text-[var(--foreground-secondary)] transition-transform ${isOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="absolute top-full mt-2 right-0 w-80 bg-[var(--surface)] border border-[var(--border)] rounded-xl shadow-lg overflow-hidden z-50">
{/* Search */}
<div className="p-3 border-b border-[var(--border)]">
<input
type="text"
placeholder="Search projects..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="input text-sm"
autoFocus
/>
</div>
{/* Project List */}
<div className="max-h-64 overflow-y-auto">
{filteredProjects.map((project) => (
<button
key={project.path}
onClick={() => {
onSelect(project)
setIsOpen(false)
setSearch('')
}}
className={`w-full flex items-center gap-3 px-4 py-3 hover:bg-[var(--surface-alt)] transition-colors ${
selectedProject?.path === project.path ? 'bg-[var(--surface-alt)]' : ''
}`}
>
<span className="text-lg">📁</span>
<div className="flex-1 text-left">
<div className="font-medium">{project.name}</div>
<div className="text-xs text-[var(--foreground-tertiary)] truncate">
{project.path}
</div>
</div>
{project.hasClaudeMd && (
<span className="badge badge-success text-xs">CLAUDE.md</span>
)}
</button>
))}
{filteredProjects.length === 0 && (
<div className="px-4 py-6 text-center text-[var(--foreground-tertiary)]">
No projects found
</div>
)}
</div>
{/* Create New */}
<div className="border-t border-[var(--border)] p-3">
<button
onClick={() => {
onCreateNew()
setIsOpen(false)
}}
className="w-full flex items-center justify-center gap-2 px-4 py-2 rounded-lg bg-[var(--primary)] text-white font-medium hover:bg-[var(--primary-dark)] transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Create New Project
</button>
</div>
</div>
)}
</div>
)
}