98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
import { useState } from 'react';
|
|
import { NavLink, useNavigate } from 'react-router-dom';
|
|
import { Plus, Folder } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { AddProjectModal } from './AddProjectModal';
|
|
import { useProjects } from '@/hooks/useProjects';
|
|
|
|
export function ProjectSidebar() {
|
|
const { projects, refresh, remove } = useProjects();
|
|
const [addOpen, setAddOpen] = useState(false);
|
|
const navigate = useNavigate();
|
|
|
|
async function handleRemove(id: string) {
|
|
try {
|
|
await remove(id);
|
|
navigate('/');
|
|
} catch (err) {
|
|
toast.error(err instanceof Error ? err.message : 'failed to remove project');
|
|
}
|
|
}
|
|
|
|
return (
|
|
<aside className="w-60 shrink-0 border-r bg-sidebar text-sidebar-foreground flex flex-col h-screen">
|
|
<div className="px-4 py-3 border-b flex items-center justify-between">
|
|
<NavLink to="/" className="font-semibold tracking-tight text-base">
|
|
BooCode
|
|
</NavLink>
|
|
<Button
|
|
size="icon-sm"
|
|
variant="ghost"
|
|
onClick={() => setAddOpen(true)}
|
|
aria-label="Add project"
|
|
>
|
|
<Plus />
|
|
</Button>
|
|
</div>
|
|
|
|
<nav className="flex-1 overflow-y-auto py-2">
|
|
{projects === null && (
|
|
<div className="px-4 py-2 text-xs text-muted-foreground">Loading…</div>
|
|
)}
|
|
{projects && projects.length === 0 && (
|
|
<div className="px-4 py-2 text-xs text-muted-foreground">No projects yet.</div>
|
|
)}
|
|
{projects?.map((p) => (
|
|
<div key={p.id} className="px-2">
|
|
<DropdownMenu>
|
|
<NavLink
|
|
to={`/project/${p.id}`}
|
|
className={({ isActive }) =>
|
|
`group flex items-center gap-2 rounded-md px-2 py-1.5 text-sm ${
|
|
isActive
|
|
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
: 'hover:bg-sidebar-accent/60'
|
|
}`
|
|
}
|
|
onContextMenu={(e) => {
|
|
e.preventDefault();
|
|
(
|
|
e.currentTarget.parentElement?.querySelector(
|
|
'[data-ctxtrigger]'
|
|
) as HTMLElement | null
|
|
)?.click();
|
|
}}
|
|
>
|
|
<Folder className="size-3.5 shrink-0 opacity-70" />
|
|
<span className="truncate" title={p.path}>
|
|
{p.name}
|
|
</span>
|
|
</NavLink>
|
|
<DropdownMenuTrigger asChild>
|
|
<button data-ctxtrigger className="hidden" aria-hidden />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start">
|
|
<DropdownMenuItem
|
|
variant="destructive"
|
|
onClick={() => void handleRemove(p.id)}
|
|
>
|
|
Remove from sidebar
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
))}
|
|
</nav>
|
|
|
|
<AddProjectModal open={addOpen} onOpenChange={setAddOpen} onAdded={refresh} />
|
|
</aside>
|
|
);
|
|
}
|