summaryrefslogtreecommitdiff
path: root/frontend/components/prism/Sidebar.tsx
blob: 80e13f3f22769c67094b9b9c538e4b9029a4bb78 (plain)
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
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" : ""}`}
              disabled={item.disabled}
            >
              <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>
  );
}