@kood/claude-code 0.1.6 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +109 -216
- package/package.json +8 -2
- package/templates/hono/CLAUDE.md +59 -328
- package/templates/hono/docs/architecture/architecture.md +93 -747
- package/templates/hono/docs/deployment/cloudflare.md +59 -513
- package/templates/hono/docs/deployment/docker.md +41 -356
- package/templates/hono/docs/deployment/index.md +54 -190
- package/templates/hono/docs/deployment/railway.md +36 -306
- package/templates/hono/docs/deployment/vercel.md +49 -434
- package/templates/hono/docs/library/ai-sdk/index.md +53 -290
- package/templates/hono/docs/library/ai-sdk/openrouter.md +19 -387
- package/templates/hono/docs/library/ai-sdk/providers.md +28 -394
- package/templates/hono/docs/library/ai-sdk/streaming.md +52 -353
- package/templates/hono/docs/library/ai-sdk/structured-output.md +63 -395
- package/templates/hono/docs/library/ai-sdk/tools.md +62 -431
- package/templates/hono/docs/library/hono/env-setup.md +24 -313
- package/templates/hono/docs/library/hono/error-handling.md +34 -295
- package/templates/hono/docs/library/hono/index.md +29 -121
- package/templates/hono/docs/library/hono/middleware.md +21 -188
- package/templates/hono/docs/library/hono/rpc.md +40 -341
- package/templates/hono/docs/library/hono/validation.md +35 -195
- package/templates/hono/docs/library/pino/index.md +42 -333
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +64 -367
- package/templates/hono/docs/library/prisma/config.md +19 -260
- package/templates/hono/docs/library/prisma/index.md +67 -320
- package/templates/hono/docs/library/zod/index.md +53 -257
- package/templates/npx/CLAUDE.md +62 -274
- package/templates/npx/docs/references/patterns.md +160 -0
- package/templates/tanstack-start/CLAUDE.md +100 -256
- package/templates/tanstack-start/docs/architecture/architecture.md +44 -589
- package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
- package/templates/tanstack-start/docs/deployment/index.md +57 -286
- package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
- package/templates/tanstack-start/docs/deployment/railway.md +40 -409
- package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +113 -69
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/guides/conventions.md +103 -0
- package/templates/tanstack-start/docs/guides/env-setup.md +34 -340
- package/templates/tanstack-start/docs/guides/getting-started.md +22 -209
- package/templates/tanstack-start/docs/guides/hooks.md +166 -0
- package/templates/tanstack-start/docs/guides/routes.md +166 -0
- package/templates/tanstack-start/docs/guides/services.md +143 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
- package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
- package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
- package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
- package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
- package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
- package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
- package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
- package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
- package/templates/tanstack-start/docs/library/prisma/setup.md +12 -112
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +26 -97
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +34 -246
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +31 -144
- package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
- package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
- package/templates/hono/docs/commands/git.md +0 -145
- package/templates/hono/docs/mcp/context7.md +0 -106
- package/templates/hono/docs/mcp/index.md +0 -176
- package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
- package/templates/hono/docs/mcp/serena.md +0 -269
- package/templates/hono/docs/mcp/sgrep.md +0 -105
- package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
- package/templates/npx/docs/commands/git.md +0 -145
- package/templates/npx/docs/mcp/index.md +0 -60
- package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
- package/templates/tanstack-start/docs/commands/git.md +0 -145
- package/templates/tanstack-start/docs/design/accessibility.md +0 -433
- package/templates/tanstack-start/docs/design/color.md +0 -235
- package/templates/tanstack-start/docs/design/spacing.md +0 -341
- package/templates/tanstack-start/docs/design/typography.md +0 -324
- package/templates/tanstack-start/docs/guides/best-practices.md +0 -950
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +0 -303
- package/templates/tanstack-start/docs/guides/prettier.md +0 -189
- package/templates/tanstack-start/docs/guides/project-templates.md +0 -710
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +0 -107
- package/templates/tanstack-start/docs/library/zod/basic-types.md +0 -186
- package/templates/tanstack-start/docs/mcp/context7.md +0 -204
- package/templates/tanstack-start/docs/mcp/index.md +0 -177
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
- package/templates/tanstack-start/docs/mcp/serena.md +0 -269
- package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
|
@@ -1,107 +1,151 @@
|
|
|
1
1
|
# UI/UX 디자인 가이드
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> TanStack Start + Tailwind CSS v4
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
@components.md
|
|
6
|
+
@safe-area.md
|
|
7
|
+
@tailwind-setup.md
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
---
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
### 디자인 시스템의 장점
|
|
11
|
+
## 핵심 원칙
|
|
12
12
|
|
|
13
|
-
|
|
|
13
|
+
| 원칙 | 설명 |
|
|
14
14
|
|------|------|
|
|
15
|
-
| **일관성** |
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
15
|
+
| **일관성** | 같은 요소는 같은 스타일과 동작 |
|
|
16
|
+
| **계층 구조** | 중요한 것이 먼저 눈에 들어옴 |
|
|
17
|
+
| **단순함** | 색상 3-5개, 폰트 2-3개, 충분한 여백 |
|
|
18
|
+
| **접근성** | 충분한 대비, 읽기 쉬운 크기, 키보드 조작 |
|
|
19
|
+
|
|
20
|
+
---
|
|
19
21
|
|
|
20
|
-
##
|
|
22
|
+
## 색상 (60-30-10 규칙)
|
|
21
23
|
|
|
22
24
|
```
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
├── typography.md # 타이포그래피 (폰트)
|
|
27
|
-
├── spacing.md # 간격과 레이아웃
|
|
28
|
-
├── components.md # 컴포넌트 가이드라인
|
|
29
|
-
├── accessibility.md # 접근성
|
|
30
|
-
├── safe-area.md # iOS Safe Area
|
|
31
|
-
└── tailwind-setup.md # Tailwind CSS 설정
|
|
25
|
+
60% - 배경색 (중립색)
|
|
26
|
+
30% - 보조색 (카드, 섹션)
|
|
27
|
+
10% - 강조색 (버튼, CTA)
|
|
32
28
|
```
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
### 의미론적 색상
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
| 색상 | 의미 | Tailwind |
|
|
33
|
+
|------|------|----------|
|
|
34
|
+
| 🟢 Green | 성공 | `text-green-600` |
|
|
35
|
+
| 🔴 Red | 오류 | `text-red-600` |
|
|
36
|
+
| 🟡 Yellow | 경고 | `text-yellow-600` |
|
|
37
|
+
| 🔵 Blue | 정보 | `text-blue-600` |
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
### 다크 모드 매핑
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
41
|
+
```
|
|
42
|
+
라이트 ←→ 다크
|
|
43
|
+
bg-white ←→ bg-gray-900
|
|
44
|
+
bg-gray-50 ←→ bg-gray-800
|
|
45
|
+
text-gray-900 ←→ text-white
|
|
46
|
+
text-gray-600 ←→ text-gray-300
|
|
47
|
+
```
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
---
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
## 폰트
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
| 폰트 | 용도 |
|
|
54
|
+
|------|------|
|
|
55
|
+
| Pretendard | 한글 (권장) |
|
|
56
|
+
| Inter | 영문 |
|
|
57
|
+
| JetBrains Mono | 코드 |
|
|
51
58
|
|
|
52
|
-
###
|
|
59
|
+
### 크기
|
|
53
60
|
|
|
54
|
-
|
|
61
|
+
```
|
|
62
|
+
text-4xl 36px h1
|
|
63
|
+
text-2xl 24px h2, h3
|
|
64
|
+
text-base 16px 본문
|
|
65
|
+
text-sm 14px 보조 텍스트
|
|
66
|
+
```
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
- 폰트는 2-3개만 사용
|
|
58
|
-
- 여백을 충분히 활용
|
|
68
|
+
### Tailwind 설정
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
```css
|
|
71
|
+
@theme {
|
|
72
|
+
--font-sans: "Pretendard", "Inter", system-ui, sans-serif;
|
|
73
|
+
--font-mono: "JetBrains Mono", monospace;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
61
76
|
|
|
62
|
-
|
|
77
|
+
---
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
- 읽기 쉬운 폰트 크기
|
|
66
|
-
- 키보드로도 조작 가능
|
|
79
|
+
## 간격 (8px 그리드)
|
|
67
80
|
|
|
68
|
-
|
|
81
|
+
| 용도 | Tailwind | 크기 |
|
|
82
|
+
|------|----------|------|
|
|
83
|
+
| 아이콘-텍스트 | gap-1 | 4px |
|
|
84
|
+
| 인라인 요소 | gap-2 | 8px |
|
|
85
|
+
| 카드 내부 | p-4 | 16px |
|
|
86
|
+
| 카드 간격 | gap-6 | 24px |
|
|
87
|
+
| 섹션 간격 | py-12 | 48px |
|
|
69
88
|
|
|
70
|
-
###
|
|
89
|
+
### 컨테이너
|
|
71
90
|
|
|
72
91
|
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
max-w-md 448px 폼
|
|
93
|
+
max-w-lg 512px 모달
|
|
94
|
+
max-w-3xl 768px 블로그
|
|
95
|
+
max-w-7xl 1280px 대시보드
|
|
76
96
|
```
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
---
|
|
79
99
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
100
|
+
## 접근성 (WCAG AA)
|
|
101
|
+
|
|
102
|
+
### 색상 대비
|
|
103
|
+
|
|
104
|
+
| 기준 | 최소 대비 | 적용 대상 |
|
|
105
|
+
|------|----------|----------|
|
|
106
|
+
| AA | 4.5:1 | 일반 텍스트 |
|
|
107
|
+
| AA (큰 텍스트) | 3:1 | 18px 이상 |
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// ❌ 색상만으로 구분
|
|
111
|
+
<span className="text-red-600">오류</span>
|
|
112
|
+
|
|
113
|
+
// ✅ 아이콘/텍스트 병행
|
|
114
|
+
<span className="text-red-600 flex items-center gap-1">
|
|
115
|
+
<XCircleIcon className="w-4 h-4" />
|
|
116
|
+
오류가 발생했습니다
|
|
117
|
+
</span>
|
|
86
118
|
```
|
|
87
119
|
|
|
88
|
-
###
|
|
120
|
+
### 키보드
|
|
89
121
|
|
|
122
|
+
```tsx
|
|
123
|
+
// ✅ 명확한 포커스 스타일
|
|
124
|
+
<button className="focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
|
125
|
+
|
|
126
|
+
// ❌ 포커스 제거 금지
|
|
127
|
+
<button className="outline-none focus:outline-none">
|
|
90
128
|
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
|
|
130
|
+
### 폼
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<label htmlFor="email">이메일</label>
|
|
134
|
+
<input
|
|
135
|
+
id="email"
|
|
136
|
+
required
|
|
137
|
+
aria-invalid={hasError}
|
|
138
|
+
aria-describedby={hasError ? "email-error" : undefined}
|
|
139
|
+
/>
|
|
140
|
+
{hasError && <p id="email-error" role="alert">오류</p>}
|
|
97
141
|
```
|
|
98
142
|
|
|
99
|
-
|
|
143
|
+
### 체크리스트
|
|
100
144
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
- [ ] 모든 이미지에 alt
|
|
146
|
+
- [ ] 폼 요소에 레이블
|
|
147
|
+
- [ ] 키보드 접근 가능
|
|
148
|
+
- [ ] 포커스 표시 유지
|
|
149
|
+
- [ ] 대비 4.5:1 이상
|
|
150
|
+
- [ ] 색상만으로 정보 전달 금지
|
|
151
|
+
- [ ] 터치 영역 44px 이상
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# iOS Safe Area
|
|
2
2
|
|
|
3
|
-
> **Framework**: TanStack Start + Tailwind CSS v4
|
|
4
3
|
> **Library**: [tailwindcss-safe-area](https://github.com/mvllow/tailwindcss-safe-area)
|
|
5
4
|
|
|
6
|
-
iOS
|
|
5
|
+
iOS 노치, 홈 인디케이터, 다이나믹 아일랜드 대응.
|
|
7
6
|
|
|
8
7
|
## 설치
|
|
9
8
|
|
|
@@ -11,307 +10,109 @@ iOS 기기의 노치, 홈 인디케이터, 다이나믹 아일랜드 등을 고
|
|
|
11
10
|
yarn add tailwindcss-safe-area
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
## 설정
|
|
15
|
-
|
|
16
|
-
### 1. Tailwind CSS 플러그인 등록
|
|
17
|
-
|
|
18
13
|
```typescript
|
|
19
14
|
// tailwind.config.ts
|
|
20
|
-
import type { Config } from 'tailwindcss'
|
|
21
15
|
import safeArea from 'tailwindcss-safe-area'
|
|
22
|
-
|
|
23
|
-
export default {
|
|
24
|
-
content: ['./src/**/*.{ts,tsx}'],
|
|
25
|
-
plugins: [safeArea],
|
|
26
|
-
} satisfies Config
|
|
16
|
+
export default { plugins: [safeArea] }
|
|
27
17
|
```
|
|
28
18
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Safe Area 유틸리티가 작동하려면 `viewport-fit=cover`가 **반드시** 필요합니다.
|
|
32
|
-
|
|
33
|
-
```html
|
|
34
|
-
<!-- app.html 또는 root layout -->
|
|
35
|
-
<meta
|
|
36
|
-
name="viewport"
|
|
37
|
-
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
|
38
|
-
/>
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
TanStack Start에서:
|
|
19
|
+
## Viewport 설정 (필수)
|
|
42
20
|
|
|
43
21
|
```typescript
|
|
44
22
|
// src/routes/__root.tsx
|
|
45
23
|
export const Route = createRootRoute({
|
|
46
24
|
head: () => ({
|
|
47
|
-
meta: [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
},
|
|
52
|
-
],
|
|
25
|
+
meta: [{
|
|
26
|
+
name: 'viewport',
|
|
27
|
+
content: 'width=device-width, initial-scale=1, viewport-fit=cover',
|
|
28
|
+
}],
|
|
53
29
|
}),
|
|
54
30
|
})
|
|
55
31
|
```
|
|
56
32
|
|
|
57
33
|
## 유틸리티 클래스
|
|
58
34
|
|
|
59
|
-
### Padding
|
|
60
|
-
|
|
35
|
+
### Padding
|
|
61
36
|
```tsx
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<header className="pt-safe">상단 노치/다이나믹 아일랜드 영역</header>
|
|
67
|
-
<footer className="pb-safe">하단 홈 인디케이터 영역</footer>
|
|
68
|
-
<div className="pl-safe pr-safe">좌우 safe area</div>
|
|
69
|
-
|
|
70
|
-
// X축, Y축
|
|
71
|
-
<div className="px-safe">좌우 safe area</div>
|
|
72
|
-
<div className="py-safe">상하 safe area</div>
|
|
37
|
+
<div className="p-safe"> {/* 전체 */}
|
|
38
|
+
<header className="pt-safe"> {/* 상단 (노치) */}
|
|
39
|
+
<footer className="pb-safe"> {/* 하단 (홈 인디케이터) */}
|
|
40
|
+
<div className="px-safe py-safe"> {/* X축, Y축 */}
|
|
73
41
|
```
|
|
74
42
|
|
|
75
|
-
### Margin
|
|
76
|
-
|
|
43
|
+
### Margin
|
|
77
44
|
```tsx
|
|
78
|
-
|
|
79
|
-
<div className="m-safe">...</div>
|
|
80
|
-
|
|
81
|
-
// 개별 방향
|
|
82
|
-
<div className="mt-safe mb-safe">상하 margin</div>
|
|
83
|
-
<div className="ml-safe mr-safe">좌우 margin</div>
|
|
84
|
-
|
|
85
|
-
// X축, Y축
|
|
86
|
-
<div className="mx-safe">좌우 safe area margin</div>
|
|
87
|
-
<div className="my-safe">상하 safe area margin</div>
|
|
45
|
+
<div className="m-safe mt-safe mb-safe mx-safe my-safe">
|
|
88
46
|
```
|
|
89
47
|
|
|
90
|
-
### Height
|
|
91
|
-
|
|
48
|
+
### Height
|
|
92
49
|
```tsx
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
전체 화면에서 safe area를 뺀 높이
|
|
96
|
-
</main>
|
|
97
|
-
|
|
98
|
-
// 최소 높이
|
|
99
|
-
<div className="min-h-screen-safe">
|
|
100
|
-
최소 높이로 safe area 적용
|
|
101
|
-
</div>
|
|
50
|
+
<main className="h-screen-safe"> {/* safe area 제외 높이 */}
|
|
51
|
+
<div className="min-h-screen-safe"> {/* 최소 높이 */}
|
|
102
52
|
```
|
|
103
53
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Safe Area가 0일 때 대체 값을 지정할 수 있습니다.
|
|
107
|
-
|
|
54
|
+
### Fallback (safe area 없을 때 대체값)
|
|
108
55
|
```tsx
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
// 상단: safe area 또는 0.5rem(2)
|
|
113
|
-
<header className="pt-safe-or-2">...</header>
|
|
114
|
-
|
|
115
|
-
// 하단: safe area 또는 1rem(4)
|
|
116
|
-
<footer className="pb-safe-or-4">...</footer>
|
|
117
|
-
|
|
118
|
-
// 좌우: safe area 또는 1.5rem(6)
|
|
119
|
-
<div className="px-safe-or-6">...</div>
|
|
56
|
+
<div className="p-safe-or-4"> {/* safe area 또는 1rem */}
|
|
57
|
+
<header className="pt-safe-or-2"> {/* 상단 또는 0.5rem */}
|
|
58
|
+
<footer className="pb-safe-or-4"> {/* 하단 또는 1rem */}
|
|
120
59
|
```
|
|
121
60
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
| 클래스 | Safe Area 있을 때 | Safe Area 없을 때 |
|
|
125
|
-
|--------|-------------------|-------------------|
|
|
126
|
-
| `p-safe-or-4` | `env(safe-area-inset-*)` | `1rem` |
|
|
127
|
-
| `pt-safe-or-2` | `env(safe-area-inset-top)` | `0.5rem` |
|
|
128
|
-
| `pb-safe-or-4` | `env(safe-area-inset-bottom)` | `1rem` |
|
|
129
|
-
| `px-safe-or-6` | `env(safe-area-inset-left/right)` | `1.5rem` |
|
|
130
|
-
|
|
131
|
-
## 실전 패턴
|
|
61
|
+
## 레이아웃 패턴
|
|
132
62
|
|
|
133
63
|
### 기본 앱 레이아웃
|
|
134
|
-
|
|
135
|
-
```tsx
|
|
136
|
-
// src/components/app-layout.tsx
|
|
137
|
-
export function AppLayout({ children }: { children: React.ReactNode }) {
|
|
138
|
-
return (
|
|
139
|
-
<div className="min-h-screen-safe flex flex-col">
|
|
140
|
-
{/* 상단 헤더 - 노치/다이나믹 아일랜드 대응 */}
|
|
141
|
-
<header className="pt-safe-or-2 px-safe-or-4 bg-white border-b">
|
|
142
|
-
<nav className="h-14 flex items-center">
|
|
143
|
-
<h1>앱 타이틀</h1>
|
|
144
|
-
</nav>
|
|
145
|
-
</header>
|
|
146
|
-
|
|
147
|
-
{/* 메인 콘텐츠 */}
|
|
148
|
-
<main className="flex-1 px-safe-or-4">
|
|
149
|
-
{children}
|
|
150
|
-
</main>
|
|
151
|
-
|
|
152
|
-
{/* 하단 탭바 - 홈 인디케이터 대응 */}
|
|
153
|
-
<footer className="pb-safe-or-2 px-safe-or-4 bg-white border-t">
|
|
154
|
-
<nav className="h-14 flex items-center justify-around">
|
|
155
|
-
{/* 탭 아이템들 */}
|
|
156
|
-
</nav>
|
|
157
|
-
</footer>
|
|
158
|
-
</div>
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 전체 화면 모달
|
|
164
|
-
|
|
165
64
|
```tsx
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
65
|
+
<div className="min-h-screen-safe flex flex-col">
|
|
66
|
+
<header className="pt-safe-or-2 px-safe-or-4 bg-white border-b">
|
|
67
|
+
<nav className="h-14 flex items-center">헤더</nav>
|
|
68
|
+
</header>
|
|
69
|
+
<main className="flex-1 px-safe-or-4">{children}</main>
|
|
70
|
+
<footer className="pb-safe-or-2 px-safe-or-4 bg-white border-t">
|
|
71
|
+
<nav className="h-14 flex items-center justify-around">탭바</nav>
|
|
72
|
+
</footer>
|
|
73
|
+
</div>
|
|
176
74
|
```
|
|
177
75
|
|
|
178
76
|
### 고정 하단 버튼
|
|
179
|
-
|
|
180
77
|
```tsx
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
{children}
|
|
187
|
-
</button>
|
|
188
|
-
</div>
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### 스크롤 콘텐츠 with 고정 헤더/푸터
|
|
194
|
-
|
|
195
|
-
```tsx
|
|
196
|
-
export function ScrollableContent() {
|
|
197
|
-
return (
|
|
198
|
-
<div className="h-screen-safe flex flex-col">
|
|
199
|
-
{/* 고정 헤더 */}
|
|
200
|
-
<header className="pt-safe px-safe-or-4 shrink-0">
|
|
201
|
-
<div className="h-14">헤더</div>
|
|
202
|
-
</header>
|
|
203
|
-
|
|
204
|
-
{/* 스크롤 영역 */}
|
|
205
|
-
<main className="flex-1 overflow-y-auto px-safe-or-4">
|
|
206
|
-
{/* 스크롤 콘텐츠 */}
|
|
207
|
-
</main>
|
|
208
|
-
|
|
209
|
-
{/* 고정 푸터 */}
|
|
210
|
-
<footer className="pb-safe px-safe-or-4 shrink-0">
|
|
211
|
-
<div className="h-14">푸터</div>
|
|
212
|
-
</footer>
|
|
213
|
-
</div>
|
|
214
|
-
)
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## CSS 직접 사용
|
|
219
|
-
|
|
220
|
-
Tailwind 유틸리티 외에 CSS에서 직접 사용할 수도 있습니다.
|
|
221
|
-
|
|
222
|
-
```css
|
|
223
|
-
.custom-container {
|
|
224
|
-
padding-top: env(safe-area-inset-top);
|
|
225
|
-
padding-bottom: env(safe-area-inset-bottom);
|
|
226
|
-
padding-left: env(safe-area-inset-left);
|
|
227
|
-
padding-right: env(safe-area-inset-right);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/* fallback 포함 */
|
|
231
|
-
.custom-header {
|
|
232
|
-
padding-top: max(env(safe-area-inset-top), 1rem);
|
|
233
|
-
}
|
|
78
|
+
<div className="fixed bottom-0 left-0 right-0 pb-safe-or-4 px-safe-or-4 bg-white border-t">
|
|
79
|
+
<button className="w-full h-12 bg-primary-600 text-white rounded-lg">
|
|
80
|
+
버튼
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
234
83
|
```
|
|
235
84
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
모바일 브라우저에서 `100vh`가 실제 뷰포트보다 클 수 있습니다.
|
|
239
|
-
|
|
240
|
-
### 해결 방법 1: h-screen-safe 사용
|
|
241
|
-
|
|
85
|
+
### 전체 화면 모달
|
|
242
86
|
```tsx
|
|
243
|
-
<div className="
|
|
244
|
-
|
|
87
|
+
<div className="fixed inset-0 bg-white z-50">
|
|
88
|
+
<div className="h-screen-safe flex flex-col p-safe">{children}</div>
|
|
245
89
|
</div>
|
|
246
90
|
```
|
|
247
91
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
```css
|
|
251
|
-
.full-height {
|
|
252
|
-
height: 100vh;
|
|
253
|
-
height: -webkit-fill-available;
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### 해결 방법 3: dvh 단위 (권장)
|
|
258
|
-
|
|
259
|
-
```css
|
|
260
|
-
.full-height {
|
|
261
|
-
height: 100dvh; /* dynamic viewport height */
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Tailwind에서:
|
|
92
|
+
## 높이 문제 해결
|
|
266
93
|
|
|
267
94
|
```tsx
|
|
95
|
+
// 동적 뷰포트 높이 (권장)
|
|
268
96
|
<div className="h-dvh">
|
|
269
|
-
{/* 동적 뷰포트 높이 */}
|
|
270
|
-
</div>
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
## 디바이스별 Safe Area
|
|
274
97
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
| iPhone 14/15 | 47px (노치) | 34px | 0px |
|
|
279
|
-
| iPhone SE | 20px (상태바) | 0px | 0px |
|
|
280
|
-
| iPad Pro | 24px | 20px | 0px |
|
|
281
|
-
| Android (일반) | 24px (상태바) | 0px | 0px |
|
|
98
|
+
// 또는
|
|
99
|
+
<div className="h-screen-safe">
|
|
100
|
+
```
|
|
282
101
|
|
|
283
102
|
## 트러블슈팅
|
|
284
103
|
|
|
285
|
-
### Safe Area가 적용되지 않음
|
|
286
|
-
|
|
287
|
-
1. `viewport-fit=cover` 메타 태그 확인
|
|
288
|
-
2. 플러그인이 올바르게 등록되었는지 확인
|
|
289
|
-
3. 실제 iOS 기기 또는 시뮬레이터에서 테스트 (데스크톱 브라우저는 safe area가 0)
|
|
290
|
-
|
|
291
|
-
### 콘텐츠가 노치에 가려짐
|
|
292
|
-
|
|
293
104
|
```tsx
|
|
294
|
-
// ❌
|
|
295
|
-
<header className="fixed top-0"
|
|
296
|
-
|
|
297
|
-
// ✅ 올바른 예
|
|
298
|
-
<header className="fixed top-0 pt-safe">...</header>
|
|
299
|
-
```
|
|
105
|
+
// ❌ 노치에 가려짐
|
|
106
|
+
<header className="fixed top-0">
|
|
300
107
|
|
|
301
|
-
|
|
108
|
+
// ✅ safe area 적용
|
|
109
|
+
<header className="fixed top-0 pt-safe">
|
|
302
110
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<button className="fixed bottom-4">...</button>
|
|
111
|
+
// ❌ 홈 인디케이터와 겹침
|
|
112
|
+
<button className="fixed bottom-4">
|
|
306
113
|
|
|
307
|
-
// ✅
|
|
114
|
+
// ✅ safe area 적용
|
|
308
115
|
<div className="fixed bottom-0 pb-safe-or-4">
|
|
309
116
|
<button>...</button>
|
|
310
117
|
</div>
|
|
311
118
|
```
|
|
312
|
-
|
|
313
|
-
## 참고 자료
|
|
314
|
-
|
|
315
|
-
- [tailwindcss-safe-area GitHub](https://github.com/mvllow/tailwindcss-safe-area)
|
|
316
|
-
- [Apple Human Interface Guidelines - Layout](https://developer.apple.com/design/human-interface-guidelines/layout)
|
|
317
|
-
- [CSS env() function](https://developer.mozilla.org/en-US/docs/Web/CSS/env)
|