feat(app): UI parity with Pencil mockup — top bar, panel cards, sidebar/event-center polish
Top bar (breadcrumb + actions + account), rounded panel cards with active accent + rich headers, sidebar count pills/collapsible groups/daemon footer, preset chips + scrollback pill, Event Center tabs + external-notify footer, JetBrains Mono + Inter via @fontsource, shared theme tokens. Backend-absent pieces are mocked (search, zoom, uptime, channels) pending SP1–SP5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+22
-13
@@ -1,11 +1,14 @@
|
||||
import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import { LayoutEngine } from "./LayoutEngine";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { PresetPicker } from "./PresetPicker";
|
||||
import { TopBar } from "./TopBar";
|
||||
import { CenterToolbar } from "./CenterToolbar";
|
||||
import { Wizard } from "./Wizard";
|
||||
import { EventCenter, type FeedEntry } from "./EventCenter";
|
||||
import { maybeNotify } from "./notify";
|
||||
import { COLORS } from "./theme";
|
||||
import { getStatusFull, applyPreset, onDaemonEvent, onDaemonRawEvent, setWorkspaceMeta, focusSurface } from "./socketBridge";
|
||||
import { leafIds } from "./layoutTypes";
|
||||
import type { Group, WorkspaceView, SurfaceState } from "./layoutTypes";
|
||||
|
||||
export function App() {
|
||||
@@ -16,6 +19,8 @@ export function App() {
|
||||
const [states, setStates] = useState<Record<string, SurfaceState>>({});
|
||||
const [feed, setFeed] = useState<FeedEntry[]>([]);
|
||||
const [wizard, setWizard] = useState(false);
|
||||
const [eventsOpen, setEventsOpen] = useState(true);
|
||||
const [focusedId, setFocusedId] = useState<string | null>(null);
|
||||
const feedId = useRef(0);
|
||||
const activeRef = useRef<string | null>(null);
|
||||
const wsRef = useRef<WorkspaceView[]>([]);
|
||||
@@ -66,28 +71,32 @@ export function App() {
|
||||
}, [refresh]);
|
||||
|
||||
const active = workspaces.find((w) => w.id === activeId) ?? null;
|
||||
const leaves = active ? leafIds(active.layout) : [];
|
||||
const effectiveFocus = focusedId && leaves.includes(focusedId) ? focusedId : leaves[0] ?? null;
|
||||
|
||||
function selectWorkspace(id: string) {
|
||||
setActiveId(id);
|
||||
setFocusedId(null);
|
||||
void setWorkspaceMeta(id, { unread: false });
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", height: "100vh", background: "#0E1116" }}>
|
||||
<Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} />
|
||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
||||
{active && (
|
||||
<div style={{ padding: 8, borderBottom: "1px solid #232A33" }}>
|
||||
<PresetPicker selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} />
|
||||
<div style={{ display: "flex", flexDirection: "column", height: "100vh", background: COLORS.bgApp }}>
|
||||
<TopBar active={active} eventsOpen={eventsOpen} onToggleEvents={() => setEventsOpen((v) => !v)} />
|
||||
<div style={{ flex: 1, display: "flex", minHeight: 0 }}>
|
||||
<Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} />
|
||||
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
||||
{active && (
|
||||
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} />
|
||||
)}
|
||||
<div style={{ flex: 1, minHeight: 0 }}>
|
||||
{active
|
||||
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} surfaces={active.surfaces} focusedId={effectiveFocus} onFocus={setFocusedId} />
|
||||
: <div style={{ color: COLORS.textMuted, padding: 24 }}>No workspace — create one to begin.</div>}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ flex: 1, minHeight: 0 }}>
|
||||
{active
|
||||
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} />
|
||||
: <div style={{ color: "#666", padding: 24 }}>No workspace — create one to begin.</div>}
|
||||
</div>
|
||||
{eventsOpen && <EventCenter feed={feed} onMarkRead={() => setFeed([])} onSelect={(sid) => { void focusSurface(sid); }} />}
|
||||
</div>
|
||||
<EventCenter feed={feed} onMarkRead={() => setFeed([])} onSelect={(sid) => { void focusSurface(sid); }} />
|
||||
{wizard && <Wizard onDone={(id) => { setWizard(false); setActiveId(id); void refresh(); }} onCancel={() => setWizard(false)} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user