diff --git a/CLAUDE.md b/CLAUDE.md index 246c94a..2202d41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,12 +12,12 @@ StartPage (활성 스프린트 여부 분기) └─ NoticePage (안내) └─ SprintCodePage (인증 코드 입력) └─ SprintIntroPage (스프린트 소개) - └─ UserInfoPage (사용자 정보 입력, step 1/7) - └─ StopCommentPage (step 2/7) - └─ StartCommentPage (step 3/7) - └─ ContinueCommentPage (step 4/7) - └─ MvpPage (step 6/7) - └─ ClosingPage (step 7/7) + └─ UserInfoPage (사용자 정보 입력, step 1/6) + └─ StopCommentPage (step 2/6) + └─ StartCommentPage (step 3/6) + └─ ContinueCommentPage (step 4/6) + └─ MvpPage (step 5/6) + └─ ClosingPage (step 6/6) ``` --- @@ -85,7 +85,7 @@ src/ │ ├── common/ → 도메인 무관한 범용 컴포넌트 │ │ ├── layout/ (PageLayout, StepLayout) │ │ ├── form/ (SelectField, InputField) -│ │ └── ui/ (ContentHeading, ImageSection, ProgressBar, MemberChip) +│ │ └── ui/ (ContentHeading, ImageSection, ProgressBar, MemberChip, FieldSection) │ ├── sprint-code/ → 스프린트 코드 도메인 │ │ └── SprintCodeInput │ ├── peer-comment/ → 피어 코멘트 도메인 @@ -139,7 +139,7 @@ src/ | `Picker` | 목록에서 항목을 선택하는 UI | `PeerMemberPicker` | | `Chip` | 선택된 항목을 표시하는 태그 UI | `MemberChip` | | `Repeater` | 동일한 입력 블록을 반복·관리 | `PeerCommentRepeater` | -| `Block` / `Section` | 여러 요소를 묶은 영역 단위 | `PeerCommentRecipientBlock` | +| `Block` / `Section` | 여러 요소를 묶은 영역 단위 | `PeerCommentRecipientBlock`, `FieldSection` | ## 🗂️ Import Aliases @@ -175,6 +175,13 @@ src/ - 인라인 스타일 또는 CSS 모듈(`.module.css`) 혼용 금지 - 새 컴포넌트/페이지 추가 시 같은 경로에 `*.css.ts` 파일 함께 생성 +### 레이아웃 기준선 + +- `StepLayout` 콘텐츠 영역 좌우 padding: `0 28px` (모든 페이지 공통 기준) +- `FieldSection`: 논리 블록 하나를 감싸는 padding wrapper (`paddingTop: 24, paddingBottom: 12`) + - **하나의 FieldSection = 하나의 논리 블록** (ContentHeading, 개별 폼 필드 등) + - `gap` 없음 — 여러 요소의 간격은 각 FieldSection이 담당 + --- @@ -219,6 +226,10 @@ src/ - 서비스 오류 → `ServiceError` → `/error` 페이지로 이동 - `useErrorHandler` 훅 활용 +### push 전 체크리스트 + +- CLAUDE.md가 현재 코드 상태를 반영하고 있는지 확인 후 업데이트 + ### 하지 말아야 할 것 - 요청 범위를 벗어난 리팩토링, 기능 추가, 코드 정리 diff --git a/src/components/common/layout/StepLayout.css.ts b/src/components/common/layout/StepLayout.css.ts index 82cce41..7702b18 100644 --- a/src/components/common/layout/StepLayout.css.ts +++ b/src/components/common/layout/StepLayout.css.ts @@ -30,7 +30,8 @@ export const contentSection = style({ overflowY: 'auto', msOverflowStyle: 'none', scrollbarWidth: 'none', - padding: '0 20px', + padding: '0 28px', + textAlign: 'left', }); export const contentSectionWithProgress = style({ diff --git a/src/components/common/ui/FieldSection.css.ts b/src/components/common/ui/FieldSection.css.ts new file mode 100644 index 0000000..68e8c32 --- /dev/null +++ b/src/components/common/ui/FieldSection.css.ts @@ -0,0 +1,8 @@ +import { style } from '@vanilla-extract/css'; + +export const container = style({ + display: 'flex', + flexDirection: 'column', + paddingTop: 24, + paddingBottom: 12, +}); diff --git a/src/components/common/ui/FieldSection.tsx b/src/components/common/ui/FieldSection.tsx new file mode 100644 index 0000000..3ebcca8 --- /dev/null +++ b/src/components/common/ui/FieldSection.tsx @@ -0,0 +1,12 @@ +import type { ReactNode } from 'react'; +import * as styles from './FieldSection.css'; + +interface FieldSectionProps { + children: ReactNode; +} + +function FieldSection({ children }: FieldSectionProps) { + return
{children}
; +} + +export default FieldSection; diff --git a/src/components/common/ui/ProgressBar.css.ts b/src/components/common/ui/ProgressBar.css.ts index e608634..e3b46c3 100644 --- a/src/components/common/ui/ProgressBar.css.ts +++ b/src/components/common/ui/ProgressBar.css.ts @@ -3,7 +3,7 @@ import { colors } from '@sopt-makers/colors'; export const container = style({ width: '100%', - padding: '10px 20px 36px', + padding: '16px 20px', }); export const track = style({ diff --git a/src/components/index.ts b/src/components/index.ts index 953d153..5bb671c 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -3,6 +3,7 @@ export { default as StepLayout } from './common/layout/StepLayout'; export { default as ProgressBar } from './common/ui/ProgressBar'; export { default as MemberChip } from './common/ui/MemberChip'; export { default as ContentHeading } from './common/ui/ContentHeading'; +export { default as FieldSection } from './common/ui/FieldSection'; export { default as ImageSection } from './common/ui/ImageSection'; export { default as SelectField } from './common/form/SelectField'; export { default as InputField } from './common/form/InputField'; diff --git a/src/components/peer-comment/PeerCommentStepTemplate.css.ts b/src/components/peer-comment/PeerCommentStepTemplate.css.ts index 57357a4..ac6d676 100644 --- a/src/components/peer-comment/PeerCommentStepTemplate.css.ts +++ b/src/components/peer-comment/PeerCommentStepTemplate.css.ts @@ -1,8 +1,7 @@ -import { globalStyle, style } from '@vanilla-extract/css'; +import { style } from '@vanilla-extract/css'; import { colors } from '@sopt-makers/colors'; import { fontsObject } from '@sopt-makers/fonts'; -/** 제목 블록 ↔ 이미지 ↔ 코멘트 반복 영역 사이 16px */ export const stepContent = style({ display: 'flex', flexDirection: 'column', @@ -10,21 +9,8 @@ export const stepContent = style({ width: '100%', }); -/** - * ContentHeading + 페이지별 안내 문단( children ) - * 제목/설명과 그 아래 문단 사이 간격 없음 - */ -export const headingBlock = style({ - display: 'flex', - flexDirection: 'column', - gap: 0, - width: '100%', -}); - -globalStyle(`${headingBlock} p`, { +export const noticeText = style({ margin: 0, - marginTop: 0, - textAlign: 'left', color: colors.white, ...fontsObject.BODY_3_14_M, }); diff --git a/src/components/peer-comment/PeerCommentStepTemplate.tsx b/src/components/peer-comment/PeerCommentStepTemplate.tsx index b36cc70..7d685d2 100644 --- a/src/components/peer-comment/PeerCommentStepTemplate.tsx +++ b/src/components/peer-comment/PeerCommentStepTemplate.tsx @@ -1,6 +1,7 @@ import { useCallback, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import ContentHeading from '../common/ui/ContentHeading'; +import FieldSection from '../common/ui/FieldSection'; import ImageSection from '../common/ui/ImageSection'; import StepLayout from '../common/layout/StepLayout'; import PeerCommentRepeater from './PeerCommentRepeater'; @@ -59,10 +60,10 @@ function PeerCommentStepTemplate({ totalSteps={totalSteps} >
-
+ -

해당 코멘트는 무기명으로 전달되어요.

-
+

해당 코멘트는 무기명으로 전달되어요.

+ {guideImages ? ( diff --git a/src/components/sprint-code/SprintCodeInput.css.ts b/src/components/sprint-code/SprintCodeInput.css.ts index 3b92fd8..2bf375d 100644 --- a/src/components/sprint-code/SprintCodeInput.css.ts +++ b/src/components/sprint-code/SprintCodeInput.css.ts @@ -6,7 +6,6 @@ export const root = style({ display: 'flex', flexDirection: 'column', gap: 12, - padding: '24px 0 12px', maxWidth: 320, textAlign: 'left', }); diff --git a/src/components/sprint-code/SprintCodeInput.tsx b/src/components/sprint-code/SprintCodeInput.tsx index ad01b4b..ee1245b 100644 --- a/src/components/sprint-code/SprintCodeInput.tsx +++ b/src/components/sprint-code/SprintCodeInput.tsx @@ -28,9 +28,7 @@ function CodeInput({ onChange(sanitized); }; - const activeBoxIndex = isInputFocused - ? Math.min(value.length, SPRINT_CODE_LENGTH - 1) - : -1; + const activeBoxIndex = isInputFocused ? Math.min(value.length, SPRINT_CODE_LENGTH - 1) : -1; return (
@@ -75,9 +73,7 @@ function CodeInput({ data-filled={isFilled} data-error={showError} > - - {digit ?? ''} - + {digit ?? ''}
); })} @@ -85,11 +81,7 @@ function CodeInput({
{showError && ( - )} diff --git a/src/pages/MvpPage.css.ts b/src/pages/MvpPage.css.ts index cb7dcbd..71acaca 100644 --- a/src/pages/MvpPage.css.ts +++ b/src/pages/MvpPage.css.ts @@ -2,19 +2,6 @@ import { style } from '@vanilla-extract/css'; import { colors } from '@sopt-makers/colors'; import { fontsObject } from '@sopt-makers/fonts'; -export const body = style({ - display: 'flex', - flexDirection: 'column', - gap: 32, - color: colors.white, -}); - -export const fields = style({ - display: 'flex', - flexDirection: 'column', - gap: 28, -}); - export const fieldGroup = style({ display: 'flex', flexDirection: 'column', diff --git a/src/pages/MvpPage.tsx b/src/pages/MvpPage.tsx index 29fec8b..861a8d1 100644 --- a/src/pages/MvpPage.tsx +++ b/src/pages/MvpPage.tsx @@ -3,7 +3,7 @@ import type { ChangeEvent } from 'react'; import { useNavigate } from 'react-router-dom'; import { FieldBox } from '@sopt-makers/ui'; import { IconUser, IconXCircle } from '@sopt-makers/icons'; -import { StepLayout, ContentHeading, MemberChip, InputField } from '@components'; +import { StepLayout, ContentHeading, FieldSection, MemberChip, InputField } from '@components'; import { usePeerMembers, useCommentForm } from '@hooks'; import type { PeerMember } from '@types'; import * as styles from './MvpPage.css'; @@ -44,7 +44,7 @@ function MvpPage() { onNext={handleSubmit} isNextDisabled={!isAllFilled} > -
+ } /> + -
-
- - -
-
- ) => setSearchQuery(e.target.value)} - /> - {searchQuery && ( - - )} -
+ +
+ - {searchQuery && filteredMembers.length > 0 && ( -
    - {filteredMembers.map((member) => ( -
  • - -
  • - ))} -
+
+
+ ) => + setSearchQuery(e.target.value) + } + /> + {searchQuery && ( + )}
- {selectedMember && ( -
- -
+ {searchQuery && filteredMembers.length > 0 && ( +
    + {filteredMembers.map((member) => ( +
  • + +
  • + ))} +
)}
- + {selectedMember && ( +
+ +
+ )}
-
+ + + + + ); } diff --git a/src/pages/SprintCodePage.tsx b/src/pages/SprintCodePage.tsx index 111b60a..e73ebb9 100644 --- a/src/pages/SprintCodePage.tsx +++ b/src/pages/SprintCodePage.tsx @@ -1,6 +1,6 @@ import { useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { SprintCodeInput, SPRINT_CODE_LENGTH, StepLayout, ContentHeading } from '@components'; +import { SprintCodeInput, SPRINT_CODE_LENGTH, StepLayout, ContentHeading, FieldSection } from '@components'; import { getSprintInfoByCode } from '@lib/api/sprint'; import { useCommentForm, useErrorHandler } from '@hooks'; import { callApi } from '@lib/apiClient'; @@ -40,16 +40,20 @@ function SprintCodePage() { currentStep={0} totalSteps={6} > - - - { - setCode(next); - setShowError(false); - }} - showError={showError} - /> + + + + + + { + setCode(next); + setShowError(false); + }} + showError={showError} + /> + ); } diff --git a/src/pages/UserInfoPage.css.ts b/src/pages/UserInfoPage.css.ts index 16152a4..410ba46 100644 --- a/src/pages/UserInfoPage.css.ts +++ b/src/pages/UserInfoPage.css.ts @@ -2,9 +2,8 @@ import { style } from '@vanilla-extract/css'; import { colors } from '@sopt-makers/colors'; import { fontsObject } from '@sopt-makers/fonts'; -export const body = style({ - display: 'flex', - flexDirection: 'column', +export const noticeText = style({ + margin: 0, textAlign: 'left', color: colors.white, ...fontsObject.BODY_3_14_M, @@ -13,10 +12,3 @@ export const body = style({ export const nameWidth = style({ width: '50%', }); - -export const inputWrapper = style({ - display: 'flex', - flexDirection: 'column', - gap: 36, - marginTop: 24, -}); diff --git a/src/pages/UserInfoPage.tsx b/src/pages/UserInfoPage.tsx index 86a4606..2499f90 100644 --- a/src/pages/UserInfoPage.tsx +++ b/src/pages/UserInfoPage.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { StepLayout, ContentHeading, SelectField } from '@components'; +import { StepLayout, ContentHeading, FieldSection, SelectField } from '@components'; import * as styles from './UserInfoPage.css'; import { TextField, useToast } from '@sopt-makers/ui'; import { getChapterCodes, getTeamCodes } from '@lib/api/chapter'; @@ -56,54 +56,60 @@ function UserInfoPage() { currentStep={1} totalSteps={6} > - -
-

+ + +

본인의 이름은 너목들의 관리자만 확인할 수 있으며,
모든 코멘트는 무기명으로 전달되어요.

+ -
- { - setName(e.target.value); - setIsError(false); - }} - /> - { - setChapterCode(value); - setIsError(false); - }} - /> - { - setTeamCode(value); - setIsError(false); - }} - /> -
-
+ + { + setName(e.target.value); + setIsError(false); + }} + /> + + + + { + setChapterCode(value); + setIsError(false); + }} + /> + + + + { + setTeamCode(value); + setIsError(false); + }} + /> + ); }