initial
This commit is contained in:
97
apps/web/src/components/ProjectSidebar.tsx
Normal file
97
apps/web/src/components/ProjectSidebar.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user