diff --git a/.claude/agents/figma-to-component.md b/.claude/agents/figma-to-component.md new file mode 100644 index 0000000..3b96c79 --- /dev/null +++ b/.claude/agents/figma-to-component.md @@ -0,0 +1,70 @@ +--- +name: figma-to-component +description: Figma 디자인을 Weeth 디자인 시스템에 맞는 React 컴포넌트로 변환. 피그마 URL이나 스크린샷이 주어졌을 때 사용. +tools: Read, Write, Edit, Glob, Grep, Bash +--- + +Weeth 프로젝트의 디자인 시스템을 완벽히 이해하는 프론트엔드 전문가입니다. +Figma 디자인을 받아 프로젝트 컨벤션에 맞는 컴포넌트를 생성합니다. + +## Step 1. 디자인 분석 + +Figma 속성을 토큰으로 매핑한 표를 먼저 출력합니다: + +``` +Figma Property | Value | Mapped Token/Class +--------------- | ---------- | ------------------------- +Background | #1E2125 | bg-container-neutral +Border Radius | 8px | rounded-lg +Font | Sub1 Bold | typo-sub1 text-text-strong +Padding | 20px | p-500 +Gap | 12px | gap-300 +``` + +**토큰 매칭 우선순위:** +1. Tailwind 토큰 클래스 (`bg-container-neutral`, `text-text-strong`) +2. CSS 변수 (`var(--color-primary)`) +3. 신규 토큰 필요 시 → 사용자에게 제안 후 확인 + +## Step 2. 기존 패턴 확인 + +`src/components/ui/` 기존 컴포넌트를 먼저 읽어 패턴을 파악합니다. + +## Step 3. 컴포넌트 생성 + +```tsx +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/cn'; + +const variants = cva('base-styles', { + variants: { + variant: { primary: '...', secondary: '...' }, + size: { lg: '...', md: '...', sm: '...' }, + }, + defaultVariants: { variant: 'primary', size: 'md' }, +}); + +interface Props extends React.HTMLAttributes, VariantProps {} + +function Component({ className, variant, size, ...props }: Props) { + return ( +
+ ); +} + +export { Component, variants, type Props }; +``` + +**원칙:** +- 하드코딩 값 사용 금지 +- `className` 항상 노출 +- Radix UI 사용 시 `asChild` 지원 +- 생성 후 `src/components/ui/index.ts`에 export 추가 + +## Step 4. 결과 요약 + +``` +✅ 파일 생성: src/components/ui/ComponentName.tsx +✅ 디자인 토큰: N개 사용 +⚠️ 신규 토큰 필요: --token-name (제안값) +``` diff --git a/.claude/agents/pr-writer.md b/.claude/agents/pr-writer.md new file mode 100644 index 0000000..34f290e --- /dev/null +++ b/.claude/agents/pr-writer.md @@ -0,0 +1,84 @@ +--- +name: pr-writer +description: 코드 변경사항을 분석해 PR 템플릿에 맞는 문서를 작성. PR 올리기 전에 사용. +tools: Bash, Read, Glob +--- + +Weeth 프로젝트의 PR 문서를 작성하는 전문가입니다. +변경된 코드를 분석해 `.github/pull_request_template.md` 형식에 맞는 PR 본문을 생성합니다. + +## 분석 순서 + +### 1. 변경사항 파악 + +```bash +git diff main...HEAD --stat # 변경된 파일 목록 +git log main...HEAD --oneline # 커밋 메시지 목록 +git diff main...HEAD # 실제 변경 내용 +``` + +### 2. 브랜치명에서 이슈번호 추출 + +```bash +git branch --show-current +# 예: feat/WTH-42-button-component → 이슈 #42 +``` + +### 3. PR 유형 판단 기준 + +| 변경 내용 | PR 유형 | +|----------|---------| +| 새 파일 생성, 새 기능 | 새로운 기능 추가 | +| 버그 수정, 오동작 해결 | 버그 수정 | +| 리팩토링, 구조 변경 | 코드 리팩토링 | +| 오타, 변수명, 탭 사이즈 | 코드에 영향 없는 변경사항 | +| 주석 추가/수정 | 주석 추가 및 수정 | +| README, md 파일 | 문서 수정 | +| package.json, CI 등 | 빌드/패키지 매니저 수정 | +| 파일/폴더 이름 변경 | 파일 혹은 폴더명 수정 | +| 파일/폴더 삭제 | 파일 혹은 폴더 삭제 | + +## 출력 형식 + +분석 후 아래 템플릿을 채워서 출력합니다. +스크린샷 섹션은 사용자가 직접 추가해야 하므로 안내 문구만 남깁니다. + +```markdown +## ✅ PR 유형 + +어떤 변경 사항이 있었나요? + +- [x] 새로운 기능 추가 ← 해당하는 항목에 x 표시 +- [ ] 버그 수정 +- [ ] 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경) +- [ ] 코드 리팩토링 +- [ ] 주석 추가 및 수정 +- [ ] 문서 수정 +- [ ] 빌드 부분 혹은 패키지 매니저 수정 +- [ ] 파일 혹은 폴더명 수정 +- [ ] 파일 혹은 폴더 삭제 + +--- + +### 📌 관련 이슈번호 + +- Closed #이슈번호 ← 브랜치명 또는 커밋에서 추출, 없으면 생략 + +--- + +### ✅ Key Changes + +- 변경사항 1 +- 변경사항 2 + +--- + +### 📸 스크린샷 or 실행영상 + + + +--- + +## 🎸 기타 사항 or 추가 코멘트 + +``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ad4bc01 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +name: CI + +on: + pull_request: + branches: [main, develop] + +jobs: + ci: + name: Lint & Build + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: TypeScript + id: typescript + continue-on-error: true + run: pnpm typecheck + + - name: ESLint + id: eslint + continue-on-error: true + run: pnpm lint + + - name: Prettier + id: prettier + continue-on-error: true + run: pnpm format:check + + - name: Build + id: build + continue-on-error: true + run: pnpm build + + - name: PR 검증 결과 코멘트 + if: always() + uses: actions/github-script@v7 + with: + script: | + const results = { + typescript: '${{ steps.typescript.outcome }}', + eslint: '${{ steps.eslint.outcome }}', + prettier: '${{ steps.prettier.outcome }}', + build: '${{ steps.build.outcome }}', + }; + + const icon = (outcome) => + outcome === 'success' ? '✅' : outcome === 'failure' ? '❌' : '⏭️'; + + const label = (outcome) => + outcome === 'success' ? '통과' : outcome === 'failure' ? '실패' : '건너뜀'; + + const allPassed = Object.values(results).every((r) => r === 'success'); + + const body = [ + '## PR 검증 결과', + '', + `${icon(results.typescript)} **TypeScript:** ${label(results.typescript)}`, + `${icon(results.eslint)} **ESLint:** ${label(results.eslint)}`, + `${icon(results.prettier)} **Prettier:** ${label(results.prettier)}`, + `${icon(results.build)} **Build:** ${label(results.build)}`, + '', + allPassed ? '🎉 모든 검증을 통과했습니다!' : '⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.', + ].join('\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const existing = comments.find( + (c) => c.user.login === 'github-actions[bot]' && c.body.includes('PR 검증 결과'), + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } diff --git a/CLAUDE.md b/CLAUDE.md index b7c8494..b6d4af6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,141 +1,55 @@ -# Weeth Frontend - Claude Code Instructions - -## Project Overview - -Weeth 프로젝트의 프론트엔드. Figma 디자인을 기반으로 디자인 시스템을 구현합니다. +# Weeth Frontend ## Tech Stack -- **React 19** + TypeScript -- **Next.js 16** (App Router) -- **Tailwind CSS v4** -- **class-variance-authority (cva)** — variant 스타일 관리 -- **`cn()` from `@/lib/cn`** — className 병합 (clsx + tailwind-merge) -- **Radix UI** — 접근성 있는 UI 프리미티브 -- **Lucide React** — 아이콘 -- **pnpm** — 패키지 매니저 (npm, yarn 사용 금지) +- React 19 + TypeScript, Next.js 16 (App Router) +- Tailwind CSS v4, class-variance-authority (cva) +- `cn()` from `@/lib/cn` — className 병합 +- Radix UI, Lucide React +- **pnpm** 전용 (npm/yarn 사용 금지) ## Project Structure ``` src/ - app/ - globals.css # 디자인 토큰 정의 (CSS variables, @utility) - components/ - ui/ # 재사용 기본 컴포넌트 (비즈니스 로직 없음) - Button.tsx - TextField.tsx - dialog.tsx - alert-dialog.tsx - breadcrumb.tsx - index.ts # 모든 ui 컴포넌트 re-export - lib/ - cn.ts # className 병합 유틸 + app/globals.css # 디자인 토큰 (CSS variables, @utility) + components/ui/ # 재사용 UI 컴포넌트, index.ts에서 re-export + lib/cn.ts # className 병합 유틸 ``` -## Design Tokens (globals.css) - -Figma 디자인을 코드로 옮길 때 반드시 아래 토큰을 우선 사용합니다. +## Design Tokens -**매칭 우선순위:** -1. CSS 변수 기반 Tailwind 클래스 (`bg-container-neutral`, `text-text-strong`) -2. 직접 CSS 변수 (`var(--color-primary)`) -3. 신규 토큰이 필요한 경우 → 사용자에게 먼저 확인 후 추가 +하드코딩 금지. 반드시 아래 토큰 우선 사용. 신규 토큰 필요 시 사용자 확인 후 추가. -**주요 토큰 카테고리:** - -| 카테고리 | 예시 클래스 | +| 카테고리 | 클래스 예시 | |----------|------------| -| 텍스트 색상 | `text-text-strong`, `text-text-normal`, `text-text-alternative`, `text-text-disabled`, `text-text-inverse` | -| 배경 | `bg-container-neutral`, `bg-container-neutral-interaction` | -| 버튼 | `bg-button-primary`, `bg-button-neutral` | -| Typography | `typo-h1` ~ `typo-h3`, `typo-sub1` ~ `typo-sub2`, `typo-body1` ~ `typo-body2`, `typo-caption1` ~ `typo-caption2`, `typo-button1` ~ `typo-button2` | -| Spacing | `p-100` ~ `p-500`, `gap-100` ~ `gap-400`, `px-200`, `py-300` 등 | - -## Component Guidelines +| 텍스트 | `text-text-strong` `text-text-normal` `text-text-alternative` `text-text-disabled` `text-text-inverse` | +| 배경 | `bg-container-neutral` `bg-container-neutral-interaction` | +| 버튼 | `bg-button-primary` `bg-button-neutral` | +| Typography | `typo-h1~h3` `typo-sub1~2` `typo-body1~2` `typo-caption1~2` `typo-button1~2` | +| Spacing | `p-100~500` `gap-100~400` | -### 기본 구조 +## Component Pattern ```tsx -import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from '@/lib/cn'; - -const componentVariants = cva('base-styles', { - variants: { - variant: { - primary: '...', - secondary: '...', - }, - size: { - lg: '...', - md: '...', - sm: '...', - }, - }, - defaultVariants: { - variant: 'primary', - size: 'md', - }, -}); - -interface ComponentProps - extends React.HTMLAttributes, - VariantProps {} - -function Component({ className, variant, size, ...props }: ComponentProps) { - return ( -
- ); -} - -export { Component, componentVariants, type ComponentProps }; -``` - -### 원칙 - -- `className` prop 항상 노출 (외부에서 커스텀 가능하도록) -- 하드코딩 값 사용 금지 — 반드시 디자인 토큰 사용 -- 새 컴포넌트는 `src/components/ui/index.ts`에 export 추가 -- Radix UI 사용 시 `asChild` 패턴으로 합성 지원 - -## Figma → Component Workflow +const variants = cva('base', { variants: { variant: {}, size: {} }, defaultVariants: {} }); -### Step 1. 디자인 분석 +interface Props extends React.HTMLAttributes, VariantProps {} -Figma 속성을 토큰으로 매핑하는 표를 먼저 작성합니다: +function Component({ className, variant, size, ...props }: Props) { + return
; +} +export { Component, variants, type Props }; ``` -Figma Property | Value | Mapped Token/Class ---------------- | ------------- | --------------------------- -Background | #1E2125 | bg-container-neutral -Border Radius | 8px | rounded-lg -Font | Sub1 Bold | typo-sub1 text-text-strong -Padding | 20px | p-500 -Gap | 12px | gap-300 -``` - -### Step 2. 코드 생성 -위 매핑 기반으로 컴포넌트 생성. - -### Step 3. 결과 요약 - -``` -✅ 파일 생성: src/components/ui/Card.tsx -✅ 디자인 토큰: 5개 사용 -⚠️ 신규 토큰 필요: --shadow-card (제안: 0 2px 8px rgba(0,0,0,0.1)) -``` +- `className` 항상 노출, Radix 사용 시 `asChild` 지원 +- 새 컴포넌트는 `src/components/ui/index.ts`에 export 추가 ## Git Conventions ``` -feat: 새 컴포넌트 또는 기능 추가 -fix: 버그 수정 -style: 스타일/토큰 수정 -refactor: 리팩토링 +feat / fix / style / refactor / ci / chore ``` -**주의:** main/master 브랜치 직접 커밋 금지 — 반드시 경고 후 확인 요청. +main 브랜치 직접 커밋 금지. diff --git a/amplify.yml b/amplify.yml new file mode 100644 index 0000000..0292d6f --- /dev/null +++ b/amplify.yml @@ -0,0 +1,19 @@ +version: 1 +frontend: + phases: + preBuild: + commands: + - corepack enable + - corepack prepare pnpm@latest --activate + - pnpm install --frozen-lockfile + build: + commands: + - pnpm build + artifacts: + baseDirectory: .next + files: + - '**/*' + cache: + paths: + - node_modules/**/* + - .next/cache/**/* diff --git a/package.json b/package.json index c3eb838..706a6b1 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,10 @@ "dev": "next dev", "build": "next build", "start": "next start", + "typecheck": "tsc --noEmit", "lint": "eslint .", - "lint:fix": "eslint . --fix" + "lint:fix": "eslint . --fix", + "format:check": "prettier --check ." }, "dependencies": { "@tanstack/react-query": "^5.62.11", diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 344c225..7aa6cc8 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -2,8 +2,8 @@ // 어떤 페이지에서든 재사용 가능한 기본 빌딩 블록 // 비즈니스 로직이 없고, 스타일과 인터랙션만 담당 -export { Button } from './Button'; -export type { ButtonProps, ButtonVariant, ButtonSize } from './Button'; +export { Button, buttonVariants } from './Button'; +export type { ButtonProps } from './Button'; export { TextField } from './TextField'; export type { TextFieldProps } from './TextField';