docs: 디자인 시스템 SSOT 개발 지침 + 릴리즈 노트 갱신
CLAUDE.md '디자인 시스템' 섹션 신규: - 쇼케이스(/design-system.html)를 단일 진실 공급원으로 명시 - 공통 컴포넌트 목록 (Button/Input/Select/PageContainer/PageHeader/Badge 등) - 카탈로그 API 사용 패턴 (getAlertLevelIntent/Label 등) - CSS 작성 6대 규칙 (인라인 색상 금지, 하드코딩 Tailwind 색상 금지, className override 정책, 시맨틱 토큰 우선, !important 절대 금지, vendor prefix 수동 대응) - 페이지 작성 표준 템플릿 - 접근성 (WCAG 2.1 Level A) 필수 사항 - 변경 사이클 (쇼케이스 → 카탈로그 → 컴포넌트 → 자동 반영) - 금지 패턴 체크리스트 RELEASE-NOTES.md [Unreleased]에 디자인 시스템 SSOT 작업 항목 추가: - 쇼케이스 페이지 + 신규 공통 컴포넌트 + 중앙 레지스트리 - 35+ feature 페이지 마이그레이션 - Badge intent 팔레트 테마 분리 - 접근성 전수 처리 (Select TypeScript 강제 등)
This commit is contained in:
부모
f4d56ea891
커밋
479a4bfc56
120
CLAUDE.md
120
CLAUDE.md
@ -101,6 +101,126 @@ make format # 프론트 prettier
|
||||
- 커밋: Conventional Commits (한국어), `.githooks/commit-msg` 검증
|
||||
- pre-commit: `frontend/` 디렉토리 기준 TypeScript + ESLint 검증
|
||||
|
||||
## 디자인 시스템 (필수 준수)
|
||||
|
||||
프론트엔드 UI는 **`/design-system.html` 쇼케이스를 단일 진실 공급원(SSOT)** 으로 한다.
|
||||
모든 페이지/컴포넌트는 쇼케이스에 정의된 컴포넌트와 토큰만 사용한다.
|
||||
|
||||
### 쇼케이스 진입
|
||||
- **URL**: https://kcg-ai-monitoring.gc-si.dev/design-system.html (메인 SPA와 별개)
|
||||
- **소스**: `frontend/design-system.html` + `frontend/src/designSystemMain.tsx` + `frontend/src/design-system/`
|
||||
- **추적 ID 체계**: `TRK-<카테고리>-<슬러그>` (예: `TRK-BADGE-critical-sm`)
|
||||
- 호버 시 툴팁, "ID 복사 모드"에서 클릭 시 클립보드 복사
|
||||
- URL 해시 딥링크: `#trk=TRK-BUTTON-primary-md`
|
||||
- **단축키 `A`**: 다크/라이트 테마 토글
|
||||
|
||||
### 공통 컴포넌트 (반드시 사용)
|
||||
|
||||
| 컴포넌트 | 위치 | 용도 |
|
||||
|---|---|---|
|
||||
| `Badge` | `@shared/components/ui/badge` | 8 intent × 4 size, **className으로 색상 override 금지** |
|
||||
| `Button` | `@shared/components/ui/button` | 5 variant × 3 size (primary/secondary/ghost/outline/destructive) |
|
||||
| `Input` / `Select` / `Textarea` / `Checkbox` / `Radio` | `@shared/components/ui/` | 폼 요소 (Select는 aria-label 타입 강제) |
|
||||
| `TabBar` / `TabButton` | `@shared/components/ui/tabs` | underline / pill / segmented 3 variant |
|
||||
| `Card` / `CardHeader` / `CardTitle` / `CardContent` | `@shared/components/ui/card` | 4 variant |
|
||||
| `PageContainer` | `@shared/components/layout` | 페이지 루트 (size sm/md/lg + fullBleed) |
|
||||
| `PageHeader` | `@shared/components/layout` | 페이지 헤더 (icon + title + description + demo + actions) |
|
||||
| `Section` | `@shared/components/layout` | Card + CardHeader + CardTitle + CardContent 단축 |
|
||||
|
||||
### 카탈로그 기반 라벨/색상
|
||||
|
||||
분류 데이터는 `frontend/src/shared/constants/`의 19+ 카탈로그를 참조한다.
|
||||
중앙 레지스트리는 `catalogRegistry.ts`이며, 쇼케이스가 자동 열거한다.
|
||||
|
||||
```tsx
|
||||
import { Badge } from '@shared/components/ui/badge';
|
||||
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
|
||||
|
||||
<Badge intent={getAlertLevelIntent(event.level)} size="sm">
|
||||
{getAlertLevelLabel(event.level, t, lang)}
|
||||
</Badge>
|
||||
```
|
||||
|
||||
ad-hoc 한글/영문 상태 문자열은 `getStatusIntent()` (statusIntent.ts) 사용.
|
||||
숫자 위험도는 `getRiskIntent(0~100)` 사용.
|
||||
|
||||
### CSS 작성 규칙
|
||||
|
||||
1. **인라인 색상 금지** — `style={{ backgroundColor: '#ef4444' }}` 같은 정적 색상은 작성 금지
|
||||
- 예외: 동적 데이터 기반 (`backgroundColor: meta.hex`, progress width `${value}%`)
|
||||
2. **하드코딩 Tailwind 색상 금지** — `bg-red-500/20 text-red-400` 같은 직접 작성 금지
|
||||
- 반드시 Badge intent 또는 카탈로그 API 호출
|
||||
3. **className override 정책**
|
||||
- ✅ 레이아웃/위치 보정: `<Badge intent="info" className="w-full justify-center">`
|
||||
- ❌ 색상/글자 크기 override: `<Badge intent="info" className="bg-red-500 text-xs">`
|
||||
4. **시맨틱 토큰 우선** — `theme.css @layer utilities`의 토큰 사용
|
||||
- `text-heading` / `text-label` / `text-hint` / `text-on-vivid` / `text-on-bright`
|
||||
- `bg-surface-raised` / `bg-surface-overlay` / `bg-card` / `bg-background`
|
||||
5. **!important 절대 금지** — `cn()` + `tailwind-merge`로 충돌 해결
|
||||
6. **`-webkit-` 벤더 prefix** — 수동 작성 CSS는 `backdrop-filter` 등 prefix 직접 추가 (Tailwind는 자동)
|
||||
|
||||
### 페이지 작성 표준 템플릿
|
||||
|
||||
```tsx
|
||||
import { PageContainer, PageHeader, Section } from '@shared/components/layout';
|
||||
import { Button } from '@shared/components/ui/button';
|
||||
import { Badge } from '@shared/components/ui/badge';
|
||||
import { getAlertLevelIntent, getAlertLevelLabel } from '@shared/constants/alertLevels';
|
||||
import { Shield, Plus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function MyPage() {
|
||||
const { t, i18n } = useTranslation('common');
|
||||
const lang = i18n.language as 'ko' | 'en';
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
icon={Shield}
|
||||
iconColor="text-blue-400"
|
||||
title="페이지 제목"
|
||||
description="페이지 설명"
|
||||
actions={
|
||||
<Button variant="primary" icon={<Plus className="w-4 h-4" />}>
|
||||
추가
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Section title="데이터 목록">
|
||||
<Badge intent={getAlertLevelIntent('HIGH')} size="sm">
|
||||
{getAlertLevelLabel('HIGH', t, lang)}
|
||||
</Badge>
|
||||
</Section>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 접근성 (a11y) 필수
|
||||
|
||||
- **`<button>`**: `type="button"` 명시 + 아이콘 전용은 `aria-label` 필수
|
||||
- **`<input>` / `<textarea>` / `<select>`**: `aria-label` 또는 `<label htmlFor>` 필수
|
||||
- **`Select` 컴포넌트**: TypeScript union type으로 `aria-label`/`aria-labelledby`/`title` 중 하나 컴파일 타임 강제
|
||||
- 위반 시 WCAG 2.1 Level A 위반 + axe DevTools 경고
|
||||
|
||||
### 변경 사이클
|
||||
|
||||
1. 디자인 변경이 필요하면 → **쇼케이스에서 먼저 미세조정** → 시각 검증
|
||||
2. 카탈로그 라벨/색상 변경 → `shared/constants/*` 또는 `variantMeta.ts`만 수정
|
||||
3. 컴포넌트 변형 추가 → `lib/theme/variants.ts` CVA에만 추가
|
||||
4. 실 페이지는 **컴포넌트만 사용**, 변경 시 자동 반영
|
||||
|
||||
### 금지 패턴 체크리스트
|
||||
|
||||
- ❌ `<Badge className="bg-red-500/20 text-red-400">` → `<Badge intent="critical">`
|
||||
- ❌ `<button className="bg-blue-600 ...">` → `<Button variant="primary">`
|
||||
- ❌ `<input className="bg-surface ...">` → `<Input>`
|
||||
- ❌ `<div className="p-5 space-y-4">` 페이지 루트 → `<PageContainer>`
|
||||
- ❌ `-m-4` negative margin 해킹 → `<PageContainer fullBleed>`
|
||||
- ❌ `style={{ color: '#ef4444' }}` 정적 색상 → 시맨틱 토큰 또는 카탈로그
|
||||
- ❌ `!important` → `cn()` 활용
|
||||
- ❌ 페이지 내 `const STATUS_COLORS = {...}` 로컬 재정의 → shared/constants 카탈로그
|
||||
|
||||
## System Flow 뷰어 (개발 단계용)
|
||||
|
||||
- **URL**: https://kcg-ai-monitoring.gc-si.dev/system-flow.html (메인 SPA와 별개)
|
||||
|
||||
@ -5,6 +5,25 @@
|
||||
## [Unreleased]
|
||||
|
||||
### 추가
|
||||
- **디자인 시스템 쇼케이스** (`/design-system.html`) — UI 단일 진실 공급원(SSOT)
|
||||
- 별도 Vite entry, 메인 SPA와 분리 (designSystem-*.js 54KB)
|
||||
- 10개 섹션: Intro / Token / Typography / Badge / Button / Form / Card / Layout / Catalog / Guide
|
||||
- 추적 ID 체계 `TRK-<카테고리>-<슬러그>` (예: `TRK-BADGE-critical-sm`)
|
||||
- 호버 시 툴팁, "ID 복사 모드", URL 해시 딥링크 `#trk=...`
|
||||
- 단축키 `A`로 다크/라이트 토글
|
||||
- 한글/영문 라벨 병기로 카탈로그 검토 용이
|
||||
- **신규 공통 컴포넌트**:
|
||||
- `Button` (5 variant × 3 size = 15) `@shared/components/ui/button`
|
||||
- `Input` / `Select` / `Textarea` / `Checkbox` / `Radio` `@shared/components/ui/`
|
||||
- `TabBar` / `TabButton` (underline / pill / segmented 3 variant) `@shared/components/ui/tabs`
|
||||
- `PageContainer` (size sm/md/lg + fullBleed) `@shared/components/layout/PageContainer`
|
||||
- `PageHeader` (icon + title + description + demo + actions) `@shared/components/layout/PageHeader`
|
||||
- `Section` (Card 단축) `@shared/components/layout/Section`
|
||||
- **중앙 레지스트리**:
|
||||
- `catalogRegistry.ts` — 23개 카탈로그 메타 (id/title/description/source/items 자동 enumerate)
|
||||
- `variantMeta.ts` — Badge intent 8종 + Button variant 5종 의미 가이드
|
||||
- `statusIntent.ts` — 한글/영문 ad-hoc 상태 → BadgeIntent + getRiskIntent(0~100)
|
||||
- **4 catalog에 intent 필드 추가**: eventStatuses / enforcementResults / enforcementActions / patrolStatuses + getXxxIntent() 헬퍼
|
||||
- **UI 공통 카탈로그 19종** (`frontend/src/shared/constants/`) — 백엔드 enum/code_master 기반 SSOT
|
||||
- violation/alert/event/enforcement/patrol/engine/userRole/device/parentResolution/
|
||||
modelDeployment/gearGroup/darkVessel/httpStatus/userAccount/loginResult/permission/
|
||||
@ -29,6 +48,10 @@
|
||||
- V016 parent workflow 누락 컬럼 일괄 추가 (17+ 컬럼, candidate_mmsi generated column)
|
||||
|
||||
### 변경
|
||||
- **35+ feature 페이지 PageContainer/PageHeader 마이그레이션** — admin/detection/enforcement/field-ops/patrol/statistics/ai-operations/parent-inference/dashboard/monitoring/surveillance/vessel/risk-assessment 전체
|
||||
- **VesselDetail `-m-4` negative margin 해킹 → `<PageContainer fullBleed>`** 정리
|
||||
- **LiveMapView fullBleed 패턴** 적용
|
||||
- **Badge intent 팔레트 테마 분리**: 라이트(파스텔 `bg-X-100 text-X-900`) + 다크(translucent `bg-X-500/20 text-X-400`)
|
||||
- **40+ 페이지 Badge/시맨틱 토큰 마이그레이션**
|
||||
- Badge className 직접 작성 → intent/size prop 변환
|
||||
- 컬러풀 액션 버튼 → `text-on-vivid` (흰색), 검색/필터 버튼 → `bg-blue-400 text-on-bright`
|
||||
@ -39,6 +62,17 @@
|
||||
- MonitoringDashboard `PagePagination` 제거 (데이터 페이지네이션 오해 해소)
|
||||
|
||||
### 수정
|
||||
- **접근성 (WCAG 2.1 Level A)** — axe DevTools 위반 전수 처리:
|
||||
- `<Select>` 컴포넌트 TypeScript union 타입으로 `aria-label`/`aria-labelledby`/`title` 컴파일 강제
|
||||
- 네이티브 `<select>` 5곳 aria-label
|
||||
- 아이콘 전용 `<button>` 16곳 aria-label
|
||||
- `<input>`/`<textarea>` 28곳 aria-label (placeholder 자동 복제 포함)
|
||||
- AIModelManagement 토글 → `role="switch"` + `aria-checked`
|
||||
- **Badge className 위반 37건 전수 제거** — `<Badge intent="...">` 패턴으로 통일
|
||||
- **하드코딩 `bg-X-500/20 text-X-400` 56곳 제거** — 카탈로그 API + intent 사용
|
||||
- **인라인 `<button>` type 누락 86곳 보정**
|
||||
- **CSS Safari 호환성**: `backdrop-filter` `-webkit-` prefix 추가 (디자인 쇼케이스)
|
||||
- `trk-pulse` keyframe outline-color → opacity (composite-only 최적화)
|
||||
- Dashboard RiskBar 단위 버그 (0~100 정수를 `*100` 하던 코드 → 범위 감지)
|
||||
- ReportManagement, TransferDetection `p-5 space-y-4` padding 복구
|
||||
- EnforcementHistory 그리드 minmax 적용으로 컬럼 잘림 해소
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user