From 62bdd79b3473262dde5fb0a90eab34fe7bf344fd Mon Sep 17 00:00:00 2001
From: Tyler Hoang <tyler@tylerhoang.xyz>
Date: Sun, 17 May 2026 13:07:40 -0700
Subject: 'UI Shell and General Architecture'

---
 frontend/components/prism/Sidebar.tsx | 101 ++++++++++++++++++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 frontend/components/prism/Sidebar.tsx

(limited to 'frontend/components/prism/Sidebar.tsx')

diff --git a/frontend/components/prism/Sidebar.tsx b/frontend/components/prism/Sidebar.tsx
new file mode 100644
index 0000000..15a2947
--- /dev/null
+++ b/frontend/components/prism/Sidebar.tsx
@@ -0,0 +1,101 @@
+import Image from "next/image";
+import type { NavItem } from "@/lib/overview";
+import { deltaClass, fmtCurrency, fmtPct } from "@/lib/format";
+import { watchlistSubtitle } from "@/lib/overview";
+import type { WatchlistResponse } from "@/types/api";
+
+type Props = {
+  navItems: NavItem[];
+  selectedKey: string;
+  currentTicker: string;
+  watchlist: WatchlistResponse;
+  watchlistError: string | null;
+  onSelectTicker: (symbol: string) => void;
+  onRemoveTicker: (symbol: string) => void;
+};
+
+export function Sidebar({
+  navItems,
+  selectedKey,
+  currentTicker,
+  watchlist,
+  watchlistError,
+  onSelectTicker,
+  onRemoveTicker
+}: Props) {
+  return (
+    <aside className="psm-side">
+      <div className="psm-brand">
+        <Image className="psm-brand-mark" src="/design-system/logo-monogram.svg" alt="" width={34} height={34} />
+        <div className="psm-brand-copy">
+          <div className="psm-brand-name">Prism</div>
+          <div className="psm-brand-sub">Market Workbench</div>
+        </div>
+      </div>
+
+      <div className="psm-side-section">
+        <div className="psm-side-label">Workspace</div>
+      </div>
+
+      <nav className="psm-nav" aria-label="Primary">
+        {navItems.map((item) => {
+          const active = item.key === selectedKey;
+          return (
+            <button
+              key={item.key}
+              type="button"
+              className={`psm-nav-item${active ? " active" : ""}${item.disabled ? " disabled" : ""}`}
+              aria-disabled={item.disabled ? "true" : undefined}
+            >
+              <span className={`psm-icon icon-${item.icon}`} aria-hidden />
+              <span className="psm-nav-copy">
+                <span>{item.label}</span>
+                {item.disabled ? <span className="psm-nav-coming">Soon</span> : null}
+              </span>
+            </button>
+          );
+        })}
+      </nav>
+
+      <div className="psm-side-section">
+        <div className="psm-side-label">Watchlist</div>
+      </div>
+
+      <div className="psm-watch">
+        <div className="psm-watch-toolbar">
+          <div className="psm-watch-limit">
+            {watchlist.items.length}/{watchlist.limit}
+          </div>
+        </div>
+
+        {watchlist.items.length === 0 ? <div className="psm-watch-empty">Saved tickers will appear here.</div> : null}
+
+        {watchlist.items.map((item) => {
+          const active = item.symbol === currentTicker;
+          return (
+            <div key={item.symbol} className={`psm-watch-row${active ? " active" : ""}`}>
+              <button type="button" className="psm-watch-select" onClick={() => onSelectTicker(item.symbol)}>
+                <span className="psm-watch-main">
+                  <span className="psm-watch-symbol">{item.symbol}</span>
+                  <span className="psm-watch-date">{watchlistSubtitle(item)}</span>
+                </span>
+                <span className="psm-watch-price">{fmtCurrency(item.quote?.price)}</span>
+                <span className={`psm-watch-change ${deltaClass(item.quote?.change_pct)}`}>{fmtPct(item.quote?.change_pct, 2, true)}</span>
+              </button>
+              <button
+                type="button"
+                aria-label={`Remove ${item.symbol} from watchlist`}
+                className="psm-watch-remove"
+                onClick={() => onRemoveTicker(item.symbol)}
+              >
+                ×
+              </button>
+            </div>
+          );
+        })}
+
+        {watchlistError ? <p className="psm-muted-copy psm-error-copy">{watchlistError}</p> : null}
+      </div>
+    </aside>
+  );
+}
-- 
cgit v1.3-2-g0d8e

