π¨ 3λ¨κ³ : κ³΅ν΅ νλ‘μ νΈ ν ν¬μ€ν - 100-hours-a-week/7-team-ddb-wiki GitHub Wiki
μμ±μΌ | 2025-05-30 |
---|---|
λ²μ | 1.1 |
μμ±μ | suzy.kang (κ°μμ§) |
κ²ν μ | kevin |
μν | MVP κ°λ° μλ£ |
λ²μ | λ μ§ | μμ±μ | κ²ν μ | λ³κ²½λ΄μ© |
---|---|---|---|---|
1.0 | 2025-04-26 | suzy | kevin | μ΄μ |
1.1 | 2025-05-30 | suzy | kevin | MVP κ°λ° ν μ€μ ꡬν λ°μ |
- μΌκ΄λ μ¬μ©μ κ²½νμ μν κ³΅ν΅ μ»΄ν¬λνΈ μ 곡
- μ¬μ¬μ© κ°λ₯ν UI μ»΄ν¬λνΈ μμ€ν ꡬμΆ
- λͺ¨λ°μΌ μΉνμ μΈ λ€λΉκ²μ΄μ λ° λ‘λ© μν νμ
- λμμΈ μμ€ν κ΅¬μΆ λ° μ μ©
- λ€κ΅μ΄ μ§μ
- 볡μ‘ν μ λλ©μ΄μ μμ€ν
- λ€ν¬ λͺ¨λ
-
Next.js (v15.3.0) + React (v19.1.0)
- App Router κΈ°λ° λΌμ°ν
- SSR/ISR νμ©μΌλ‘ SEO μ΅μ ν
- React 19μ use() ν νμ©ν λ°μ΄ν° νμΉ
-
Zustand (v5.0.3)
- μμ λ²λ€ μ¬μ΄μ¦λ‘ λͺ¨λ°μΌ μΉ μ΅μ ν
- μλ² μ»΄ν¬λνΈ νΈνμ±
- μ§κ΄μ μΈ APIλ‘ λΉ λ₯Έ κ°λ° κ°λ₯
-
Tailwind CSS (v5)
- CSS λ³μ κΈ°λ° ν λ§ μμ€ν
- μ νΈλ¦¬ν° ν΄λμ€ μ κ·Όλ²
- @import λ°©μ μ¬μ©
-
shadcn/ui κΈ°λ° μ»΄ν¬λνΈ
- Form, Input, Button λ± κΈ°λ³Έ μ»΄ν¬λνΈ
- Radix UI κΈ°λ°
-
Pretendard Variable Font
- κ°λ³ ν°νΈλ‘ μ΅μ ν
- 100-900 weight μ§μ
- Next.js localFont μ¬μ©
-
iconoir-react
- μΌκ΄λ μμ΄μ½ μμ€ν
- Tree-shaking μ§μ
-
sonner
- κ°λ²Όμ΄ ν μ€νΈ λΌμ΄λΈλ¬λ¦¬
- 컀μ€ν°λ§μ΄μ§ κ°λ₯
src/
βββ app/
β βββ globals.css # μ μ μ€νμΌ λ° λμμΈ μμ€ν
β βββ layout.tsx # λ£¨νΈ λ μ΄μμ
βββ shared/
βββ components/ # κ³΅ν΅ μ»΄ν¬λνΈ
β βββ alert-dialog/
β βββ background-panel/
β βββ bottom-navigation/
β βββ button/
β βββ card/
β βββ checkbox/
β βββ feedback-button/
β βββ form/
β βββ header/
β βββ input/
β βββ label/
β βββ loading-spinner/
β βββ textarea/
β βββ toaster/
βββ fonts/ # ν°νΈ μ€μ
β βββ pretendard.ts
β βββ PretendardVariable.woff2
βββ hooks/ # κ³΅ν΅ ν
β βββ useImageUpload.ts
βββ lib/ # μ νΈλ¦¬ν° ν¨μ
βββ providers/ # μ μ νλ‘λ°μ΄λ
βββ QueryProvider.tsx
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--secondary: oklch(0.967 0.001 286.375);
--muted: oklch(0.967 0.001 286.375);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
/* ... */
}
--font-size-heading-1: 22px;
--font-size-heading-2: 18px;
--font-size-heading-3: 16px;
--font-size-body: 14px;
--font-size-caption: 12px;
--font-size-button: 14px;
--font-size-input: 14px;
--font-size-label: 12px;
.heading-1 {
font-size: 22px;
font-weight: 700;
line-height: 1.2;
}
.heading-2 {
font-size: 18px;
font-weight: 600;
line-height: 1.3;
}
.heading-3 {
font-size: 16px;
font-weight: 600;
line-height: 1.4;
}
.body-text {
font-size: 14px;
line-height: 1.5;
}
.caption {
font-size: 12px;
line-height: 1.4;
}
.button-text {
font-size: 14px;
font-weight: 500;
}
.input-text {
font-size: 14px;
line-height: 1.5;
}
<html>
<body>
<QueryProvider>
<BackgroundPanel /> {/* λ°μ€ν¬ν± λ°°κ²½ */}
<div className="mobile-container">
{' '}
{/* λͺ¨λ°μΌ 컨ν
μ΄λ */}
<main>
{children}
<Toaster />
</main>
</div>
</QueryProvider>
<FeedbackButton /> {/* νΌλλ°± λ²νΌ */}
</body>
</html>
- μ΅λ λλΉ: 430px
- μ΅μ λλΉ: 375px
- λμ΄: 100svh
- κ·Έλ¦Όμ ν¨κ³Όλ‘ λͺ¨λ°μΌ λλ°μ΄μ€ λλ
-
μν : νμ΄μ§ μ λͺ©κ³Ό λ€λ‘κ°κΈ° λ²νΌμ ν¬ν¨ν ν€λ
-
Props & Interface:
interface HeaderProps { title?: string; showBackButton?: boolean; onBackClick?: () => void; rightElement?: React.ReactNode; }
-
κΈ°λ₯:
- λ€λ‘κ°κΈ° λ²νΌ (κΈ°λ³Έ: router.back())
- μ€μ μ λ ¬λ μ λͺ©
- μ°μΈ‘ μμ 컀μ€ν°λ§μ΄μ§
-
μ€νμΌλ§:
- λμ΄: 64px (h-16)
- νλ¨ ν λ리
- μ’μ° ν¨λ©: 16px
-
μν : μ±μ μ£Όμ μΉμ κ° μ΄λμ μν νλ¨ λ΄λΉκ²μ΄μ λ°
-
Props & Interface:
- Props μμ (λ΄λΆ μ€μ μ¬μ©)
-
κΈ°λ₯:
- 3κ° ν: λ€μ΄μ΄λ¦¬(/moments), ν(/), MY(/mypage)
- νμ¬ κ²½λ‘ κΈ°λ° νμ±ν μν
- μμ΄μ½ λ³κ²½ (νμ±/λΉνμ±)
-
λ€λΉκ²μ΄μ μμ΄ν :
{ label: 'λ€μ΄μ΄λ¦¬', icon: <Book />, solidIcon: <BookSolid />, href: '/moments' }, { label: 'ν', icon: <Home />, solidIcon: <HomeAlt />, href: '/', isHome: true }, { label: 'MY', icon: <UserCircle />, solidIcon: <UserCircle />, href: '/mypage' }
-
μ€νμΌλ§:
- λμ΄: 80px (h-20)
- κ³ μ μμΉ (νλ¨)
- ν λ²νΌ ν¬κΈ° κ°μ‘°
- μν : μΌμμ μΈ μλ¦Ό λ©μμ§ νμ
-
Props & Interface:
- sonnerμ ToasterProps μμ
-
κΈ°λ₯:
- μλ μ¬λΌμ§
- λ€μν νμ μ§μ (success, error, info)
- ν λ§ μ°λ
-
μ€νμΌλ§:
- CSS λ³μ κΈ°λ° μ€νμΌ
- 컀μ€ν toaster.css
- μν : λ°μ€ν¬ν± νκ²½μμ λΈλλ λ°°κ²½ νμ
-
κΈ°λ₯:
- λͺ¨λ°μΌμμλ μ¨κΉ
- λν λ‘κ³ μ λλ©μ΄μ
- λΈλλ λ©μμ§ νμ
-
μ€νμΌλ§:
- μ’μΈ‘ μ λ ¬
- λΆμ μ λλ©μ΄μ (dolphinFloat)
- μν : μ¬μ©μ νΌλλ°± μμ§μ μν νλ‘ν λ²νΌ
-
κΈ°λ₯:
- Google Forms μ°λ
- μ νμμ μ΄κΈ°
-
μ€νμΌλ§:
- κ³ μ μμΉ (μ°νλ¨)
- λ°μ€ν¬ν±μμλ§ νμ
- νΈλ² ν¨κ³Ό
- μ΄λͺ¨μ§ μμ΄μ½ (π)
- shadcn/ui κΈ°λ°
- λ€μν variant μ§μ
- μ κ·Όμ± κ³ λ €
- νΌ μ ν¨μ± κ²μ¬ ν΅ν©
- μλ¬ μν νμ
- input-text ν΄λμ€ μ μ©
- μλ λμ΄ μ‘°μ (μ νμ )
- κΈμ μ μ ν
- μλ¬ μν νμ
- react-hook-form ν΅ν©
- μΌκ΄λ νΌ λ μ΄μμ
- μλ¬ λ©μμ§ νμ
-
μν : μ΄λ―Έμ§ μ λ‘λ μ²λ¦¬
-
κΈ°λ₯:
- νμΌ μ ν¨μ± κ²μ¬
- μ λ‘λ μ§ν μν
- μλ¬ μ²λ¦¬
-
λ°νκ°:
{ uploadImage: (file: File, type: string) => Promise<string | null>, isLoading: boolean, error: Error | null }
- μν : TanStack Query ν΄λΌμ΄μΈνΈ μ 곡
-
μ€μ :
- κΈ°λ³Έ μΊμ μκ°
- μλ¬ μ¬μλ
- μ μ μλ¬ μ²λ¦¬
- κ°λ³ ν°νΈ μ¬μ©
- display: swap
- λ‘컬 ν°νΈ λ‘λ©
- Next.js Image μ»΄ν¬λνΈ
- μ μ ν sizes μμ±
- lazy loading
- Tree-shaking
- λμ μν¬νΈ
- CSS λ³μλ‘ ν λ§ κ΄λ¦¬
- μ μ ν νκ·Έ μ¬μ©
- ARIA λΌλ²¨
- ν μμ κ΄λ¦¬
- ν¬μ»€μ€ νμ
- 375px - 430px μ΅μ ν
- ν°μΉ μΉνμ μΈν°νμ΄μ€
- μ€μ μ λ ¬λ λͺ¨λ°μΌ λ·°
- λ°°κ²½ ν¨λλ‘ λΈλλ©
- νλλ³ μλ¬ λ©μμ§
- μ 체 νΌ μλ¬ μ²λ¦¬
- ν μ€νΈ λ©μμ§ νμ
- μ¬μλ μ΅μ
- λ‘λ© μ€νΌλ ꡬν
- μ€ν λ¦¬λΆ μΆκ°
- μ κ·Όμ± κ°μ