"use client"; import { useEffect, useState, type ReactNode } from "react"; import { readOpen, writeOpen } from "../lib/collapsible"; // 사이드바 패널의 공용 collapsible 래퍼. // - 헤더 row 가 button 으로 동작 → 클릭 시 본문 토글, aria-expanded 로 보조기술 알림. // - 사용자 선택은 localStorage 에 panel-open: 로 영속. // - 본문은 `hidden` 으로 숨겨 unmount 시키지 않음 → InvestmentNote 입력 텍스트, // AlertsPanel 폼 partial 입력, RelatedStocks fetch 결과 등이 토글 사이클 동안 // 보존. (조건부 렌더로 children 을 끄면 re-mount → 데이터 재요청/입력 손실.) // // SSR 안전: // 초기 state 를 readOpen() 으로 lazy 초기화하면 서버는 window 가 없어 defaultOpen 을 // 반환하고, 클라이언트 hydration 첫 렌더에서 localStorage 값과 다르면 React 가 // hydration mismatch 를 경고함. useState(defaultOpen) → useEffect 에서 sync 패턴으로 // 해결 — 1프레임 미세 jank 는 감수 (localStorage 값이 default 와 다를 때만, 사이드바 // 패널이라 시선 끄는 위치도 아님). type Props = { title: ReactNode; subtitle?: ReactNode; storageKey: string; defaultOpen?: boolean; children: ReactNode; className?: string; }; export function Collapsible({ title, subtitle, storageKey, defaultOpen = true, children, className = "rounded-md border border-zinc-800 bg-zinc-900/40", }: Props) { const [open, setOpen] = useState(defaultOpen); useEffect(() => { setOpen(readOpen(storageKey, defaultOpen)); }, [storageKey, defaultOpen]); const toggle = () => { setOpen((cur) => { const next = !cur; writeOpen(storageKey, next); return next; }); }; return (
{children}
); }