// 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 (
WING-OPS는 데스크톱 전용 고정 뷰포트 애플리케이션입니다. 화면 전체를 채우는 고정 레이아웃(100vh)으로, flex 기반의 패널 구조를 사용합니다. KRDS 가이드라인을 기반으로 xlarge / xxlarge 구간에 최적화되어 있습니다.
화면 크기에 따라 반응형 레이아웃을 사용하여 환경에 최적화된 구조로 표시됩니다. WING-OPS 사용 구간(xl, 2xl)은 cyan으로 강조되어 있습니다.
컬럼, 마진, 거터로 구성된 그리드 시스템입니다. 데스크톱 전용으로 xl / 2xl 두 구간만 지원합니다.
WING-OPS 애플리케이션의 기본 레이아웃 구조와 KRDS Sub-page 영역 매핑입니다.
UI 요소 간의 간격과 여백을 정의하여 콘텐츠의 위계와 가독성을 조율합니다. Tailwind spacing 토큰과 직결되며, 막대 길이는 실제 px 비율입니다.
모든 여백과 간격을 4point 단위로 설정해 규칙성을 확보합니다. 컴팩트한 컴포넌트의 경우, 2의 배수 단위를 제한적으로 사용합니다.
어노테이션은 spacing 값의 4px 배수 multiplier를 표시합니다.
UI 요소의 레이어 스택 순서입니다. 높은 z-index가 위에 표시되며, 이 값은 디자인 시스템의 이상적 설계 값으로 실제 코드는 이 값에 맞춰 정정되어야 합니다.
App Shell CSS 클래스와 KRDS Grid 규칙 비교 — 코드 작성 시 참조용입니다.