1362 lines
49 KiB
TypeScript
1362 lines
49 KiB
TypeScript
// 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 (
|
||
<div className="pt-24 px-8 pb-16 flex flex-col gap-16 items-start justify-start max-w-[1440px]">
|
||
{/* ── 섹션 1: 헤더 + 개요 ── */}
|
||
<div
|
||
className="w-full border-b border-solid pb-8 flex flex-col gap-4"
|
||
style={{ borderColor: isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0' }}
|
||
>
|
||
<div className="flex flex-col gap-2">
|
||
<h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}>
|
||
Layout
|
||
</h1>
|
||
<p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
|
||
WING-OPS는 데스크톱 전용 고정 뷰포트 애플리케이션입니다. 화면 전체를 채우는 고정
|
||
레이아웃(100vh)으로, flex 기반의 패널 구조를 사용합니다. KRDS 가이드라인을 기반으로
|
||
xlarge / xxlarge 구간에 최적화되어 있습니다.
|
||
</p>
|
||
</div>
|
||
<div className="grid grid-cols-3 gap-3 w-full max-w-3xl">
|
||
{[
|
||
{ label: 'Viewport', value: '100vh fixed', desc: 'overflow: hidden' },
|
||
{ label: 'Layout', value: 'flex 기반', desc: 'grid 보조' },
|
||
{ label: 'Min Width', value: '1280px', desc: 'xl: 이상' },
|
||
].map((item) => (
|
||
<div
|
||
key={item.label}
|
||
className="rounded-lg border border-solid px-4 py-3 flex flex-col gap-1"
|
||
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
|
||
>
|
||
<span
|
||
className="font-mono text-caption uppercase"
|
||
style={{ letterSpacing: '1px', color: t.textMuted }}
|
||
>
|
||
{item.label}
|
||
</span>
|
||
<span className="font-mono text-base font-bold" style={{ color: t.textAccent }}>
|
||
{item.value}
|
||
</span>
|
||
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
|
||
{item.desc}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 2: Breakpoint 타임라인 ── */}
|
||
<div className="w-full flex flex-col gap-6">
|
||
<div className="flex flex-col gap-2">
|
||
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
|
||
Breakpoint
|
||
</h2>
|
||
<p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
|
||
화면 크기에 따라 반응형 레이아웃을 사용하여 환경에 최적화된 구조로 표시됩니다. WING-OPS
|
||
사용 구간(xl, 2xl)은 cyan으로 강조되어 있습니다.
|
||
</p>
|
||
</div>
|
||
|
||
{/* 타임라인 다이어그램 */}
|
||
<div
|
||
className="rounded-lg border border-solid p-8"
|
||
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder, boxShadow: t.cardShadow }}
|
||
>
|
||
{/* 가로축 마커 (분기점 라벨) */}
|
||
<div className="relative h-7 mb-2">
|
||
{TIMELINE_MARKERS.map((px) => (
|
||
<div
|
||
key={px}
|
||
className="absolute -translate-x-1/2 flex flex-col items-center"
|
||
style={{ left: `${toPercent(px)}%` }}
|
||
>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(66,71,84,0.50)' : '#1e293b',
|
||
color: '#ffffff',
|
||
}}
|
||
>
|
||
{px}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 분기점 점선 + tier 막대 컨테이너 */}
|
||
<div className="relative" style={{ minHeight: '270px' }}>
|
||
{/* 수직 점선 */}
|
||
{TIMELINE_MARKERS.map((px) => (
|
||
<div
|
||
key={`line-${px}`}
|
||
className="absolute top-0 bottom-0 border-l border-dashed"
|
||
style={{
|
||
left: `${toPercent(px)}%`,
|
||
borderColor: isDark ? 'rgba(140,144,159,0.25)' : 'rgba(148,163,184,0.40)',
|
||
}}
|
||
/>
|
||
))}
|
||
|
||
{/* tier 막대들 */}
|
||
<div className="flex flex-col gap-2.5 pt-2">
|
||
{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 (
|
||
<div
|
||
key={bp.krdsName}
|
||
className="relative rounded border border-solid flex items-center px-3"
|
||
style={{
|
||
marginLeft: `${startPct}%`,
|
||
width: `${widthPct}%`,
|
||
minHeight: '34px',
|
||
backgroundColor: barColor,
|
||
borderColor: bp.inUse
|
||
? isDark
|
||
? 'rgba(76,215,246,0.45)'
|
||
: 'rgba(6,182,212,0.40)'
|
||
: isDark
|
||
? 'rgba(140,144,159,0.25)'
|
||
: 'rgba(148,163,184,0.30)',
|
||
}}
|
||
>
|
||
{/* 컬럼 그리드 미니 */}
|
||
<div className="flex items-center gap-[2px] mr-3">
|
||
{Array.from({ length: bp.columns }).map((_, i) => (
|
||
<div
|
||
key={i}
|
||
style={{
|
||
width: '4px',
|
||
height: '14px',
|
||
backgroundColor: bp.inUse
|
||
? isDark
|
||
? 'rgba(76,215,246,0.55)'
|
||
: 'rgba(6,182,212,0.45)'
|
||
: isDark
|
||
? 'rgba(140,144,159,0.40)'
|
||
: 'rgba(148,163,184,0.35)',
|
||
borderRadius: '1px',
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
<span
|
||
className="font-mono text-caption font-bold"
|
||
style={{ color: labelColor }}
|
||
>
|
||
{bp.krdsName}
|
||
</span>
|
||
{bp.inUse && (
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0 ml-2"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(34,197,94,0.18)' : 'rgba(34,197,94,0.12)',
|
||
color: '#22c55e',
|
||
}}
|
||
>
|
||
in use
|
||
</span>
|
||
)}
|
||
<span
|
||
className="font-mono text-caption ml-auto"
|
||
style={{ color: bp.inUse ? t.textSecondary : dimText }}
|
||
>
|
||
{bp.columns} cols
|
||
</span>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 범례 */}
|
||
<div
|
||
className="flex items-center gap-4 pt-6 mt-6 border-t border-solid"
|
||
style={{ borderColor: isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0' }}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<div
|
||
className="w-3 h-3 rounded border border-solid"
|
||
style={{
|
||
backgroundColor: accentTint,
|
||
borderColor: isDark ? 'rgba(76,215,246,0.45)' : 'rgba(6,182,212,0.40)',
|
||
}}
|
||
/>
|
||
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
|
||
WING-OPS 사용 중
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<div
|
||
className="w-3 h-3 rounded border border-solid"
|
||
style={{
|
||
backgroundColor: dimBg,
|
||
borderColor: isDark ? 'rgba(140,144,159,0.25)' : 'rgba(148,163,184,0.30)',
|
||
}}
|
||
/>
|
||
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
|
||
미지원 (1280px 미만)
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 3: Grid 시각화 카드 ── */}
|
||
<div className="w-full flex flex-col gap-6">
|
||
<div className="flex flex-col gap-2">
|
||
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
|
||
Grid
|
||
</h2>
|
||
<p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
|
||
컬럼, 마진, 거터로 구성된 그리드 시스템입니다. 데스크톱 전용으로 xl / 2xl 두 구간만
|
||
지원합니다.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-5 w-full">
|
||
{DEVICE_SPECS.map((spec) => (
|
||
<div
|
||
key={spec.device}
|
||
className="rounded-lg border border-solid overflow-hidden flex flex-col"
|
||
style={{
|
||
backgroundColor: t.cardBg,
|
||
borderColor: t.cardBorder,
|
||
boxShadow: t.cardShadow,
|
||
}}
|
||
>
|
||
{/* 카드 헤더 */}
|
||
<div
|
||
className="px-5 py-4 border-b border-solid flex flex-col gap-1"
|
||
style={{ borderColor: isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0' }}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<span className="font-korean text-caption" style={{ color: t.textMuted }}>
|
||
Breakpoint
|
||
</span>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(66,71,84,0.50)' : '#1e293b',
|
||
color: '#ffffff',
|
||
}}
|
||
>
|
||
{spec.prefix}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<span className="font-korean text-caption" style={{ color: t.textMuted }}>
|
||
Width
|
||
</span>
|
||
<span className="font-mono text-body-2" style={{ color: t.textPrimary }}>
|
||
{spec.width}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 시각화 영역 */}
|
||
<div
|
||
className="px-5 py-6 flex flex-col gap-3"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(10,14,26,0.40)' : 'rgba(248,250,252,0.60)',
|
||
}}
|
||
>
|
||
{/* 컬럼 위 어노테이션 (margin / gutter) */}
|
||
<div className="flex items-center justify-between gap-1">
|
||
{Array.from({ length: spec.columns + 1 }).map((_, idx) => {
|
||
const isEdge = idx === 0 || idx === spec.columns;
|
||
return (
|
||
<span
|
||
key={idx}
|
||
className="font-mono text-caption rounded px-1 py-0.5"
|
||
style={{
|
||
fontSize: '9px',
|
||
backgroundColor: isEdge
|
||
? isDark
|
||
? 'rgba(66,71,84,0.50)'
|
||
: '#1e293b'
|
||
: 'transparent',
|
||
color: isEdge ? '#ffffff' : t.textMuted,
|
||
border: isEdge ? 'none' : `1px solid ${t.cardBorder}`,
|
||
}}
|
||
>
|
||
{isEdge ? (spec.prefix === '2xl' ? '32' : '24') : '24'}
|
||
</span>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* 컬럼 막대 시각화 */}
|
||
<div className="flex items-stretch gap-1" style={{ minHeight: '120px' }}>
|
||
{Array.from({ length: spec.columns }).map((_, i) => (
|
||
<div
|
||
key={i}
|
||
className="flex-1 rounded-sm"
|
||
style={{
|
||
backgroundColor: accentTint,
|
||
border: `1px solid ${isDark ? 'rgba(76,215,246,0.30)' : 'rgba(6,182,212,0.25)'}`,
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* 가짜 TopBar mockup */}
|
||
<div
|
||
className="rounded border border-solid flex items-center justify-between px-3 py-1.5 mt-2"
|
||
style={{
|
||
backgroundColor: cardSurface,
|
||
borderColor: isDark ? 'rgba(66,71,84,0.30)' : '#e2e8f0',
|
||
}}
|
||
>
|
||
<span
|
||
className="font-sans text-caption font-bold"
|
||
style={{ color: t.textAccent }}
|
||
>
|
||
WING-OPS
|
||
</span>
|
||
<div className="flex gap-2">
|
||
{['예측', 'HNS', '구조', '관리'].map((label) => (
|
||
<span
|
||
key={label}
|
||
className="font-korean text-caption"
|
||
style={{ color: t.textMuted }}
|
||
>
|
||
{label}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 카드 푸터 — Grid 정보 */}
|
||
<div
|
||
className="px-5 py-3 border-t border-solid grid grid-cols-3 gap-3"
|
||
style={{ borderColor: isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0' }}
|
||
>
|
||
{[
|
||
{ label: 'Columns', value: `${spec.columns}` },
|
||
{ label: 'Gutter', value: spec.gutter },
|
||
{ label: 'Margin', value: spec.margin },
|
||
].map((info) => (
|
||
<div key={info.label} className="flex flex-col gap-0.5">
|
||
<span
|
||
className="font-mono text-caption uppercase"
|
||
style={{ letterSpacing: '0.5px', color: t.textMuted, fontSize: '9px' }}
|
||
>
|
||
{info.label}
|
||
</span>
|
||
<span className="font-mono text-caption" style={{ color: t.textPrimary }}>
|
||
{info.value}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 미지원 안내 */}
|
||
<div
|
||
className="rounded border border-solid px-4 py-3 flex items-center gap-3"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(249,115,22,0.06)' : 'rgba(249,115,22,0.05)',
|
||
borderColor: isDark ? 'rgba(249,115,22,0.25)' : 'rgba(249,115,22,0.20)',
|
||
}}
|
||
>
|
||
<span className="font-mono text-base" style={{ color: '#f97316' }}>
|
||
⚠
|
||
</span>
|
||
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
|
||
<strong style={{ color: '#f97316' }}>1280px 미만 미지원</strong> — Mobile / Tablet
|
||
구간(xs / s / md / lg)은 데스크톱 전용 운영 정책에 따라 지원하지 않습니다.
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 4: App Shell + KRDS Region 매핑 ── */}
|
||
<div className="w-full flex flex-col gap-6">
|
||
<div className="flex flex-col gap-2">
|
||
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
|
||
App Shell
|
||
</h2>
|
||
<p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
|
||
WING-OPS 애플리케이션의 기본 레이아웃 구조와 KRDS Sub-page 영역 매핑입니다.
|
||
</p>
|
||
</div>
|
||
|
||
{/* 다이어그램 */}
|
||
<div
|
||
className="rounded-lg border border-solid overflow-hidden w-full max-w-3xl"
|
||
style={{
|
||
backgroundColor: isDark ? '#0a0e1a' : '#f8fafc',
|
||
borderColor: t.cardBorder,
|
||
boxShadow: t.cardShadow,
|
||
}}
|
||
>
|
||
{/* TopBar */}
|
||
<div
|
||
className="flex items-center justify-between px-4 border-b border-solid"
|
||
style={{
|
||
height: '48px',
|
||
backgroundColor: isDark ? 'rgba(59,130,246,0.10)' : 'rgba(59,130,246,0.06)',
|
||
borderColor: isDark ? 'rgba(59,130,246,0.25)' : 'rgba(59,130,246,0.18)',
|
||
}}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<span className="font-mono text-body-2 font-bold" style={{ color: '#3b82f6' }}>
|
||
TopBar
|
||
</span>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(59,130,246,0.18)' : 'rgba(59,130,246,0.12)',
|
||
color: '#3b82f6',
|
||
}}
|
||
>
|
||
KRDS · Header
|
||
</span>
|
||
</div>
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
h-[52px] / shrink-0
|
||
</span>
|
||
</div>
|
||
|
||
{/* SubMenuBar */}
|
||
<div
|
||
className="flex items-center justify-between px-4 border-b border-solid"
|
||
style={{
|
||
height: '32px',
|
||
backgroundColor: isDark ? 'rgba(6,182,212,0.08)' : 'rgba(6,182,212,0.05)',
|
||
borderColor: isDark ? 'rgba(6,182,212,0.20)' : 'rgba(6,182,212,0.15)',
|
||
}}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<span className="font-mono text-caption font-bold" style={{ color: '#06b6d4' }}>
|
||
SubMenuBar
|
||
</span>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(6,182,212,0.18)' : 'rgba(6,182,212,0.12)',
|
||
color: '#06b6d4',
|
||
}}
|
||
>
|
||
KRDS · Sub Navigation
|
||
</span>
|
||
</div>
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
shrink-0 / 조건부
|
||
</span>
|
||
</div>
|
||
|
||
{/* Content Area */}
|
||
<div className="flex flex-row" style={{ height: '220px' }}>
|
||
{/* Sidebar */}
|
||
<div
|
||
className="flex flex-col items-center justify-center gap-2 border-r border-solid"
|
||
style={{
|
||
width: '160px',
|
||
backgroundColor: isDark ? 'rgba(168,85,247,0.08)' : 'rgba(168,85,247,0.05)',
|
||
borderColor: isDark ? 'rgba(168,85,247,0.20)' : 'rgba(168,85,247,0.15)',
|
||
}}
|
||
>
|
||
<span className="font-mono text-caption font-bold" style={{ color: '#a855f7' }}>
|
||
Sidebar
|
||
</span>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(168,85,247,0.18)' : 'rgba(168,85,247,0.12)',
|
||
color: '#a855f7',
|
||
}}
|
||
>
|
||
KRDS · Left Menu
|
||
</span>
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
flex-col / border-r
|
||
</span>
|
||
</div>
|
||
|
||
{/* Main Content */}
|
||
<div
|
||
className="flex-1 flex flex-col items-center justify-center gap-2"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(34,197,94,0.06)' : 'rgba(34,197,94,0.04)',
|
||
}}
|
||
>
|
||
<span className="font-mono text-caption font-bold" style={{ color: '#22c55e' }}>
|
||
Content
|
||
</span>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(34,197,94,0.18)' : 'rgba(34,197,94,0.12)',
|
||
color: '#22c55e',
|
||
}}
|
||
>
|
||
KRDS · Main Contents
|
||
</span>
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
flex-1 / overflow-y-auto
|
||
</span>
|
||
</div>
|
||
|
||
{/* Right Panel (조건부) */}
|
||
<div
|
||
className="flex flex-col items-center justify-center gap-2 border-l border-dashed"
|
||
style={{
|
||
width: '120px',
|
||
backgroundColor: isDark ? 'rgba(249,115,22,0.06)' : 'rgba(249,115,22,0.04)',
|
||
borderColor: isDark ? 'rgba(249,115,22,0.25)' : 'rgba(249,115,22,0.20)',
|
||
}}
|
||
>
|
||
<span className="font-mono text-caption font-bold" style={{ color: '#f97316' }}>
|
||
Right Panel
|
||
</span>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(249,115,22,0.15)' : 'rgba(249,115,22,0.10)',
|
||
color: '#f97316',
|
||
}}
|
||
>
|
||
KRDS · Right Menu
|
||
</span>
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
조건부 렌더
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Footer 안내 */}
|
||
<div
|
||
className="border-t border-dashed px-4 py-2 flex items-center justify-center gap-2"
|
||
style={{
|
||
borderColor: isDark ? 'rgba(140,144,159,0.20)' : 'rgba(148,163,184,0.30)',
|
||
backgroundColor: isDark ? 'rgba(140,144,159,0.04)' : 'rgba(148,163,184,0.04)',
|
||
}}
|
||
>
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{ backgroundColor: dimBg, color: dimText }}
|
||
>
|
||
KRDS · Footer
|
||
</span>
|
||
<span className="font-korean text-caption" style={{ color: dimText }}>
|
||
풀스크린 앱 — 미사용
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 매핑 표 (다이어그램 부속) */}
|
||
<div
|
||
className="rounded-lg border border-solid overflow-hidden w-full"
|
||
style={{
|
||
backgroundColor: t.tableContainerBg,
|
||
borderColor: t.cardBorder,
|
||
boxShadow: t.cardShadow,
|
||
}}
|
||
>
|
||
<div
|
||
className="grid"
|
||
style={{
|
||
gridTemplateColumns: '160px 160px 1fr',
|
||
backgroundColor: t.tableHeaderBg,
|
||
borderBottom: `1px solid ${t.tableRowBorder}`,
|
||
}}
|
||
>
|
||
{(['KRDS Region', 'WING-OPS Component', 'Implementation'] as const).map((col) => (
|
||
<div key={col} className="py-2.5 px-4">
|
||
<span
|
||
className="font-mono text-caption uppercase"
|
||
style={{ letterSpacing: '1px', color: t.textMuted }}
|
||
>
|
||
{col}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
{SUB_PAGE_MAPPINGS.map((row, idx) => (
|
||
<div
|
||
key={row.krdsRegion}
|
||
className="grid items-center"
|
||
style={{
|
||
gridTemplateColumns: '160px 160px 1fr',
|
||
borderTop: idx === 0 ? 'none' : `1px solid ${t.tableRowBorder}`,
|
||
}}
|
||
>
|
||
<div className="py-2.5 px-4 flex items-center gap-2">
|
||
<div
|
||
className="w-2 h-2 rounded-full shrink-0"
|
||
style={{ backgroundColor: row.color }}
|
||
/>
|
||
<span className="font-mono text-caption" style={{ color: t.textPrimary }}>
|
||
{row.krdsRegion}
|
||
</span>
|
||
</div>
|
||
<div className="py-2.5 px-4">
|
||
<span className="font-mono text-caption" style={{ color: t.textSecondary }}>
|
||
{row.wingOpsComponent}
|
||
</span>
|
||
</div>
|
||
<div className="py-2.5 px-4">
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
{row.implementation}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 5: Spacing Scale 막대 시각화 ── */}
|
||
<div className="w-full flex flex-col gap-6">
|
||
<div className="flex flex-col gap-2">
|
||
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
|
||
Spacing
|
||
</h2>
|
||
<p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
|
||
UI 요소 간의 간격과 여백을 정의하여 콘텐츠의 위계와 가독성을 조율합니다. Tailwind
|
||
spacing 토큰과 직결되며, 막대 길이는 실제 px 비율입니다.
|
||
</p>
|
||
</div>
|
||
|
||
<div
|
||
className="rounded-lg border border-solid p-6 flex flex-col gap-2.5"
|
||
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder, boxShadow: t.cardShadow }}
|
||
>
|
||
{SPACING_TOKENS.map((token) => {
|
||
const widthPct = (token.px / SPACING_MAX_PX) * 100;
|
||
return (
|
||
<div
|
||
key={token.className}
|
||
className="grid items-center"
|
||
style={{ gridTemplateColumns: '90px 60px 1fr 200px' }}
|
||
>
|
||
{/* 토큰 배지 */}
|
||
<span
|
||
className="font-mono text-caption rounded border border-solid px-2 py-0.5 inline-block w-fit"
|
||
style={{
|
||
color: t.textAccent,
|
||
backgroundColor: accentTintLight,
|
||
borderColor: isDark ? 'rgba(76,215,246,0.30)' : 'rgba(6,182,212,0.25)',
|
||
}}
|
||
>
|
||
{token.className}
|
||
</span>
|
||
{/* px 값 */}
|
||
<span
|
||
className="font-mono text-caption font-bold text-right pr-3"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
{token.px}px
|
||
</span>
|
||
{/* 막대 시각화 */}
|
||
<div className="flex items-center">
|
||
<div
|
||
className="rounded-sm"
|
||
style={{
|
||
width: `${widthPct}%`,
|
||
minWidth: '4px',
|
||
height: '14px',
|
||
backgroundColor: accentTint,
|
||
border: `1px solid ${isDark ? 'rgba(76,215,246,0.40)' : 'rgba(6,182,212,0.35)'}`,
|
||
}}
|
||
/>
|
||
</div>
|
||
{/* 사용처 */}
|
||
<span
|
||
className="font-korean text-caption text-right"
|
||
style={{ color: t.textMuted }}
|
||
>
|
||
{token.usage}
|
||
</span>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 6: 4pt Grid 원칙 + Multiplier ── */}
|
||
<div className="w-full flex flex-col gap-6">
|
||
<div className="flex flex-col gap-2">
|
||
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
|
||
4pt Grid
|
||
</h2>
|
||
<p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
|
||
모든 여백과 간격을 4point 단위로 설정해 규칙성을 확보합니다. 컴팩트한 컴포넌트의 경우,
|
||
2의 배수 단위를 제한적으로 사용합니다.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-5">
|
||
{/* Part A — 원칙 카드 */}
|
||
<div
|
||
className="rounded-lg border border-solid p-5 flex flex-col gap-3"
|
||
style={{
|
||
backgroundColor: t.cardBg,
|
||
borderColor: t.cardBorder,
|
||
boxShadow: t.cardShadow,
|
||
}}
|
||
>
|
||
<span
|
||
className="font-mono text-caption uppercase"
|
||
style={{ letterSpacing: '1px', color: t.textMuted }}
|
||
>
|
||
Principles
|
||
</span>
|
||
<div className="flex flex-col gap-3">
|
||
{[
|
||
{
|
||
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) => (
|
||
<div key={item.label} className="flex flex-col gap-1">
|
||
<div className="flex items-center gap-2">
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{ backgroundColor: accentTintLight, color: t.textAccent }}
|
||
>
|
||
{item.label}
|
||
</span>
|
||
<span className="font-korean text-caption" style={{ color: t.textPrimary }}>
|
||
{item.text}
|
||
</span>
|
||
</div>
|
||
<span className="font-mono text-caption pl-1" style={{ color: t.textMuted }}>
|
||
{item.example}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Part B — Multiplier 어노테이션 시각화 */}
|
||
<div
|
||
className="rounded-lg border border-solid p-5 flex flex-col gap-3"
|
||
style={{
|
||
backgroundColor: t.cardBg,
|
||
borderColor: t.cardBorder,
|
||
boxShadow: t.cardShadow,
|
||
}}
|
||
>
|
||
<span
|
||
className="font-mono text-caption uppercase"
|
||
style={{ letterSpacing: '1px', color: t.textMuted }}
|
||
>
|
||
Multiplier Annotation
|
||
</span>
|
||
<div
|
||
className="rounded relative flex items-center justify-center"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(10,14,26,0.50)' : 'rgba(248,250,252,0.80)',
|
||
minHeight: '220px',
|
||
padding: '40px',
|
||
}}
|
||
>
|
||
{/* 어노테이션 — top */}
|
||
<div className="absolute top-3 left-1/2 -translate-x-1/2 flex items-center gap-1">
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(66,71,84,0.50)' : '#1e293b',
|
||
color: '#ffffff',
|
||
fontSize: '10px',
|
||
}}
|
||
>
|
||
{CARD_ANNOTATIONS[0].multiplier}={CARD_ANNOTATIONS[0].px}
|
||
</span>
|
||
</div>
|
||
{/* 어노테이션 — left */}
|
||
<div className="absolute left-3 top-1/2 -translate-y-1/2">
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(66,71,84,0.50)' : '#1e293b',
|
||
color: '#ffffff',
|
||
fontSize: '10px',
|
||
}}
|
||
>
|
||
{CARD_ANNOTATIONS[1].multiplier}={CARD_ANNOTATIONS[1].px}
|
||
</span>
|
||
</div>
|
||
{/* 어노테이션 — right */}
|
||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(66,71,84,0.50)' : '#1e293b',
|
||
color: '#ffffff',
|
||
fontSize: '10px',
|
||
}}
|
||
>
|
||
{CARD_ANNOTATIONS[2].multiplier}={CARD_ANNOTATIONS[2].px}
|
||
</span>
|
||
</div>
|
||
{/* 어노테이션 — bottom */}
|
||
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 flex items-center gap-1">
|
||
<span
|
||
className="font-mono text-caption rounded px-1.5 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(66,71,84,0.50)' : '#1e293b',
|
||
color: '#ffffff',
|
||
fontSize: '10px',
|
||
}}
|
||
>
|
||
{CARD_ANNOTATIONS[3].multiplier}={CARD_ANNOTATIONS[3].px}
|
||
</span>
|
||
</div>
|
||
|
||
{/* 실제 카드 mockup */}
|
||
<div
|
||
className="rounded-lg border border-solid flex flex-col gap-2 px-5 py-4"
|
||
style={{
|
||
backgroundColor: cardSurface,
|
||
borderColor: isDark ? 'rgba(76,215,246,0.40)' : 'rgba(6,182,212,0.30)',
|
||
minWidth: '180px',
|
||
}}
|
||
>
|
||
<span
|
||
className="font-korean text-caption font-bold"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
카드 타이틀
|
||
</span>
|
||
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
|
||
본문 내용 예시
|
||
</span>
|
||
<div
|
||
className="rounded px-2 py-1 self-start mt-1"
|
||
style={{ backgroundColor: accentTintLight }}
|
||
>
|
||
<span className="font-mono text-caption" style={{ color: t.textAccent }}>
|
||
Action
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p className="font-korean text-caption" style={{ color: t.textMuted }}>
|
||
어노테이션은 spacing 값의 4px 배수 multiplier를 표시합니다.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 7: Z-Index Layers ── */}
|
||
<div className="w-full flex flex-col gap-6">
|
||
<div className="flex flex-col gap-2">
|
||
<div className="flex items-center gap-3">
|
||
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
|
||
Z-Index Layers
|
||
</h2>
|
||
<span
|
||
className="font-mono text-caption rounded px-2 py-0.5"
|
||
style={{
|
||
backgroundColor: isDark ? 'rgba(76,215,246,0.10)' : 'rgba(6,182,212,0.08)',
|
||
color: t.textAccent,
|
||
}}
|
||
>
|
||
디자인 시스템 진실 소스
|
||
</span>
|
||
</div>
|
||
<p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
|
||
UI 요소의 레이어 스택 순서입니다. 높은 z-index가 위에 표시되며, 이 값은 디자인 시스템의
|
||
이상적 설계 값으로 실제 코드는 이 값에 맞춰 정정되어야 합니다.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-0" style={{ maxWidth: '600px' }}>
|
||
{Z_LAYERS.map((layer, idx) => (
|
||
<div
|
||
key={layer.name}
|
||
className="flex flex-row items-stretch"
|
||
style={{ marginLeft: `${idx * 16}px` }}
|
||
>
|
||
<div className="w-12 shrink-0 flex items-center justify-end pr-3">
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
{layer.zIndex}
|
||
</span>
|
||
</div>
|
||
<div
|
||
className="flex-1 flex flex-row items-center gap-3 px-4 py-3 border border-solid"
|
||
style={{
|
||
backgroundColor: isDark ? `${layer.color}15` : `${layer.color}0c`,
|
||
borderColor: isDark ? `${layer.color}35` : `${layer.color}28`,
|
||
borderRadius: '6px',
|
||
marginBottom: '-1px',
|
||
}}
|
||
>
|
||
<div
|
||
className="w-2 h-2 rounded-full shrink-0"
|
||
style={{ backgroundColor: layer.color }}
|
||
/>
|
||
<span className="font-mono text-caption font-bold" style={{ color: layer.color }}>
|
||
{layer.name}
|
||
</span>
|
||
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
|
||
{layer.description}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 8: CSS 클래스 + KRDS Grid Rules 통합 ── */}
|
||
<div className="w-full flex flex-col gap-6">
|
||
<div className="flex flex-col gap-2">
|
||
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
|
||
Reference
|
||
</h2>
|
||
<p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
|
||
App Shell CSS 클래스와 KRDS Grid 규칙 비교 — 코드 작성 시 참조용입니다.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-5">
|
||
{/* CSS 클래스 */}
|
||
<div
|
||
className="rounded-lg border border-solid overflow-hidden"
|
||
style={{
|
||
backgroundColor: t.tableContainerBg,
|
||
borderColor: t.cardBorder,
|
||
boxShadow: t.cardShadow,
|
||
}}
|
||
>
|
||
<div
|
||
className="px-4 py-2.5 border-b border-solid"
|
||
style={{
|
||
backgroundColor: t.tableHeaderBg,
|
||
borderColor: t.tableRowBorder,
|
||
}}
|
||
>
|
||
<span
|
||
className="font-mono text-caption uppercase"
|
||
style={{ letterSpacing: '1px', color: t.textMuted }}
|
||
>
|
||
CSS Classes
|
||
</span>
|
||
</div>
|
||
{SHELL_CLASSES.map((cls, idx) => (
|
||
<div
|
||
key={cls.className}
|
||
className="px-4 py-3 flex flex-col gap-1"
|
||
style={{
|
||
borderTop: idx === 0 ? 'none' : `1px solid ${t.tableRowBorder}`,
|
||
}}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<span
|
||
className="font-mono text-caption rounded border border-solid px-1.5 py-0.5"
|
||
style={{
|
||
color: t.textAccent,
|
||
backgroundColor: accentTintLight,
|
||
borderColor: isDark ? 'rgba(76,215,246,0.30)' : 'rgba(6,182,212,0.25)',
|
||
}}
|
||
>
|
||
{cls.className}
|
||
</span>
|
||
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
|
||
{cls.role}
|
||
</span>
|
||
</div>
|
||
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
|
||
{cls.styles}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* KRDS Grid Rules */}
|
||
<div
|
||
className="rounded-lg border border-solid overflow-hidden"
|
||
style={{
|
||
backgroundColor: t.tableContainerBg,
|
||
borderColor: t.cardBorder,
|
||
boxShadow: t.cardShadow,
|
||
}}
|
||
>
|
||
<div
|
||
className="px-4 py-2.5 border-b border-solid"
|
||
style={{
|
||
backgroundColor: t.tableHeaderBg,
|
||
borderColor: t.tableRowBorder,
|
||
}}
|
||
>
|
||
<span
|
||
className="font-mono text-caption uppercase"
|
||
style={{ letterSpacing: '1px', color: t.textMuted }}
|
||
>
|
||
KRDS vs WING-OPS
|
||
</span>
|
||
</div>
|
||
{GRID_RULES.map((rule, idx) => (
|
||
<div
|
||
key={rule.name}
|
||
className="px-4 py-3 flex flex-col gap-1"
|
||
style={{
|
||
borderTop: idx === 0 ? 'none' : `1px solid ${t.tableRowBorder}`,
|
||
}}
|
||
>
|
||
<span className="font-mono text-caption font-bold" style={{ color: t.textPrimary }}>
|
||
{rule.name}
|
||
</span>
|
||
<div className="flex items-center gap-1.5">
|
||
<span className="font-korean text-caption" style={{ color: t.textMuted }}>
|
||
KRDS:
|
||
</span>
|
||
<span className="font-mono text-caption" style={{ color: t.textSecondary }}>
|
||
{rule.krds}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-1.5">
|
||
<span className="font-korean text-caption" style={{ color: t.textMuted }}>
|
||
WING:
|
||
</span>
|
||
<span className="font-mono text-caption" style={{ color: t.textAccent }}>
|
||
{rule.wingOps}
|
||
</span>
|
||
</div>
|
||
<span className="font-korean text-caption" style={{ color: t.textMuted }}>
|
||
{rule.note}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default LayoutContent;
|