169 lines
3.9 KiB
Markdown
169 lines
3.9 KiB
Markdown
# 13. 모바일 반응형 패턴
|
|
|
|
> 대상: 프론트엔드 개발자
|
|
> 최종 업데이트: 2026-03-20
|
|
|
|
---
|
|
|
|
## 목차
|
|
|
|
| 번호 | 항목 |
|
|
|------|------|
|
|
| 13.1 | [대상 디바이스](#131-대상-디바이스) |
|
|
| 13.2 | [그리드 레이아웃](#132-그리드-레이아웃) |
|
|
| 13.3 | [테이블 대응](#133-테이블-대응) |
|
|
| 13.4 | [모달/다이얼로그](#134-모달다이얼로그) |
|
|
| 13.5 | [버튼/액션](#135-버튼액션) |
|
|
| 13.6 | [텍스트 처리](#136-텍스트-처리) |
|
|
| 13.7 | [체크리스트](#137-체크리스트) |
|
|
|
|
---
|
|
|
|
## 13.1 대상 디바이스
|
|
|
|
| 디바이스 | 너비 | Tailwind 접두사 |
|
|
|---------|------|----------------|
|
|
| Galaxy Z Fold 5 (접힌) | 344px | 기본 (접두사 없음) |
|
|
| 일반 모바일 | 375-430px | 기본 |
|
|
| 커스텀 브레이크포인트 | 480px | `xs:` |
|
|
| 태블릿 | 768px | `md:` |
|
|
| 데스크탑 | 1024px+ | `lg:` |
|
|
|
|
커스텀 브레이크포인트 `xs` (480px)는 `tailwind.config.ts`에 정의:
|
|
|
|
```typescript
|
|
// tailwind.config.ts
|
|
theme: {
|
|
screens: {
|
|
xs: '480px',
|
|
// sm, md, lg, xl 기본값 유지
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 13.2 그리드 레이아웃
|
|
|
|
**모바일 퍼스트**: 항상 `grid-cols-1`에서 시작, 위로 확장
|
|
|
|
```typescript
|
|
// ✅ 올바른 패턴
|
|
<div className="grid grid-cols-1 xs:grid-cols-2 md:grid-cols-4 gap-4">
|
|
|
|
// ❌ 데스크탑 퍼스트
|
|
<div className="grid grid-cols-4 gap-4">
|
|
```
|
|
|
|
### 폼 레이아웃 예시
|
|
|
|
```typescript
|
|
// 기본 정보 폼 (4컬럼 -> 모바일 1컬럼)
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div>견적번호</div>
|
|
<div>접수일</div>
|
|
<div>수주처</div>
|
|
<div>현장명</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 13.3 테이블 대응
|
|
|
|
### 방법 1: IntegratedListTemplateV2 모바일 카드 (권장)
|
|
|
|
```typescript
|
|
<IntegratedListTemplateV2
|
|
renderMobileCard={(item) => (
|
|
<MobileCard
|
|
title={item.name}
|
|
subtitle={item.code}
|
|
fields={[
|
|
{ label: '수량', value: item.quantity },
|
|
{ label: '금액', value: formatNumber(item.amount) },
|
|
]}
|
|
/>
|
|
)}
|
|
/>
|
|
```
|
|
|
|
### 방법 2: 가로 스크롤
|
|
|
|
```typescript
|
|
<div className="overflow-x-auto">
|
|
<Table className="min-w-[600px]">
|
|
{/* 테이블 내용 */}
|
|
</Table>
|
|
</div>
|
|
```
|
|
|
|
### 방법 3: 컬럼 숨김
|
|
|
|
```typescript
|
|
<TableHead className="hidden md:table-cell">비고</TableHead>
|
|
<TableCell className="hidden md:table-cell">{item.memo}</TableCell>
|
|
```
|
|
|
|
---
|
|
|
|
## 13.4 모달/다이얼로그
|
|
|
|
```typescript
|
|
// ✅ 모바일에서 화면 벗어나지 않게
|
|
<DialogContent className="max-w-[calc(100vw-2rem)] max-h-[calc(100vh-2rem)]">
|
|
```
|
|
|
|
모바일에서 모달 내 폼:
|
|
- 필드를 1컬럼으로 쌓기
|
|
- 하단 버튼은 `flex-col` 또는 `flex-wrap`
|
|
|
|
---
|
|
|
|
## 13.5 버튼/액션
|
|
|
|
```typescript
|
|
// ✅ 모바일: 아이콘만, 태블릿+: 아이콘+텍스트
|
|
<Button size="sm" className="md:size-default">
|
|
<Save className="h-4 w-4 md:mr-2" />
|
|
<span className="hidden md:inline">저장</span>
|
|
</Button>
|
|
```
|
|
|
|
버튼 그룹 래핑:
|
|
|
|
```typescript
|
|
<div className="flex items-center flex-wrap gap-1 md:gap-3">
|
|
<Button>...</Button>
|
|
<Button>...</Button>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 13.6 텍스트 처리
|
|
|
|
| 상황 | 클래스 |
|
|
|------|--------|
|
|
| 한 줄 말줄임 | `truncate` |
|
|
| 여러 줄 말줄임 | `line-clamp-2` |
|
|
| 줄바꿈 방지 (한글) | `break-keep` |
|
|
| 긴 단어 강제 줄바꿈 | `break-all` |
|
|
| 반응형 폰트 | `text-sm md:text-base` |
|
|
|
|
---
|
|
|
|
## 13.7 체크리스트
|
|
|
|
새 페이지/컴포넌트 작성 시:
|
|
|
|
| # | 항목 |
|
|
|---|------|
|
|
| 1 | 그리드: `grid-cols-1`에서 시작하는가? |
|
|
| 2 | 테이블: 모바일 카드 또는 가로 스크롤 적용했는가? |
|
|
| 3 | 모달: `max-w-[calc(100vw-2rem)]` 적용했는가? |
|
|
| 4 | 버튼: 모바일 아이콘만/데스크탑 아이콘+텍스트 패턴인가? |
|
|
| 5 | 긴 텍스트: `truncate` 또는 `line-clamp` 처리했는가? |
|
|
| 6 | Galaxy Fold (344px) 너비에서 깨지지 않는가? |
|
|
| 7 | 줌 방지: input `font-size` 16px 이상인가? |
|