# 컴포넌트 스펙

모든 컴포넌트는 `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

상태, 카테고리, 태그 표시에 사용한다.

### 변형

| Variant | 배경 | 텍스트 |
|---------|------|--------|
| `default` | `--color-bg-elevated` | `--color-text-primary` |
| `primary` | `--color-primary-subtle` | `--color-primary-text` |
| `success` | #DCFCE7 | #166534 |
| `warning` | #FEF9C3 | #854D0E |
| `danger` | #FEE2E2 | #991B1B |
| `info` | #E0F2FE | #075985 |

### 스펙

- 패딩: `px-2 py-0.5`
- 폰트: `text-xs font-medium`
- 모양: `rounded-full` (pill) 또는 `rounded-md` (square)

---

## 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`
