Skip to content

Commit d66af8f

Browse files
SOIVclaude
andcommitted
feat(web): 1차 UI/UX 전면 개편 — 다크 사이드바 레이아웃으로 전환
- global.css: 다크 모드 디자인 토큰 시스템 구축 (--bg, --accent 등) - shell.css / AppShell.tsx: 고정 220px 좌측 사이드바 레이아웃으로 재설계 - home/admin/settings/login.css: 하드코딩 라이트 컬러 → 다크 토큰으로 전환 - main.tsx: AppShell A/B/C/D 변형 및 shell 전환 드롭다운 UI 제거 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2345734 commit d66af8f

8 files changed

Lines changed: 548 additions & 421 deletions

File tree

Lines changed: 86 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { ReactNode } from "react";
2-
32
import "../styles/shell.css";
43

54
export 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+
1820
export 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
}

apps/web/src/main.tsx

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -121,34 +121,35 @@ function App({ installMode }: { installMode: InstallMode }) {
121121
);
122122
}
123123

124-
// Authenticated shell
125124
return (
126-
<AppShell
127-
installMode={installMode}
128-
route={effectiveRoute}
129-
isAdmin={isAdmin}
130-
notice={notice}
131-
onNavigate={navigate}
132-
onLogout={onLogout}
133-
onOpenSettings={() => setIsSettingsOpen(true)}
134-
>
135-
{effectiveRoute === "home" && <HomeView onOpenSettings={() => setIsSettingsOpen(true)} />}
136-
{effectiveRoute === "admin" && <AdminView isAdmin={isAdmin} />}
137-
{isSettingsOpen && (
138-
<SettingsView
139-
isAdmin={isAdmin}
140-
onClose={() => setIsSettingsOpen(false)}
141-
onToggleAdmin={() => {
142-
setIsAdmin((prev) => {
143-
const next = !prev;
144-
setNotice(next ? "Admin authority enabled (mock)." : "Admin authority disabled.");
145-
return next;
146-
});
147-
}}
148-
onSaved={() => setNotice("Settings saved (mock).")}
149-
/>
150-
)}
151-
</AppShell>
125+
<>
126+
<AppShell
127+
installMode={installMode}
128+
route={effectiveRoute}
129+
isAdmin={isAdmin}
130+
notice={notice}
131+
onNavigate={navigate}
132+
onLogout={onLogout}
133+
onOpenSettings={() => setIsSettingsOpen(true)}
134+
>
135+
{effectiveRoute === "home" && <HomeView onOpenSettings={() => setIsSettingsOpen(true)} />}
136+
{effectiveRoute === "admin" && <AdminView isAdmin={isAdmin} />}
137+
{isSettingsOpen && (
138+
<SettingsView
139+
isAdmin={isAdmin}
140+
onClose={() => setIsSettingsOpen(false)}
141+
onToggleAdmin={() => {
142+
setIsAdmin((prev) => {
143+
const next = !prev;
144+
setNotice(next ? "Admin authority enabled (mock)." : "Admin authority disabled.");
145+
return next;
146+
});
147+
}}
148+
onSaved={() => setNotice("Settings saved (mock).")}
149+
/>
150+
)}
151+
</AppShell>
152+
</>
152153
);
153154
}
154155

0 commit comments

Comments
 (0)