Spaces:
Running
Running
File size: 6,533 Bytes
1955a0f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | import { Link, useLocation, useNavigate } from "react-router-dom";
import { LayoutDashboard } from "lucide-react";
import { cn } from "../utils/cn";
import { useTheme } from "../context/ThemeContext";
import AnimatedLogo from "./../components/AnimatedLogo";
import Logo from "./../components/Logo";
import { useLaunch } from "./LaunchContext";
import { MODULES, ModuleId, moduleHref } from "./registry";
/**
* Platform sidebar — wires the BRIDGE MODULES direct-launch section plus the
* active module's navigation registry. Launching a module plays its
* random-reassembly animation (re-seeded every launch).
*/
export default function PlatformSidebar({ module, onClose }: { module: ModuleId; onClose?: () => void }) {
const location = useLocation();
const navigate = useNavigate();
const { config } = useTheme();
const { startLaunch } = useLaunch();
const def = MODULES[module];
const launch = (target: ModuleId | "hub") => {
onClose?.();
startLaunch(target);
};
const isActiveModule = (id: ModuleId) => location.pathname.startsWith(MODULES[id].basePath);
const isActivePath = (path: string) => {
const full = moduleHref(module, path);
return path === "" ? location.pathname === full : location.pathname.startsWith(full);
};
return (
<div className="w-64 border-r border-white/5 bg-zinc-950/90 md:bg-zinc-950/60 backdrop-blur-2xl flex flex-col shrink-0 h-full relative z-20 shadow-[4px_0_24px_-4px_rgba(0,0,0,0.8)]">
<div className="h-16 flex items-center px-6 border-b border-white/5 shrink-0 gap-3">
<AnimatedLogo className="w-7 h-7" />
<Logo wordmark={def.wordmark} />
</div>
<nav className="flex-1 overflow-y-auto py-4 px-3 space-y-1">
{/* BRIDGE MODULES — direct-launch section (Phase 5) */}
<div className="mb-4">
<div className="px-3 pb-2 text-[9px] font-bold tracking-[0.22em] uppercase text-zinc-600 font-mono">
Bridge Modules
</div>
<div className="space-y-1.5">
{(["e", "s"] as ModuleId[]).map((id) => {
const m = MODULES[id];
const active = isActiveModule(id);
const Icon = m.icon;
return (
<button
key={id}
type="button"
onClick={() => launch(id)}
aria-current={active ? "page" : undefined}
className={cn(
"w-full flex items-center gap-3 px-3 py-2.5 text-[13px] font-medium rounded-lg transition-all duration-300 relative group overflow-hidden border text-left",
active
? "bg-white/5 border-white/10 shadow-[inset_0_1px_1px_rgba(255,255,255,0.05)]"
: "text-zinc-400 hover:text-zinc-200 hover:bg-white/5 border-transparent"
)}
style={active ? { color: m.theme.accentText } : {}}
>
{active && (
<span
className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 rounded-r-full"
style={{ backgroundColor: m.theme.accent, boxShadow: `0 0 15px ${m.theme.accent}` }}
/>
)}
<span
className="w-5 h-5 rounded-md flex items-center justify-center shrink-0 border transition-transform duration-300 group-hover:scale-110"
style={{
backgroundColor: `${m.theme.accent}14`,
borderColor: `${m.theme.accent}44`,
}}
>
<Icon className="w-3 h-3" style={{ color: m.theme.accent }} />
</span>
<span className="flex-1">{m.label}</span>
<span
className="w-1.5 h-1.5 rounded-full shrink-0 transition-opacity"
style={{
backgroundColor: m.theme.accent,
boxShadow: `0 0 8px ${m.theme.accent}`,
opacity: active ? 1 : 0.35,
}}
/>
</button>
);
})}
</div>
<button
type="button"
onClick={() => launch("hub")}
className="mt-1.5 w-full flex items-center gap-3 px-3 py-2 text-[11px] font-medium rounded-lg text-zinc-500 hover:text-zinc-300 hover:bg-white/5 border border-transparent transition-all duration-300 text-left"
>
<LayoutDashboard className="w-3.5 h-3.5" />
Platform Hub
</button>
</div>
{/* Active module navigation */}
<div className="px-3 pb-2 pt-1 text-[9px] font-bold tracking-[0.22em] uppercase text-zinc-600 font-mono">
{def.label}
</div>
{def.nav.map((item) => {
const isActive = isActivePath(item.path);
const full = moduleHref(module, item.path);
return (
<button
key={item.name}
type="button"
onClick={() => {
onClose?.();
if (location.pathname !== full) navigate(full);
}}
aria-current={isActive ? "page" : undefined}
className={cn(
"w-full flex items-center gap-3 px-3 py-2.5 text-[13px] font-medium rounded-lg transition-all duration-300 relative group overflow-hidden border text-left",
isActive
? "bg-white/5 border-white/10 shadow-[inset_0_1px_1px_rgba(255,255,255,0.05)]"
: "text-zinc-400 hover:text-zinc-200 hover:bg-white/5 border-transparent"
)}
style={isActive ? { color: config.accentText } : {}}
>
{isActive && (
<div
className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 rounded-r-full"
style={{
backgroundColor: config.accent,
boxShadow: `0 0 15px ${config.accent}`,
}}
/>
)}
<item.icon
className={cn("w-4 h-4 transition-transform duration-300 group-hover:scale-110", isActive ? "" : "text-zinc-500")}
style={isActive ? { color: config.accent, filter: `drop-shadow(0 0 8px ${config.accent})` } : {}}
/>
{item.name}
</button>
);
})}
</nav>
</div>
);
}
|