generated from gc/template-java-maven
- 디자인 시스템 CSS 변수 토큰 적용 (success/warning/danger/info) - PeriodFilter 공통 컴포넌트 생성 및 통계 페이지 적용 - SERVICE_BADGE_VARIANTS 공통 상수 추출 - 통계/요청로그/키관리/관리자 페이지 레퍼런스 디자인 반영 - 테이블 규격 통일 (h-8/h-7, px-3 py-1, text-xs, Button xs) - 타이틀 아이콘 전체 페이지 통일 - 카드 테두리 디자인 통일 (border + rounded-xl) - FHD 1920x1080 최적화
251 lines
7.6 KiB
Markdown
251 lines
7.6 KiB
Markdown
# 컴포넌트 스펙
|
|
|
|
모든 컴포넌트는 `cn()` 유틸리티(`src/utils/cn.ts`)를 사용하고, CSS 변수 토큰을 통해 색상을 지정한다.
|
|
|
|
---
|
|
|
|
## Button
|
|
|
|
### 변형 (Variant)
|
|
|
|
| Variant | 배경 | 텍스트 | 테두리 | 용도 |
|
|
|---------|------|--------|--------|------|
|
|
| `primary` | `--color-primary` | white | none | 주요 CTA |
|
|
| `secondary` | `--color-secondary` | `--color-secondary-text` | none | 보조 액션 |
|
|
| `accent` | `--color-accent` | `--color-accent-text` | none | 포인트 액션 |
|
|
| `outline` | transparent | `--color-primary` | `--color-primary` | 조용한 강조 |
|
|
| `ghost` | transparent | `--color-text-secondary` | none | 최소 강조 |
|
|
| `danger` | `--color-danger` | white | none | 삭제, 경고 |
|
|
|
|
### 사이즈
|
|
|
|
| Size | padding | font-size | height | border-radius |
|
|
|------|---------|-----------|--------|---------------|
|
|
| `sm` | `px-3 py-1.5` | `text-sm` | 32px | `rounded-md` |
|
|
| `md` | `px-4 py-2` | `text-base` | 40px | `rounded-lg` |
|
|
| `lg` | `px-6 py-3` | `text-lg` | 48px | `rounded-lg` |
|
|
|
|
### 상태
|
|
|
|
- **disabled**: `opacity-50 cursor-not-allowed pointer-events-none`
|
|
- **loading**: 스피너 아이콘 표시, 텍스트 유지, disabled 처리
|
|
- **hover**: 각 variant에 맞는 `-hover` 토큰 적용
|
|
- **active**: 각 variant에 맞는 `-active` 토큰 적용
|
|
- **focus-visible**: `ring-2 ring-[var(--color-primary)] ring-offset-2`
|
|
|
|
### 구현 예시
|
|
|
|
```tsx
|
|
const buttonVariants = {
|
|
primary: 'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)] active:bg-[var(--color-primary-active)]',
|
|
secondary: 'bg-[var(--color-secondary)] text-[var(--color-secondary-text)] hover:bg-[var(--color-secondary-hover)]',
|
|
accent: 'bg-[var(--color-accent)] text-[var(--color-accent-text)] hover:bg-[var(--color-accent-hover)]',
|
|
outline: 'border border-[var(--color-primary)] text-[var(--color-primary)] hover:bg-[var(--color-primary-subtle)]',
|
|
ghost: 'text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-surface)] hover:text-[var(--color-text-primary)]',
|
|
danger: 'bg-[var(--color-danger)] text-white hover:opacity-90',
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Badge
|
|
|
|
상태, 카테고리, 태그 표시에 사용한다. Shape에 따라 Pill과 Filled로 구분한다.
|
|
|
|
### Pill 변형 (rounded-full) — 상태 표시용
|
|
|
|
| Variant | 용도 | Light 배경 | Dark 배경 |
|
|
|---------|------|-----------|-----------|
|
|
| `default` | 기본 | `--color-bg-base` | `--color-bg-base` |
|
|
| `success` | 성공/활성 | green-50 | green-500/15 |
|
|
| `warning` | 경고/대기 | amber-50 | amber-500/15 |
|
|
| `danger` | 에러/비활성 | red-50 | red-500/15 |
|
|
| `info` | 정보/식별자 | blue-50 | blue-500/15 |
|
|
|
|
### Filled 변형 (rounded-md) — 카테고리/서비스 라벨용
|
|
|
|
Chart Palette와 1:1 매핑. `className="rounded-md"`를 추가하여 Filled shape 적용.
|
|
|
|
| Variant | Chart # | Light 배경 | Light 텍스트 | Dark 배경 | Dark 텍스트 |
|
|
|---------|---------|-----------|-------------|-----------|-------------|
|
|
| `blue` | 1 | primary-100 | primary-900 | primary-900 | primary-300 |
|
|
| `gold` | 2 | accent-100 | accent-900 | accent-900 | accent-300 |
|
|
| `rose` | 3 | #fce4ec | #B5607D | rgba(212,131,155,0.12) | #D4839B |
|
|
| `teal` | 4 | #e0f2f1 | #2E7D6E | rgba(91,181,166,0.12) | #5BB5A6 |
|
|
| `lavender` | 5 | #f3e8ff | #7B5DA5 | rgba(183,159,212,0.12) | #B79FD4 |
|
|
| `coral` | 6 | #fff3e0 | #A86440 | rgba(212,149,107,0.12) | #D4956B |
|
|
|
|
> **주의**: success, warning, danger, info는 Pill 전용. Filled에 사용 금지.
|
|
|
|
### 스펙
|
|
|
|
- **Pill**: `rounded-full`, `px-2 py-0.5`, `text-xs font-medium`
|
|
- **Filled**: `rounded-md`, `px-3 py-1`, `text-xs font-medium`
|
|
- 사이즈: `sm` (px-1.5 py-0.5 text-[10px]), `md` (px-2 py-0.5 text-xs)
|
|
|
|
---
|
|
|
|
## Card
|
|
|
|
콘텐츠를 담는 기본 컨테이너.
|
|
|
|
### 구조
|
|
|
|
```html
|
|
<div class="rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-surface)] shadow-sm">
|
|
<!-- Card Header -->
|
|
<div class="border-b border-[var(--color-border)] px-6 py-4">
|
|
<h3 class="text-base font-semibold text-[var(--color-text-primary)]">제목</h3>
|
|
<p class="text-sm text-[var(--color-text-secondary)]">부제목</p>
|
|
</div>
|
|
|
|
<!-- Card Body -->
|
|
<div class="p-6">
|
|
<!-- 콘텐츠 -->
|
|
</div>
|
|
|
|
<!-- Card Footer (선택) -->
|
|
<div class="border-t border-[var(--color-border)] px-6 py-4">
|
|
<!-- 액션 버튼 등 -->
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### 변형
|
|
|
|
| Variant | 설명 |
|
|
|---------|------|
|
|
| `default` | border + shadow-sm |
|
|
| `elevated` | shadow-md, 모달 내부 카드 |
|
|
| `flat` | border만, shadow 없음 |
|
|
| `interactive` | hover 시 shadow-md + scale-[1.01] |
|
|
|
|
---
|
|
|
|
## Input
|
|
|
|
### 기본 스펙
|
|
|
|
```html
|
|
<div class="flex flex-col gap-1.5">
|
|
<label class="text-sm font-medium text-[var(--color-text-primary)]">레이블</label>
|
|
<input
|
|
class="
|
|
w-full rounded-lg border border-[var(--color-border)]
|
|
bg-[var(--color-bg-surface)] px-3 py-2
|
|
text-base text-[var(--color-text-primary)]
|
|
placeholder:text-[var(--color-text-tertiary)]
|
|
focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent
|
|
disabled:opacity-50 disabled:cursor-not-allowed
|
|
"
|
|
placeholder="입력하세요"
|
|
/>
|
|
<p class="text-xs text-[var(--color-text-tertiary)]">도움말 텍스트</p>
|
|
</div>
|
|
```
|
|
|
|
### 상태
|
|
|
|
| 상태 | 테두리 색상 |
|
|
|------|-------------|
|
|
| 기본 | `--color-border` |
|
|
| 포커스 | `--color-primary` (ring) |
|
|
| 오류 | `--color-danger` |
|
|
| 성공 | `--color-success` |
|
|
| 비활성 | `--color-border` + opacity-50 |
|
|
|
|
### 사이즈
|
|
|
|
| Size | padding | font-size | height |
|
|
|------|---------|-----------|--------|
|
|
| `sm` | `px-3 py-1.5` | `text-sm` | 32px |
|
|
| `md` | `px-3 py-2` | `text-base` | 40px |
|
|
| `lg` | `px-4 py-3` | `text-lg` | 48px |
|
|
|
|
---
|
|
|
|
## Modal
|
|
|
|
### 구조
|
|
|
|
```html
|
|
<!-- Backdrop -->
|
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
|
<!-- Dialog -->
|
|
<div class="
|
|
relative w-full max-w-md mx-4 rounded-2xl
|
|
bg-[var(--color-bg-elevated)] shadow-xl
|
|
">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between border-b border-[var(--color-border)] px-6 py-4">
|
|
<h2 class="text-lg font-semibold text-[var(--color-text-primary)]">제목</h2>
|
|
<button class="...">✕</button>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="px-6 py-6">...</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="flex justify-end gap-3 border-t border-[var(--color-border)] px-6 py-4">
|
|
<Button variant="ghost">취소</Button>
|
|
<Button variant="primary">확인</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### 사이즈
|
|
|
|
| Size | max-width |
|
|
|------|-----------|
|
|
| `sm` | `max-w-sm` (384px) |
|
|
| `md` | `max-w-md` (448px) |
|
|
| `lg` | `max-w-lg` (512px) |
|
|
| `xl` | `max-w-xl` (576px) |
|
|
|
|
### 접근성
|
|
|
|
- `role="dialog"`, `aria-modal="true"`, `aria-labelledby` 필수
|
|
- 열릴 때 첫 번째 포커스 가능 요소로 포커스 이동
|
|
- Escape 키로 닫기
|
|
- Backdrop 클릭 시 닫기 (선택적)
|
|
- 열린 동안 body 스크롤 잠금
|
|
|
|
---
|
|
|
|
## Toast
|
|
|
|
### 위치
|
|
|
|
`fixed bottom-4 right-4 z-[60]` (Modal보다 위)
|
|
|
|
### 변형
|
|
|
|
| Variant | 아이콘 | 색상 |
|
|
|---------|--------|------|
|
|
| `success` | CheckCircle | `--color-success` |
|
|
| `warning` | AlertTriangle | `--color-warning` |
|
|
| `error` | XCircle | `--color-danger` |
|
|
| `info` | Info | `--color-info` |
|
|
|
|
### 구조
|
|
|
|
```html
|
|
<div class="
|
|
flex items-start gap-3 w-80 rounded-xl p-4
|
|
bg-[var(--color-bg-elevated)] shadow-lg
|
|
border border-[var(--color-border)]
|
|
">
|
|
<!-- 아이콘 -->
|
|
<!-- 텍스트 -->
|
|
<!-- 닫기 버튼 -->
|
|
</div>
|
|
```
|
|
|
|
### 동작
|
|
|
|
- 기본 자동 닫힘: 4000ms
|
|
- 진입: `translate-y-2 opacity-0` → `translate-y-0 opacity-100`
|
|
- 퇴장: `translate-y-0 opacity-100` → `translate-y-2 opacity-0`
|
|
- 트랜지션: `duration-200 ease-out`
|