Three lower-sidebar panels are now wrapped in a shared Collapsible so mobile readers can fold away sections they're not actively using. New shared pieces: - lib/collapsible.ts — readOpen/writeOpen helpers backed by localStorage under 'panel-open:<name>'. Per-panel-name (not per-code) because the preference is a panel-level habit; users don't want different fold state for every symbol. - components/Collapsible.tsx — button-as-header + chevron + body. aria-expanded for AT. Body uses 'hidden' instead of conditional unmount so textarea text, partial alert form input, and RelatedStocks fetch results all survive collapse cycles. SSR-safe: useState(defaultOpen) then useEffect-sync to avoid React hydration mismatch when localStorage diverges from default. Panel refactors: - RelatedStocks: outer rounded div + header replaced with Collapsible; loading/error/empty/data states all rendered inside body (header stays visible so user can still toggle/recognize the section even while loading). - InvestmentNote: same swap. saved.updatedAt drives the subtitle. - AlertsPanel: same swap. current price drives the subtitle. Defaults are open=true everywhere; the surface area is the toggle itself, not a forced collapse. User's first interaction sets the sticky preference.
30 lines
1.1 KiB
TypeScript
30 lines
1.1 KiB
TypeScript
// 사이드바 collapsible 패널의 펼침/접힘 상태 영속 저장.
|
|
// 키 형식: `panel-open:<name>` — 종목별이 아닌 panel-name 단위. 사용자는 "관련 종목은 늘
|
|
// 접어 둔다" 같은 panel 단위 습관이 있지 종목별로 다르게 설정하지 않음. 모든 종목 페이지에서
|
|
// 같은 상태로 보이는 것이 직관적.
|
|
//
|
|
// 값 형식: '1' = open, '0' = closed. 다른 값은 default 로 폴백.
|
|
// 쿼타/잠금 환경에선 silently fail — 그 세션 동안 default 상태 유지.
|
|
|
|
const STORAGE_PREFIX = "panel-open:";
|
|
|
|
export function readOpen(key: string, defaultOpen: boolean): boolean {
|
|
if (typeof window === "undefined") return defaultOpen;
|
|
try {
|
|
const v = window.localStorage.getItem(STORAGE_PREFIX + key);
|
|
if (v === null) return defaultOpen;
|
|
return v === "1";
|
|
} catch {
|
|
return defaultOpen;
|
|
}
|
|
}
|
|
|
|
export function writeOpen(key: string, open: boolean): void {
|
|
if (typeof window === "undefined") return;
|
|
try {
|
|
window.localStorage.setItem(STORAGE_PREFIX + key, open ? "1" : "0");
|
|
} catch {
|
|
// quota / private mode — 다음 페이지 진입엔 default 로 되돌아감.
|
|
}
|
|
}
|