meridian-amd / shell.jsx
Cukinator's picture
Deploy Frontend2 to Space
bc1328f
// Shell: sidebar + topbar + inbox panel + command palette
const Sidebar = ({ view, setView, collapsed }) => {
const nav = [
{ id: "home", label: "Home", icon: "home" },
{ id: "inbox", label: "Inbox", icon: "inbox", badge: 4 },
{ id: "issues", label: "Issues", icon: "issues" },
{ id: "sprints", label: "Sprints", icon: "sprint" },
{ id: "roadmap", label: "Roadmap", icon: "roadmap" },
{ id: "docs", label: "Docs", icon: "docs" },
{ id: "chat", label: "VaultMind AI", icon: "sparkle" },
{ id: "code", label: "Code Editor", icon: "code" },
{ id: "compute", label: "Compute", icon: "sprint" },
{ id: "aimarket", label: "AI Infrastructure", icon: "bolt" },
{ id: "nexus", label: "Nexus", icon: "sparkle" },
{ id: "prs", label: "Pull requests", icon: "pr" },
{ id: "team", label: "Team", icon: "team" },
{ id: "settings", label: "Settings", icon: "settings" },
];
return (
<nav className="sidebar">
<div className="sb-brand">
<span className="sb-logo" aria-hidden />
<div style={{ lineHeight: 1.15 }}>
<div className="name">Meridian</div>
<div className="ws mono">helix · enterprise</div>
</div>
<span style={{ marginLeft: "auto" }} className="mono muted-2" title="workspace shortcut"></span>
</div>
<div className="sb-section">
{nav.map(n => (
<button key={n.id} className={`sb-item ${view === n.id ? "active" : ""}`} onClick={() => setView(n.id)}>
<span className="sb-ind" />
<Icon name={n.icon} className="sb-icon" />
<span>{n.label}</span>
{n.badge && <span className="sb-badge">{n.badge}</span>}
</button>
))}
</div>
<div className="sb-section">
<div className="sb-label"><span>Projects</span><span className="count">{PROJECTS.length}</span></div>
{PROJECTS.map(p => (
<button key={p.id} className="sb-proj" onClick={() => window.openProject(p)}>
<span className="dot" style={{ background: p.color }} />
<span className="truncate">{p.name}</span>
<span className="sb-badge mono">{p.code}</span>
</button>
))}
</div>
<div className="sb-section">
<div className="sb-label"><span>Views</span><Icon name="plus" size={12} /></div>
{[
{ id: "assigned", label: "Assigned to me", count: 7 },
{ id: "review", label: "Needs review", count: 3 },
{ id: "urgent", label: "Urgent", count: 2 },
{ id: "recent", label: "Recently updated", count: 12 },
].map(v => (
<button key={v.id} className="sb-proj" onClick={() => { setView("issues"); window.openFilter(v.label); }}>
<Icon name="filter" size={14} style={{ color: "var(--fg-3)" }} />
<span className="truncate">{v.label}</span>
<span className="sb-badge mono">{v.count}</span>
</button>
))}
</div>
<div style={{ flex: 1 }} />
<div className="sb-foot">
<Avatar user={PEOPLE[0]} size="sm" />
<div className="who" style={{ lineHeight: 1.2, flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12.5, fontWeight: 500 }}>{PEOPLE[0].name}</div>
<div className="mono muted-2" style={{ fontSize: 10.5 }}>online · EU</div>
</div>
<button className="icon-btn" title="Account menu" onClick={() => window.openTeammate(PEOPLE[0])}><Icon name="more" size={14} /></button>
</div>
</nav>
);
};
const Topbar = ({ view, onToggleSidebar, onOpenPalette, onOpenInbox, onOpenTweaks, theme, setTheme }) => {
const crumb = {
home: ["Home"],
inbox: ["Inbox", "All activity"],
issues: ["Projects", "Aurora", "Issues"],
sprints: ["Sprints", "Iteration 42"],
roadmap: ["Roadmap", "Q2 — Q3 2026"],
docs: ["Docs", "Engineering handbook", "ADR 041 — Canvas rendering pipeline"],
prs: ["Code", "Pull requests"],
team: ["Team", "Members"],
issue: ["Projects", "Aurora", "AUR-412"],
chat: ["VaultMind AI", "Chat"],
compute: ["Compute", "GPU Instances"],
aimarket: ["AI Infrastructure", "Studios & Models"],
nexus: ["Nexus", "Cloud AI Platform"],
settings: ["Settings", "Workspace"],
}[view] || ["Home"];
return (
<header className="topbar">
<button className="icon-btn" onClick={onToggleSidebar} title="Toggle sidebar"><Icon name="sidebar" size={16} /></button>
<div className="breadcrumbs">
{crumb.map((c, i) => (
<React.Fragment key={i}>
{i > 0 && <span className="sep">/</span>}
<span className={i === crumb.length - 1 ? "current" : ""}>{c}</span>
</React.Fragment>
))}
</div>
<div className="topbar-spacer" />
<button className="search" onClick={onOpenPalette} style={{ cursor: "pointer" }}>
<Icon name="search" size={13} />
<span style={{ flex: 1, textAlign: "left", color: "var(--fg-3)" }}>Search, jump to, or ask…</span>
<span className="kbd-hint mono">⌘K</span>
</button>
<div className="topbar-actions">
<button className="icon-btn" onClick={() => setTheme(theme === "dark" ? "light" : "dark")} title="Toggle theme">
<Icon name={theme === "dark" ? "sun" : "moon"} size={15} />
</button>
<button className="icon-btn" title="Notifications" onClick={onOpenInbox}>
<Icon name="bell" size={15} /><span className="dot" />
</button>
<button className="btn sm" onClick={() => window.openPicker({
title: "Create",
options: [
{ value: "issue", label: "New issue", icon: "issues", hint: "C" },
{ value: "doc", label: "New document", icon: "docs", hint: "⌘⇧D" },
{ value: "pr", label: "Open pull request", icon: "pr" },
{ value: "invite", label: "Invite teammate", icon: "team" },
],
onChoose: (o) => {
if (o.value === "issue") window.openNewIssue();
else if (o.value === "doc") window.openNewDoc();
else if (o.value === "pr") window.openNewPR();
else if (o.value === "invite") window.openInvite();
},
})}>
<Icon name="plus" size={13} /> New
</button>
</div>
</header>
);
};
// ---- Command palette ----
const PALETTE_ITEMS = [
{ group: "Jump to", items: [
{ id: "p-home", label: "Home", icon: "home", kbd: "G H", action: (setView) => setView("home") },
{ id: "p-inbox", label: "Inbox", icon: "inbox", kbd: "G I", action: (setView) => setView("inbox") },
{ id: "p-issues", label: "Issues", icon: "issues", kbd: "G S", action: (setView) => setView("issues") },
{ id: "p-docs", label: "Docs", icon: "docs", kbd: "G D", action: (setView) => setView("docs") },
{ id: "p-roadmap", label: "Roadmap", icon: "roadmap", kbd: "G R", action: (setView) => setView("roadmap") },
{ id: "p-prs", label: "Pull requests", icon: "pr", kbd: "G P", action: (setView) => setView("prs") },
{ id: "p-sprints", label: "Sprints", icon: "sprint", action: (setView) => setView("sprints") },
]},
{ group: "Create", items: [
{ id: "c-issue", label: "Create new issue…", icon: "plus", kbd: "C", action: () => window.openNewIssue() },
{ id: "c-doc", label: "Create new document…", icon: "doc-add", kbd: "⌘⇧D", action: () => window.openNewDoc() },
{ id: "c-pr", label: "Open pull request…", icon: "pr", action: () => window.openNewPR() },
]},
{ group: "Recent", items: [
{ id: "r-1", label: "AUR-412 — Rebuild canvas-rendering pipeline", icon: "issues", action: (setView) => setView("issue") },
{ id: "r-2", label: "Aurora collaborative canvas PRD", icon: "docs", action: (setView) => setView("docs") },
{ id: "r-3", label: "#2341 perf(canvas): tile-based rendering", icon: "pr", action: (setView) => setView("prs") },
{ id: "r-4", label: "Iteration 42 — Apr 14 – Apr 28", icon: "sprint", action: (setView) => setView("sprints") },
]},
{ group: "Ask Meridian AI", items: [
{ id: "ai-1", label: "Summarize Iteration 42 progress", icon: "sparkle", action: (setView) => { window.chatInitialQuery = "Summarize Iteration 42 progress"; setView("chat"); } },
{ id: "ai-2", label: "What's blocking Aurora?", icon: "sparkle", action: (setView) => { window.chatInitialQuery = "What's blocking Aurora?"; setView("chat"); } },
{ id: "ai-3", label: "Draft release notes for last sprint", icon: "sparkle", action: (setView) => { window.chatInitialQuery = "Draft release notes for last sprint"; setView("chat"); } },
]},
];
const CommandPalette = ({ open, onClose, setView }) => {
const [q, setQ] = React.useState("");
const [sel, setSel] = React.useState(0);
React.useEffect(() => {
if (!open) { setQ(""); setSel(0); }
}, [open]);
const all = React.useMemo(() => {
const query = q.trim().toLowerCase();
return PALETTE_ITEMS.map(g => ({
...g,
items: g.items.filter(it => !query || it.label.toLowerCase().includes(query))
})).filter(g => g.items.length);
}, [q]);
const flat = all.flatMap(g => g.items);
const go = (it) => {
if (it.action) it.action(setView);
onClose();
};
const onKey = (e) => {
if (e.key === "ArrowDown") { e.preventDefault(); setSel(s => Math.min(s + 1, flat.length - 1)); }
else if (e.key === "ArrowUp") { e.preventDefault(); setSel(s => Math.max(s - 1, 0)); }
else if (e.key === "Enter") { const it = flat[sel]; if (it) go(it); }
else if (e.key === "Escape") onClose();
};
if (!open) return null;
return (
<div className="overlay" onClick={onClose}>
<div className="palette" onClick={e => e.stopPropagation()}>
<div className="palette-input">
<Icon name="search" size={16} style={{ color: "var(--fg-2)" }} />
<input
autoFocus
placeholder="Search, jump to, ask…"
value={q}
onChange={e => { setQ(e.target.value); setSel(0); }}
onKeyDown={onKey}
/>
<span className="kbd-hint mono">ESC</span>
</div>
<div className="palette-list">
{all.map((g, gi) => (
<div key={gi}>
<div className="palette-group">{g.group}</div>
{g.items.map((it) => {
const idx = flat.indexOf(it);
return (
<button
key={it.id}
className={`palette-item ${idx === sel ? "sel" : ""}`}
onMouseEnter={() => setSel(idx)}
onClick={() => go(it)}
>
<Icon name={it.icon} size={15} style={{ color: "var(--fg-2)" }} />
<span className="truncate">{it.label}</span>
{it.kbd && <span className="k mono">{it.kbd}</span>}
</button>
);
})}
</div>
))}
</div>
</div>
</div>
);
};
// ---- Inbox panel ----
const InboxPanel = ({ open, onClose }) => {
const [filter, setFilter] = React.useState("all");
const list = INBOX.filter(n => filter === "all" || (filter === "unread" && n.unread));
const kindIcon = { issue: "issues", pr: "pr", doc: "docs" };
return (
<aside className={`inbox-panel ${open ? "open" : ""}`}>
<div style={{ padding: "10px 14px", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "center", gap: 8 }}>
<Icon name="inbox" size={15} />
<strong style={{ fontSize: 13.5 }}>Inbox</strong>
<span className="mono muted-2" style={{ fontSize: 11 }}>{INBOX.filter(n => n.unread).length} unread</span>
<div style={{ flex: 1 }} />
<div className="segmented">
<button className={filter === "all" ? "on" : ""} onClick={() => setFilter("all")}>All</button>
<button className={filter === "unread" ? "on" : ""} onClick={() => setFilter("unread")}>Unread</button>
</div>
<button className="icon-btn" onClick={onClose}><Icon name="x" size={14} /></button>
</div>
<div className="scroll-y" style={{ flex: 1 }}>
{list.map(n => {
const user = PEOPLE.find(p => p.id === n.from);
return (
<button key={n.id} onClick={() => { window.toast(`Opening ${n.target}`); onClose(); }} className="flex items-center gap-12" style={{
width: "100%", padding: "10px 14px", borderBottom: "1px solid var(--border-subtle)",
textAlign: "left", background: n.unread ? "transparent" : "var(--bg-0)", opacity: n.unread ? 1 : 0.75
}}>
{n.unread && <span style={{ width: 6, height: 6, borderRadius: "50%", background: "var(--accent)", flexShrink: 0 }} />}
{!n.unread && <span style={{ width: 6, flexShrink: 0 }} />}
{user ? <Avatar user={user} size="sm" /> : <span className="avatar sm" style={{ background: "var(--bg-3)" }}><Icon name={kindIcon[n.kind]} size={10} /></span>}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 12.5, color: "var(--fg-0)", lineHeight: 1.35 }}>
{user && <strong style={{ fontWeight: 600 }}>{user.name.split(" ")[0]} </strong>}
<span className="muted">{n.text} </span>
<span className="mono" style={{ color: "var(--accent)" }}>{n.target}</span>
</div>
<div className="truncate muted" style={{ fontSize: 11.5, marginTop: 2 }}>{n.snippet}</div>
</div>
<span className="mono muted-2" style={{ fontSize: 10.5, flexShrink: 0 }}>{n.time}</span>
</button>
);
})}
</div>
</aside>
);
};
// ---- Tweaks panel ----
const TweaksPanel = ({ open, onClose, settings, setSettings }) => {
const accents = [
{ id: "lime", value: "oklch(0.85 0.17 145)" },
{ id: "cyan", value: "oklch(0.78 0.13 220)" },
{ id: "violet", value: "oklch(0.72 0.18 300)" },
{ id: "amber", value: "oklch(0.80 0.14 75)" },
{ id: "rose", value: "oklch(0.72 0.17 20)" },
];
return (
<div className={`tweaks ${open ? "open" : ""}`}>
<div className="flex items-center justify-between">
<h4>Tweaks</h4>
<button className="icon-btn" onClick={onClose}><Icon name="x" size={12} /></button>
</div>
<div className="tweak-row">
<label>Accent</label>
<div className="swatches">
{accents.map(a => (
<button
key={a.id}
className={`swatch ${settings.accent === a.id ? "sel" : ""}`}
style={{ background: a.value }}
onClick={() => setSettings(s => ({ ...s, accent: a.id }))}
/>
))}
</div>
</div>
<div className="tweak-row">
<label>Theme</label>
<div className="segmented">
{["dark","light"].map(t => (
<button key={t} className={settings.theme === t ? "on" : ""} onClick={() => setSettings(s => ({...s, theme: t}))}>{t}</button>
))}
</div>
</div>
<div className="tweak-row">
<label>Density</label>
<div className="segmented">
{["compact","default","relaxed"].map(d => (
<button key={d} className={settings.density === d ? "on" : ""} onClick={() => setSettings(s => ({...s, density: d}))}>{d}</button>
))}
</div>
</div>
<div className="tweak-row">
<label>Sidebar</label>
<div className="segmented">
{["expanded","collapsed"].map(d => (
<button key={d} className={settings.sidebar === d ? "on" : ""} onClick={() => setSettings(s => ({...s, sidebar: d}))}>{d}</button>
))}
</div>
</div>
<div className="tweak-row">
<label>Card style</label>
<div className="segmented">
{["detailed","minimal"].map(d => (
<button key={d} className={settings.cardStyle === d ? "on" : ""} onClick={() => setSettings(s => ({...s, cardStyle: d}))}>{d}</button>
))}
</div>
</div>
</div>
);
};
const HintBar = ({ onOpenPalette }) => (
<div className="hint-bar">
<span><kbd>⌘K</kbd> search</span>
<span><kbd>C</kbd> new issue</span>
<span><kbd>G</kbd>+<kbd>I</kbd> inbox</span>
<span><kbd>?</kbd> help</span>
</div>
);
Object.assign(window, { Sidebar, Topbar, CommandPalette, InboxPanel, TweaksPanel, HintBar });