feat(symbol-page): keyboard navigation for period tabs and watchlist

- ←/→ : 기간 탭 이전/다음 (양끝 clamp — wrap-around 안 함, 사용자가
  끝에 갇혔는지 인지 가능).
- S : 관심종목 토글 (watchlist.toggle, StarButton 자동 동기화).
- 입력 필드 포커스 시 무시 — 검색창/메모 텍스트 편집 방해 금지.
- modifier(Ctrl/Cmd/Alt) 가 눌리면 단축키 양보 — 브라우저/OS 단축키
  보존 (ex. Cmd+→ 줄 끝 이동, Alt+← 뒤로가기 등).
- ShortcutsHelp 에 새 단축키 등록 → ? 키 헬프 오버레이에서 발견 가능.
- PeriodTabs 에 `periodNeighbor(id, dir)` 헬퍼 export — 페이지가
  PERIODS 순서를 직접 알 필요 없도록.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
claude-owner
2026-05-29 00:24:03 +09:00
parent 6f71aef451
commit 093ac86d1a
3 changed files with 45 additions and 1 deletions

View File

@@ -12,7 +12,7 @@ import { PeerComparePanel } from "../../components/PeerComparePanel";
import { NewsList } from "../../components/NewsList";
import { OrderbookPanel } from "../../components/OrderbookPanel";
import { PeriodReturns } from "../../components/PeriodReturns";
import { PeriodTabs, periodSpec, type Period } from "../../components/PeriodTabs";
import { PeriodTabs, periodNeighbor, periodSpec, type Period } from "../../components/PeriodTabs";
import { PredictionPanel } from "../../components/PredictionPanel";
import { PriceHero } from "../../components/PriceHero";
import { PriceTargets } from "../../components/PriceTargets";
@@ -25,6 +25,7 @@ import { TradingValuePanel } from "../../components/TradingValuePanel";
import { api, type ChartPayload, type LatestPredictionResponse } from "../../lib/api";
import { recent } from "../../lib/recent";
import { readTargets, type Targets } from "../../lib/targets";
import { watchlist } from "../../lib/watchlist";
import { markRecorded, wasRecordedToday } from "../../lib/views";
export default function CodePage({ params }: { params: { code: string } }) {
@@ -78,6 +79,38 @@ export default function CodePage({ params }: { params: { code: string } }) {
setTargets(readTargets(code));
}, [code]);
// 종목 페이지 키보드 단축키:
// ←/→ : 기간 탭 이전/다음 (양끝 clamp — wrap 안 함, 사용자가 끝 인지)
// S : 관심종목 토글
// 입력 필드(검색창, 메모 등) 포커스 시 무시 — 텍스트 편집 방해 금지.
// modifier(Ctrl/Cmd/Alt) 가 눌리면 브라우저/OS 단축키 양보.
useEffect(() => {
if (typeof window === "undefined") return;
const isEditableTarget = (t: EventTarget | null): boolean => {
if (!(t instanceof HTMLElement)) return false;
const tag = t.tagName;
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
if (t.isContentEditable) return true;
return false;
};
const onKey = (e: KeyboardEvent) => {
if (e.ctrlKey || e.metaKey || e.altKey) return;
if (isEditableTarget(e.target)) return;
if (e.key === "ArrowLeft") {
e.preventDefault();
setPeriod((cur) => periodNeighbor(cur, -1));
} else if (e.key === "ArrowRight") {
e.preventDefault();
setPeriod((cur) => periodNeighbor(cur, 1));
} else if (e.key === "s" || e.key === "S") {
e.preventDefault();
watchlist.toggle(code);
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [code]);
useEffect(() => {
let alive = true;
setErr(null);