diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-19 00:31:58 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-19 00:31:58 -0700 |
| commit | 12e64dcdd11d151c03339540ace5603777be3b88 (patch) | |
| tree | 1350e0d42be6450d916025ec893938b3ae7e44ed /frontend/components/prism | |
| parent | 5eab2822091e20f9cf07bfe10ed20b5f2e3380bc (diff) | |
feat: add VolumeCard to overview left column (today vs 30d avg)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'frontend/components/prism')
| -rw-r--r-- | frontend/components/prism/VolumeCard.tsx | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/frontend/components/prism/VolumeCard.tsx b/frontend/components/prism/VolumeCard.tsx new file mode 100644 index 0000000..08b4059 --- /dev/null +++ b/frontend/components/prism/VolumeCard.tsx @@ -0,0 +1,51 @@ +import type { TickerOverview } from "@/types/api"; +import { fmtNumber } from "@/lib/format"; + +function activityLabel(ratio: number): string { + if (ratio >= 1.5) return "elevated activity"; + if (ratio < 0.7) return "below average"; + return "normal activity"; +} + +export function VolumeCard({ overview }: { overview: TickerOverview }) { + const today = overview.stats.volume; + const avg = overview.stats.average_volume; + if (today == null && avg == null) return null; + + const max = Math.max(today ?? 0, avg ?? 0); + const todayPct = today != null && max > 0 ? Math.round((today / max) * 100) : 0; + const avgPct = avg != null && max > 0 ? Math.round((avg / max) * 100) : 0; + const ratio = today != null && avg != null && avg > 0 ? today / avg : null; + + return ( + <section className="psm-card"> + <div className="psm-card-head"> + <div> + <div className="psm-eyebrow">Volume</div> + <h2 className="psm-card-title">Volume</h2> + </div> + </div> + <div className="psm-vol-list"> + <div className="psm-vol-row"> + <span className="psm-vol-label">Today</span> + <div className="psm-vol-track"> + <div className="psm-vol-fill accent" style={{ width: `${todayPct}%` }} /> + </div> + <span className="psm-vol-value">{today != null ? fmtNumber(today, 0) : "—"}</span> + </div> + <div className="psm-vol-row"> + <span className="psm-vol-label">30d avg</span> + <div className="psm-vol-track"> + <div className="psm-vol-fill" style={{ width: `${avgPct}%` }} /> + </div> + <span className="psm-vol-value">{avg != null ? fmtNumber(avg, 0) : "—"}</span> + </div> + </div> + {ratio != null && ( + <p className="psm-muted-copy" style={{ marginTop: "var(--sp-3)" }}> + {ratio >= 1 ? "↑" : "↓"} {ratio.toFixed(2)}× average · {activityLabel(ratio)} + </p> + )} + </section> + ); +} |
