11import type { ReactNode } from "react" ;
2-
32import "../styles/shell.css" ;
43
54export type RouteKey = "login" | "home" | "admin" ;
@@ -15,6 +14,9 @@ interface AppShellProps {
1514 children : ReactNode ;
1615}
1716
17+ // 추후 모듈 로더에서 주입될 목록 (현재는 mock)
18+ const INSTALLED_MODULES : { id : string ; label : string ; icon : string } [ ] = [ ] ;
19+
1820export function AppShell ( {
1921 installMode,
2022 route,
@@ -25,90 +27,99 @@ export function AppShell({
2527 onOpenSettings,
2628 children,
2729} : AppShellProps ) {
28- const currentTime = new Date ( ) . toLocaleTimeString ( "ko-KR" , {
29- hour : "2-digit" ,
30- minute : "2-digit" ,
31- } ) ;
32-
3330 return (
34- < main className = "frame" >
35- < header className = "shell-header" >
36- < div className = "shell-brand-wrap" >
37- < div className = "shell-logo" > FS</ div >
38- < div >
39- < p className = "shell-brand" > Fieldstack Control Plane</ p >
40- < p className = "shell-subbrand" > Personal modular workspace</ p >
41- </ div >
42- </ div >
43-
44- < div className = "shell-header-right" >
45- < span className = "shell-time" >
46- { currentTime }
47- </ span >
48- < span className = { `badge ${ installMode === "bypass" ? "badge-danger" : "badge-soft" } ` } >
49- { installMode === "bypass" ? "DEV BYPASS" : "NORMAL MODE" }
50- </ span >
31+ < div className = "shell" >
32+ { /* ── Sidebar ─────────────────────────────────────── */ }
33+ < aside className = "shell-sidebar" aria-label = "사이드바 네비게이션" >
34+ < div className = "shell-brand" >
35+ < div className = "shell-brand-logo" aria-hidden = "true" > FS</ div >
36+ < span className = "shell-brand-name" > Fieldstack</ span >
5137 </ div >
52- </ header >
5338
54- { notice ? < p className = "shell-notice" > { notice } </ p > : null }
39+ < nav className = "shell-nav" aria-label = "주 메뉴" >
40+ { /* Workspace */ }
41+ < p className = "shell-nav-label" aria-hidden = "true" > Workspace</ p >
42+ < ul className = "shell-nav-list" >
43+ < li >
44+ < button
45+ type = "button"
46+ className = "shell-nav-item"
47+ aria-current = { route === "home" ? "page" : undefined }
48+ onClick = { ( ) => onNavigate ( "home" ) }
49+ >
50+ < span className = "shell-nav-icon" aria-hidden = "true" > ⊞</ span >
51+ Home
52+ </ button >
53+ </ li >
54+ </ ul >
5555
56- < section className = "shell-layout" >
57- < aside className = "shell-side" aria-label = "Core navigation" >
58- < div className = "shell-side-block" >
59- < p className = "shell-side-title" > Workspace</ p >
60- < ul className = "shell-nav-list" >
61- < li >
62- < button
63- className = "shell-nav-button"
64- type = "button"
65- aria-current = { route === "home" ? "page" : undefined }
66- onClick = { ( ) => onNavigate ( "home" ) }
67- >
68- Home Hub
69- </ button >
70- </ li >
71- < li >
72- < button className = "shell-nav-button" type = "button" onClick = { onOpenSettings } >
73- General Settings
74- </ button >
75- </ li >
76- </ ul >
77- </ div >
78-
79- < div className = "shell-side-block" >
80- < p className = "shell-side-title" > Operations</ p >
81- < ul className = "shell-nav-list" >
82- { isAdmin ? (
83- < li >
56+ { /* Modules */ }
57+ < p className = "shell-nav-label" aria-hidden = "true" > Modules</ p >
58+ < ul className = "shell-nav-list" aria-label = "설치된 모듈" >
59+ { INSTALLED_MODULES . length > 0 ? (
60+ INSTALLED_MODULES . map ( ( mod ) => (
61+ < li key = { mod . id } >
8462 < button
85- className = "shell-nav-button"
8663 type = "button"
87- aria-current = { route === "admin" ? "page" : undefined }
88- onClick = { ( ) => onNavigate ( "admin" ) }
64+ className = "shell-nav-item"
65+ onClick = { ( ) => { window . location . hash = mod . id ; } }
8966 >
90- Admin Console
67+ < span className = "shell-nav-icon" aria-hidden = "true" > { mod . icon } </ span >
68+ { mod . label }
9169 </ button >
9270 </ li >
93- ) : null }
94- < li >
95- < button className = "shell-nav-button" type = "button" onClick = { onLogout } >
96- Sign Out
97- </ button >
98- </ li >
99- </ ul >
100- </ div >
71+ ) )
72+ ) : (
73+ < li className = "shell-nav-empty" > 모듈 없음</ li >
74+ ) }
75+ </ ul >
76+ </ nav >
10177
102- < div className = "shell-health" >
103- < p className = "shell-health-title" > System Snapshot</ p >
104- < p className = "shell-health-line" > Core: Online</ p >
105- < p className = "shell-health-line" > Modules: 0 active</ p >
106- < p className = "shell-health-line" > Auth: Session valid</ p >
107- </ div >
108- </ aside >
78+ { /* Footer */ }
79+ < div className = "shell-sidebar-footer" >
80+ { installMode === "bypass" && (
81+ < div className = "shell-bypass-pill" aria-label = "개발 bypass 모드 활성" >
82+ DEV BYPASS
83+ </ div >
84+ ) }
85+ < button type = "button" className = "shell-nav-item" onClick = { onOpenSettings } >
86+ < span className = "shell-nav-icon" aria-hidden = "true" > ⚙</ span >
87+ Settings
88+ </ button >
89+ { isAdmin && (
90+ < button
91+ type = "button"
92+ className = "shell-nav-item"
93+ aria-current = { route === "admin" ? "page" : undefined }
94+ onClick = { ( ) => onNavigate ( "admin" ) }
95+ >
96+ < span className = "shell-nav-icon" aria-hidden = "true" > ⚡</ span >
97+ Admin
98+ </ button >
99+ ) }
100+ < button
101+ type = "button"
102+ className = "shell-nav-item shell-nav-item-danger"
103+ onClick = { onLogout }
104+ >
105+ < span className = "shell-nav-icon" aria-hidden = "true" > ↪</ span >
106+ Sign Out
107+ </ button >
108+ </ div >
109+ </ aside >
109110
110- < section className = "shell-content" > { children } </ section >
111- </ section >
112- </ main >
111+ { /* ── Body ─────────────────────────────────────────── */ }
112+ < div className = "shell-body" >
113+ { notice && (
114+ < div className = "shell-notice" role = "status" aria-live = "polite" >
115+ < span className = "shell-notice-dot" aria-hidden = "true" />
116+ { notice }
117+ </ div >
118+ ) }
119+ < main className = "shell-content" >
120+ { children }
121+ </ main >
122+ </ div >
123+ </ div >
113124 ) ;
114125}
0 commit comments