PeriodTabs (reviewer 488fb32 concern):
- Drop role=tablist/tab/aria-selected. These imply a full ARIA tab
pattern (tabpanel, aria-controls, roving tabIndex, internal Arrow
focus traversal) which we don't implement — page-level left/right keys
drive period change instead. Switch to plain button group with
aria-pressed={active}, which accurately describes a segmented
toggle. Comment updated to match.
OrderbookPanel sticky (new slice):
- Add optional sticky prop. When true, panel uses 'sticky top-4' with
a slightly heavier bg-zinc-900/80 + backdrop-blur so it stays
legible over the panels scrolling behind it.
- page.tsx passes sticky={isIntraday} — only 1D (10m bars) mode
benefits, since orderbook timeliness drops on daily+ charts and the
fixed slot would harm sidebar layout stability there.
76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import type { ChartInterval } from "../lib/api";
|
|
|
|
// 7-탭 기간 셀렉터. 각 탭이 (interval, days) 쌍으로 풀린다.
|
|
// 1D 만 10분봉, 5Y/MAX 는 자동으로 주봉/월봉 사용 (포인트 수 폭주 방지).
|
|
|
|
export type Period = "1D" | "1W" | "1M" | "3M" | "1Y" | "5Y" | "ALL";
|
|
|
|
type PeriodSpec = { id: Period; label: string; interval: ChartInterval; days: number };
|
|
|
|
const PERIODS: PeriodSpec[] = [
|
|
{ id: "1D", label: "1일", interval: "10m", days: 1 },
|
|
{ id: "1W", label: "1주", interval: "1d", days: 7 },
|
|
{ id: "1M", label: "1달", interval: "1d", days: 30 },
|
|
{ id: "3M", label: "3달", interval: "1d", days: 90 },
|
|
{ id: "1Y", label: "1년", interval: "1d", days: 365 },
|
|
{ id: "5Y", label: "5년", interval: "1w", days: 365 * 5 },
|
|
{ id: "ALL", label: "최대", interval: "1mo", days: 365 * 20 },
|
|
];
|
|
|
|
export function periodSpec(id: Period): PeriodSpec {
|
|
return PERIODS.find((p) => p.id === id) ?? PERIODS[2];
|
|
}
|
|
|
|
// ←/→ 키 네비게이션용 — 양끝 clamp (wrap-around 안 함, 사용자가 끝에 갇혔는지 인지 가능).
|
|
export function periodNeighbor(id: Period, dir: -1 | 1): Period {
|
|
const idx = PERIODS.findIndex((p) => p.id === id);
|
|
if (idx < 0) return id;
|
|
const next = idx + dir;
|
|
if (next < 0 || next >= PERIODS.length) return id;
|
|
return PERIODS[next].id;
|
|
}
|
|
|
|
type Props = {
|
|
value: Period;
|
|
onChange: (id: Period, interval: ChartInterval, days: number) => void;
|
|
};
|
|
|
|
export function PeriodTabs({ value, onChange }: Props) {
|
|
// 활성 탭 스타일:
|
|
// 이전엔 `bg-zinc-700` 만 사용해 헤더의 다른 zinc-톤 버튼(비교/공유/Star) 과 색이 거의 동일,
|
|
// 페이지에서 "지금 어느 기간이 선택돼 있는지" 한눈에 안 들어왔음.
|
|
// 한국 컬러 상승 톤(rose-500) 으로 칠해서 zinc 버튼군 사이에서 즉시 식별. 다른 모듈의 활성/positive
|
|
// 상태(목표가 라인, 등락 양수 등) 도 같은 rose 계열이라 톤 일관성 유지.
|
|
// ARIA: 이건 tabpanel 을 가진 진짜 탭이 아니라 차트 기간 segmented control 임.
|
|
// role=tablist/tab 을 붙이면 스크린리더가 aria-controls·roving tabIndex·Arrow focus 이동 같은
|
|
// ARIA 탭 패턴을 기대하는데 여기엔 그 인프라가 없음 (←/→ 는 페이지 전역 단축키로 처리). 단순
|
|
// 버튼 그룹 + aria-pressed 가 정확한 표현.
|
|
return (
|
|
<div
|
|
aria-label="기간 선택"
|
|
className="inline-flex rounded-full border border-zinc-800 bg-zinc-900 p-1 text-xs"
|
|
>
|
|
{PERIODS.map((p) => {
|
|
const active = value === p.id;
|
|
return (
|
|
<button
|
|
key={p.id}
|
|
type="button"
|
|
aria-pressed={active}
|
|
onClick={() => onChange(p.id, p.interval, p.days)}
|
|
className={
|
|
active
|
|
? "rounded-full bg-rose-500 px-3 py-1 font-semibold text-white shadow-sm shadow-rose-500/40"
|
|
: "rounded-full px-3 py-1 text-zinc-400 hover:text-zinc-100"
|
|
}
|
|
>
|
|
{p.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|