wing-ops/docs/DESIGN-SYSTEM.backup.md

14 KiB

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

import Modal from '@common/components/ui/Modal';

<Modal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="제목"
  size="md"
  footer={
    <>
      <button className="wing-btn wing-btn-secondary" onClick={handleCancel}>취소</button>
      <button className="wing-btn wing-btn-primary" onClick={handleConfirm}>확인</button>
    </>
  }
>
  <p>모달 내용</p>
</Modal>
Prop 타입 기본값 설명
isOpen boolean 필수 표시 여부
onClose () => void 필수 닫기 콜백
title string 필수 헤더 제목
size 'sm' | 'md' | 'lg' 'md' 너비 (400/560/720px)
children ReactNode 필수 본문
footer ReactNode - 하단 버튼 영역
closeOnBackdrop boolean true 배경 클릭 닫기

Pagination

import Pagination from '@common/components/ui/Pagination';

<Pagination
  currentPage={page}
  totalPages={totalPages}
  onPageChange={setPage}
/>
Prop 타입 기본값 설명
currentPage number 필수 현재 페이지 (1-based)
totalPages number 필수 전체 페이지 수
onPageChange (page: number) => void 필수 페이지 변경 콜백
showFirstLast boolean true 처음/끝 버튼 표시

DataTable

import DataTable, { Column } from '@common/components/ui/DataTable';

interface Post {
  id: number;
  title: string;
  author: string;
  createdAt: string;
}

const columns: Column<Post>[] = [
  { 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() },
];

<DataTable
  columns={columns}
  data={posts}
  onRowClick={(post) => navigate(`/board/${post.id}`)}
/>
Prop 타입 기본값 설명
columns Column<T>[] 필수 컬럼 정의
data T[] 필수 데이터 배열
onRowClick (row: T) => void - 행 클릭 콜백
stickyHeader boolean true 헤더 고정
emptyMessage string '데이터가 없습니다.' 빈 상태 메시지

SidePanel

import SidePanel from '@common/components/ui/SidePanel';

<SidePanel
  position="right"
  width="default"
  header={<span className="wing-title">상세 정보</span>}
  footer={
    <button className="wing-btn wing-btn-primary w-full">저장</button>
  }
>
  <div className="p-3">
    <div className="wing-info-row">
      <span className="wing-info-label">상태</span>
      <Badge color="green">정상</Badge>
    </div>
  </div>
</SidePanel>
Prop 타입 기본값 설명
position 'left' | 'right' 필수 배치 방향
width 'narrow' | 'default' | 'wide' 'default' 너비 (280/300/340px)
header ReactNode - 헤더 영역
footer ReactNode - 하단 영역

Badge

import Badge from '@common/components/ui/Badge';

<Badge color="green">정상</Badge>      {/* 상태 표현 */}
<Badge color="red">위험</Badge>        {/* 상태 표현 */}
<Badge>공지사항</Badge>                 {/* 단순 분류 → neutral */}
<Badge>자료실</Badge>                   {/* 단순 분류 → neutral */}
Prop 타입 기본값 설명
color 'red' | 'blue' | 'green' | 'yellow' | 'purple' | 'cyan' | 'neutral' 'neutral' 배지 색상

4. 적용 예시: Before → After

Before (raw Tailwind 복붙)

{/* 검색 */}
<input
  type="text"
  placeholder="검색..."
  className="w-64 px-4 py-2 text-sm bg-bg-2 border border-border rounded text-text-1
    placeholder-text-3 focus:border-primary-cyan focus:outline-none"
/>

{/* 테이블 */}
<table className="w-full text-sm">
  <thead>
    <tr className="border-b border-border">
      <th className="px-4 py-3 text-left text-xs font-bold text-text-3">제목</th>
    </tr>
  </thead>
  <tbody>
    <tr className="border-b border-border hover:bg-bg-2 cursor-pointer">
      <td className="px-4 py-4 text-sm text-text-2">{item.title}</td>
    </tr>
  </tbody>
</table>

{/* 배지 — 의미 없는 컬러 분화 */}
<span className="px-2.5 py-0.5 rounded text-xs font-semibold bg-red-500/20 text-red-400">
  공지사항
</span>

{/* 페이지네이션 — 직접 구현 */}
<button className="px-3 py-1.5 text-sm rounded bg-bg-2 text-text-3 ...">이전</button>
<button className="px-3 py-1.5 text-sm rounded bg-bg-2 text-text-3 ...">다음</button>

After (디자인 시스템)

{/* 검색 */}
<input type="text" placeholder="검색..." className="wing-input-search" />

{/* 테이블 — React 컴포넌트 */}
<DataTable columns={columns} data={posts} onRowClick={handleClick} />

{/* 배지 — neutral로 통일 */}
<Badge>공지사항</Badge>
<Badge>자료실</Badge>
<Badge color="green">진행중</Badge>   {/* 상태만 컬러 */}

{/* 페이지네이션 — React 컴포넌트 */}
<Pagination currentPage={page} totalPages={totalPages} onPageChange={setPage} />

5. 마이그레이션 가이드

작업 순서

  1. 파일 내 raw Tailwind 문자열을 wing-* 클래스로 교체
  2. 반복되는 행동 패턴 (모달, 페이지네이션, 테이블)을 React 컴포넌트로 교체
  3. 의미 없는 컬러 배지를 <Badge> (neutral)로 교체
  4. 브라우저에서 시각적 동일성 확인

판단 기준

순수 시각 + 5회 이상 반복 → wing-* CSS 클래스
동작 포함 + 5회 이상 반복 → React 컴포넌트
1-2회 사용              → 인라인 Tailwind 유지
도메인 전용 시각         → components.css (유지)