Files
roboco/panel/src/components/layout/sidebar.tsx
T

192 lines
5.8 KiB
TypeScript
Raw Normal View History

2026-04-20 15:10:54 +02:00
"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import {
LayoutDashboard,
ListTodo,
Kanban,
Activity,
ChevronLeft,
Settings,
Bot,
Shield,
BookOpen,
2026-06-03 06:35:03 +02:00
Boxes,
2026-04-20 15:10:54 +02:00
FolderGit2,
GitBranch,
Database,
Cpu,
Sparkles,
Building2,
Radio,
Share2,
2026-04-20 15:10:54 +02:00
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Separator } from "@/components/ui/separator";
2026-04-20 15:10:54 +02:00
import { useUIStore } from "@/store";
// Flat list, in the exact order product wants the sidebar to read top to
// bottom. Notifications lives only in the header's NotificationBell now.
export const navItems = [
2026-04-20 15:10:54 +02:00
{ title: "Overview", href: "/overview", icon: LayoutDashboard },
{ title: "Task Assistant", href: "/prompter", icon: Sparkles },
2026-04-20 15:10:54 +02:00
{ title: "Tasks", href: "/tasks", icon: ListTodo },
{ title: "Kanban", href: "/kanban", icon: Kanban },
{ title: "Git", href: "/git", icon: GitBranch },
2026-04-20 15:10:54 +02:00
{ title: "Projects", href: "/projects", icon: FolderGit2 },
2026-06-03 06:35:03 +02:00
{ title: "Products", href: "/products", icon: Boxes },
{ title: "Social", href: "/social", icon: Share2 },
2026-04-20 15:10:54 +02:00
{ title: "Knowledge Base", href: "/knowledge-base", icon: Database },
{ title: "A2A", href: "/a2a", icon: Radio },
{ title: "Agents", href: "/agents", icon: Bot },
2026-04-20 15:10:54 +02:00
{ title: "Journals", href: "/journals", icon: BookOpen },
{ title: "Auditor", href: "/auditor", icon: Shield },
2026-04-20 15:10:54 +02:00
{ title: "Metrics", href: "/metrics", icon: Activity },
];
// Business moved out of the main nav — it lives with the settings-adjacent
// links, separated from navItems by a single Separator (see SidebarFooter).
const footerItems = [
{ title: "Business", href: "/business", icon: Building2 },
{ title: "AI Providers", href: "/settings/ai-providers", icon: Cpu },
{ title: "Settings", href: "/settings", icon: Settings },
];
/**
* The navigation links, shared by the desktop sidebar and the mobile Sheet
* drawer so both stay in sync. `collapsed` hides labels (desktop rail);
* `onNavigate` lets the mobile drawer close itself when a link is tapped.
*/
export function SidebarNav({
collapsed = false,
onNavigate,
}: {
collapsed?: boolean;
onNavigate?: () => void;
}) {
2026-04-20 15:10:54 +02:00
const pathname = usePathname();
return (
<nav className="space-y-1 px-2">
{navItems.map((item) => {
const isActive = pathname.startsWith(item.href);
return (
<Link
2026-07-02 15:36:49 +02:00
prefetch={false}
key={item.href}
href={item.href}
onClick={onNavigate}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
2026-06-29 05:38:21 +02:00
collapsed && "justify-center px-2",
)}
title={collapsed ? item.title : undefined}
>
<item.icon className="h-5 w-5 shrink-0" />
{!collapsed && <span>{item.title}</span>}
</Link>
);
})}
</nav>
);
}
/** Footer links (AI Providers, Settings), shared by desktop + mobile. */
export function SidebarFooter({
collapsed = false,
onNavigate,
}: {
collapsed?: boolean;
onNavigate?: () => void;
}) {
return (
<div className="space-y-2">
<Separator />
<div className="space-y-1">
{footerItems.map((item) => (
<Link
prefetch={false}
key={item.href}
href={item.href}
onClick={onNavigate}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
collapsed && "justify-center px-2",
)}
title={collapsed ? item.title : undefined}
>
<item.icon className="h-5 w-5" />
{!collapsed && <span>{item.title}</span>}
</Link>
))}
</div>
</div>
);
}
export function Sidebar() {
2026-04-20 15:10:54 +02:00
const { sidebarCollapsed, setSidebarCollapsed } = useUIStore();
return (
<aside
className={cn(
// Hidden on mobile — the Header's hamburger opens the same nav in a
// Sheet drawer there (see MobileSidebar). Shown from md upward.
"hidden h-screen flex-col border-r bg-background transition-all duration-300 md:flex",
2026-06-29 05:38:21 +02:00
sidebarCollapsed ? "w-16" : "w-64",
2026-04-20 15:10:54 +02:00
)}
>
{/* Logo */}
<div className="flex h-16 items-center justify-between border-b px-4">
{!sidebarCollapsed && (
2026-07-02 15:36:49 +02:00
<Link
href="/overview"
className="flex items-center gap-2"
prefetch={false}
>
2026-04-20 15:10:54 +02:00
<Image
src="/roboco-logo.png"
alt="RoboCo"
width={32}
height={32}
priority
unoptimized
2026-04-20 15:10:54 +02:00
className="h-8 w-8 rounded"
/>
<span className="font-semibold text-lg">RoboCo</span>
</Link>
)}
<Button
variant="ghost"
size="icon"
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
className={cn(sidebarCollapsed && "mx-auto")}
>
<ChevronLeft
className={cn(
"h-4 w-4 transition-transform",
2026-06-29 05:38:21 +02:00
sidebarCollapsed && "rotate-180",
2026-04-20 15:10:54 +02:00
)}
/>
</Button>
</div>
{/* Navigation */}
<ScrollArea className="flex-1 py-4">
<SidebarNav collapsed={sidebarCollapsed} />
2026-04-20 15:10:54 +02:00
</ScrollArea>
{/* Footer */}
<div className="border-t p-2">
<SidebarFooter collapsed={sidebarCollapsed} />
2026-04-20 15:10:54 +02:00
</div>
</aside>
);
}