# WING-OPS 디자인 시스템 (백업) Tailwind CSS + @apply 기반 디자인 시스템. `wing-*` CSS 클래스와 React UI 컴포넌트로 구성. --- ## 브랜드 (Brand) ### 로고 해양 환경 위기대응을 위한 통합 솔루션 **WING**의 로고. - **파일**: `frontend/public/wing_logo_white.svg` - **네이티브 크기**: 280 × 20px (비율 14:1) - **색상**: 단색 흰색 (다크 배경 전용) #### 로고 규격 | 용도 | 높이 | Tailwind | 비고 | |------|------|----------|------| | Header | 14px | `h-3.5` | TopBar 52px 높이 내 사용 (현재) | | Standard | 24px | `h-6` | 일반 UI, 문서 내 | | Large | 32px | `h-8` | 로그인, 랜딩 화면 | | **Minimum** | **14px** | `h-3.5` | 이보다 작게 사용 금지 | #### 여백 규칙 (Clear Space) - 최소 여백: 로고 높이의 **50%** (상하좌우) - 로고 주변에 다른 텍스트나 아이콘이 침범하지 않도록 유지 ### 테마 컬러 다크 테마 기반. 게시판(Board) 메뉴에서 추출한 컬러 체계. #### 배경 (Background) — 딥 네이비 | 토큰 | CSS 변수 | HEX | 용도 | |------|----------|-----|------| | `bg-bg-0` | `--bg0` | `#0a0e1a` | 최하위 배경 (body, input) | | `bg-bg-1` | `--bg1` | `#0f1524` | 패널, 모달, 푸터 배경 | | `bg-bg-2` | `--bg2` | `#121929` | 테이블 헤더, elevated 영역 | | `bg-bg-3` | `--bg3` | `#1a2236` | 카드, 보조 버튼, 비활성 요소 | | `bg-hover` | `--bgH` | `#1e2844` | 행 hover | #### 텍스트 (Text) | 토큰 | CSS 변수 | HEX | 용도 | |------|----------|-----|------| | `text-text-1` | `--t1` | `#edf0f7` | 주 텍스트 (제목, 본문) | | `text-text-2` | `--t2` | `#b0b8cc` | 보조 텍스트 (라벨, 설명) | | `text-text-3` | `--t3` | `#8690a6` | 비활성/메타 (날짜, 조회수) | #### 브랜드 강조색 (Accent) | 토큰 | HEX | 용도 | |------|-----|------| | `primary-cyan` | `#06b6d4` | 주 강조색 — 활성 상태, 링크, CTA | | `primary-blue` | `#3b82f6` | 보조 강조색 — 그라데이션 끝점 | | **Primary Gradient** | `linear-gradient(135deg, #06b6d4, #3b82f6)` | Primary 버튼 | #### 시맨틱 (Semantic) | 토큰 | HEX | 용도 | |------|-----|------| | `red` / `danger` | `#ef4444` | 삭제, 필수 표시(*) | | `orange` | `#f97316` | 경고 | | `yellow` | `#eab308` | 주의 | | `green` | `#22c55e` | 성공, 정상 | | `purple` | `#a855f7` | 특수 강조 | #### 테두리 (Border) | 토큰 | CSS 변수 | HEX | 용도 | |------|----------|-----|------| | `border` | `--bd` | `#1e2a42` | 기본 구분선 | | `border-light` | `--bdL` | `#2a3a5c` | 밝은 테두리 | #### 오버레이 (Overlay) | 토큰 | 값 | 용도 | |------|-----|------| | `overlay` | `rgba(0, 0, 0, 0.55)` | 모달 배경 오버레이 | --- ## 디자인 원칙 ### 컬러 사용 규칙 - **상태 표현** (위험/정상/주의) → 컬러 배지 사용 (red, green, yellow) - **단순 분류** (공지사항, 자료실, Q&A) → 기본 텍스트 컬러 또는 neutral 배지 - **강조** (고정글, 선택 항목) → accent 컬러 (cyan) - **원칙**: 색상은 정보를 전달할 때만 사용. 장식 목적 금지. --- ## 1. 토큰 ### 컬러 팔레트 | CSS 변수 | 값 | 용도 | |----------|-----|------| | `--bg0` | `#0a0e1a` | 최하위 배경 (body, input) | | `--bg1` | `#0f1524` | 기본 패널 배경 | | `--bg2` | `#121929` | 테이블 헤더, elevated | | `--bg3` | `#1a2236` | 카드, 섹션 배경 | | `--bgH` | `#1e2844` | hover 상태 | | `--bd` | `#1e2a42` | 기본 테두리 | | `--bdL` | `#2a3a5c` | 밝은 테두리 | | `--t1` | `#edf0f7` | 기본 텍스트 (밝음) | | `--t2` | `#b0b8cc` | 보조 텍스트 | | `--t3` | `#8690a6` | 비활성/메타 텍스트 | | `--cyan` | `#06b6d4` | Primary accent | | `--blue` | `#3b82f6` | Secondary accent | | `--purple` | `#a855f7` | 특수 강조 | | `--red` | `#ef4444` | 위험/삭제 | | `--orange` | `#f97316` | 경고 | | `--yellow` | `#eab308` | 주의 | | `--green` | `#22c55e` | 성공/정상 | ### Z-Index 스케일 | 변수 | 값 | 용도 | |------|-----|------| | `--z-dropdown` | 100 | 드롭다운, 콤보박스 | | `--z-sticky` | 200 | sticky 헤더 | | `--z-overlay` | 1000 | 오버레이 | | `--z-modal` | 10000 | 모달 | | `--z-toast` | 10100 | 토스트 알림 | ### 패널 너비 | 변수 | 값 | 용도 | |------|-----|------| | `--panel-narrow` | 280px | 좁은 사이드패널 | | `--panel-default` | 300px | 기본 사이드패널 | | `--panel-wide` | 340px | 넓은 사이드패널 | ### 타이포그래피 (Tailwind) | 클래스 | 크기 | 용도 | |--------|------|------| | `text-wing-meta` | 9px | 메타 텍스트, 날짜, 부가 정보 | | `text-wing-caption` | 10px | 캡션, 설명, 라벨 부연 | | `text-wing-body` | 11px | 본문, 라벨, 값 (가장 많이 사용) | | `text-wing-heading` | 13px | 섹션 헤더 | | `text-wing-title` | 15px | 페이지/모달 타이틀 | --- ## 2. CSS 클래스 (`wing-*`) 모든 클래스는 `frontend/src/common/styles/wing.css`에 정의. ### Layout | 클래스 | 설명 | |--------|------| | `wing-panel` | flex column, full height, overflow hidden | | `wing-panel-scroll` | flex-1, overflow-y-auto, thin scrollbar | | `wing-panel-right` | 우측 사이드패널 (border-l, 300px) | | `wing-panel-left` | 좌측 사이드패널 (border-r, 300px) | | `wing-header-bar` | 헤더 바 (flex between, border-b, px-5) | | `wing-sidebar` | 사이드바 (flex col, border-r, bg1) | ### Card / Section | 클래스 | 설명 | |--------|------| | `wing-card` | 카드 (rounded-md, p-4, border, bg3) | | `wing-card-sm` | 작은 카드 (rounded-sm, p-3) | | `wing-section` | 섹션 (rounded-md, p-4, mb-3) | | `wing-section-header` | 섹션 제목 (13px bold) | | `wing-section-desc` | 섹션 설명 (10px, t3 색상) | ### Typography | 클래스 | 설명 | |--------|------| | `wing-title` | 15px bold korean | | `wing-subtitle` | 10px korean, t3 색상 | | `wing-label` | 11px semibold korean | | `wing-value` | 11px semibold mono | | `wing-meta` | 9px korean, t3 색상 | ### Button | 클래스 | 설명 | |--------|------| | `wing-btn` | 기본 버튼 (px-3, py-1.5, 11px) | | `wing-btn-primary` | cyan→blue gradient, white | | `wing-btn-secondary` | bg3, border, t2 색상 | | `wing-btn-outline` | transparent, border | | `wing-btn-pdf` | blue 테마 | | `wing-btn-danger` | red 테마 | ### Input / Select / Textarea | 클래스 | 설명 | |--------|------| | `wing-input` | 기본 입력 (w-full, 11px, cyan focus) | | `wing-select` | 셀렉트 (커스텀 화살표 포함) | | `wing-textarea` | 텍스트영역 (resize-vertical, min-h 80px) | | `wing-input-search` | 검색 입력 (256px 고정) | ### Table | 클래스 | 설명 | |--------|------| | `wing-table` | 테이블 (w-full, 10px, collapse) | | `wing-table-head` | 헤더 셀 (bg2, t3, bold) | | `wing-table-cell` | 데이터 셀 (t2, border-b) | | `wing-table-row` | 행 hover (bgH, cursor-pointer) | ### Badge | 클래스 | 설명 | |--------|------| | `wing-badge` | 기본 배지 (inline-flex, 9px bold) | | `wing-badge-neutral` | 회색 (단순 분류용 기본값) | | `wing-badge-red` | 위험/삭제 | | `wing-badge-blue` | 정보 | | `wing-badge-green` | 성공/정상 | | `wing-badge-yellow` | 주의 | | `wing-badge-purple` | 특수 | | `wing-badge-cyan` | 주요 | ### Modal | 클래스 | 설명 | |--------|------| | `wing-overlay` | 오버레이 (fixed, blur, z-modal) | | `wing-modal` | 모달 컨테이너 (rounded-xl, bg1) | | `wing-modal-header` | 모달 헤더 (flex between, border-b) | | `wing-modal-body` | 모달 본문 (flex-1, scroll) | | `wing-modal-footer` | 모달 푸터 (flex end, border-t) | | `wing-modal-sm` | 400px | | `wing-modal-md` | 560px | | `wing-modal-lg` | 720px | ### Tab | 클래스 | 설명 | |--------|------| | `wing-tab-bar` | 탭 바 (flex, rounded-lg, border) | | `wing-tab` | 탭 아이템 (flex-1, text-center) | | `wing-tab.active` | 활성 탭 (cyan border/bg/text) | ### Utility | 클래스 | 설명 | |--------|------| | `wing-divider` | 구분선 (1px, full width) | | `wing-info-row` | 키-값 행 (flex between) | | `wing-info-label` | 키 라벨 (10px, t3) | | `wing-info-value` | 값 (11px, semibold mono) | --- ## 3. React 컴포넌트 위치: `frontend/src/common/components/ui/` ### Modal ```tsx import Modal from '@common/components/ui/Modal'; setIsOpen(false)} title="제목" size="md" footer={ <> } >

모달 내용

``` | Prop | 타입 | 기본값 | 설명 | |------|------|--------|------| | `isOpen` | `boolean` | 필수 | 표시 여부 | | `onClose` | `() => void` | 필수 | 닫기 콜백 | | `title` | `string` | 필수 | 헤더 제목 | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 너비 (400/560/720px) | | `children` | `ReactNode` | 필수 | 본문 | | `footer` | `ReactNode` | - | 하단 버튼 영역 | | `closeOnBackdrop` | `boolean` | `true` | 배경 클릭 닫기 | ### Pagination ```tsx import Pagination from '@common/components/ui/Pagination'; ``` | Prop | 타입 | 기본값 | 설명 | |------|------|--------|------| | `currentPage` | `number` | 필수 | 현재 페이지 (1-based) | | `totalPages` | `number` | 필수 | 전체 페이지 수 | | `onPageChange` | `(page: number) => void` | 필수 | 페이지 변경 콜백 | | `showFirstLast` | `boolean` | `true` | 처음/끝 버튼 표시 | ### DataTable ```tsx import DataTable, { Column } from '@common/components/ui/DataTable'; interface Post { id: number; title: string; author: string; createdAt: string; } const columns: Column[] = [ { key: 'id', label: '번호', width: '60px', align: 'center' }, { key: 'title', label: '제목' }, { key: 'author', label: '작성자', width: '100px' }, { key: 'createdAt', label: '작성일', width: '120px', render: (val) => new Date(val as string).toLocaleDateString() }, ]; navigate(`/board/${post.id}`)} /> ``` | Prop | 타입 | 기본값 | 설명 | |------|------|--------|------| | `columns` | `Column[]` | 필수 | 컬럼 정의 | | `data` | `T[]` | 필수 | 데이터 배열 | | `onRowClick` | `(row: T) => void` | - | 행 클릭 콜백 | | `stickyHeader` | `boolean` | `true` | 헤더 고정 | | `emptyMessage` | `string` | `'데이터가 없습니다.'` | 빈 상태 메시지 | ### SidePanel ```tsx import SidePanel from '@common/components/ui/SidePanel'; 상세 정보} footer={ } >
상태 정상
``` | Prop | 타입 | 기본값 | 설명 | |------|------|--------|------| | `position` | `'left' \| 'right'` | 필수 | 배치 방향 | | `width` | `'narrow' \| 'default' \| 'wide'` | `'default'` | 너비 (280/300/340px) | | `header` | `ReactNode` | - | 헤더 영역 | | `footer` | `ReactNode` | - | 하단 영역 | ### Badge ```tsx import Badge from '@common/components/ui/Badge'; 정상 {/* 상태 표현 */} 위험 {/* 상태 표현 */} 공지사항 {/* 단순 분류 → neutral */} 자료실 {/* 단순 분류 → neutral */} ``` | Prop | 타입 | 기본값 | 설명 | |------|------|--------|------| | `color` | `'red' \| 'blue' \| 'green' \| 'yellow' \| 'purple' \| 'cyan' \| 'neutral'` | `'neutral'` | 배지 색상 | --- ## 4. 적용 예시: Before → After ### Before (raw Tailwind 복붙) ```tsx {/* 검색 */} {/* 테이블 */}
제목
{item.title}
{/* 배지 — 의미 없는 컬러 분화 */} 공지사항 {/* 페이지네이션 — 직접 구현 */} ``` ### After (디자인 시스템) ```tsx {/* 검색 */} {/* 테이블 — React 컴포넌트 */} {/* 배지 — neutral로 통일 */} 공지사항 자료실 진행중 {/* 상태만 컬러 */} {/* 페이지네이션 — React 컴포넌트 */} ``` --- ## 5. 마이그레이션 가이드 ### 작업 순서 1. 파일 내 raw Tailwind 문자열을 `wing-*` 클래스로 교체 2. 반복되는 행동 패턴 (모달, 페이지네이션, 테이블)을 React 컴포넌트로 교체 3. 의미 없는 컬러 배지를 `` (neutral)로 교체 4. 브라우저에서 시각적 동일성 확인 ### 판단 기준 ``` 순수 시각 + 5회 이상 반복 → wing-* CSS 클래스 동작 포함 + 5회 이상 반복 → React 컴포넌트 1-2회 사용 → 인라인 Tailwind 유지 도메인 전용 시각 → components.css (유지) ```