diff --git a/frontend/src/common/styles/base.css b/frontend/src/common/styles/base.css index aefe5f5..bd49569 100644 --- a/frontend/src/common/styles/base.css +++ b/frontend/src/common/styles/base.css @@ -23,6 +23,84 @@ --fM: JetBrains Mono, monospace; --rS: 6px; --rM: 8px; + + /* === Design Token System === */ + + /* Static */ + --static-black: #131415; + --static-white: #ffffff; + + /* Gray */ + --gray-100: #f1f5f9; + --gray-200: #e2e8f0; + --gray-300: #cbd5e1; + --gray-400: #94a3b8; + --gray-500: #64748b; + --gray-600: #475569; + --gray-700: #334155; + --gray-800: #1e293b; + --gray-900: #0f172a; + --gray-1000: #020617; + + /* Blue */ + --blue-100: #dbeafe; + --blue-200: #bfdbfe; + --blue-300: #93c5fd; + --blue-400: #60a5fa; + --blue-500: #3b82f6; + --blue-600: #2563eb; + --blue-700: #1d4ed8; + --blue-800: #1e40af; + --blue-900: #1e3a8a; + --blue-1000: #172554; + + /* Green */ + --green-100: #dcfce7; + --green-200: #bbf7d0; + --green-300: #86efac; + --green-400: #4ade80; + --green-500: #22c55e; + --green-600: #16a34a; + --green-700: #15803d; + --green-800: #166534; + --green-900: #14532d; + --green-1000: #052e16; + + /* Yellow */ + --yellow-100: #fef9c3; + --yellow-200: #fef08a; + --yellow-300: #fde047; + --yellow-400: #facc15; + --yellow-500: #eab308; + --yellow-600: #ca8a04; + --yellow-700: #a16207; + --yellow-800: #854d0e; + --yellow-900: #713f12; + --yellow-1000: #422006; + + /* Red */ + --red-100: #fee2e2; + --red-200: #fecaca; + --red-300: #fca5a5; + --red-400: #f87171; + --red-500: #ef4444; + --red-600: #dc2626; + --red-700: #b91c1c; + --red-800: #991b1b; + --red-900: #7f1d1d; + --red-1000: #450a0a; + + /* Purple */ + --purple-100: #f3e8ff; + --purple-200: #e9d5ff; + --purple-300: #d8b4fe; + --purple-400: #c084fc; + --purple-500: #a855f7; + --purple-600: #9333ea; + --purple-700: #7e22ce; + --purple-800: #6b21a8; + --purple-900: #581c87; + --purple-1000: #3b0764; } * { diff --git a/frontend/src/pages/design/ColorPaletteContent.tsx b/frontend/src/pages/design/ColorPaletteContent.tsx index b0ac543..803f4e7 100644 --- a/frontend/src/pages/design/ColorPaletteContent.tsx +++ b/frontend/src/pages/design/ColorPaletteContent.tsx @@ -1,158 +1,434 @@ -// ColorPaletteContent.tsx — WING-OPS Color Palette 콘텐츠 (다크/라이트 테마 지원) +// ColorPaletteContent.tsx — WING-OPS Color 파운데이션 페이지 (다크/라이트 테마 지원) +import { useState } from 'react'; import type { DesignTheme } from './designTheme'; -// ---------- 데이터 타입 ---------- +// ---------- 타입 ---------- -interface PrimitiveColorStep { +interface ColorStep { + step: number; + color: string; +} + +interface Marker { + step: number; label: string; +} + +interface ContrastRating { + step: number; + rating: string; +} + +interface ColorScaleBarProps { + steps: ColorStep[]; + markers?: Marker[]; + contrastRatings?: ContrastRating[]; + darkBg?: boolean; + isDark: boolean; +} + +interface ColorToken { + name: string; hex: string; } -interface PrimitiveColorGroup { - name: string; - steps: PrimitiveColorStep[]; +interface ColorTokenGroup { + title: string; + tokens: ColorToken[]; } -interface SemanticToken { - token: string; - dark: string; - light: string; - usage: string[]; -} +// ---------- 데이터 ---------- -interface SemanticCategory { - name: string; - tokens: SemanticToken[]; -} +const TRANSPARENCY_STEPS = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]; -// ---------- Primitive Colors 데이터 ---------- +const GRAY_STEPS: ColorStep[] = [ + { step: 0, color: '#f8fafc' }, + { step: 5, color: '#f1f5f9' }, + { step: 10, color: '#e2e8f0' }, + { step: 20, color: '#cbd5e1' }, + { step: 30, color: '#94a3b8' }, + { step: 40, color: '#64748b' }, + { step: 50, color: '#475569' }, + { step: 60, color: '#334155' }, + { step: 70, color: '#1e293b' }, + { step: 80, color: '#0f172a' }, + { step: 90, color: '#0c1322' }, + { step: 95, color: '#070d19' }, + { step: 100, color: '#020617' }, +]; -const PRIMITIVE_COLORS: PrimitiveColorGroup[] = [ +const PRIMARY_STEPS: ColorStep[] = [ + { step: 0, color: '#ecfeff' }, + { step: 5, color: '#cffafe' }, + { step: 10, color: '#a5f3fc' }, + { step: 20, color: '#67e8f9' }, + { step: 30, color: '#22d3ee' }, + { step: 40, color: '#06b6d4' }, + { step: 50, color: '#0891b2' }, + { step: 60, color: '#0e7490' }, + { step: 70, color: '#155e75' }, + { step: 80, color: '#164e63' }, + { step: 90, color: '#134152' }, + { step: 95, color: '#0c3140' }, + { step: 100, color: '#042f2e' }, +]; + +const SECONDARY_STEPS: ColorStep[] = [ + { step: 0, color: '#f0f4fa' }, + { step: 5, color: '#e1e8f4' }, + { step: 10, color: '#c3d1e8' }, + { step: 20, color: '#8da4c8' }, + { step: 30, color: '#5a7eb0' }, + { step: 40, color: '#3d6399' }, + { step: 50, color: '#2d4f80' }, + { step: 60, color: '#1e3a66' }, + { step: 70, color: '#1a2236' }, + { step: 80, color: '#121929' }, + { step: 90, color: '#0f1524' }, + { step: 95, color: '#0a0e1a' }, + { step: 100, color: '#050811' }, +]; + +const PRIMARY_CONTRAST_RATINGS: ContrastRating[] = PRIMARY_STEPS.map(({ step }) => ({ + step, + rating: step <= 30 ? 'AAA' : step <= 50 ? 'AA' : 'AAA', +})); + +const SECONDARY_CONTRAST_RATINGS: ContrastRating[] = SECONDARY_STEPS.map(({ step }) => ({ + step, + rating: step <= 30 ? 'AAA' : step <= 50 ? 'AA' : 'AAA', +})); + +const COLOR_TOKEN_GROUPS: ColorTokenGroup[] = [ { - name: 'Navy', - steps: [ - { label: '0', hex: '#0a0e1a' }, - { label: '1', hex: '#0f1524' }, - { label: '2', hex: '#121929' }, - { label: '3', hex: '#1a2236' }, - { label: 'hover', hex: '#1e2844' }, + title: 'Static', + tokens: [ + { name: 'static.black', hex: '#131415' }, + { name: 'static.white', hex: '#ffffff' }, ], }, { - name: 'Cyan', - steps: [ - { label: '00', hex: '#ecfeff' }, { label: '10', hex: '#cffafe' }, - { label: '20', hex: '#a5f3fc' }, { label: '30', hex: '#67e8f9' }, - { label: '40', hex: '#22d3ee' }, { label: '50', hex: '#06b6d4' }, - { label: '60', hex: '#0891b2' }, { label: '70', hex: '#0e7490' }, - { label: '80', hex: '#155e75' }, { label: '90', hex: '#164e63' }, - { label: '100', hex: '#083344' }, + title: 'Gray', + tokens: [ + { name: 'gray.100', hex: '#f1f5f9' }, + { name: 'gray.200', hex: '#e2e8f0' }, + { name: 'gray.300', hex: '#cbd5e1' }, + { name: 'gray.400', hex: '#94a3b8' }, + { name: 'gray.500', hex: '#64748b' }, + { name: 'gray.600', hex: '#475569' }, + { name: 'gray.700', hex: '#334155' }, + { name: 'gray.800', hex: '#1e293b' }, + { name: 'gray.900', hex: '#0f172a' }, + { name: 'gray.1000', hex: '#020617' }, ], }, { - name: 'Blue', - steps: [ - { label: '00', hex: '#eff6ff' }, { label: '10', hex: '#dbeafe' }, - { label: '20', hex: '#bfdbfe' }, { label: '30', hex: '#93c5fd' }, - { label: '40', hex: '#60a5fa' }, { label: '50', hex: '#3b82f6' }, - { label: '60', hex: '#2563eb' }, { label: '70', hex: '#1d4ed8' }, - { label: '80', hex: '#1e40af' }, { label: '90', hex: '#1e3a8a' }, - { label: '100', hex: '#172554' }, + title: 'Blue', + tokens: [ + { name: 'blue.100', hex: '#dbeafe' }, + { name: 'blue.200', hex: '#bfdbfe' }, + { name: 'blue.300', hex: '#93c5fd' }, + { name: 'blue.400', hex: '#60a5fa' }, + { name: 'blue.500', hex: '#3b82f6' }, + { name: 'blue.600', hex: '#2563eb' }, + { name: 'blue.700', hex: '#1d4ed8' }, + { name: 'blue.800', hex: '#1e40af' }, + { name: 'blue.900', hex: '#1e3a8a' }, + { name: 'blue.1000', hex: '#172554' }, ], }, { - name: 'Red', - steps: [ - { label: '00', hex: '#fef2f2' }, { label: '10', hex: '#fee2e2' }, - { label: '20', hex: '#fecaca' }, { label: '30', hex: '#fca5a5' }, - { label: '40', hex: '#f87171' }, { label: '50', hex: '#ef4444' }, - { label: '60', hex: '#dc2626' }, { label: '70', hex: '#b91c1c' }, - { label: '80', hex: '#991b1b' }, { label: '90', hex: '#7f1d1d' }, - { label: '100', hex: '#450a0a' }, + title: 'Green', + tokens: [ + { name: 'green.100', hex: '#dcfce7' }, + { name: 'green.200', hex: '#bbf7d0' }, + { name: 'green.300', hex: '#86efac' }, + { name: 'green.400', hex: '#4ade80' }, + { name: 'green.500', hex: '#22c55e' }, + { name: 'green.600', hex: '#16a34a' }, + { name: 'green.700', hex: '#15803d' }, + { name: 'green.800', hex: '#166534' }, + { name: 'green.900', hex: '#14532d' }, + { name: 'green.1000', hex: '#052e16' }, ], }, { - name: 'Green', - steps: [ - { label: '00', hex: '#f0fdf4' }, { label: '10', hex: '#dcfce7' }, - { label: '20', hex: '#bbf7d0' }, { label: '30', hex: '#86efac' }, - { label: '40', hex: '#4ade80' }, { label: '50', hex: '#22c55e' }, - { label: '60', hex: '#16a34a' }, { label: '70', hex: '#15803d' }, - { label: '80', hex: '#166534' }, { label: '90', hex: '#14532d' }, - { label: '100', hex: '#052e16' }, + title: 'Yellow', + tokens: [ + { name: 'yellow.100', hex: '#fef9c3' }, + { name: 'yellow.200', hex: '#fef08a' }, + { name: 'yellow.300', hex: '#fde047' }, + { name: 'yellow.400', hex: '#facc15' }, + { name: 'yellow.500', hex: '#eab308' }, + { name: 'yellow.600', hex: '#ca8a04' }, + { name: 'yellow.700', hex: '#a16207' }, + { name: 'yellow.800', hex: '#854d0e' }, + { name: 'yellow.900', hex: '#713f12' }, + { name: 'yellow.1000', hex: '#422006' }, ], }, { - name: 'Orange', - steps: [ - { label: '00', hex: '#fff7ed' }, { label: '10', hex: '#ffedd5' }, - { label: '20', hex: '#fed7aa' }, { label: '30', hex: '#fdba74' }, - { label: '40', hex: '#fb923c' }, { label: '50', hex: '#f97316' }, - { label: '60', hex: '#ea580c' }, { label: '70', hex: '#c2410c' }, - { label: '80', hex: '#9a3412' }, { label: '90', hex: '#7c2d12' }, - { label: '100', hex: '#431407' }, + title: 'Red', + tokens: [ + { name: 'red.100', hex: '#fee2e2' }, + { name: 'red.200', hex: '#fecaca' }, + { name: 'red.300', hex: '#fca5a5' }, + { name: 'red.400', hex: '#f87171' }, + { name: 'red.500', hex: '#ef4444' }, + { name: 'red.600', hex: '#dc2626' }, + { name: 'red.700', hex: '#b91c1c' }, + { name: 'red.800', hex: '#991b1b' }, + { name: 'red.900', hex: '#7f1d1d' }, + { name: 'red.1000', hex: '#450a0a' }, ], }, { - name: 'Yellow', - steps: [ - { label: '00', hex: '#fefce8' }, { label: '10', hex: '#fef9c3' }, - { label: '20', hex: '#fef08a' }, { label: '30', hex: '#fde047' }, - { label: '40', hex: '#facc15' }, { label: '50', hex: '#eab308' }, - { label: '60', hex: '#ca8a04' }, { label: '70', hex: '#a16207' }, - { label: '80', hex: '#854d0e' }, { label: '90', hex: '#713f12' }, - { label: '100', hex: '#422006' }, + title: 'Purple', + tokens: [ + { name: 'purple.100', hex: '#f3e8ff' }, + { name: 'purple.200', hex: '#e9d5ff' }, + { name: 'purple.300', hex: '#d8b4fe' }, + { name: 'purple.400', hex: '#c084fc' }, + { name: 'purple.500', hex: '#a855f7' }, + { name: 'purple.600', hex: '#9333ea' }, + { name: 'purple.700', hex: '#7e22ce' }, + { name: 'purple.800', hex: '#6b21a8' }, + { name: 'purple.900', hex: '#581c87' }, + { name: 'purple.1000', hex: '#3b0764' }, ], }, ]; -// ---------- Semantic Colors 데이터 ---------- +// ---------- 헬퍼 ---------- -const SEMANTIC_CATEGORIES: SemanticCategory[] = [ - { - name: 'Text', - tokens: [ - { token: 'text-1', dark: '#edf0f7', light: '#0f172a', usage: ['기본 텍스트 색상', '아이콘 기본 색상'] }, - { token: 'text-2', dark: '#b0b8cc', light: '#475569', usage: ['보조 텍스트 색상'] }, - { token: 'text-3', dark: '#8690a6', light: '#94a3b8', usage: ['비활성 텍스트', '플레이스홀더'] }, - ], - }, - { - name: 'Background', - tokens: [ - { token: 'bg-0', dark: '#0a0e1a', light: '#f8fafc', usage: ['페이지 배경'] }, - { token: 'bg-1', dark: '#0f1524', light: '#ffffff', usage: ['사이드바', '패널'] }, - { token: 'bg-2', dark: '#121929', light: '#f1f5f9', usage: ['테이블 헤더'] }, - { token: 'bg-3', dark: '#1a2236', light: '#e2e8f0', usage: ['카드 배경'] }, - { token: 'bg-hover', dark: '#1e2844', light: '#cbd5e1', usage: ['호버 상태'] }, - ], - }, - { - name: 'Border', - tokens: [ - { token: 'border', dark: '#1e2a42', light: '#cbd5e1', usage: ['기본 구분선'] }, - { token: 'border-light', dark: '#2a3a5c', light: '#e2e8f0', usage: ['연한 구분선'] }, - ], - }, - { - name: 'Accent', - tokens: [ - { token: 'primary-cyan', dark: '#06b6d4', light: '#06b6d4', usage: ['주요 강조', '활성 상태'] }, - { token: 'primary-blue', dark: '#3b82f6', light: '#0891b2', usage: ['보조 강조'] }, - { token: 'primary-purple', dark: '#a855f7', light: '#6366f1', usage: ['3차 강조'] }, - ], - }, - { - name: 'Status', - tokens: [ - { token: 'status-red', dark: '#ef4444', light: '#dc2626', usage: ['위험', '삭제'] }, - { token: 'status-orange', dark: '#f97316', light: '#c2410c', usage: ['주의'] }, - { token: 'status-yellow', dark: '#eab308', light: '#b45309', usage: ['경고'] }, - { token: 'status-green', dark: '#22c55e', light: '#047857', usage: ['정상', '성공'] }, - ], - }, -]; +const hexToRgb = (hex: string): string => { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgb(${r}, ${g}, ${b})`; +}; + +// ---------- 내부 컴포넌트: ColorScaleBar ---------- + +const ColorScaleBar = ({ + steps, + markers, + contrastRatings, + darkBg = false, + isDark, +}: ColorScaleBarProps) => { + const badgeBg = isDark ? '#374151' : '#374151'; + const badgeText = isDark ? '#e5e7eb' : '#fff'; + + const getContrastRating = (step: number): string | undefined => { + return contrastRatings?.find((r) => r.step === step)?.rating; + }; + + const getMarker = (step: number): Marker | undefined => { + return markers?.find((m) => m.step === step); + }; + + return ( +
+ Foundations +
- WING-OPS 인터페이스에서 사용되는 색상 체계입니다. Primitive Token(원시 팔레트)과 Semantic Token(의미 기반 토큰) 두 계층으로 구성됩니다.
+ 브랜드 아이덴티티를 유지하기 위한 컬러 사용 기준입니다.
핵심 정보를 강조하고 현재 상태를 명확하게 전달합니다.
+ Primary 색상은 해양 방제 시스템의 핵심 인터랙션 요소에 사용됩니다. Cyan~Blue 그라디언트가 주요 액션 버튼과 강조 요소에 적용됩니다. +
+ ++ Light Mode +
++ Dark Mode +
++ Secondary 색상은 UI의 배경과 구조적 요소에 사용됩니다. Navy 계열로 다크 모드의 깊이감과 계층 구조를 표현합니다. +
+ ++ Light Mode +
++ Dark Mode +
++ Gray 색상은 주로 배경, 텍스트, 구분 선에 사용되며, 시각적 집중을 방해하지 않고 콘텐츠에 초점을 맞추도록 도와주는 중립적인 색상이다. +
++ 표준형 스타일의 그레이 색상은 주요 색상과 선명한 모드에서의 조화를 고려해 블루 그레이 계열을 사용한다. +
+ ++ 투명도와 음영을 활용하여 정보의 집중도를 조절합니다. 배경의 음영 처리는 투명도 65%를 사용합니다. +
+ +