trustgraph/ts/packages/workbench/src/components/layout/sidebar.tsx

169 lines
5.3 KiB
TypeScript
Raw Normal View History

2026-04-05 22:44:45 -05:00
import { NavLink } from "react-router";
import {
MessageSquareText,
LibraryBig,
Rotate3d,
Workflow,
Settings,
TestTube2,
Wifi,
WifiOff,
Database,
ChevronDown,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { useConnectionState } from "@/providers/socket-provider";
import { useSessionStore } from "@/hooks/use-session-store";
import { useFlows } from "@/hooks/use-flows";
import { useSettings } from "@/providers/settings-provider";
// ---------------------------------------------------------------------------
// Nav item
// ---------------------------------------------------------------------------
interface NavItemProps {
to: string;
icon: React.ElementType;
label: string;
}
function NavItem({ to, icon: Icon, label }: NavItemProps) {
return (
<NavLink to={to} className="w-full">
{({ isActive }) => (
<div
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-brand-600/20 text-brand-400"
: "text-fg-muted hover:bg-surface-200 hover:text-fg",
)}
>
<Icon className="h-4 w-4 shrink-0" />
<span className="truncate">{label}</span>
</div>
)}
</NavLink>
);
}
// ---------------------------------------------------------------------------
// Connection status badge
// ---------------------------------------------------------------------------
function ConnectionBadge() {
const state = useConnectionState();
const isConnected =
state.status === "connected" ||
state.status === "authenticated" ||
state.status === "unauthenticated";
return (
<div
className={cn(
"flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium",
isConnected ? "text-success" : "text-fg-subtle",
)}
>
<span
className={cn(
"h-2 w-2 shrink-0 rounded-full",
isConnected ? "bg-success animate-pulse" : "bg-fg-subtle",
)}
/>
{isConnected ? (
<Wifi className="h-3.5 w-3.5" />
) : (
<WifiOff className="h-3.5 w-3.5" />
)}
<span className="truncate capitalize">{state.status}</span>
</div>
);
}
// ---------------------------------------------------------------------------
// Flow selector dropdown
// ---------------------------------------------------------------------------
function FlowSelectorDropdown() {
const { flows } = useFlows();
const flowId = useSessionStore((s) => s.flowId);
const setFlowId = useSessionStore((s) => s.setFlowId);
const collection = useSettings((s) => s.settings.collection);
return (
<div className="space-y-2 px-3">
{/* Flow selector */}
<div className="space-y-1">
<label className="flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wider text-fg-subtle">
<Workflow className="h-3 w-3" />
Flow
</label>
<div className="relative">
<select
value={flowId}
onChange={(e) => setFlowId(e.target.value)}
className="w-full appearance-none rounded-md border border-border bg-surface-100 py-1.5 pl-2.5 pr-7 text-xs text-fg focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500"
>
<option value="default">default</option>
{flows.map((f) => (
<option key={f.id} value={f.id}>
{f.id}
</option>
))}
</select>
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-3 w-3 -translate-y-1/2 text-fg-subtle" />
</div>
</div>
{/* Collection badge */}
<div className="flex items-center gap-1.5 rounded-md bg-surface-100 px-2.5 py-1.5 text-xs text-fg-muted">
<Database className="h-3 w-3 shrink-0 text-fg-subtle" />
<span className="truncate">{collection}</span>
</div>
</div>
);
}
// ---------------------------------------------------------------------------
// Sidebar
// ---------------------------------------------------------------------------
export function Sidebar() {
return (
<aside className="flex h-screen w-sidebar shrink-0 flex-col border-r border-border bg-surface-50">
{/* Logo area */}
<div className="flex h-14 items-center gap-2 px-4">
<TestTube2 className="h-5 w-5 text-brand-500" />
<span className="text-lg font-bold text-fg">TrustGraph</span>
</div>
{/* Divider */}
<div className="mx-3 border-t border-border" />
{/* Flow & collection selectors */}
<div className="py-3">
<FlowSelectorDropdown />
</div>
{/* Divider */}
<div className="mx-3 border-t border-border" />
{/* Navigation links */}
<nav className="flex flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-3">
<NavItem to="/chat" icon={MessageSquareText} label="Chat" />
<NavItem to="/library" icon={LibraryBig} label="Library" />
<NavItem to="/graph" icon={Rotate3d} label="Graph" />
<NavItem to="/flows" icon={Workflow} label="Flows" />
<NavItem to="/settings" icon={Settings} label="Settings" />
</nav>
{/* Footer: connection badge */}
<div className="border-t border-border px-2 py-2">
<ConnectionBadge />
</div>
</aside>
);
}