/* ============================================================================ * 서명전에 — Web App Shell * * 모바일: iOS 목업과 동일한 레이아웃을 풀뷰포트로 전개. * 하단 탭바 유지, 상단 StatusBar 제거. * 데스크톱(≥900px): 좌측 사이드바 + 우측 메인 컬럼(최대 520px 센터 정렬)으로 * 모바일 뷰를 그대로 중앙 렌더링. "모바일 웹 우선"이라 * 레이아웃은 바꾸지 않고 주변 크롬만 바꾼다. * ========================================================================== */ const { useState, useEffect, useRef, useMemo, useCallback } = React; /* -------------------- Tab Bar (모바일 하단) -------------------- */ function TabBar({ tab, onTab, onFab }) { const items = [ { id: "home", label: "홈", icon: "house" }, { id: "history",label: "기록", icon: "clock" }, { id: "_fab", label: null, icon: null }, { id: "help", label: "도움말", icon: "help" }, { id: "account",label: "계정", icon: "person" }, ]; return (
{items.map((it) => it.id === "_fab" ? (
) : ( ), )}
); } /* -------------------- Sidebar (데스크톱) -------------------- */ function Sidebar({ tab, onTab, onFab, onOpenBilling, credits, unlimited = false, collapsed, onToggleCollapse }) { const items = [ { id: "home", label: "홈", icon: "house" }, { id: "history", label: "기록", icon: "clock" }, { id: "help", label: "도움말", icon: "help" }, { id: "account", label: "계정", icon: "person" }, ]; return ( ); } /* -------------------- Sheet (레거시 — 신규 코드는 BottomSheet 사용) -------------------- * @deprecated Task #125 이후 신규 하단 시트는 `BottomSheet` (TDS 공식 컴포넌트) * 를 사용해요. 이 함수는 `app.jsx` 같은 레거시 엔트리가 남아 있을 때 * 깨지지 않도록 호환용으로 유지 중이에요. * - title → `…}>` * - detent="large|medium|small" → `maxHeight` / `expandedMaxHeight` (px) * ------------------------------------------------------------------------------ */ function Sheet({ open, onClose, children, title, detent = "medium" }) { useEffect(() => { if (!open) return; const onKey = (e) => e.key === "Escape" && onClose?.(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); const maxHeight = detent === "large" ? "88dvh" : detent === "small" ? "42dvh" : "66dvh"; return (
{title && (
{title}
)}
{children}
); } /* -------------------- Action Sheet (모바일) / Menu (데스크톱) -------------------- */ function ActionSheet({ open, onClose, actions = [], title }) { useEffect(() => { if (!open) return; const onKey = (e) => e.key === "Escape" && onClose?.(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); return (
{title && ( typeof title === "string" ? (
{title}
) : ( /* ReactNode title — 호출부에서 자기 스타일로 렌더 (경고 배너 등 큰 요소를 넣을 수 있게). bottom border 만 기본 구분선으로. */
{title}
) )} {actions.map((a, i) => ( ))}
); } /* -------------------- Web Shell -------------------- */ /* 모바일에서는 풀뷰포트, 데스크톱에서는 사이드바 + 센터 컬럼. */ function WebShell({ children, tab, onTab, onFab, onOpenBilling, credits, unlimited = false, hideNav = false, sidebarCollapsed, onToggleSidebar }) { return (
{!hideNav && ( )}
{children}
{!hideNav && (
)}
); } /* Export */ Object.assign(window, { WebShell, Sidebar, TabBar, Sheet, ActionSheet });