/* ============================================================================
* 서명전에 — 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 && (
)}
{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 });