create-dailyshot-web-app 1.0.0
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/index.js +197 -0
- package/package.json +24 -0
- package/template/_claude/CLAUDE.md +299 -0
- package/template/components.json +20 -0
- package/template/eslintrc.cjs +61 -0
- package/template/gitignore +37 -0
- package/template/next.config.mjs +11 -0
- package/template/package.json +39 -0
- package/template/postcss.config.mjs +11 -0
- package/template/prettierrc +10 -0
- package/template/src/app/globals.css +46 -0
- package/template/src/app/layout.tsx +38 -0
- package/template/src/app/page.tsx +19 -0
- package/template/src/components/ui/.gitkeep +0 -0
- package/template/src/hooks/.gitkeep +0 -0
- package/template/src/lib/utils.ts +9 -0
- package/template/src/themes/config/animations.ts +103 -0
- package/template/src/themes/config/colors.ts +246 -0
- package/template/src/themes/config/index.ts +6 -0
- package/template/src/themes/config/screens.ts +5 -0
- package/template/src/themes/config/typography.ts +150 -0
- package/template/src/themes/default.ts +10 -0
- package/template/src/themes/index.ts +4 -0
- package/template/src/themes/plugin/base.ts +28 -0
- package/template/src/themes/plugin/typography.ts +40 -0
- package/template/src/themes/theme.ts +24 -0
- package/template/tailwind.config.ts +27 -0
- package/template/tsconfig.json +39 -0
package/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync, spawnSync } from 'child_process';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const CYAN = '\x1b[36m';
|
|
13
|
+
const GREEN = '\x1b[32m';
|
|
14
|
+
const RED = '\x1b[31m';
|
|
15
|
+
const YELLOW = '\x1b[33m';
|
|
16
|
+
const BOLD = '\x1b[1m';
|
|
17
|
+
const RESET = '\x1b[0m';
|
|
18
|
+
|
|
19
|
+
function logSuccess(msg) {
|
|
20
|
+
console.log(`${GREEN}✓${RESET} ${msg}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function logError(msg) {
|
|
24
|
+
console.error(`${RED}✗${RESET} ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function logStep(msg) {
|
|
28
|
+
console.log(`\n${CYAN}${BOLD}${msg}${RESET}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function prompt(question) {
|
|
32
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
rl.question(question, (answer) => {
|
|
35
|
+
rl.close();
|
|
36
|
+
resolve(answer.trim());
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function copyDir(src, dest) {
|
|
42
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
43
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const srcPath = path.join(src, entry.name);
|
|
47
|
+
const destPath = path.join(dest, entry.name);
|
|
48
|
+
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
copyDir(srcPath, destPath);
|
|
51
|
+
} else {
|
|
52
|
+
fs.copyFileSync(srcPath, destPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
console.log(`
|
|
59
|
+
${CYAN}${BOLD} ╔══════════════════════════════════════════╗
|
|
60
|
+
║ create-dailyshot-web-app ║
|
|
61
|
+
║ Next.js + TypeScript + Tailwind CSS ║
|
|
62
|
+
║ with Dailyshot Design System ║
|
|
63
|
+
╚══════════════════════════════════════════╝${RESET}
|
|
64
|
+
`);
|
|
65
|
+
|
|
66
|
+
// 1. Get project name
|
|
67
|
+
let projectName = process.argv[2];
|
|
68
|
+
|
|
69
|
+
if (!projectName) {
|
|
70
|
+
projectName = await prompt(`${BOLD}프로젝트 이름:${RESET} `);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!projectName) {
|
|
74
|
+
logError('프로젝트 이름을 입력해주세요.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Validate project name
|
|
79
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
|
|
80
|
+
logError('프로젝트 이름은 영문, 숫자, 하이픈, 언더스코어만 사용할 수 있습니다.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const projectDir = path.resolve(process.cwd(), projectName);
|
|
85
|
+
|
|
86
|
+
// 2. Check if directory exists
|
|
87
|
+
if (fs.existsSync(projectDir)) {
|
|
88
|
+
logError(`"${projectName}" 디렉토리가 이미 존재합니다.`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logStep('1. 프로젝트 생성 중...');
|
|
93
|
+
|
|
94
|
+
// 3. Copy template
|
|
95
|
+
const templateDir = path.resolve(__dirname, 'template');
|
|
96
|
+
copyDir(templateDir, projectDir);
|
|
97
|
+
logSuccess('템플릿 파일 복사 완료');
|
|
98
|
+
|
|
99
|
+
// 4. Rename dotfiles
|
|
100
|
+
const dotfileRenames = [
|
|
101
|
+
['gitignore', '.gitignore'],
|
|
102
|
+
['eslintrc.cjs', '.eslintrc.cjs'],
|
|
103
|
+
['prettierrc', '.prettierrc'],
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
for (const [from, to] of dotfileRenames) {
|
|
107
|
+
const src = path.join(projectDir, from);
|
|
108
|
+
if (fs.existsSync(src)) {
|
|
109
|
+
fs.renameSync(src, path.join(projectDir, to));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 5. Rename _claude to .claude
|
|
114
|
+
const claudeSrc = path.join(projectDir, '_claude');
|
|
115
|
+
if (fs.existsSync(claudeSrc)) {
|
|
116
|
+
fs.renameSync(claudeSrc, path.join(projectDir, '.claude'));
|
|
117
|
+
}
|
|
118
|
+
logSuccess('설정 파일 구성 완료');
|
|
119
|
+
|
|
120
|
+
// 6. Update package.json with project name
|
|
121
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
122
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
123
|
+
pkg.name = projectName;
|
|
124
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
125
|
+
logSuccess(`package.json 프로젝트명 설정: ${projectName}`);
|
|
126
|
+
|
|
127
|
+
// 7. Git init
|
|
128
|
+
logStep('2. Git 저장소 초기화 중...');
|
|
129
|
+
try {
|
|
130
|
+
execSync('git init', { cwd: projectDir, stdio: 'pipe' });
|
|
131
|
+
logSuccess('Git 저장소 초기화 완료');
|
|
132
|
+
} catch {
|
|
133
|
+
console.log(`${YELLOW}⚠${RESET} Git 초기화 실패 - 건너뜁니다.`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 8. Install dependencies
|
|
137
|
+
logStep('3. 의존성 설치 중...');
|
|
138
|
+
|
|
139
|
+
// Detect package manager
|
|
140
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
141
|
+
let pkgManager = 'pnpm';
|
|
142
|
+
if (userAgent.startsWith('yarn')) {
|
|
143
|
+
pkgManager = 'yarn';
|
|
144
|
+
} else if (userAgent.startsWith('npm')) {
|
|
145
|
+
pkgManager = 'npm';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const installResult = spawnSync(pkgManager, ['install'], {
|
|
149
|
+
cwd: projectDir,
|
|
150
|
+
stdio: 'inherit',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (installResult.status === 0) {
|
|
154
|
+
logSuccess('의존성 설치 완료');
|
|
155
|
+
} else {
|
|
156
|
+
console.log(`${YELLOW}⚠${RESET} 의존성 설치 실패 - 직접 설치해주세요.`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 9. Initial commit
|
|
160
|
+
try {
|
|
161
|
+
execSync('git add -A', { cwd: projectDir, stdio: 'pipe' });
|
|
162
|
+
execSync('git commit -m "Initial commit: Dailyshot web scaffolding"', {
|
|
163
|
+
cwd: projectDir,
|
|
164
|
+
stdio: 'pipe',
|
|
165
|
+
});
|
|
166
|
+
logSuccess('초기 커밋 생성 완료');
|
|
167
|
+
} catch {
|
|
168
|
+
// skip if git commit fails
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 10. Done!
|
|
172
|
+
console.log(`
|
|
173
|
+
${GREEN}${BOLD}✨ 프로젝트가 생성되었습니다!${RESET}
|
|
174
|
+
|
|
175
|
+
${BOLD}cd ${projectName}${RESET}
|
|
176
|
+
${BOLD}pnpm dev${RESET}
|
|
177
|
+
|
|
178
|
+
${CYAN}포함된 설정:${RESET}
|
|
179
|
+
• Next.js 14 (App Router)
|
|
180
|
+
• TypeScript (strict mode)
|
|
181
|
+
• Tailwind CSS + Dailyshot 디자인 시스템
|
|
182
|
+
• ESLint + Prettier
|
|
183
|
+
• shadcn/ui (new-york style)
|
|
184
|
+
• Claude AI 개발 가이드 (.claude/CLAUDE.md)
|
|
185
|
+
|
|
186
|
+
${CYAN}디자인 시스템:${RESET}
|
|
187
|
+
• typo-* 타이포그래피 클래스 (typo-headline1-600, typo-body1-400, ...)
|
|
188
|
+
• 커스텀 컬러 팔레트 (orange, blue, green, red, purple, ...)
|
|
189
|
+
• 반응형 브레이크포인트 (desktop:, tablet:, mobile:)
|
|
190
|
+
• 커스텀 애니메이션 (accordion, dialog, sheet, tooltip, ...)
|
|
191
|
+
`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main().catch((e) => {
|
|
195
|
+
logError(e.message);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-dailyshot-web-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dailyshot 디자인 시스템이 적용된 Next.js 프로젝트 스캐폴딩 CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-dailyshot-web-app": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18.0.0"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"dailyshot",
|
|
18
|
+
"nextjs",
|
|
19
|
+
"scaffolding",
|
|
20
|
+
"tailwindcss",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
23
|
+
"license": "PRIVATE"
|
|
24
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Dailyshot Web Scaffolding - Claude 개발 가이드
|
|
2
|
+
|
|
3
|
+
이 파일은 Dailyshot 프로젝트의 Claude AI 개발 지침을 정의합니다.
|
|
4
|
+
|
|
5
|
+
## 프로젝트 개요
|
|
6
|
+
|
|
7
|
+
- **Tech Stack**: Next.js 14, React 18, TypeScript, Tailwind CSS, shadcn/ui
|
|
8
|
+
- **패키지 매니저**: pnpm
|
|
9
|
+
- **라우팅**: Next.js App Router (`src/app/`)
|
|
10
|
+
- **스타일링**: Tailwind CSS + Dailyshot 커스텀 디자인 시스템
|
|
11
|
+
- **폰트**: Pretendard Variable (CDN)
|
|
12
|
+
|
|
13
|
+
## 🔴 CRITICAL: Dailyshot 디자인 시스템
|
|
14
|
+
|
|
15
|
+
프로젝트의 일관된 디자인을 위해 정의된 Tailwind CSS 디자인 시스템을 **반드시** 사용해야 합니다.
|
|
16
|
+
|
|
17
|
+
### 1. 타이포그래피 시스템 (typo-* 클래스)
|
|
18
|
+
|
|
19
|
+
기본 Tailwind의 `text-sm`, `text-lg` 등을 **절대 사용하지 마세요**. 반드시 `typo-*` 클래스를 사용하세요.
|
|
20
|
+
|
|
21
|
+
#### 사용법: `typo-{variant}-{weight}`
|
|
22
|
+
|
|
23
|
+
| Variant | Font Size | Line Height | Letter Spacing | 용도 |
|
|
24
|
+
|---------|-----------|-------------|----------------|------|
|
|
25
|
+
| `display` | 32px | 44px | -0.3px | 대형 제목, 히어로 텍스트 |
|
|
26
|
+
| `headline1` | 24px | 28px | -0.3px | 페이지 제목 |
|
|
27
|
+
| `headline2` | 20px | 24px | -0.3px | 섹션 제목 |
|
|
28
|
+
| `title1` | 18px | 24px | -0.3px | 서브 제목, 카드 제목 |
|
|
29
|
+
| `body1` | 16px | 24px | 0px | 본문 텍스트 (기본) |
|
|
30
|
+
| `body2` | 14px | 20px | 0px | 보조 텍스트 |
|
|
31
|
+
| `caption` | 12px | 16px | 0px | 캡션, 도움말 텍스트 |
|
|
32
|
+
| `tag` | 10px | 12px | 0px | 태그, 뱃지 |
|
|
33
|
+
| `content1` | 16px | 26px | 0px | 긴 컨텐츠 읽기용 |
|
|
34
|
+
| `content2` | 15px | 22px | 0px | 컨텐츠 읽기용 (작음) |
|
|
35
|
+
|
|
36
|
+
#### Font Weight
|
|
37
|
+
|
|
38
|
+
| Weight | 값 | 설명 |
|
|
39
|
+
|--------|-----|------|
|
|
40
|
+
| `300` | Light | 얇은 텍스트 |
|
|
41
|
+
| `400` | Regular | 기본 텍스트 |
|
|
42
|
+
| `500` | Medium | 중간 굵기 |
|
|
43
|
+
| `600` | SemiBold | 강조 텍스트 |
|
|
44
|
+
| `700` | Bold | 굵은 텍스트 |
|
|
45
|
+
|
|
46
|
+
#### 사용 예시
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
{/* ✅ 올바른 사용 */}
|
|
50
|
+
<h1 className="typo-display-700">대형 제목</h1>
|
|
51
|
+
<h2 className="typo-headline1-600">페이지 제목</h2>
|
|
52
|
+
<h3 className="typo-headline2-600">섹션 제목</h3>
|
|
53
|
+
<h4 className="typo-title1-500">서브 제목</h4>
|
|
54
|
+
<p className="typo-body1-400">본문 텍스트</p>
|
|
55
|
+
<p className="typo-body2-400">보조 텍스트</p>
|
|
56
|
+
<span className="typo-caption-400">캡션</span>
|
|
57
|
+
<span className="typo-tag-500">태그</span>
|
|
58
|
+
<article className="typo-content1-400">긴 읽기 컨텐츠</article>
|
|
59
|
+
|
|
60
|
+
{/* ❌ 금지 - 기본 Tailwind 타이포그래피 */}
|
|
61
|
+
<p className="text-sm">...</p>
|
|
62
|
+
<p className="text-lg font-bold">...</p>
|
|
63
|
+
<p className="text-[16px] leading-6">...</p>
|
|
64
|
+
<p className="text-base">...</p>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### 전체 클래스 목록 (50개)
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
typo-display-300, typo-display-400, typo-display-500, typo-display-600, typo-display-700
|
|
71
|
+
typo-headline1-300, typo-headline1-400, typo-headline1-500, typo-headline1-600, typo-headline1-700
|
|
72
|
+
typo-headline2-300, typo-headline2-400, typo-headline2-500, typo-headline2-600, typo-headline2-700
|
|
73
|
+
typo-title1-300, typo-title1-400, typo-title1-500, typo-title1-600, typo-title1-700
|
|
74
|
+
typo-body1-300, typo-body1-400, typo-body1-500, typo-body1-600, typo-body1-700
|
|
75
|
+
typo-body2-300, typo-body2-400, typo-body2-500, typo-body2-600, typo-body2-700
|
|
76
|
+
typo-caption-300, typo-caption-400, typo-caption-500, typo-caption-600, typo-caption-700
|
|
77
|
+
typo-tag-300, typo-tag-400, typo-tag-500, typo-tag-600, typo-tag-700
|
|
78
|
+
typo-content1-300, typo-content1-400, typo-content1-500, typo-content1-600, typo-content1-700
|
|
79
|
+
typo-content2-300, typo-content2-400, typo-content2-500, typo-content2-600, typo-content2-700
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. 컬러 시스템
|
|
83
|
+
|
|
84
|
+
정의된 컬러 팔레트만 사용하세요. **임의 색상값 사용 금지.**
|
|
85
|
+
|
|
86
|
+
#### 기본 색상
|
|
87
|
+
|
|
88
|
+
| 클래스 | 값 | 용도 |
|
|
89
|
+
|--------|-----|------|
|
|
90
|
+
| `white` | #ffffff | 기본 배경 |
|
|
91
|
+
| `black` | #1e1e1e | 기본 텍스트 |
|
|
92
|
+
|
|
93
|
+
#### 그레이스케일
|
|
94
|
+
|
|
95
|
+
| 클래스 | 값 | 용도 |
|
|
96
|
+
|--------|-----|------|
|
|
97
|
+
| `gray-50` | #FBFBFB | 가장 밝은 배경 |
|
|
98
|
+
| `gray-100` | #F7F7F7 | 밝은 배경 |
|
|
99
|
+
| `gray-200` | #EFEFEF | 디바이더, 배경 |
|
|
100
|
+
| `gray-300` | #E3E3E3 | 보더 |
|
|
101
|
+
| `gray-400` | #C4C4C4 | 비활성 아이콘 |
|
|
102
|
+
| `gray-500` | #A8A8A8 | 플레이스홀더 |
|
|
103
|
+
| `gray-600` | #7B7B7B | 보조 텍스트 |
|
|
104
|
+
| `gray-700` | #707070 | 보조 텍스트 (진) |
|
|
105
|
+
| `gray-800` | #5D5D5D | 강조 보조 텍스트 |
|
|
106
|
+
| `gray-900` | #464646 | 진한 텍스트 |
|
|
107
|
+
| `gray-950` | #282828 | 가장 진한 텍스트 |
|
|
108
|
+
|
|
109
|
+
#### 메인 컬러
|
|
110
|
+
|
|
111
|
+
| 색상 | 범위 | 대표값 (500) | 용도 |
|
|
112
|
+
|------|------|-------------|------|
|
|
113
|
+
| `orange` | 50-900 | #FE5000 | **프라이머리 컬러**, CTA, 강조 |
|
|
114
|
+
| `blue` | 50-950 | #5566FB | 정보, 링크 |
|
|
115
|
+
| `green` | 50-900 | #01B9A0 | 성공, 긍정 |
|
|
116
|
+
| `red` | 50-900 | #FF624D | 에러, 경고, 위험 |
|
|
117
|
+
| `purple` | 50-900 | #9C53EA | 특별, 프리미엄 |
|
|
118
|
+
| `yellow` | 50, 100, 500 | #FFC215 | 주의, 별점 |
|
|
119
|
+
|
|
120
|
+
#### 라이트 컬러
|
|
121
|
+
|
|
122
|
+
| 색상 | 범위 | 용도 |
|
|
123
|
+
|------|------|------|
|
|
124
|
+
| `light-orange` | 50-600 | 오렌지 배경, 하이라이트 |
|
|
125
|
+
| `light-blue` | 50-400 | 블루 배경 |
|
|
126
|
+
| `light-purple` | 50-400 | 퍼플 배경 |
|
|
127
|
+
|
|
128
|
+
#### 특수 색상
|
|
129
|
+
|
|
130
|
+
| 색상 | 범위 | 용도 |
|
|
131
|
+
|------|------|------|
|
|
132
|
+
| `paper` | 50-300 | 빈티지/페이퍼 느낌 |
|
|
133
|
+
| `glow` | 100-300 | 글로우/하이라이트 효과 |
|
|
134
|
+
|
|
135
|
+
#### 알파 색상
|
|
136
|
+
|
|
137
|
+
| 클래스 | 투명도 | 용도 |
|
|
138
|
+
|--------|--------|------|
|
|
139
|
+
| `black-alpha-4` | 4% | 매우 옅은 오버레이 |
|
|
140
|
+
| `black-alpha-30` | 30% | 가벼운 오버레이 |
|
|
141
|
+
| `black-alpha-40` | 40% | 중간 오버레이 |
|
|
142
|
+
| `black-alpha-55` | 55% | 진한 오버레이 |
|
|
143
|
+
| `black-alpha-75` | 75% | 매우 진한 오버레이 |
|
|
144
|
+
|
|
145
|
+
#### 사용 예시
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
{/* ✅ 올바른 사용 */}
|
|
149
|
+
<div className="bg-white text-black">...</div>
|
|
150
|
+
<div className="bg-orange-500 text-white">CTA 버튼</div>
|
|
151
|
+
<div className="bg-gray-100 border border-gray-300">카드</div>
|
|
152
|
+
<span className="text-red-500">에러 메시지</span>
|
|
153
|
+
<div className="bg-light-orange-100">하이라이트 영역</div>
|
|
154
|
+
<div className="bg-black-alpha-40">오버레이</div>
|
|
155
|
+
|
|
156
|
+
{/* ❌ 금지 - 임의 색상값 */}
|
|
157
|
+
<div className="bg-[#FE5000]">...</div>
|
|
158
|
+
<div className="text-[#1234ff]">...</div>
|
|
159
|
+
<div className="border-[rgb(200,200,200)]">...</div>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 3. 반응형 브레이크포인트
|
|
163
|
+
|
|
164
|
+
기본 Tailwind 브레이크포인트(`sm:`, `md:`, `lg:`, `xl:`)를 **사용하지 마세요**.
|
|
165
|
+
|
|
166
|
+
| 브레이크포인트 | 조건 | 용도 |
|
|
167
|
+
|---------------|------|------|
|
|
168
|
+
| `desktop:` | min-width: 1025px | 데스크탑 전용 스타일 |
|
|
169
|
+
| `tablet:` | max-width: 768px | 태블릿 이하 스타일 |
|
|
170
|
+
| `mobile:` | max-width: 480px | 모바일 전용 스타일 |
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
{/* ✅ 올바른 사용 */}
|
|
174
|
+
<div className="p-6 tablet:p-4 mobile:p-2">...</div>
|
|
175
|
+
<div className="grid grid-cols-3 tablet:grid-cols-2 mobile:grid-cols-1">...</div>
|
|
176
|
+
<div className="hidden desktop:block">데스크탑에서만 보임</div>
|
|
177
|
+
|
|
178
|
+
{/* ❌ 금지 - 기본 Tailwind 브레이크포인트 */}
|
|
179
|
+
<div className="md:p-4 lg:p-6">...</div>
|
|
180
|
+
<div className="sm:grid-cols-1 lg:grid-cols-3">...</div>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 4. 애니메이션
|
|
184
|
+
|
|
185
|
+
정의된 애니메이션 클래스를 사용하세요.
|
|
186
|
+
|
|
187
|
+
| 애니메이션 | 용도 |
|
|
188
|
+
|-----------|------|
|
|
189
|
+
| `animate-accordion-down` / `animate-accordion-up` | 아코디언 열기/닫기 |
|
|
190
|
+
| `animate-dialog-overlay-in` / `animate-dialog-overlay-out` | 다이얼로그 오버레이 |
|
|
191
|
+
| `animate-dialog-content-in` / `animate-dialog-content-out` | 다이얼로그 컨텐츠 |
|
|
192
|
+
| `animate-dropdown-in` / `animate-dropdown-out` | 드롭다운 메뉴 |
|
|
193
|
+
| `animate-sheet-in-from-{top,bottom,left,right}` | 시트 열기 |
|
|
194
|
+
| `animate-sheet-out-to-{top,bottom,left,right}` | 시트 닫기 |
|
|
195
|
+
| `animate-tooltip-in` / `animate-tooltip-out` | 툴팁 |
|
|
196
|
+
| `animate-skeleton-pulse` | 스켈레톤 로딩 |
|
|
197
|
+
|
|
198
|
+
### 5. 커스텀 유틸리티 클래스
|
|
199
|
+
|
|
200
|
+
| 클래스 | 용도 |
|
|
201
|
+
|--------|------|
|
|
202
|
+
| `absolute-center` | 절대 위치 + 중앙 정렬 (flex) |
|
|
203
|
+
| `scrollbar-hide` | 스크롤바 숨김 (cross-browser) |
|
|
204
|
+
| `no-scrollbar` | overflow auto + 스크롤바 숨김 |
|
|
205
|
+
| `visually-hidden` | 시각적으로 숨김 (접근성 유지) |
|
|
206
|
+
| `font-pretendard` | Pretendard 폰트 적용 |
|
|
207
|
+
|
|
208
|
+
### 6. 커스텀 미디어 쿼리 Variant
|
|
209
|
+
|
|
210
|
+
| Variant | 조건 | 용도 |
|
|
211
|
+
|---------|------|------|
|
|
212
|
+
| `can-hover:` | `@media (hover: hover)` | 호버 가능한 디바이스에서만 |
|
|
213
|
+
| `can-active:` | `@media (pointer: fine)` | 정밀 포인터 디바이스에서만 |
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
{/* 마우스가 있는 디바이스에서만 호버 효과 */}
|
|
217
|
+
<button className="can-hover:hover:bg-orange-50">버튼</button>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## 코드 스타일 규칙
|
|
221
|
+
|
|
222
|
+
### Import 규칙
|
|
223
|
+
|
|
224
|
+
- **절대 경로 사용 필수**: 같은 폴더를 제외하고 항상 `@/` 접두사 사용
|
|
225
|
+
- ✅ `import { cn } from '@/lib/utils';`
|
|
226
|
+
- ❌ `import { cn } from '../../lib/utils';`
|
|
227
|
+
|
|
228
|
+
### 네이밍 규칙
|
|
229
|
+
|
|
230
|
+
| 대상 | 규칙 | 예시 |
|
|
231
|
+
|------|------|------|
|
|
232
|
+
| interface | PascalCase (I 접두사 금지) | `UserProfile` (❌ `IUserProfile`) |
|
|
233
|
+
| type alias | PascalCase (T 접두사 금지) | `ButtonVariant` (❌ `TButtonVariant`) |
|
|
234
|
+
| 변수 | camelCase / PascalCase / UPPER_CASE | `userName`, `Button`, `MAX_COUNT` |
|
|
235
|
+
| 함수 | camelCase / PascalCase | `getUserName`, `Button` |
|
|
236
|
+
| 컴포넌트 | PascalCase | `ProductCard` |
|
|
237
|
+
|
|
238
|
+
### Prettier 포매팅
|
|
239
|
+
|
|
240
|
+
| 설정 | 값 |
|
|
241
|
+
|------|-----|
|
|
242
|
+
| printWidth | 100 |
|
|
243
|
+
| singleQuote | true |
|
|
244
|
+
| semi | true |
|
|
245
|
+
| trailingComma | all |
|
|
246
|
+
| tabWidth | 2 |
|
|
247
|
+
| arrowParens | always |
|
|
248
|
+
|
|
249
|
+
### 유틸리티 함수
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
// cn() - Tailwind 클래스 병합 유틸리티
|
|
253
|
+
import { cn } from '@/lib/utils';
|
|
254
|
+
|
|
255
|
+
// 조건부 클래스 적용
|
|
256
|
+
<div className={cn('typo-body1-400 text-black', isActive && 'text-orange-500')}>
|
|
257
|
+
텍스트
|
|
258
|
+
</div>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Frontend 코드 품질 가이드라인
|
|
262
|
+
|
|
263
|
+
### 핵심 원칙
|
|
264
|
+
|
|
265
|
+
1. **가독성**: 매직 넘버를 명명된 상수로 대체, 복잡한 로직 컴포넌트화
|
|
266
|
+
2. **예측 가능성**: 일관된 반환 타입, Single Responsibility
|
|
267
|
+
3. **응집력**: 기능/도메인별 코드 구조화
|
|
268
|
+
4. **결합도**: Component Composition으로 Props Drilling 제거
|
|
269
|
+
|
|
270
|
+
### shadcn/ui 컴포넌트
|
|
271
|
+
|
|
272
|
+
- shadcn CLI로 설치: `npx shadcn@latest add [component-name]`
|
|
273
|
+
- 컴포넌트 위치: `src/components/ui/`
|
|
274
|
+
- 유틸리티: `src/lib/utils.ts` (cn 함수)
|
|
275
|
+
- shadcn 컴포넌트 내부에서는 디자인 시스템 예외 허용
|
|
276
|
+
|
|
277
|
+
## 개발 명령어
|
|
278
|
+
|
|
279
|
+
| 명령어 | 설명 |
|
|
280
|
+
|--------|------|
|
|
281
|
+
| `pnpm dev` | 개발 서버 실행 |
|
|
282
|
+
| `pnpm build` | 프로덕션 빌드 |
|
|
283
|
+
| `pnpm lint` | ESLint 검사 |
|
|
284
|
+
| `pnpm lint:fix` | ESLint 자동 수정 |
|
|
285
|
+
| `pnpm format` | Prettier 포매팅 |
|
|
286
|
+
| `pnpm typecheck` | TypeScript 타입 검사 |
|
|
287
|
+
|
|
288
|
+
## 코드 리뷰 체크리스트
|
|
289
|
+
|
|
290
|
+
개발 완료 전 반드시 확인:
|
|
291
|
+
|
|
292
|
+
1. ✅ 모든 색상이 정의된 컬러 팔레트를 사용하는가?
|
|
293
|
+
2. ✅ 모든 텍스트가 `typo-*` 클래스를 사용하는가?
|
|
294
|
+
3. ✅ 반응형 디자인이 `desktop:`, `tablet:`, `mobile:` 브레이크포인트를 사용하는가?
|
|
295
|
+
4. ✅ 임의의 값(`[#000]`, `[16px]` 등)을 사용하지 않았는가?
|
|
296
|
+
5. ✅ 컴포넌트가 Frontend 가이드라인을 따르는가?
|
|
297
|
+
6. ✅ Import 경로가 절대 경로(`@/`)를 사용하는가?
|
|
298
|
+
7. ✅ 타입 에러가 없는가? (`pnpm typecheck`)
|
|
299
|
+
8. ✅ 린트 에러가 없는가? (`pnpm lint`)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "tailwind.config.ts",
|
|
8
|
+
"css": "src/app/globals.css",
|
|
9
|
+
"baseColor": "slate",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
plugins: ['no-relative-import-paths', 'prettier'],
|
|
3
|
+
extends: [
|
|
4
|
+
'@rushstack/eslint-config/profile/web-app',
|
|
5
|
+
'plugin:prettier/recommended',
|
|
6
|
+
'next/core-web-vitals',
|
|
7
|
+
],
|
|
8
|
+
rules: {
|
|
9
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
10
|
+
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
11
|
+
'@typescript-eslint/no-floating-promises': 'off',
|
|
12
|
+
'@rushstack/no-new-null': 'off',
|
|
13
|
+
'@rushstack/typedef-var': 'off',
|
|
14
|
+
'no-relative-import-paths/no-relative-import-paths': [
|
|
15
|
+
'warn',
|
|
16
|
+
{ allowSameFolder: true, rootDir: 'src', prefix: '@' },
|
|
17
|
+
],
|
|
18
|
+
'@typescript-eslint/explicit-member-accessibility': [
|
|
19
|
+
'warn',
|
|
20
|
+
{
|
|
21
|
+
overrides: {
|
|
22
|
+
constructors: 'no-public',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
'@typescript-eslint/naming-convention': [
|
|
27
|
+
'warn',
|
|
28
|
+
{
|
|
29
|
+
selector: 'objectLiteralProperty',
|
|
30
|
+
format: ['camelCase', 'PascalCase', 'UPPER_CASE', 'snake_case'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
selector: 'interface',
|
|
34
|
+
format: ['PascalCase'],
|
|
35
|
+
custom: { regex: '^I[A-Z]', match: false },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
selector: 'typeAlias',
|
|
39
|
+
format: ['PascalCase'],
|
|
40
|
+
custom: { regex: '^T[A-Z]', match: false },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
selector: 'typeParameter',
|
|
44
|
+
format: ['PascalCase'],
|
|
45
|
+
custom: { regex: '^T[A-Z]', match: false },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
selector: 'variable',
|
|
49
|
+
format: ['camelCase', 'PascalCase', 'UPPER_CASE', 'snake_case'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
selector: 'function',
|
|
53
|
+
format: ['camelCase', 'PascalCase'],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
selector: 'typeLike',
|
|
57
|
+
format: ['PascalCase'],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
/node_modules
|
|
3
|
+
/.pnp
|
|
4
|
+
.pnp.js
|
|
5
|
+
.yarn/install-state.gz
|
|
6
|
+
|
|
7
|
+
# testing
|
|
8
|
+
/coverage
|
|
9
|
+
|
|
10
|
+
# next.js
|
|
11
|
+
/.next/
|
|
12
|
+
/out/
|
|
13
|
+
|
|
14
|
+
# production
|
|
15
|
+
/build
|
|
16
|
+
|
|
17
|
+
# misc
|
|
18
|
+
.DS_Store
|
|
19
|
+
*.pem
|
|
20
|
+
|
|
21
|
+
# debug
|
|
22
|
+
npm-debug.log*
|
|
23
|
+
yarn-debug.log*
|
|
24
|
+
yarn-error.log*
|
|
25
|
+
|
|
26
|
+
# local env files
|
|
27
|
+
.env*.local
|
|
28
|
+
.env
|
|
29
|
+
|
|
30
|
+
# vercel
|
|
31
|
+
.vercel
|
|
32
|
+
|
|
33
|
+
# typescript
|
|
34
|
+
*.tsbuildinfo
|
|
35
|
+
next-env.d.ts
|
|
36
|
+
|
|
37
|
+
.omc/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-dailyshot-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
10
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
11
|
+
"format": "prettier --write 'src/**/*.{ts,tsx,css,json}'",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"next": "^14.2.35",
|
|
16
|
+
"react": "^18.3.1",
|
|
17
|
+
"react-dom": "^18.3.1",
|
|
18
|
+
"clsx": "^2.1.1",
|
|
19
|
+
"tailwind-merge": "^2.6.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@rushstack/eslint-config": "^4.0.5",
|
|
23
|
+
"@tailwindcss/container-queries": "^0.1.1",
|
|
24
|
+
"@types/node": "^20.17.12",
|
|
25
|
+
"@types/react": "^18.3.18",
|
|
26
|
+
"@types/react-dom": "^18.3.5",
|
|
27
|
+
"autoprefixer": "^10.4.20",
|
|
28
|
+
"cssnano": "^7.0.6",
|
|
29
|
+
"eslint": "^8.57.0",
|
|
30
|
+
"eslint-config-next": "^14.2.35",
|
|
31
|
+
"eslint-config-prettier": "^9.1.0",
|
|
32
|
+
"eslint-plugin-no-relative-import-paths": "^1.5.5",
|
|
33
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
34
|
+
"postcss": "^8.4.49",
|
|
35
|
+
"prettier": "^3.3.2",
|
|
36
|
+
"tailwindcss": "^3.4.17",
|
|
37
|
+
"typescript": "^5.2.2"
|
|
38
|
+
}
|
|
39
|
+
}
|