"use client"; // 글로벌 키보드 단축키 헬프 오버레이. // `?` (Shift+/) 로 열고 ESC 또는 바깥 클릭 / 닫기 버튼으로 닫음. // 다른 슬라이스에서 키보드 단축키를 추가해도 발견 가능성을 유지하기 위한 공용 패널. // // 등록되는 단축키는 여기 한 곳에서 정의 — 실제 핸들러는 각 컴포넌트가 가지고 있고, // 이 컴포넌트는 단순 표시 + `?` 토글만 담당. 단일 출처(Single Source of Truth)는 아니지만, // 이 사이트가 사용하는 단축키 수가 적어 수동 동기화로 충분. import { useEffect, useRef, useState } from "react"; type Shortcut = { keys: string[]; // ['Ctrl', 'K'] 같은 표시용 토큰. 시각적으로 + 로 join. label: string; scope?: string; // '전역' / '종목 페이지' 등 — group 헤더 라벨. }; const SHORTCUTS: Shortcut[] = [ { scope: "전역", keys: ["/"], label: "종목 검색창 포커스" }, { scope: "전역", keys: ["Ctrl", "K"], label: "종목 검색창 포커스 (Mac: ⌘K)" }, { scope: "전역", keys: ["Esc"], label: "팝오버 / 오버레이 닫기" }, { scope: "전역", keys: ["?"], label: "이 도움말 열기 / 닫기" }, { scope: "종목 페이지", keys: ["F"], label: "차트 전체화면 토글" }, { scope: "검색 팝오버", keys: ["↑", "↓"], label: "결과 이동" }, { scope: "검색 팝오버", keys: ["Enter"], label: "선택한 종목으로 이동" }, ]; function 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; } export function ShortcutsHelp() { const [open, setOpen] = useState(false); const closeBtnRef = useRef(null); // `?` 키 = 토글. 입력 중에는 무시 (사용자가 실제 ? 입력하려는 의도). // ESC 는 모달 안에서 onKeyDown 으로도 처리하지만, 글로벌 핸들러로도 닫음 — 포커스가 // 모달 밖에 있어도 닫히도록. useEffect(() => { if (typeof window === "undefined") return; const onKey = (e: KeyboardEvent) => { if (e.key === "?" && !isEditableTarget(e.target)) { e.preventDefault(); setOpen((v) => !v); return; } if (e.key === "Escape" && open) { setOpen(false); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open]); // 열릴 때 닫기 버튼에 포커스 — 키보드 사용자가 바로 Tab 흐름을 잡을 수 있게. useEffect(() => { if (open) closeBtnRef.current?.focus(); }, [open]); // 화면 우하단 floating 버튼 — 모바일/마우스 사용자가 키보드 없이도 발견 가능. // 모달이 열려 있으면 같은 버튼이 닫기 역할. return ( <> {open && (
{ if (e.target === e.currentTarget) setOpen(false); }} >

키보드 단축키

입력 필드에 포커스가 있으면 단축키는 동작하지 않습니다.

)} ); } // scope 별로 묶어 한 줄에 [키] — 설명 형태로 표시. 같은 scope 가 연속되면 헤더 생략해서 // 시각적 노이즈 줄임. function ShortcutTable() { let prevScope: string | undefined; return ( ); }