feat(price-hero): 오늘의 흐름 OHLV mini strip under change line

PriceHero gains an optional flow={open,high,low,volume} prop. Rendered
as a compact 4-KPI inline strip (시/고/저/거래량) below the change
line. Wraps naturally on narrow widths via flex-wrap.

page.tsx computes flow from chart.ohlcv tail inside the existing
useMemo:
- 1D (10m): groups bars by same KST date as last bar, aggregates as
  session OHLV (open=first, high=max, low=min, volume=sum).
- 1d/1w/1mo: just the last valid bar's OHLV (each bar already
  represents one unit).

Volume formatted with the same 억/만 rule as StockChart fmtKVolume —
small enough to inline at use site instead of carving out a shared
format module. High/low labels get rose-300/sky-300 ink as a soft
directional hint while the numbers themselves stay neutral (zinc-200)
to avoid implying a misleading +/- sign.
This commit is contained in:
claude-owner
2026-06-01 10:20:25 +09:00
parent 31e7a67a0d
commit b1323373a5
2 changed files with 91 additions and 6 deletions

View File

@@ -17,8 +17,20 @@ type Props = {
series?: number[];
// social-proof — "오늘 N명이 봤어요" 배지. null/0 이면 숨김.
viewsToday?: number | null;
// 오늘(또는 마지막 봉) 흐름 요약. page.tsx 에서 ohlcv 끝부분으로 계산해 전달.
// null 이면 strip 자체를 숨김 (데이터 없음 + 레이아웃 jitter 방지).
flow?: { open: number; high: number; low: number; volume: number } | null;
};
// 거래량 한국식 단위 (억/만) — StockChart 의 fmtKVolume 과 같은 규칙. 둘 다 작아서
// 별도 모듈로 빼지 않고 사용처에 인라인 유지.
function fmtVolume(n: number): string {
if (!Number.isFinite(n) || n <= 0) return "-";
if (n >= 1e8) return `${(n / 1e8).toFixed(1)}`;
if (n >= 1e4) return `${(n / 1e4).toFixed(1)}`;
return n.toLocaleString();
}
export function PriceHero({
name,
code,
@@ -28,6 +40,7 @@ export function PriceHero({
asOf,
series,
viewsToday,
flow,
}: Props) {
const change = current != null && prev != null ? current - prev : null;
const pct = change != null && prev ? (change / prev) * 100 : null;
@@ -71,6 +84,37 @@ export function PriceHero({
</span>
)}
</div>
{flow && (
// 오늘의 흐름 요약 — 시/고/저/거래량 4-KPI mini strip. 차트 hover 없이도 진입 즉시 보임.
// 고/저 가격 자체에는 색을 칠하지 않음 (양수/음수 의미가 없으므로 톤이 거짓 신호가 됨).
// 대신 라벨만 sky-300(저) / rose-300(고) 으로 살짝 칠해 직관 보조.
<div className="mt-3 flex flex-wrap items-baseline gap-x-4 gap-y-1 text-[11px] text-zinc-400">
<span>
{" "}
<span className="tabular-nums text-zinc-200">
{flow.open.toLocaleString()}
</span>
</span>
<span>
<span className="text-rose-300"></span>{" "}
<span className="tabular-nums text-zinc-200">
{flow.high.toLocaleString()}
</span>
</span>
<span>
<span className="text-sky-300"></span>{" "}
<span className="tabular-nums text-zinc-200">
{flow.low.toLocaleString()}
</span>
</span>
<span>
{" "}
<span className="tabular-nums text-zinc-200">
{fmtVolume(flow.volume)}
</span>
</span>
</div>
)}
</div>
{series && series.length >= 2 && (