"use client"; import * as React from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { LayoutDashboard, Bot, GitBranch, Play, CheckSquare, Layers, Radio, PanelLeft, PanelLeftClose, Sun, MoonStar, Monitor, } from "lucide-react"; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar, } from "@/components/ui/sidebar"; type NavItem = { title: string; href: string; icon: React.ComponentType<{ className?: string }>; match?: (pathname: string) => boolean; }; const primary: NavItem[] = [ { title: "Dashboard", href: "/dashboard", icon: LayoutDashboard, match: (p) => p === "/dashboard", }, { title: "Agents", href: "/agents", icon: Bot, match: (p) => p.startsWith("/agents"), }, { title: "Pipelines", href: "/", icon: GitBranch, match: (p) => p === "/" || p.startsWith("/flows"), }, { title: "Runs", href: "/runs", icon: Play, match: (p) => p === "/runs" || p.startsWith("/executions"), }, { title: "Approvals", href: "/approvals", icon: CheckSquare, match: (p) => p.startsWith("/approvals"), }, { title: "Environments", href: "/environments", icon: Layers, match: (p) => p.startsWith("/environments"), }, { title: "Gateway", href: "/gateway", icon: Radio, match: (p) => p.startsWith("/gateway"), }, ]; export function AppSidebar({ ...props }: React.ComponentProps) { const pathname = usePathname() || "/"; return (
Langship
{primary.map((item) => { const Icon = item.icon; const active = item.match ? item.match(pathname) : pathname === item.href; return ( {item.title} ); })}
); } function LangshipMark() { // Tiny anchor/route mark — placeholder for the real Langship logo. return ( ); } function CollapseToggle() { const { toggleSidebar, state } = useSidebar(); const collapsed = state === "collapsed"; const Icon = collapsed ? PanelLeft : PanelLeftClose; return ( {collapsed ? "Expand" : "Collapse"} ); } // ThemeToggle is a 3-way segmented control (Light / System / Dark) that // writes the theme to . Persists in localStorage. When the // sidebar is collapsed to icons, it renders a single button that cycles // through the three themes instead. function ThemeToggle() { const { state } = useSidebar(); const collapsed = state === "collapsed"; const [theme, setTheme] = React.useState<"light" | "dark" | "system">("system"); React.useEffect(() => { const saved = (typeof window !== "undefined" && (localStorage.getItem("flow-theme") as | "light" | "dark" | "system" | null)) || "system"; setTheme(saved); apply(saved); }, []); function apply(t: "light" | "dark" | "system") { const root = document.documentElement; const prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; const dark = t === "dark" || (t === "system" && prefersDark); root.classList.toggle("dark", dark); localStorage.setItem("flow-theme", t); } function pick(next: "light" | "dark" | "system") { setTheme(next); apply(next); } const items: { id: "light" | "system" | "dark"; icon: typeof Sun; label: string }[] = [ { id: "light", icon: Sun, label: "Light" }, { id: "system", icon: Monitor, label: "System" }, { id: "dark", icon: MoonStar, label: "Dark" }, ]; if (collapsed) { const current = items.find((i) => i.id === theme) ?? items[1]; const Icon = current.icon; const next = items[(items.findIndex((i) => i.id === theme) + 1) % items.length].id; return ( pick(next)} className="text-muted-foreground" title={`Theme: ${current.label} (click to cycle)`} > {current.label} ); } return (
{items.map((it) => { const Icon = it.icon; const active = theme === it.id; return ( ); })}
); }