feat(design): ITSM+Manager Variant style applied
ITSM (style.css): - CSS tokens: indigo -> cyan(#00A0C8)+navy(#003366) palette - Background: deeper navy (#001020, #001530, #001e3c) - Sidebar active: cyan left bar + light bg (not full gradient) - Buttons: solid cyan, rounded - Logo icon: navy-to-cyan gradient Manager (React): - GNB: white header, navy branding, cyan badge - Sidebar: white bg, cyan active border + light bg, navy text - StatCard: cyan top bar, light blue icon box (screenshot9 pattern) - AppLayout: navy page title Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7839d9724c
commit
6fffc03074
@ -2,9 +2,8 @@
|
|||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
/* ─── KWCAG 2.1 웹접근성 포커스 표시 (GS인증 필수) ─ */
|
/* ─── KWCAG 2.1 웹접근성 포커스 표시 (GS인증 필수) ─ */
|
||||||
/* outline:none 대신 :focus-visible 로 키보드 포커스만 표시 */
|
|
||||||
:focus-visible {
|
:focus-visible {
|
||||||
outline: 2px solid #818cf8 !important;
|
outline: 2px solid #00A0C8 !important;
|
||||||
outline-offset: 2px !important;
|
outline-offset: 2px !important;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
@ -14,53 +13,55 @@
|
|||||||
/* 스킵 네비게이션 (키보드 접근성) */
|
/* 스킵 네비게이션 (키보드 접근성) */
|
||||||
.skip-nav {
|
.skip-nav {
|
||||||
position: absolute; top: -60px; left: 8px; z-index: 99999;
|
position: absolute; top: -60px; left: 8px; z-index: 99999;
|
||||||
background: #818cf8; color: #fff; padding: 8px 16px;
|
background: #00A0C8; color: #fff; padding: 8px 16px;
|
||||||
border-radius: 0 0 8px 8px; font-size: 14px; font-weight: 600;
|
border-radius: 0 0 8px 8px; font-size: 14px; font-weight: 600;
|
||||||
transition: top .2s; text-decoration: none;
|
transition: top .2s; text-decoration: none;
|
||||||
}
|
}
|
||||||
.skip-nav:focus { top: 0; }
|
.skip-nav:focus { top: 0; }
|
||||||
|
|
||||||
/* ─── Design Tokens (Nifty Dark) ────────────────── */
|
/* ─── Design Tokens (Variant Navy-Cyan Dark) ────── */
|
||||||
:root {
|
:root {
|
||||||
/* backgrounds */
|
/* backgrounds — Variant 딥네이비 계열 */
|
||||||
--main-bg: #0f172a;
|
--main-bg: #001020;
|
||||||
--sidebar-bg: #1e293b;
|
--sidebar-bg: #001530;
|
||||||
--card-bg: #1e293b;
|
--card-bg: #001e3c;
|
||||||
--card-inner: #0f172a;
|
--card-inner: #001020;
|
||||||
--input-bg: #0f172a;
|
--input-bg: #001530;
|
||||||
|
|
||||||
/* borders & shadows */
|
/* borders & shadows — 시안 계열 */
|
||||||
--border: rgba(255,255,255,.07);
|
--border: rgba(0,160,200,.15);
|
||||||
--shadow-sm: 0 2px 8px rgba(0,0,0,.3);
|
--border-strong: rgba(0,160,200,.30);
|
||||||
--shadow-md: 0 4px 20px rgba(0,0,0,.35);
|
--shadow-sm: 0 2px 8px rgba(0,10,30,.4);
|
||||||
--shadow-lg: 0 8px 40px rgba(0,0,0,.4);
|
--shadow-md: 0 4px 20px rgba(0,10,30,.45), 0 1px 4px rgba(0,160,200,.08);
|
||||||
|
--shadow-lg: 0 8px 40px rgba(0,10,30,.5), 0 2px 8px rgba(0,160,200,.12);
|
||||||
|
|
||||||
/* text — KWCAG 2.1 AA 색상 대비 4.5:1 이상 보장 */
|
/* text — KWCAG 2.1 AA 색상 대비 4.5:1 이상 보장 */
|
||||||
--text-bright: #f8fafc; /* 대비 15.8:1 (배경 #0f172a 대비) */
|
--text-bright: #e8f4fd; /* 대비 기준 유지 */
|
||||||
--text-primary: #cbd5e1; /* 대비 9.2:1 */
|
--text-primary: #b8d4ea;
|
||||||
--text-muted: #94a3b8; /* 대비 4.7:1 ✅ (기존 #64748b=3.1:1 → 개선) */
|
--text-muted: #7ba7c4;
|
||||||
|
|
||||||
/* brand colors */
|
/* brand colors — Variant 팔레트 */
|
||||||
--accent: #818cf8;
|
--accent: #00A0C8; /* 시안 메인 */
|
||||||
--accent-dark: #6366f1;
|
--accent-dark: #005A8C; /* 미드블루 */
|
||||||
|
--brand-navy: #003366; /* 딥네이비 */
|
||||||
--green: #34d399;
|
--green: #34d399;
|
||||||
--yellow: #fbbf24;
|
--yellow: #fbbf24;
|
||||||
--red: #f87171;
|
--red: #f87171;
|
||||||
--orange: #fb923c;
|
--orange: #fb923c;
|
||||||
--purple: #a78bfa;
|
--purple: #a78bfa;
|
||||||
--cyan: #22d3ee;
|
--cyan: #00A0C8;
|
||||||
|
|
||||||
/* sidebar active */
|
/* sidebar active — 시안 강조 */
|
||||||
--sidebar-active-bg: #6366f1;
|
--sidebar-active-bg: rgba(0,160,200,.18);
|
||||||
--sidebar-hover-bg: rgba(255,255,255,.06);
|
--sidebar-hover-bg: rgba(0,160,200,.08);
|
||||||
|
|
||||||
/* typography */
|
/* typography — Pretendard 우선 */
|
||||||
--font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
--font: "Pretendard", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
|
||||||
/* radii */
|
/* radii */
|
||||||
--radius: 8px;
|
--radius: 8px;
|
||||||
--radius-lg: 14px;
|
--radius-lg: 12px;
|
||||||
--radius-xl: 20px;
|
--radius-xl: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Base ──────────────────────────────────────── */
|
/* ─── Base ──────────────────────────────────────── */
|
||||||
@ -93,10 +94,10 @@ html, body {
|
|||||||
}
|
}
|
||||||
.logo-icon {
|
.logo-icon {
|
||||||
width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;
|
width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;
|
||||||
background: linear-gradient(135deg, var(--accent-dark), #8b5cf6);
|
background: linear-gradient(135deg, #005A8C, #00A0C8);
|
||||||
color: #fff; font-size: 20px; font-weight: 900;
|
color: #fff; font-size: 20px; font-weight: 900;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
box-shadow: 0 4px 14px rgba(99,102,241,.45);
|
box-shadow: 0 4px 14px rgba(0,160,200,.35);
|
||||||
}
|
}
|
||||||
.logo-title { font-size: 16px; font-weight: 800; color: var(--text-bright); letter-spacing: -.01em; }
|
.logo-title { font-size: 16px; font-weight: 800; color: var(--text-bright); letter-spacing: -.01em; }
|
||||||
.logo-sub { font-size: 10px; color: var(--text-muted); letter-spacing: .04em; text-transform: uppercase; }
|
.logo-sub { font-size: 10px; color: var(--text-muted); letter-spacing: .04em; text-transform: uppercase; }
|
||||||
@ -117,9 +118,10 @@ html, body {
|
|||||||
}
|
}
|
||||||
.nav-item:hover { background: var(--sidebar-hover-bg); color: var(--text-primary); }
|
.nav-item:hover { background: var(--sidebar-hover-bg); color: var(--text-primary); }
|
||||||
.nav-item.active {
|
.nav-item.active {
|
||||||
background: linear-gradient(90deg, var(--accent-dark), #8b5cf6);
|
background: var(--sidebar-active-bg);
|
||||||
color: #fff; font-weight: 600;
|
color: var(--accent); font-weight: 700;
|
||||||
box-shadow: 0 4px 14px rgba(99,102,241,.35);
|
border-left: 3px solid var(--accent);
|
||||||
|
padding-left: 9px;
|
||||||
}
|
}
|
||||||
.nav-icon { font-size: 16px; width: 20px; text-align: center; }
|
.nav-icon { font-size: 16px; width: 20px; text-align: center; }
|
||||||
|
|
||||||
@ -216,11 +218,12 @@ html, body {
|
|||||||
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
||||||
.btn:active { transform: none; }
|
.btn:active { transform: none; }
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: linear-gradient(135deg, var(--accent-dark), #8b5cf6);
|
background: var(--accent);
|
||||||
color: #fff; border-color: transparent;
|
color: #fff; border-color: transparent;
|
||||||
box-shadow: 0 2px 10px rgba(99,102,241,.35);
|
box-shadow: 0 2px 10px rgba(0,160,200,.3);
|
||||||
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
.btn-primary:hover { box-shadow: 0 4px 18px rgba(99,102,241,.5); }
|
.btn-primary:hover { background: var(--accent-dark); box-shadow: 0 4px 18px rgba(0,160,200,.45); }
|
||||||
.btn-secondary { background: rgba(255,255,255,.06); border-color: var(--border); color: var(--text-primary); }
|
.btn-secondary { background: rgba(255,255,255,.06); border-color: var(--border); color: var(--text-primary); }
|
||||||
.btn-secondary:hover { background: rgba(255,255,255,.1); }
|
.btn-secondary:hover { background: rgba(255,255,255,.1); }
|
||||||
.btn-approve { background: rgba(52,211,153,.15); color: var(--green); border-color: rgba(52,211,153,.3); }
|
.btn-approve { background: rgba(52,211,153,.15); color: var(--green); border-color: rgba(52,211,153,.3); }
|
||||||
@ -290,7 +293,7 @@ html, body {
|
|||||||
.stat-card.purple .stat-value { color: var(--purple); }
|
.stat-card.purple .stat-value { color: var(--purple); }
|
||||||
.stat-card.orange .stat-value { color: var(--orange); }
|
.stat-card.orange .stat-value { color: var(--orange); }
|
||||||
.stat-card.cyan .stat-value { color: var(--cyan); }
|
.stat-card.cyan .stat-value { color: var(--cyan); }
|
||||||
.stat-card.accent .stat-icon { background: rgba(129,140,248,.15); color: var(--accent); }
|
.stat-card.accent .stat-icon { background: rgba(0,160,200,.15); color: var(--accent); }
|
||||||
.stat-card.green .stat-icon { background: rgba(52,211,153,.15); color: var(--green); }
|
.stat-card.green .stat-icon { background: rgba(52,211,153,.15); color: var(--green); }
|
||||||
.stat-card.yellow .stat-icon { background: rgba(251,191,36,.15); color: var(--yellow); }
|
.stat-card.yellow .stat-icon { background: rgba(251,191,36,.15); color: var(--yellow); }
|
||||||
.stat-card.red .stat-icon { background: rgba(248,113,113,.15); color: var(--red); }
|
.stat-card.red .stat-icon { background: rgba(248,113,113,.15); color: var(--red); }
|
||||||
|
|||||||
@ -8,32 +8,58 @@ interface Props {
|
|||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StatCard({ title, value, sub, icon, color = '#e8ecff', trend, onClick }: Props) {
|
/* Variant 스타일 StatCard — screenshot9 카드 패턴 적용 */
|
||||||
|
export function StatCard({ title, value, sub, icon, color = '#E8F0F8', trend, onClick }: Props) {
|
||||||
return (
|
return (
|
||||||
<div onClick={onClick} style={{
|
<div
|
||||||
background: '#fff', border: '1px solid #e2e8f0',
|
onClick={onClick}
|
||||||
borderRadius: 10, padding: '20px 22px',
|
style={{
|
||||||
|
background: '#ffffff',
|
||||||
|
border: '1px solid #E2E8F0',
|
||||||
|
borderTop: '3px solid #00A0C8', /* 상단 시안 바 */
|
||||||
|
borderRadius: 12,
|
||||||
|
padding: '20px 22px',
|
||||||
cursor: onClick ? 'pointer' : 'default',
|
cursor: onClick ? 'pointer' : 'default',
|
||||||
transition: 'box-shadow .15s',
|
transition: 'all .2s',
|
||||||
|
boxShadow: '0 2px 8px rgba(0,51,102,.06)',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
const el = e.currentTarget as HTMLDivElement
|
||||||
|
el.style.boxShadow = '0 8px 24px rgba(0,51,102,.12)'
|
||||||
|
el.style.transform = 'translateY(-2px)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
const el = e.currentTarget as HTMLDivElement
|
||||||
|
el.style.boxShadow = '0 2px 8px rgba(0,51,102,.06)'
|
||||||
|
el.style.transform = 'translateY(0)'
|
||||||
}}
|
}}
|
||||||
onMouseEnter={e => { if (onClick) (e.currentTarget as HTMLDivElement).style.boxShadow = '0 4px 16px rgba(0,0,0,.1)' }}
|
|
||||||
onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.boxShadow = 'none' }}
|
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 14 }}>
|
||||||
|
{/* 아이콘 박스 — screenshot9 연파랑 박스 스타일 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 46, height: 46, borderRadius: 10, background: color,
|
width: 48, height: 48, borderRadius: 10,
|
||||||
|
background: '#E8F0F8',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
fontSize: 22, flexShrink: 0,
|
fontSize: 22, flexShrink: 0,
|
||||||
}}>{icon}</div>
|
}}>{icon}</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<div style={{ fontSize: 26, fontWeight: 700, lineHeight: 1.2 }}>{value}</div>
|
<div style={{ fontSize: 11, fontWeight: 700, color: '#00A0C8',
|
||||||
<div style={{ fontSize: 12, color: '#64748b', marginTop: 2 }}>{title}</div>
|
letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 4 }}>
|
||||||
{sub && <div style={{ fontSize: 11, color: '#94a3b8' }}>{sub}</div>}
|
{title}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 28, fontWeight: 800, lineHeight: 1.1,
|
||||||
|
color: '#003366', letterSpacing: '-.02em' }}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
{sub && <div style={{ fontSize: 11, color: '#94A3B8', marginTop: 3 }}>{sub}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{trend && (
|
{trend && (
|
||||||
<div style={{ marginTop: 10, fontSize: 11,
|
<div style={{
|
||||||
color: trend.up ? '#16a34a' : '#dc2626' }}>
|
marginTop: 12, fontSize: 12, fontWeight: 600,
|
||||||
|
color: trend.up ? '#16a34a' : '#dc2626',
|
||||||
|
display: 'flex', alignItems: 'center', gap: 4,
|
||||||
|
}}>
|
||||||
{trend.up ? '▲' : '▼'} {Math.abs(trend.val)}% 전주 대비
|
{trend.up ? '▲' : '▼'} {Math.abs(trend.val)}% 전주 대비
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export function AppLayout() {
|
|||||||
<main style={{ flex: 1, overflow: 'auto', display: 'flex', flexDirection: 'column' }}>
|
<main style={{ flex: 1, overflow: 'auto', display: 'flex', flexDirection: 'column' }}>
|
||||||
{/* 페이지 타이틀 바 */}
|
{/* 페이지 타이틀 바 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '14px 28px', background: '#fff',
|
padding: '12px 28px', background: '#ffffff',
|
||||||
borderBottom: '1px solid #e2e8f0',
|
borderBottom: '1px solid #e2e8f0',
|
||||||
display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
|
display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
|
||||||
}}>
|
}}>
|
||||||
|
|||||||
@ -12,34 +12,53 @@ export function GNB() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<header style={{
|
<header style={{
|
||||||
height: 'var(--gnb-h)', background: 'var(--gnb-bg)',
|
height: 'var(--gnb-h)',
|
||||||
|
background: '#ffffff',
|
||||||
|
borderBottom: '1px solid #E2E8F0',
|
||||||
|
boxShadow: '0 1px 8px rgba(0,51,102,.06)',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||||
padding: '0 20px', flexShrink: 0, position: 'sticky', top: 0, zIndex: 100,
|
padding: '0 24px', flexShrink: 0, position: 'sticky', top: 0, zIndex: 100,
|
||||||
}}>
|
}}>
|
||||||
{/* 로고 */}
|
{/* 로고 — Variant 스타일 */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
<span style={{ fontSize: 20 }}>🛡️</span>
|
<div style={{
|
||||||
<span style={{ color: '#fff', fontWeight: 700, fontSize: 15 }}>GUARDiA Manager</span>
|
width: 32, height: 32, borderRadius: 8,
|
||||||
<span style={{ color: '#7c85a8', fontSize: 11, marginLeft: 4 }}>v2.0</span>
|
background: 'linear-gradient(135deg, #003366, #00A0C8)',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
color: '#fff', fontWeight: 900, fontSize: 16, flexShrink: 0,
|
||||||
|
}}>G</div>
|
||||||
|
<span style={{ color: '#003366', fontWeight: 800, fontSize: 15, letterSpacing: '-.01em' }}>
|
||||||
|
GUARDiA Manager
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 10, fontWeight: 700, color: '#00A0C8',
|
||||||
|
background: 'rgba(0,160,200,.1)', borderRadius: 20,
|
||||||
|
padding: '2px 8px', letterSpacing: '.04em',
|
||||||
|
}}>v2.0</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 우측 */}
|
{/* 우측 */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
<a href="http://zioinfo.co.kr:8001" target="_blank" rel="noreferrer"
|
<a href="http://zioinfo.co.kr:8443" target="_blank" rel="noreferrer"
|
||||||
style={{ color: '#7c85a8', fontSize: 11, display: 'flex', alignItems: 'center', gap: 4 }}>
|
style={{
|
||||||
|
color: '#64748B', fontSize: 12, display: 'flex', alignItems: 'center', gap: 4,
|
||||||
|
textDecoration: 'none', padding: '5px 10px', borderRadius: 6,
|
||||||
|
border: '1px solid #E2E8F0',
|
||||||
|
}}>
|
||||||
🌐 ITSM 바로가기
|
🌐 ITSM 바로가기
|
||||||
</a>
|
</a>
|
||||||
{user && (
|
{user && (
|
||||||
<>
|
<>
|
||||||
<span style={{
|
<span style={{
|
||||||
background: '#4f6ef7', color: '#fff',
|
background: 'rgba(0,90,140,.08)', color: '#005A8C',
|
||||||
padding: '3px 12px', borderRadius: 20, fontSize: 11, fontWeight: 600,
|
padding: '4px 14px', borderRadius: 20, fontSize: 12, fontWeight: 600,
|
||||||
|
border: '1px solid rgba(0,90,140,.18)',
|
||||||
}}>
|
}}>
|
||||||
👤 {user.display_name ?? user.username}
|
👤 {user.display_name ?? user.username}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={logout} style={{
|
<button onClick={logout} style={{
|
||||||
background: 'transparent', border: '1px solid rgba(255,255,255,.2)',
|
background: 'transparent', border: '1px solid #E2E8F0',
|
||||||
color: '#b0b7cc', padding: '4px 12px', borderRadius: 6, fontSize: 12, cursor: 'pointer',
|
color: '#64748B', padding: '5px 12px', borderRadius: 6, fontSize: 12, cursor: 'pointer',
|
||||||
}}>로그아웃</button>
|
}}>로그아웃</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -37,42 +37,66 @@ const NAV: NavItem[] = [
|
|||||||
]},
|
]},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/* Variant 스타일 색상 상수 */
|
||||||
|
const V = {
|
||||||
|
navy: '#003366',
|
||||||
|
blue: '#005A8C',
|
||||||
|
cyan: '#00A0C8',
|
||||||
|
cyanLt: 'rgba(0,160,200,.10)',
|
||||||
|
cyanAct: 'rgba(0,160,200,.15)',
|
||||||
|
muted: '#64748B',
|
||||||
|
text: '#334155',
|
||||||
|
border: '#E2E8F0',
|
||||||
|
bg: '#F8FAFC',
|
||||||
|
}
|
||||||
|
|
||||||
function NavGroup({ item }: { item: NavItem }) {
|
function NavGroup({ item }: { item: NavItem }) {
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
|
|
||||||
if (!item.children) {
|
if (!item.children) {
|
||||||
return (
|
return (
|
||||||
<NavLink to={item.path!} style={({ isActive }) => ({
|
<NavLink to={item.path!} style={({ isActive }) => ({
|
||||||
display: 'flex', alignItems: 'center', gap: 8,
|
display: 'flex', alignItems: 'center', gap: 8,
|
||||||
padding: '8px 16px', fontSize: 13, color: isActive ? '#1a3a6b' : '#475569',
|
padding: '8px 16px', fontSize: 13,
|
||||||
background: isActive ? '#dde4f5' : 'transparent',
|
color: isActive ? V.navy : V.muted,
|
||||||
borderLeft: isActive ? '3px solid #4f6ef7' : '3px solid transparent',
|
background: isActive ? V.cyanAct : 'transparent',
|
||||||
|
borderLeft: isActive ? `3px solid ${V.cyan}` : '3px solid transparent',
|
||||||
|
fontWeight: isActive ? 700 : 400,
|
||||||
transition: 'all .15s',
|
transition: 'all .15s',
|
||||||
|
textDecoration: 'none',
|
||||||
})}>
|
})}>
|
||||||
<span style={{ width: 18, textAlign: 'center' }}>{item.icon}</span>
|
<span style={{ width: 18, textAlign: 'center' }}>{item.icon}</span>
|
||||||
{item.label}
|
{item.label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button onClick={() => setOpen(o => !o)} style={{
|
<button onClick={() => setOpen(o => !o)} style={{
|
||||||
width: '100%', display: 'flex', alignItems: 'center', gap: 8,
|
width: '100%', display: 'flex', alignItems: 'center', gap: 8,
|
||||||
padding: '8px 16px', fontSize: 13, color: '#1e293b', background: 'none',
|
padding: '8px 16px', fontSize: 13, color: V.navy, background: 'none',
|
||||||
border: 'none', cursor: 'pointer', fontWeight: 600,
|
border: 'none', cursor: 'pointer', fontWeight: 700,
|
||||||
|
letterSpacing: '-.01em',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ width: 18, textAlign: 'center' }}>{item.icon}</span>
|
<span style={{ width: 18, textAlign: 'center' }}>{item.icon}</span>
|
||||||
{item.label}
|
{item.label}
|
||||||
<span style={{ marginLeft: 'auto', fontSize: 10, transition: 'transform .2s',
|
<span style={{
|
||||||
transform: open ? 'rotate(180deg)' : 'rotate(0)' }}>▾</span>
|
marginLeft: 'auto', fontSize: 9, color: V.cyan,
|
||||||
|
transition: 'transform .2s',
|
||||||
|
transform: open ? 'rotate(180deg)' : 'rotate(0)',
|
||||||
|
}}>▾</span>
|
||||||
</button>
|
</button>
|
||||||
{open && item.children.map(c => (
|
{open && item.children.map(c => (
|
||||||
<NavLink key={c.path} to={c.path!} style={({ isActive }) => ({
|
<NavLink key={c.path} to={c.path!} style={({ isActive }) => ({
|
||||||
display: 'flex', alignItems: 'center',
|
display: 'flex', alignItems: 'center',
|
||||||
padding: '7px 16px 7px 42px', fontSize: 12.5,
|
padding: '7px 16px 7px 42px', fontSize: 12.5,
|
||||||
color: isActive ? '#1a3a6b' : '#64748b',
|
color: isActive ? V.navy : V.muted,
|
||||||
background: isActive ? '#dde4f5' : 'transparent',
|
background: isActive ? V.cyanAct : 'transparent',
|
||||||
borderLeft: isActive ? '3px solid #4f6ef7' : '3px solid transparent',
|
borderLeft: isActive ? `3px solid ${V.cyan}` : '3px solid transparent',
|
||||||
|
fontWeight: isActive ? 600 : 400,
|
||||||
transition: 'all .15s',
|
transition: 'all .15s',
|
||||||
|
textDecoration: 'none',
|
||||||
})}>
|
})}>
|
||||||
{c.label}
|
{c.label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
@ -84,11 +108,24 @@ function NavGroup({ item }: { item: NavItem }) {
|
|||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
return (
|
return (
|
||||||
<aside style={{
|
<aside style={{
|
||||||
width: 'var(--sidebar-w)', background: 'var(--sidebar-bg)',
|
width: 'var(--sidebar-w)',
|
||||||
borderRight: '1px solid var(--border)', height: '100%',
|
background: '#ffffff',
|
||||||
|
borderRight: `1px solid ${V.border}`,
|
||||||
|
height: '100%',
|
||||||
display: 'flex', flexDirection: 'column', overflowY: 'auto',
|
display: 'flex', flexDirection: 'column', overflowY: 'auto',
|
||||||
|
boxShadow: '2px 0 12px rgba(0,51,102,.06)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ padding: '14px 0', flex: 1 }}>
|
{/* 사이드바 헤더 */}
|
||||||
|
<div style={{
|
||||||
|
padding: '16px 16px 12px',
|
||||||
|
borderBottom: `1px solid ${V.border}`,
|
||||||
|
}}>
|
||||||
|
<div style={{ fontSize: 10, fontWeight: 700, color: V.cyan,
|
||||||
|
letterSpacing: '.1em', textTransform: 'uppercase', marginBottom: 2 }}>
|
||||||
|
Navigation
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '8px 0', flex: 1 }}>
|
||||||
{NAV.map((item, i) => <NavGroup key={i} item={item} />)}
|
{NAV.map((item, i) => <NavGroup key={i} item={item} />)}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user