From 257c25be1d5b6cba422ffe4f8bc2a31e76e1c68e Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Mon, 18 May 2026 22:14:42 -0700 Subject: feat: add RatiosCard component with hero KPIs, sparklines, and detail rows --- frontend/app/prism-shell.css | 187 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) (limited to 'frontend/app') diff --git a/frontend/app/prism-shell.css b/frontend/app/prism-shell.css index 9a37bdd..424ebb3 100644 --- a/frontend/app/prism-shell.css +++ b/frontend/app/prism-shell.css @@ -1490,3 +1490,190 @@ font-variant-numeric: tabular-nums; text-align: right; } + +/* ── Key ratios tab ─────────────────────────────── */ + +.psm-ratio-card { + display: flex; + flex-direction: column; + gap: var(--sp-5); +} + +.psm-ratio-heroes { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--sp-3); +} + +.psm-ratio-hero { + display: flex; + flex-direction: column; + gap: var(--sp-3); + min-width: 0; + padding: var(--sp-4); + background: var(--ink-2); + border: 1px solid var(--line-1); + border-radius: var(--r-2); +} + +.psm-ratio-hero-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--sp-3); +} + +.psm-ratio-hero-label { + color: var(--fg-3); + font-size: var(--fs-12); + font-weight: 600; + letter-spacing: var(--tr-wider); + text-transform: uppercase; +} + +.psm-ratio-hero-sector { + color: var(--fg-4); + font-family: var(--font-mono); + font-size: var(--fs-12); + font-variant-numeric: tabular-nums; + text-align: right; +} + +.psm-ratio-hero-value { + font-family: var(--font-mono); + font-size: var(--fs-32); + line-height: 1; + font-variant-numeric: tabular-nums; +} + +.psm-ratio-hero-spark { + width: 100%; + height: 52px; +} + +.psm-ratio-detail { + display: flex; + flex-direction: column; + gap: var(--sp-5); +} + +.psm-ratio-group-label { + display: grid; + grid-template-columns: minmax(0, 1fr) 96px 96px 88px; + gap: var(--sp-3); + align-items: center; + padding-bottom: var(--sp-2); + border-bottom: 1px solid var(--line-1); + color: var(--fg-4); + font-size: var(--fs-12); + font-weight: 600; + letter-spacing: var(--tr-wider); + text-transform: uppercase; +} + +.psm-ratio-group-label span:nth-child(2), +.psm-ratio-group-label span:nth-child(3) { + text-align: right; +} + +.psm-ratio-group-label span:last-child { + text-align: center; +} + +.psm-ratio-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 96px 96px 88px; + gap: var(--sp-3); + align-items: center; + padding: var(--sp-3) 0; + border-bottom: 1px solid var(--ink-2); +} + +.psm-ratio-row-label { + color: var(--fg-2); + font-size: var(--fs-13); +} + +.psm-ratio-row-value, +.psm-ratio-row-sector { + color: var(--fg-1); + font-family: var(--font-mono); + font-size: var(--fs-13); + font-variant-numeric: tabular-nums; + text-align: right; + white-space: nowrap; +} + +.psm-ratio-row-sector { + color: var(--fg-4); +} + +.psm-ratio-mini-spark { + width: 88px; + height: 24px; +} + +.psm-ratio-mini-spark, +.psm-ratio-hero-spark { + display: block; +} + +.psm-ratio-spark-empty { + display: inline-flex; + align-items: center; + justify-content: center; + width: 88px; + height: 24px; + color: var(--fg-4); + font-family: var(--font-mono); + font-size: var(--fs-12); + font-variant-numeric: tabular-nums; +} + +.psm-ratio-hero .psm-ratio-spark-empty { + width: 100%; + height: 52px; + justify-content: flex-start; +} + +@media (max-width: 980px) { + .psm-ratio-group-label, + .psm-ratio-row { + grid-template-columns: minmax(0, 1fr) 88px 88px 72px; + } + + .psm-ratio-mini-spark, + .psm-ratio-spark-empty { + width: 72px; + } +} + +@media (max-width: 680px) { + .psm-ratio-heroes { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .psm-ratio-group-label { + grid-template-columns: minmax(0, 1fr) 84px 84px 64px; + gap: var(--sp-2); + } + + .psm-ratio-row { + grid-template-columns: minmax(0, 1fr) 84px 84px 64px; + gap: var(--sp-2); + } + + .psm-ratio-hero-head { + flex-direction: column; + align-items: flex-start; + } + + .psm-ratio-hero-sector { + text-align: left; + } + + .psm-ratio-mini-spark, + .psm-ratio-spark-empty { + width: 64px; + } +} -- cgit v1.3-2-g0d8e From 16d9eb4f864fe8c29a9dee57ec47f77b34ae0df4 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Mon, 18 May 2026 22:18:20 -0700 Subject: feat: wire Ratios subtab into FinancialsPage, move tab strip up from FinancialsCard --- frontend/app/prism-shell.css | 6 +++ frontend/components/prism/FinancialsCard.tsx | 20 -------- frontend/components/prism/FinancialsPage.tsx | 68 ++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 38 deletions(-) (limited to 'frontend/app') diff --git a/frontend/app/prism-shell.css b/frontend/app/prism-shell.css index 424ebb3..4e65ced 100644 --- a/frontend/app/prism-shell.css +++ b/frontend/app/prism-shell.css @@ -1116,6 +1116,11 @@ overflow: hidden; } +.psm-fin-tab-bar { + border-bottom: 1px solid var(--line-1); + margin-bottom: 0; +} + .psm-fin-header { display: flex; align-items: stretch; @@ -1156,6 +1161,7 @@ display: flex; align-items: center; gap: var(--sp-1); + margin-left: auto; } .psm-fin-period-btn { diff --git a/frontend/components/prism/FinancialsCard.tsx b/frontend/components/prism/FinancialsCard.tsx index 94a6618..43a2dc2 100644 --- a/frontend/components/prism/FinancialsCard.tsx +++ b/frontend/components/prism/FinancialsCard.tsx @@ -9,16 +9,9 @@ type Props = { data: FinancialsResponse; statement: StatementKey; period: PeriodKey; - onChangeStatement: (s: StatementKey) => void; onChangePeriod: (p: PeriodKey) => void; }; -const STMT_LABELS: Record = { - income: "INCOME", - balance: "BALANCE", - cash_flow: "CASH FLOW", -}; - function fmtFinVal(val: number | null | undefined, isMargin: boolean): string { if (val === null || val === undefined) return "—"; if (isMargin) return `${(val * 100).toFixed(1)}%`; @@ -70,7 +63,6 @@ export function FinancialsCard({ data, statement, period, - onChangeStatement, onChangePeriod, }: Props) { const stmt = data[statement]; @@ -79,18 +71,6 @@ export function FinancialsCard({ return (
-
- {(["income", "balance", "cash_flow"] as StatementKey[]).map((key) => ( - - ))} -
{(["annual", "quarterly"] as PeriodKey[]).map((p) => ( + ))} +
+
+ {statement === "ratios" ? ( + + ) : ( + <> + {finState === "loading" && ( +
+ )} + {finState === "error" && ( +
+

Financial statements unavailable for {ticker}.

+
+ )} + {finState === "ready" && data && ( + + )} + )} ); -- cgit v1.3-2-g0d8e