111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
"use client";
|
||
|
||
import React from "react";
|
||
import Link from "next/link";
|
||
import { usePathname } from "next/navigation";
|
||
import {
|
||
LayoutDashboard,
|
||
ClipboardList,
|
||
Search,
|
||
Code2,
|
||
GitBranch,
|
||
Bot,
|
||
GitFork,
|
||
Rocket,
|
||
ShieldCheck,
|
||
Activity,
|
||
Plug,
|
||
BarChart3,
|
||
Settings2,
|
||
Sparkles,
|
||
} from "lucide-react";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
export interface NavItem {
|
||
href: string;
|
||
label: string;
|
||
icon: React.ComponentType<{ className?: string }>;
|
||
badge?: string;
|
||
}
|
||
|
||
export const NAV: NavItem[] = [
|
||
{ href: "/", label: "Overview", icon: LayoutDashboard },
|
||
{ href: "/requirements", label: "Requirements", icon: ClipboardList },
|
||
{ href: "/context", label: "Context Explorer", icon: Search },
|
||
{ href: "/code", label: "Code", icon: Code2 },
|
||
{ href: "/traceability", label: "Traceability", icon: GitBranch },
|
||
{ href: "/ask-asf", label: "Ask ASF", icon: Sparkles, badge: "AI" },
|
||
{ href: "/jobs", label: "Jobs", icon: Activity },
|
||
{ href: "/ai-agents", label: "AI Agents", icon: Bot, badge: "α" },
|
||
{ href: "/pipelines", label: "Pipelines", icon: GitFork },
|
||
{ href: "/deployments", label: "Deployments", icon: Rocket },
|
||
{ href: "/quality", label: "Quality", icon: ShieldCheck },
|
||
{ href: "/observability", label: "Observability", icon: Activity },
|
||
{ href: "/integrations", label: "Integrations", icon: Plug },
|
||
{ href: "/usage", label: "Usage", icon: BarChart3 },
|
||
{ href: "/settings", label: "Settings", icon: Settings2 },
|
||
];
|
||
|
||
export function Sidebar() {
|
||
const path = usePathname();
|
||
return (
|
||
<aside className="hidden lg:flex w-64 shrink-0 border-r border-slate-200/80 bg-white/60 backdrop-blur-sm flex-col">
|
||
<div className="h-16 flex items-center gap-3 px-5 border-b border-slate-200/80">
|
||
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-brand-500 to-accent shadow-soft grid place-items-center text-white font-bold">
|
||
A
|
||
</div>
|
||
<div className="min-w-0">
|
||
<div className="text-sm font-semibold text-slate-800 leading-tight">
|
||
{process.env.NEXT_PUBLIC_APP_NAME || "ASF"}
|
||
</div>
|
||
<div className="text-[11px] text-slate-500 leading-tight">
|
||
Software Factory
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<nav className="flex-1 overflow-y-auto p-3 space-y-0.5">
|
||
{NAV.map(({ href, label, icon: Icon, badge }) => {
|
||
const active = href === "/" ? path === "/" : path.startsWith(href);
|
||
return (
|
||
<Link
|
||
key={href}
|
||
href={href}
|
||
className={cn("asf-nav-item", active && "asf-nav-item-active")}
|
||
>
|
||
<Icon className="w-4.5 h-4.5 w-[18px] h-[18px] shrink-0" />
|
||
<span className="flex-1">{label}</span>
|
||
{badge && (
|
||
<span className="asf-chip border-accent/20 bg-accent/10 text-accent-muted !px-1.5">
|
||
{badge}
|
||
</span>
|
||
)}
|
||
</Link>
|
||
);
|
||
})}
|
||
</nav>
|
||
|
||
<div className="p-3 border-t border-slate-200/80">
|
||
<button
|
||
onClick={() => {
|
||
// keyboard shortcut is handled in layout; this button also triggers.
|
||
const ev = new CustomEvent("asf:open-command");
|
||
window.dispatchEvent(ev);
|
||
}}
|
||
className="w-full asf-btn-secondary justify-between !px-3"
|
||
type="button"
|
||
>
|
||
<span className="flex items-center gap-2">
|
||
<Sparkles className="w-4 h-4 text-accent" />
|
||
<span className="text-slate-700">Ask ASF…</span>
|
||
</span>
|
||
<kbd className="asf-chip !text-[10px]">⌘ K</kbd>
|
||
</button>
|
||
<p className="mt-3 text-[11px] text-slate-400 leading-relaxed">
|
||
Tip: every lifecycle element in ASF is an <span className="font-mono text-slate-600 bg-slate-100 rounded px-1">Artifact</span>.
|
||
</p>
|
||
</div>
|
||
</aside>
|
||
);
|
||
}
|