🎨 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 개발 ν›„ μ‹€μ œ κ΅¬ν˜„ 반영

λ°°κ²½ (Background)

ν”„λ‘œμ νŠΈ λͺ©ν‘œ (Objective)

  • μΌκ΄€λœ μ‚¬μš©μž κ²½ν—˜μ„ μœ„ν•œ 곡톡 μ»΄ν¬λ„ŒνŠΈ 제곡
  • μž¬μ‚¬μš© κ°€λŠ₯ν•œ UI μ»΄ν¬λ„ŒνŠΈ μ‹œμŠ€ν…œ ꡬ좕
  • λͺ¨λ°”일 μΉœν™”μ μΈ λ„€λΉ„κ²Œμ΄μ…˜ 및 λ‘œλ”© μƒνƒœ ν‘œμ‹œ
  • λ””μžμΈ μ‹œμŠ€ν…œ ꡬ좕 및 적용

λͺ©ν‘œκ°€ μ•„λ‹Œ 것 (Non-goals)

  • λ‹€κ΅­μ–΄ 지원
  • λ³΅μž‘ν•œ μ• λ‹ˆλ©”μ΄μ…˜ μ‹œμŠ€ν…œ
  • 닀크 λͺ¨λ“œ

섀계 및 기술 자료 (Architecture and Technical Documentation)

μ•„ν‚€ν…μ²˜ κ°œμš” (Architecture Overview)

ν”„λ ˆμž„μ›Œν¬/라이브러리

  • Next.js (v15.3.0) + React (v19.1.0)
    • App Router 기반 λΌμš°νŒ…
    • SSR/ISR ν™œμš©μœΌλ‘œ SEO μ΅œμ ν™”
    • React 19의 use() ν›… ν™œμš©ν•œ 데이터 페칭

μƒνƒœ κ΄€λ¦¬

  • Zustand (v5.0.3)
    • μž‘μ€ λ²ˆλ“€ μ‚¬μ΄μ¦ˆλ‘œ λͺ¨λ°”일 μ›Ή μ΅œμ ν™”
    • μ„œλ²„ μ»΄ν¬λ„ŒνŠΈ ν˜Έν™˜μ„±
    • 직관적인 API둜 λΉ λ₯Έ 개발 κ°€λŠ₯

UI/μŠ€νƒ€μΌλ§

  • 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

λ””μžμΈ μ‹œμŠ€ν…œ

색상 μ‹œμŠ€ν…œ (CSS λ³€μˆ˜)

: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
  • 그림자 효과둜 λͺ¨λ°”일 λ””λ°”μ΄μŠ€ λŠλ‚Œ

κ΅¬ν˜„λœ μ»΄ν¬λ„ŒνŠΈ

Header : νŽ˜μ΄μ§€ 헀더

  • μ—­ν• : νŽ˜μ΄μ§€ 제λͺ©κ³Ό λ’€λ‘œκ°€κΈ° λ²„νŠΌμ„ ν¬ν•¨ν•œ 헀더

  • Props & Interface:

    interface HeaderProps {
      title?: string;
      showBackButton?: boolean;
      onBackClick?: () => void;
      rightElement?: React.ReactNode;
    }
  • κΈ°λŠ₯:

    • λ’€λ‘œκ°€κΈ° λ²„νŠΌ (κΈ°λ³Έ: router.back())
    • 쀑앙 μ •λ ¬λœ 제λͺ©
    • 우츑 μš”μ†Œ μ»€μŠ€ν„°λ§ˆμ΄μ§•
  • μŠ€νƒ€μΌλ§:

    • 높이: 64px (h-16)
    • ν•˜λ‹¨ ν…Œλ‘λ¦¬
    • 쒌우 νŒ¨λ”©: 16px

BottomNavigation : ν•˜λ‹¨ λ„€λΉ„κ²Œμ΄μ…˜

  • μ—­ν• : μ•±μ˜ μ£Όμš” μ„Ήμ…˜ κ°„ 이동을 μœ„ν•œ ν•˜λ‹¨ λ‚΄λΉ„κ²Œμ΄μ…˜ λ°”

  • 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)
    • κ³ μ • μœ„μΉ˜ (ν•˜λ‹¨)
    • ν™ˆ λ²„νŠΌ 크기 κ°•μ‘°

Toaster : ν† μŠ€νŠΈ λ©”μ‹œμ§€

  • μ—­ν• : μΌμ‹œμ μΈ μ•Œλ¦Ό λ©”μ‹œμ§€ ν‘œμ‹œ
  • Props & Interface:
    • sonner의 ToasterProps 상속
  • κΈ°λŠ₯:
    • μžλ™ 사라짐
    • λ‹€μ–‘ν•œ νƒ€μž… 지원 (success, error, info)
    • ν…Œλ§ˆ 연동
  • μŠ€νƒ€μΌλ§:
    • CSS λ³€μˆ˜ 기반 μŠ€νƒ€μΌ
    • μ»€μŠ€ν…€ toaster.css

BackgroundPanel : λ°°κ²½ νŒ¨λ„

  • μ—­ν• : λ°μŠ€ν¬ν†± ν™˜κ²½μ—μ„œ λΈŒλžœλ“œ λ°°κ²½ ν‘œμ‹œ
  • κΈ°λŠ₯:
    • λͺ¨λ°”μΌμ—μ„œλŠ” μˆ¨κΉ€
    • λŒν•€ 둜고 μ• λ‹ˆλ©”μ΄μ…˜
    • λΈŒλžœλ“œ λ©”μ‹œμ§€ ν‘œμ‹œ
  • μŠ€νƒ€μΌλ§:
    • 쒌츑 μ •λ ¬
    • λΆ€μœ  μ• λ‹ˆλ©”μ΄μ…˜ (dolphinFloat)

FeedbackButton : ν”Όλ“œλ°± λ²„νŠΌ

  • μ—­ν• : μ‚¬μš©μž ν”Όλ“œλ°± μˆ˜μ§‘μ„ μœ„ν•œ ν”Œλ‘œνŒ… λ²„νŠΌ
  • κΈ°λŠ₯:
    • Google Forms 연동
    • μƒˆ νƒ­μ—μ„œ μ—΄κΈ°
  • μŠ€νƒ€μΌλ§:
    • κ³ μ • μœ„μΉ˜ (μš°ν•˜λ‹¨)
    • λ°μŠ€ν¬ν†±μ—μ„œλ§Œ ν‘œμ‹œ
    • ν˜Έλ²„ 효과
    • 이λͺ¨μ§€ μ•„μ΄μ½˜ (πŸ’Œ)

Form κ΄€λ ¨ μ»΄ν¬λ„ŒνŠΈ

Button

  • shadcn/ui 기반
  • λ‹€μ–‘ν•œ variant 지원
  • μ ‘κ·Όμ„± κ³ λ €

Input

  • 폼 μœ νš¨μ„± 검사 톡합
  • μ—λŸ¬ μƒνƒœ ν‘œμ‹œ
  • input-text 클래슀 적용

Textarea

  • μžλ™ 높이 쑰절 (선택적)
  • κΈ€μž 수 μ œν•œ
  • μ—λŸ¬ μƒνƒœ ν‘œμ‹œ

FormFormFieldFormItemFormLabelFormMessage

  • react-hook-form 톡합
  • μΌκ΄€λœ 폼 λ ˆμ΄μ•„μ›ƒ
  • μ—λŸ¬ λ©”μ‹œμ§€ ν‘œμ‹œ

곡톡 ν›…

useImageUpload

  • μ—­ν• : 이미지 μ—…λ‘œλ“œ 처리

  • κΈ°λŠ₯:

    • 파일 μœ νš¨μ„± 검사
    • μ—…λ‘œλ“œ μ§„ν–‰ μƒνƒœ
    • μ—λŸ¬ 처리
  • λ°˜ν™˜κ°’:

    {
      uploadImage: (file: File, type: string) => Promise<string | null>,
      isLoading: boolean,
      error: Error | null
    }

Provider

QueryProvider

  • μ—­ν• : TanStack Query ν΄λΌμ΄μ–ΈνŠΈ 제곡
  • μ„€μ •:
    • κΈ°λ³Έ μΊμ‹œ μ‹œκ°„
    • μ—λŸ¬ μž¬μ‹œλ„
    • μ „μ—­ μ—λŸ¬ 처리

μ„±λŠ₯ μ΅œμ ν™”

폰트 μ΅œμ ν™”

  • κ°€λ³€ 폰트 μ‚¬μš©
  • display: swap
  • 둜컬 폰트 λ‘œλ”©

이미지 μ΅œμ ν™”

  • Next.js Image μ»΄ν¬λ„ŒνŠΈ
  • μ μ ˆν•œ sizes 속성
  • lazy loading

λ²ˆλ“€ μ΅œμ ν™”

  • Tree-shaking
  • 동적 μž„ν¬νŠΈ
  • CSS λ³€μˆ˜λ‘œ ν…Œλ§ˆ 관리

μ ‘κ·Όμ„±

μ‹œλ§¨ν‹± HTML

  • μ μ ˆν•œ νƒœκ·Έ μ‚¬μš©
  • ARIA 라벨

ν‚€λ³΄λ“œ λ„€λΉ„κ²Œμ΄μ…˜

  • νƒ­ μˆœμ„œ 관리
  • 포컀슀 ν‘œμ‹œ

λ°˜μ‘ν˜• λ””μžμΈ

λͺ¨λ°”일 μš°μ„ 

  • 375px - 430px μ΅œμ ν™”
  • ν„°μΉ˜ μΉœν™”μ  μΈν„°νŽ˜μ΄μŠ€

λ°μŠ€ν¬ν†± λŒ€μ‘

  • 쀑앙 μ •λ ¬λœ λͺ¨λ°”일 λ·°
  • λ°°κ²½ νŒ¨λ„λ‘œ λΈŒλžœλ”©

μ—λŸ¬ 처리

폼 μ—λŸ¬

  • ν•„λ“œλ³„ μ—λŸ¬ λ©”μ‹œμ§€
  • 전체 폼 μ—λŸ¬ 처리

API μ—λŸ¬

  • ν† μŠ€νŠΈ λ©”μ‹œμ§€ ν‘œμ‹œ
  • μž¬μ‹œλ„ μ˜΅μ…˜

ν–₯ν›„ κ³„νš

  • λ‘œλ”© μŠ€ν”Όλ„ˆ κ΅¬ν˜„
  • μŠ€ν† λ¦¬λΆ μΆ”κ°€
  • μ ‘κ·Όμ„± κ°œμ„ 
⚠️ **GitHub.com Fallback** ⚠️