Merge sidebar-rail: collapsed icon rail
This commit is contained in:
+1
-1
@@ -164,7 +164,7 @@ export function App() {
|
|||||||
<div style={{ display: "flex", flexDirection: "column", height: "100vh", background: COLORS.bgApp }}>
|
<div style={{ display: "flex", flexDirection: "column", height: "100vh", background: COLORS.bgApp }}>
|
||||||
<TopBar active={active} eventsOpen={eventsOpen} onToggleEvents={() => setEventsOpen((v) => !v)} onShowEvents={() => setEventsOpen(true)} sidebarOpen={sidebarOpen} onToggleSidebar={() => setSidebarOpen((v) => !v)} unread={unread} onOpenSettings={() => { if (config) setSettingsOpen(true); }} />
|
<TopBar active={active} eventsOpen={eventsOpen} onToggleEvents={() => setEventsOpen((v) => !v)} onShowEvents={() => setEventsOpen(true)} sidebarOpen={sidebarOpen} onToggleSidebar={() => setSidebarOpen((v) => !v)} unread={unread} onOpenSettings={() => { if (config) setSettingsOpen(true); }} />
|
||||||
<div style={{ flex: 1, display: "flex", minHeight: 0 }}>
|
<div style={{ flex: 1, display: "flex", minHeight: 0 }}>
|
||||||
{sidebarOpen && <Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} onDelete={setDeleteTarget} health={health} connected={connected} />}
|
<Sidebar railMode={!sidebarOpen} groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} onDelete={setDeleteTarget} health={health} connected={connected} />
|
||||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
||||||
{active && (
|
{active && (
|
||||||
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} onOpenSearch={() => { if (effectiveFocus) { setSearchSurfaceId(effectiveFocus); setSearchNonce((n) => n + 1); } }} />
|
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} onOpenSearch={() => { if (effectiveFocus) { setSearchSurfaceId(effectiveFocus); setSearchNonce((n) => n + 1); } }} />
|
||||||
|
|||||||
+33
-1
@@ -26,8 +26,9 @@ function aggregate(w: WorkspaceView): SurfaceState | "stopped" {
|
|||||||
interface DropAt { section: string; index: number }
|
interface DropAt { section: string; index: number }
|
||||||
|
|
||||||
export function Sidebar({
|
export function Sidebar({
|
||||||
groups, workspaces, activeId, onSelect, onNew, onDelete, health, connected,
|
railMode, groups, workspaces, activeId, onSelect, onNew, onDelete, health, connected,
|
||||||
}: {
|
}: {
|
||||||
|
railMode: boolean;
|
||||||
groups: Group[];
|
groups: Group[];
|
||||||
workspaces: WorkspaceView[];
|
workspaces: WorkspaceView[];
|
||||||
activeId: string | null;
|
activeId: string | null;
|
||||||
@@ -183,6 +184,37 @@ export function Sidebar({
|
|||||||
|
|
||||||
const section = (key: string, items: WorkspaceView[]) => items.map((w, i) => row(w, key, items, i));
|
const section = (key: string, items: WorkspaceView[]) => items.map((w, i) => row(w, key, items, i));
|
||||||
|
|
||||||
|
// Collapsed: a narrow rail of status rings so terminal activity stays visible.
|
||||||
|
if (railMode) {
|
||||||
|
const rail = [
|
||||||
|
...pinned,
|
||||||
|
...groups.slice().sort((a, b) => a.order - b.order).flatMap((g) => byGroup(g.id)),
|
||||||
|
...ungrouped,
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: 48, flex: "0 0 48px", background: COLORS.bgSidebar, height: "100%", padding: "10px 0", boxSizing: "border-box", borderRight: `1px solid ${COLORS.borderSubtle}`, gap: 8 }}>
|
||||||
|
<button onClick={onNew} title="New workspace"
|
||||||
|
style={{ display: "flex", alignItems: "center", justifyContent: "center", width: 30, height: 30, borderRadius: 8, background: COLORS.bgElevated, border: `1px solid ${COLORS.borderStrong}`, color: COLORS.textPrimary, cursor: "pointer" }}>
|
||||||
|
<Plus size={15} />
|
||||||
|
</button>
|
||||||
|
<div style={{ flex: 1, overflowY: "auto", display: "flex", flexDirection: "column", alignItems: "center", gap: 6, minHeight: 0 }}>
|
||||||
|
{rail.map((w) => {
|
||||||
|
const isActive = w.id === activeId;
|
||||||
|
return (
|
||||||
|
<button key={w.id} onClick={() => onSelect(w.id)} title={w.name}
|
||||||
|
style={{ position: "relative", display: "flex", alignItems: "center", justifyContent: "center", width: 32, height: 32, borderRadius: 8, cursor: "pointer", background: isActive ? COLORS.bgElevated : "transparent", border: `1px solid ${isActive ? COLORS.borderStrong : "transparent"}` }}>
|
||||||
|
<span style={{ width: 12, height: 12, borderRadius: "50%", border: `2px solid ${STATE_COLOR[aggregate(w)]}`, boxSizing: "border-box" }} />
|
||||||
|
{w.unread && <span style={{ position: "absolute", top: 3, right: 3, width: 7, height: 7, borderRadius: "50%", background: COLORS.accent }} />}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<span title={connected ? "spaceshd · live" : "spaceshd · offline"}
|
||||||
|
style={{ width: 8, height: 8, borderRadius: "50%", background: connected ? COLORS.stDone : COLORS.textMuted, flex: "0 0 8px" }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", flexDirection: "column", width: 248, flex: "0 0 248px", background: COLORS.bgSidebar, height: "100%", padding: 14, boxSizing: "border-box" }}>
|
<div style={{ display: "flex", flexDirection: "column", width: 248, flex: "0 0 248px", background: COLORS.bgSidebar, height: "100%", padding: 14, boxSizing: "border-box" }}>
|
||||||
<button onClick={onNew}
|
<button onClick={onNew}
|
||||||
|
|||||||
Reference in New Issue
Block a user