// LayoutContent.tsx — WING-OPS Layout 카탈로그 (KT 디자인시스템 시각화 영감) import type { DesignTheme } from './designTheme'; // ---------- 데이터 타입 ---------- interface Breakpoint { name: string; krdsName: string; prefix: string; minWidth: number; maxWidth: number | null; columns: number; inUse: boolean; note: string; } interface DeviceSpec { device: string; prefix: string; width: string; columns: number; gutter: string; margin: string; supported: boolean; } interface SpacingToken { className: string; rem: string; px: number; usage: string; } interface ZLayer { name: string; zIndex: number; description: string; color: string; } interface ShellClass { className: string; role: string; styles: string; } interface GridRule { name: string; krds: string; wingOps: string; note: string; } interface SubPageMapping { krdsRegion: string; wingOpsComponent: string; implementation: string; color: string; } interface MultiplierAnnotation { position: 'top' | 'left' | 'right' | 'bottom'; multiplier: string; px: number; } // ---------- Breakpoints ---------- const BREAKPOINTS: Breakpoint[] = [ { name: '-', krdsName: 'xsmall', prefix: '-', minWidth: 0, maxWidth: 360, columns: 4, inUse: false, note: '미지원', }, { name: '-', krdsName: 'small', prefix: '-', minWidth: 360, maxWidth: 768, columns: 4, inUse: false, note: '미지원 (모바일)', }, { name: 'md', krdsName: 'medium', prefix: 'md:', minWidth: 768, maxWidth: 1024, columns: 8, inUse: false, note: '미지원 (태블릿)', }, { name: 'lg', krdsName: 'large', prefix: 'lg:', minWidth: 1024, maxWidth: 1280, columns: 12, inUse: false, note: '미지원', }, { name: 'xl', krdsName: 'xlarge', prefix: 'xl:', minWidth: 1280, maxWidth: 1440, columns: 12, inUse: true, note: 'WING-OPS 최소 지원', }, { name: '2xl', krdsName: 'xxlarge', prefix: '2xl:', minWidth: 1440, maxWidth: null, columns: 12, inUse: true, note: 'WING-OPS 주 사용 해상도', }, ]; // 타임라인 정규화 (0 ~ 2000px 범위) const TIMELINE_MAX = 2000; const toPercent = (px: number) => Math.min(100, (px / TIMELINE_MAX) * 100); const TIMELINE_MARKERS = [360, 768, 1024, 1280, 1440, 1920]; // ---------- Device Specs (xl/2xl만 활성) ---------- const DEVICE_SPECS: DeviceSpec[] = [ { device: 'Desktop xl', prefix: 'xl', width: '1280px – 1439px', columns: 12, gutter: '24px (gap-6)', margin: '24px (px-6)', supported: true, }, { device: 'Desktop 2xl', prefix: '2xl', width: '≥ 1440px', columns: 12, gutter: '24px (gap-6)', margin: '32px (px-8)', supported: true, }, ]; // ---------- Spacing Scale ---------- const SPACING_TOKENS: SpacingToken[] = [ { className: '0.5', rem: '0.125rem', px: 2, usage: '미세 간격' }, { className: '1', rem: '0.25rem', px: 4, usage: '최소 간격 (gap-1)' }, { className: '1.5', rem: '0.375rem', px: 6, usage: '컴팩트 간격 (gap-1.5)' }, { className: '2', rem: '0.5rem', px: 8, usage: '기본 간격 (gap-2, p-2)' }, { className: '2.5', rem: '0.625rem', px: 10, usage: '중간 간격' }, { className: '3', rem: '0.75rem', px: 12, usage: '표준 간격 (gap-3, p-3)' }, { className: '4', rem: '1rem', px: 16, usage: '넓은 간격 (p-4, gap-4)' }, { className: '5', rem: '1.25rem', px: 20, usage: '패널 패딩 (px-5, py-5)' }, { className: '6', rem: '1.5rem', px: 24, usage: '섹션 간격 (gap-6, p-6)' }, { className: '8', rem: '2rem', px: 32, usage: '큰 간격 (px-8, gap-8)' }, { className: '16', rem: '4rem', px: 64, usage: '최대 간격' }, ]; const SPACING_MAX_PX = Math.max(...SPACING_TOKENS.map((s) => s.px)); // ---------- Z-Index Layers (논리적 계층 — 디자인 시스템 진실 소스) ---------- const Z_LAYERS: ZLayer[] = [ { name: 'Tooltip', zIndex: 60, description: '툴팁, 드롭다운 메뉴', color: '#a855f7' }, { name: 'Popup', zIndex: 50, description: '팝업, 지도 오버레이', color: '#f97316' }, { name: 'Modal', zIndex: 40, description: '모달 다이얼로그, 백드롭', color: '#ef4444' }, { name: 'TopBar', zIndex: 30, description: '상단 네비게이션 바', color: '#3b82f6' }, { name: 'Sidebar', zIndex: 20, description: '사이드바, 패널', color: '#06b6d4' }, { name: 'Content', zIndex: 10, description: '메인 콘텐츠 영역', color: '#22c55e' }, { name: 'Base', zIndex: 0, description: '기본 레이어, 배경', color: '#9ba3b8' }, ]; // ---------- App Shell Classes ---------- const SHELL_CLASSES: ShellClass[] = [ { className: '.wing-panel', role: '탭 콘텐츠 패널', styles: 'flex flex-col h-full overflow-hidden', }, { className: '.wing-panel-scroll', role: '패널 내 스크롤 영역', styles: 'flex-1 overflow-y-auto', }, { className: '.wing-header-bar', role: '패널 헤더', styles: 'flex items-center justify-between shrink-0 px-5 border-b', }, { className: '.wing-sidebar', role: '사이드바', styles: 'flex flex-col border-r border-stroke' }, ]; // ---------- KRDS Grid Rules ---------- const GRID_RULES: GridRule[] = [ { name: 'Grid base', krds: '8pt grid', wingOps: 'Tailwind 4px base + 8pt 권장', note: 'gap-2=8px, p-4=16px, gap-6=24px 등 8pt 배수 우선', }, { name: 'Max container', krds: '1200px fixed', wingOps: 'max-w-[1440px]', note: '데스크톱 전용 풀스크린 앱 특성 반영', }, { name: 'Screen margin', krds: '24px (≥medium)', wingOps: 'px-5 ~ px-8 (20–32px)', note: 'KRDS medium+ 기준 충족', }, { name: 'Column gutter', krds: '16–24px (medium+)', wingOps: 'gap-2 ~ gap-6 (8–24px)', note: 'KRDS 권장 범위 내 사용', }, { name: 'Sub-page 구조', krds: 'Header → Left → Main → Right → Footer', wingOps: 'TopBar → Sidebar → Content', note: 'Footer 없음 — 풀스크린 앱', }, ]; // ---------- KRDS Sub-page Mappings ---------- const SUB_PAGE_MAPPINGS: SubPageMapping[] = [ { krdsRegion: 'Header', wingOpsComponent: 'TopBar', implementation: 'h-[52px] / shrink-0 / z-30', color: '#3b82f6', }, { krdsRegion: 'Sub Navigation', wingOpsComponent: 'SubMenuBar', implementation: 'shrink-0 / 조건부 렌더', color: '#06b6d4', }, { krdsRegion: 'Left Menu', wingOpsComponent: 'Sidebar', implementation: '가변 너비 / flex-col / border-r', color: '#a855f7', }, { krdsRegion: 'Main Contents', wingOpsComponent: 'Content', implementation: 'flex-1 / overflow-y-auto', color: '#22c55e', }, { krdsRegion: 'Right Menu', wingOpsComponent: 'Right Panel', implementation: '조건부 / 탭 콘텐츠 내부', color: '#f97316', }, { krdsRegion: 'Footer', wingOpsComponent: '없음', implementation: '풀스크린 앱 — 미사용', color: '#9ba3b8', }, ]; // ---------- Multiplier 어노테이션 (4pt Grid 데모용) ---------- const CARD_ANNOTATIONS: MultiplierAnnotation[] = [ { position: 'top', multiplier: 'x4', px: 16 }, { position: 'left', multiplier: 'x5', px: 20 }, { position: 'right', multiplier: 'x5', px: 20 }, { position: 'bottom', multiplier: 'x4', px: 16 }, ]; // ---------- Props ---------- interface LayoutContentProps { theme: DesignTheme; } // ---------- 컴포넌트 ---------- export const LayoutContent = ({ theme }: LayoutContentProps) => { const t = theme; const isDark = t.mode === 'dark'; // 시각화 색상 const accentTint = isDark ? 'rgba(76,215,246,0.18)' : 'rgba(6,182,212,0.14)'; const accentTintLight = isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)'; const dimBg = isDark ? 'rgba(140,144,159,0.12)' : 'rgba(148,163,184,0.10)'; const dimText = isDark ? '#8c909f' : '#94a3b8'; const cardSurface = isDark ? '#1b1f2c' : '#ffffff'; return (
{/* ── 섹션 1: 헤더 + 개요 ── */}

Layout

WING-OPS는 데스크톱 전용 고정 뷰포트 애플리케이션입니다. 화면 전체를 채우는 고정 레이아웃(100vh)으로, flex 기반의 패널 구조를 사용합니다. KRDS 가이드라인을 기반으로 xlarge / xxlarge 구간에 최적화되어 있습니다.

{[ { label: 'Viewport', value: '100vh fixed', desc: 'overflow: hidden' }, { label: 'Layout', value: 'flex 기반', desc: 'grid 보조' }, { label: 'Min Width', value: '1280px', desc: 'xl: 이상' }, ].map((item) => (
{item.label} {item.value} {item.desc}
))}
{/* ── 섹션 2: Breakpoint 타임라인 ── */}

Breakpoint

화면 크기에 따라 반응형 레이아웃을 사용하여 환경에 최적화된 구조로 표시됩니다. WING-OPS 사용 구간(xl, 2xl)은 cyan으로 강조되어 있습니다.

{/* 타임라인 다이어그램 */}
{/* 가로축 마커 (분기점 라벨) */}
{TIMELINE_MARKERS.map((px) => (
{px}
))}
{/* 분기점 점선 + tier 막대 컨테이너 */}
{/* 수직 점선 */} {TIMELINE_MARKERS.map((px) => (
))} {/* tier 막대들 */}
{BREAKPOINTS.map((bp) => { const startPct = toPercent(bp.minWidth); const endPct = bp.maxWidth ? toPercent(bp.maxWidth) : 100; const widthPct = endPct - startPct; const barColor = bp.inUse ? accentTint : dimBg; const labelColor = bp.inUse ? t.textAccent : dimText; return (
{/* 컬럼 그리드 미니 */}
{Array.from({ length: bp.columns }).map((_, i) => (
))}
{bp.krdsName} {bp.inUse && ( in use )} {bp.columns} cols
); })}
{/* 범례 */}
WING-OPS 사용 중
미지원 (1280px 미만)
{/* ── 섹션 3: Grid 시각화 카드 ── */}

Grid

컬럼, 마진, 거터로 구성된 그리드 시스템입니다. 데스크톱 전용으로 xl / 2xl 두 구간만 지원합니다.

{DEVICE_SPECS.map((spec) => (
{/* 카드 헤더 */}
Breakpoint {spec.prefix}
Width {spec.width}
{/* 시각화 영역 */}
{/* 컬럼 위 어노테이션 (margin / gutter) */}
{Array.from({ length: spec.columns + 1 }).map((_, idx) => { const isEdge = idx === 0 || idx === spec.columns; return ( {isEdge ? (spec.prefix === '2xl' ? '32' : '24') : '24'} ); })}
{/* 컬럼 막대 시각화 */}
{Array.from({ length: spec.columns }).map((_, i) => (
))}
{/* 가짜 TopBar mockup */}
WING-OPS
{['예측', 'HNS', '구조', '관리'].map((label) => ( {label} ))}
{/* 카드 푸터 — Grid 정보 */}
{[ { label: 'Columns', value: `${spec.columns}` }, { label: 'Gutter', value: spec.gutter }, { label: 'Margin', value: spec.margin }, ].map((info) => (
{info.label} {info.value}
))}
))}
{/* 미지원 안내 */}
1280px 미만 미지원 — Mobile / Tablet 구간(xs / s / md / lg)은 데스크톱 전용 운영 정책에 따라 지원하지 않습니다.
{/* ── 섹션 4: App Shell + KRDS Region 매핑 ── */}

App Shell

WING-OPS 애플리케이션의 기본 레이아웃 구조와 KRDS Sub-page 영역 매핑입니다.

{/* 다이어그램 */}
{/* TopBar */}
TopBar KRDS · Header
h-[52px] / shrink-0
{/* SubMenuBar */}
SubMenuBar KRDS · Sub Navigation
shrink-0 / 조건부
{/* Content Area */}
{/* Sidebar */}
Sidebar KRDS · Left Menu flex-col / border-r
{/* Main Content */}
Content KRDS · Main Contents flex-1 / overflow-y-auto
{/* Right Panel (조건부) */}
Right Panel KRDS · Right Menu 조건부 렌더
{/* Footer 안내 */}
KRDS · Footer 풀스크린 앱 — 미사용
{/* 매핑 표 (다이어그램 부속) */}
{(['KRDS Region', 'WING-OPS Component', 'Implementation'] as const).map((col) => (
{col}
))}
{SUB_PAGE_MAPPINGS.map((row, idx) => (
{row.krdsRegion}
{row.wingOpsComponent}
{row.implementation}
))}
{/* ── 섹션 5: Spacing Scale 막대 시각화 ── */}

Spacing

UI 요소 간의 간격과 여백을 정의하여 콘텐츠의 위계와 가독성을 조율합니다. Tailwind spacing 토큰과 직결되며, 막대 길이는 실제 px 비율입니다.

{SPACING_TOKENS.map((token) => { const widthPct = (token.px / SPACING_MAX_PX) * 100; return (
{/* 토큰 배지 */} {token.className} {/* px 값 */} {token.px}px {/* 막대 시각화 */}
{/* 사용처 */} {token.usage}
); })}
{/* ── 섹션 6: 4pt Grid 원칙 + Multiplier ── */}

4pt Grid

모든 여백과 간격을 4point 단위로 설정해 규칙성을 확보합니다. 컴팩트한 컴포넌트의 경우, 2의 배수 단위를 제한적으로 사용합니다.

{/* Part A — 원칙 카드 */}
Principles
{[ { label: '기본', text: '4px base — 모든 spacing은 4의 배수', example: '4 / 8 / 12 / 16 / 20 / 24 / 32', }, { label: '권장', text: '8pt 배수 우선 — gap-2, p-4, gap-6', example: '8 / 16 / 24 / 32 / 64', }, { label: '예외', text: '컴팩트 컴포넌트만 4px / 12px 사용', example: 'gap-1, gap-3, p-1.5', }, ].map((item) => (
{item.label} {item.text}
{item.example}
))}
{/* Part B — Multiplier 어노테이션 시각화 */}
Multiplier Annotation
{/* 어노테이션 — top */}
{CARD_ANNOTATIONS[0].multiplier}={CARD_ANNOTATIONS[0].px}
{/* 어노테이션 — left */}
{CARD_ANNOTATIONS[1].multiplier}={CARD_ANNOTATIONS[1].px}
{/* 어노테이션 — right */}
{CARD_ANNOTATIONS[2].multiplier}={CARD_ANNOTATIONS[2].px}
{/* 어노테이션 — bottom */}
{CARD_ANNOTATIONS[3].multiplier}={CARD_ANNOTATIONS[3].px}
{/* 실제 카드 mockup */}
카드 타이틀 본문 내용 예시
Action

어노테이션은 spacing 값의 4px 배수 multiplier를 표시합니다.

{/* ── 섹션 7: Z-Index Layers ── */}

Z-Index Layers

디자인 시스템 진실 소스

UI 요소의 레이어 스택 순서입니다. 높은 z-index가 위에 표시되며, 이 값은 디자인 시스템의 이상적 설계 값으로 실제 코드는 이 값에 맞춰 정정되어야 합니다.

{Z_LAYERS.map((layer, idx) => (
{layer.zIndex}
{layer.name} {layer.description}
))}
{/* ── 섹션 8: CSS 클래스 + KRDS Grid Rules 통합 ── */}

Reference

App Shell CSS 클래스와 KRDS Grid 규칙 비교 — 코드 작성 시 참조용입니다.

{/* CSS 클래스 */}
CSS Classes
{SHELL_CLASSES.map((cls, idx) => (
{cls.className} {cls.role}
{cls.styles}
))}
{/* KRDS Grid Rules */}
KRDS vs WING-OPS
{GRID_RULES.map((rule, idx) => (
{rule.name}
KRDS: {rule.krds}
WING: {rule.wingOps}
{rule.note}
))}
); }; export default LayoutContent;