Files
sam-docs/frontend/v1/06-styling-guide.md
유병철 8f939d3609 docs: [frontend] 프론트엔드 아키텍처/가이드 문서 v1 작성
- _index.md: 문서 목록 및 버전 관리
- 01~09: 아키텍처, API패턴, 컴포넌트, 폼, 스타일, 인증, 대시보드, 컨벤션
- 10: 문서 API 연동 스펙 (api-specs에서 이관)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:24:25 +09:00

226 lines
5.1 KiB
Markdown

# 06. 스타일링 가이드
> **대상**: 프론트엔드 개발자, 디자이너
> **버전**: 1.0.0
> **최종 수정**: 2026-03-09
---
## 1. 기술 스택
| 도구 | 역할 |
|------|------|
| **Tailwind CSS 4** | 유틸리티 클래스 기반 스타일링 |
| **shadcn/ui** | Radix UI 기반 컴포넌트 라이브러리 |
| **CSS Variables** | 테마 토큰 (다크모드 대비) |
| **lucide-react** | 아이콘 |
---
## 2. 기본 규칙
### 사용
```typescript
// ✅ Tailwind 클래스 사용
<div className="flex items-center gap-2 p-4 bg-muted rounded-lg">
// ✅ shadcn/ui 컴포넌트
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
```
### 금지
```typescript
// ❌ 인라인 스타일
<div style={{ display: 'flex', padding: '16px' }}>
// ❌ CSS 모듈 / styled-components
import styles from './Component.module.css';
// ❌ 전역 CSS (globals.css 외)
```
---
## 3. 레이아웃 패딩 규칙
```
AuthenticatedLayout (<main>) → 패딩 없음
└── PageLayout → p-0 md:space-y-6 (패딩 담당)
└── 콘텐츠 영역
```
**핵심**: page.tsx에서 추가 패딩 래퍼 금지 (이중 패딩 방지)
```typescript
// ✅ 올바름
<PageLayout>
<PageHeader title="..." />
<Card>...</Card>
</PageLayout>
// ❌ 이중 패딩
<div className="p-6"> {/* ← 금지 */}
<PageLayout>
...
</PageLayout>
</div>
```
---
## 4. 간격 시스템
| 용도 | 클래스 | 값 |
|------|--------|-----|
| 섹션 간 간격 | `space-y-6` | 24px |
| 카드 내부 간격 | `space-y-4` | 16px |
| 인라인 요소 간격 | `gap-2` | 8px |
| 폼 필드 간격 | `space-y-4` | 16px |
| 그리드 갭 | `gap-4` 또는 `gap-6` | 16px / 24px |
---
## 5. 반응형 패턴
```typescript
// 그리드: 모바일 1열 → 데스크톱 2~4열
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
// 숨기기/보이기
<div className="hidden md:block"> {/* 데스크톱만 */}
<div className="block md:hidden"> {/* 모바일만 */}
// 폰트 크기 반응형
<h1 className="text-lg md:text-2xl font-bold">
```
### 브레이크포인트
| 접두사 | 최소 너비 | 대상 |
|--------|----------|------|
| (없음) | 0px | 모바일 |
| `sm:` | 640px | 소형 태블릿 |
| `md:` | 768px | 태블릿 |
| `lg:` | 1024px | 데스크톱 |
| `xl:` | 1280px | 넓은 화면 |
---
## 6. 색상 시스템
shadcn/ui CSS 변수 기반 — 다크모드 자동 대응:
| 용도 | 클래스 | 설명 |
|------|--------|------|
| 배경 | `bg-background` | 페이지 배경 |
| 카드 | `bg-card` | 카드 배경 |
| 강조 | `bg-muted` | 연한 배경 (테이블 호버 등) |
| 텍스트 | `text-foreground` | 기본 텍스트 |
| 보조 텍스트 | `text-muted-foreground` | 설명, 플레이스홀더 |
| 테두리 | `border` | 기본 테두리 |
| 위험 | `text-destructive` | 삭제, 에러 |
### 상태 색상
| 상태 | 텍스트 | 배경 |
|------|--------|------|
| 입금/증가 | `text-blue-600` | `bg-blue-50` |
| 출금/감소 | `text-red-600` | `bg-red-50` |
| 성공/완료 | `text-green-600` | `bg-green-50` |
| 경고/대기 | `text-orange-500` | `bg-orange-50` |
| 비활성 | `text-gray-400` | `bg-gray-50` |
---
## 7. 컴포넌트 스타일 규칙
### 버튼
```typescript
// 주요 액션
<Button size="sm">등록</Button>
// 보조 액션
<Button variant="outline" size="sm">취소</Button>
// 위험 액션 (삭제)
<Button variant="destructive" size="sm">삭제</Button>
```
### 테이블
```typescript
// 기본 셀 정렬
<TableCell className="text-center"> {/* 날짜, 상태, 번호 */}
<TableCell className="text-right"> {/* 금액 */}
<TableCell className="text-left"> {/* 텍스트 (기본) */}
// 합계 행
<TableRow className="bg-muted/50 font-medium">
```
### Badge
```typescript
<Badge variant="outline">기본</Badge>
<Badge variant="default">활성</Badge>
<Badge variant="destructive">에러</Badge>
```
---
## 8. 팝업/모달 규칙
| 용도 | 컴포넌트 | 비고 |
|------|---------|------|
| 확인/취소 | AlertDialog (Radix) | `alert()`, `confirm()` 금지 |
| 데이터 입력 | Dialog (Radix) | `prompt()` 금지 |
| 알림 | `toast` (sonner) | 성공/에러 피드백 |
| 검색+선택 | SearchableSelectionModal | 커스텀 Dialog 조합 금지 |
```typescript
// ✅ 토스트 사용
import { toast } from 'sonner';
toast.success('저장되었습니다.');
toast.error('저장에 실패했습니다.');
// ❌ alert 금지
alert('저장되었습니다.');
```
---
## 9. 아이콘
**lucide-react** 사용:
```typescript
import { FileText, Settings, Search, Plus, Trash2 } from 'lucide-react';
// 인라인 아이콘
<FileText className="h-4 w-4" />
// 버튼 내 아이콘
<Button size="sm">
<Plus className="h-4 w-4 mr-1" />
등록
</Button>
```
---
## 10. 금액 표시
```typescript
import { formatNumber } from '@/lib/utils/amount';
formatNumber(1234567) // "1,234,567"
formatNumber(0) // "0"
formatNumber(undefined) // "0"
```
테이블에서:
```typescript
<TableCell className="text-right text-blue-600">
{formatNumber(item.amount)}
</TableCell>
```