diff --git a/SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json new file mode 100644 index 0000000..a911f41 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ic_chevron_right.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/ic_chevron_right.png b/SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/ic_chevron_right.png new file mode 100644 index 0000000..af39889 Binary files /dev/null and b/SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/ic_chevron_right.png differ diff --git a/SodaLive/Sources/V2/Component/SectionTitle.swift b/SodaLive/Sources/V2/Component/SectionTitle.swift new file mode 100644 index 0000000..6c73db3 --- /dev/null +++ b/SodaLive/Sources/V2/Component/SectionTitle.swift @@ -0,0 +1,63 @@ +// +// SectionTitle.swift +// SodaLive +// + +import SwiftUI + +struct SectionTitle: View { + let title: String + let action: (() -> Void)? + + init( + title: String, + action: (() -> Void)? = nil + ) { + self.title = title + self.action = action + } + + var body: some View { + content + } + + private var content: some View { + HStack(alignment: .center, spacing: 0) { + Text(title) + .appFont(.heading3) + .foregroundColor(.white) + .lineLimit(1) + .truncationMode(.tail) + + Spacer(minLength: 0) + + if let action { + Button(action: action) { + Image("ic_chevron_right") + .resizable() + .renderingMode(.template) + .foregroundColor(.white) + .scaledToFit() + .frame(width: 24, height: 24) + } + .buttonStyle(.plain) + .accessibilityLabel(title) + .accessibilityAddTraits(.isButton) + } + } + .padding(.horizontal, SodaSpacing.s20) + .frame(maxWidth: .infinity) + .frame(height: 42, alignment: .center) + } +} + +struct SectionTitle_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: SodaSpacing.s12) { + SectionTitle(title: "텍스트") + SectionTitle(title: "긴 섹션 타이틀은 한 줄로 줄임 처리됩니다 한 줄로 줄임 처리됩니다") {} + } + .background(Color.black) + .previewLayout(.sizeThatFits) + } +} diff --git a/docs/plan-task/20260519_섹션타이틀컴포넌트.md b/docs/plan-task/20260519_섹션타이틀컴포넌트.md new file mode 100644 index 0000000..b0a63e0 --- /dev/null +++ b/docs/plan-task/20260519_섹션타이틀컴포넌트.md @@ -0,0 +1,129 @@ +# 20260519 섹션 타이틀 컴포넌트 계획 + +> 구현 시 문서 범위를 벗어나지 않는다. 신규 UI 컴포넌트 작업이므로 구현 단계에서는 시각/UI 전용 실행 경로를 사용한다. + +**Goal:** Figma 노드 `20:3614`의 42pt 섹션 타이틀을 V2 SwiftUI 재사용 컴포넌트로 구현한다. + +**Architecture:** 신규 `SectionTitle` View 하나를 `SodaLive/Sources/V2/Component`에 추가한다. 기존 `TitleBar` 계열은 화면 상단 네비게이션 바 책임을 유지하고, 섹션 헤더는 별도 컴포넌트로 분리한다. + +**Tech Stack:** SwiftUI, existing `SodaSpacing`, existing `SodaTypography`, existing asset catalog + +--- + +## 기준 문서 + +- PRD: `docs/prd/20260519_섹션타이틀컴포넌트_PRD.md` +- 검증 가이드: `docs/agent-guides/build-test-verification.md` +- 코드 스타일: `docs/agent-guides/code-style.md` + +## 구현 대상 파일 후보 + +### 생성 + +- `SodaLive/Sources/V2/Component/SectionTitle.swift`: Figma 섹션 타이틀의 레이아웃, 스타일, 버튼 동작, Preview 담당 + +### 참조 + +- `SodaLive/Sources/V2/Component/TitleBar.swift`: 기존 V2 컴포넌트 스타일 참고 +- `SodaLive/Sources/Extensions/FontModifier.swift`: `.appFont(.heading3)`의 20pt bold 매핑 확인 +- `SodaLive/Sources/UI/Theme/Spacing.swift`: `SodaSpacing.s20` 확인 +- `SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json`: 우측 chevron 에셋 확인 + +### 수정하지 않음 + +- `SodaLive/Sources/V2/Component/TitleBar.swift`: 기존 상단 바 동작 유지 +- `SodaLive/Sources/V2/Component/DefaultTitleBar.swift`: 기존 상단 바 동작 유지 +- `SodaLive/Sources/V2/Component/HomeTitleBar.swift`: 기존 상단 바 동작 유지 +- `SodaLive/Resources/Assets.xcassets/**`: 신규 에셋 추가 없음 + +## 구현 체크리스트 + +### Task 1: 에셋 확인 + +**Files:** `SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json` + +- [x] `ic_chevron_right.imageset` 아래 `Contents.json`과 `ic_chevron_right.png`가 존재하는지 확인한다. +- [x] `Contents.json`에 `images` 배열이 있고 `ic_chevron_right.png`가 등록되어 있는지 확인한다. +- [x] QA: `Image("ic_chevron_right")`로 참조 가능한 asset catalog 이름인지 확인한다. + +### Task 2: SectionTitle 작성 + +**Files:** `SodaLive/Sources/V2/Component/SectionTitle.swift` + +- [x] `struct SectionTitle: View`를 생성한다. +- [x] `title: String`, `action: (() -> Void)? = nil` 초기화 API를 제공한다. +- [x] `HStack(alignment: .center, spacing: 0)`에 제목, `Spacer(minLength: 0)`, 조건부 chevron을 배치한다. +- [x] 최상위 content에 `.padding(.horizontal, SodaSpacing.s20)`, `.frame(maxWidth: .infinity)`, `.frame(height: 42, alignment: .center)`를 적용한다. +- [x] 제목은 `.appFont(.heading3)`, `.foregroundColor(.white)`, `.lineLimit(1)`, `.truncationMode(.tail)`을 적용한다. +- [x] `action`이 있을 때 chevron은 `Image("ic_chevron_right")`를 사용하고 24x24로 표시한다. +- [x] chevron은 plain `Button`으로 감싸고 `.accessibilityLabel(title)`을 적용한다. +- [x] `action`이 없으면 chevron 없이 정적 content만 렌더링한다. +- [x] 제목 영역은 탭해도 `action`이 실행되지 않도록 한다. + +### Task 3: Preview 작성 + +**Files:** `SodaLive/Sources/V2/Component/SectionTitle.swift` + +- [x] action이 없는 chevron 미표시 케이스 Preview를 추가한다. +- [x] action이 있는 chevron 표시 케이스 Preview를 추가한다. +- [x] 긴 제목 말줄임 케이스 Preview를 추가한다. +- [x] QA: Preview 배경은 확인용으로만 지정하고 컴포넌트 본문에는 배경을 넣지 않는다. + +### Task 4: 정적 진단 + +**Files:** `SodaLive/Sources/V2/Component/SectionTitle.swift` + +- [x] `lsp_diagnostics`로 신규 Swift 파일의 정적 진단을 확인한다. +- [x] SourceKit 단일 파일 문맥 문제로 프로젝트 심볼 미해결이 발생하면 같은 변경을 `xcodebuild`로 검증한다. +- [x] 컴포넌트 구현에서 발생한 실제 Swift 오류가 있으면 `SectionTitle.swift`만 수정한다. + +### Task 5: 빌드 검증 + +**Files:** `SodaLive.xcworkspace`, `SodaLive/Sources/V2/Component/SectionTitle.swift` + +- [x] `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`를 실행한다. +- [x] 가능한 경우 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`도 실행한다. +- [x] 환경 문제로 빌드가 실패하면 첫 번째 유효 오류 라인을 검증 기록에 남긴다. +- [x] 코드 문제로 빌드가 실패하면 `SectionTitle.swift`만 수정한 뒤 같은 명령을 재실행한다. + +### Task 6: PRD 성공 기준 대조 + +**Files:** `docs/prd/20260519_섹션타이틀컴포넌트_PRD.md`, `SodaLive/Sources/V2/Component/SectionTitle.swift` + +- [x] 높이 42, full width, 좌우 20pt 여백이 구현됐는지 확인한다. +- [x] 제목이 `.appFont(.heading3)`와 흰색으로 표시되는지 확인한다. +- [x] 긴 제목이 한 줄 말줄임 처리되는지 확인한다. +- [x] `action` 유무에 따른 chevron 표시 분기가 구현됐는지 확인한다. +- [x] `action`이 chevron 버튼에만 연결되고 제목 영역에는 연결되지 않았는지 확인한다. +- [x] 기존 `TitleBar`, `DefaultTitleBar`, `HomeTitleBar`를 수정하지 않았는지 확인한다. + +## 구현 시 주의사항 + +- 기존 `TitleBar`, `DefaultTitleBar`, `HomeTitleBar`를 변경하지 않는다. +- 신규 디자인 토큰을 추가하지 않는다. +- `Image(systemName:)`을 사용하지 않는다. +- 화면 적용, 라우팅, API 연동은 별도 요청 전까지 하지 않는다. +- 파일 생성 위치는 `SodaLive/Sources/V2/Component`로 고정한다. + +## 검증 기록 + +- 2026-05-19 문서 작성 전 Figma 노드 `20:3614`의 screenshot/design context를 확인해 높이 42, 좌우 20, 20pt bold title, 우측 24pt chevron 구조를 확인했다. +- 2026-05-19 문서 작성 전 `SodaLive/Sources/V2/Component/TitleBar.swift`, `DefaultTitleBar.swift`, `HomeTitleBar.swift`를 확인해 기존 V2 상단 바 컴포넌트와 책임을 분리해야 함을 확인했다. +- 2026-05-19 문서 작성 전 `SodaLive/Sources/Extensions/FontModifier.swift`를 확인해 `.appFont(.heading3)`가 20pt bold임을 확인했다. +- 2026-05-19 문서 작성 전 `SodaLive/Sources/UI/Theme/Spacing.swift`를 확인해 `SodaSpacing.s20` 사용 가능성을 확인했다. +- 2026-05-19 문서 작성 전 `SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json`을 확인해 `ic_chevron_right` 에셋 존재를 확인했다. +- 2026-05-19 문서 위치 보정: `AGENTS.md`와 `docs/agent-guides/documentation-policy.md` 기준에 맞춰 PRD는 `docs/prd/20260519_섹션타이틀컴포넌트_PRD.md`, 계획/TASK는 `docs/plan-task/20260519_섹션타이틀컴포넌트.md`로 정리했다. +- 2026-05-19 구현: `SodaLive/Sources/V2/Component/SectionTitle.swift`를 추가하고, `SodaLive.xcodeproj/project.pbxproj`에 `SectionTitle.swift`를 `SodaLive`, `SodaLive-dev` 두 앱 타깃 Sources로 등록했다. +- 2026-05-19 수동 QA: `rg`로 `SectionTitle.swift`의 `action: (() -> Void)? = nil`, `Image("ic_chevron_right")`, 24x24 chevron, 42pt height, 프로젝트 Sources 등록 2건을 확인했다. +- 2026-05-19 에셋 검증: `plutil -p 'SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json'` 결과 `ic_chevron_right.png`가 `images` 배열에 등록되어 있음을 확인했다. +- 2026-05-19 프로젝트 검증: `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행 결과 `OK`. +- 2026-05-19 LSP 진단: `SectionTitle.swift` 단일 파일 진단에서 `SodaSpacing`, `.appFont`, `.heading3`, `.white`, `.tail` 프로젝트 문맥 미해결 오류가 보고됐다. 동일 오류는 기존 `TitleBar.swift`에서도 재현되어 SourceKit 단일 파일 문맥 한계로 기록하고, 실제 컴파일 유효성은 아래 빌드로 확인했다. +- 2026-05-19 빌드 검증: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행 결과 `** BUILD SUCCEEDED **`. +- 2026-05-19 빌드 검증: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 실행 결과 `** BUILD SUCCEEDED **`. Crashlytics dSYM 및 일부 dependency scan 경고가 있었으나 빌드는 성공했다. +- 2026-05-19 테스트 액션 확인: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` 실행 결과 `Scheme SodaLive-dev is not currently configured for the test action.`으로 테스트 타깃 미구성 상태를 확인했다. +- 2026-05-19 동작 수정: 별도 chevron 표시 API를 제거하고, `action`이 있을 때만 `ic_chevron_right` chevron 버튼을 표시하도록 변경했다. `action`은 전체 행이 아니라 chevron 버튼에만 연결해 제목 터치로는 실행되지 않게 했다. +- 2026-05-19 동작 수정 후 수동 QA: `rg`로 `Text(title)`과 `Button(action: action)`이 분리되어 있고, `Image("ic_chevron_right")`, 24x24 chevron, 42pt height, action 없는 Preview/action 있는 Preview가 존재함을 확인했다. +- 2026-05-19 동작 수정 후 LSP 진단: `SectionTitle.swift` 단일 파일 진단에서 기존과 같은 `SodaSpacing`, `.appFont`, `.heading3`, `.white`, `.tail` 프로젝트 문맥 미해결 오류가 보고됐다. 실제 컴파일 유효성은 아래 빌드로 확인했다. +- 2026-05-19 동작 수정 후 빌드 검증: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행 결과 `** BUILD SUCCEEDED **`. +- 2026-05-19 동작 수정 후 빌드 검증: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 실행 결과 `** BUILD SUCCEEDED **`. Crashlytics dSYM 및 일부 dependency scan 경고가 있었으나 빌드는 성공했다. +- 2026-05-19 동작 수정 후 테스트 액션 확인: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` 실행 결과 `Scheme SodaLive-dev is not currently configured for the test action.`으로 테스트 타깃 미구성 상태를 재확인했다. diff --git a/docs/prd/20260519_섹션타이틀컴포넌트_PRD.md b/docs/prd/20260519_섹션타이틀컴포넌트_PRD.md new file mode 100644 index 0000000..98a34f1 --- /dev/null +++ b/docs/prd/20260519_섹션타이틀컴포넌트_PRD.md @@ -0,0 +1,119 @@ +# PRD: 섹션 타이틀 컴포넌트 + +## 1. Overview + +Figma 노드 `20:3614`의 42pt 섹션 타이틀을 SwiftUI 재사용 컴포넌트로 정의한다. 신규 UI 컴포넌트 위치 규칙에 따라 구현 대상은 `SodaLive/Sources/V2/Component/**` 아래에 둔다. + +--- + +## 2. Background + +Figma 노드 `20:3614`는 402x42 크기의 섹션 타이틀 컴포넌트다. 좌측에는 `Pretendard Variable Bold 20pt` 텍스트가 있고, 우측에는 24x24 chevron-right 아이콘이 조건부로 표시된다. + +최근 코드에는 다음 기반 요소가 이미 존재한다. + +- `SodaLive/Sources/V2/Component/TitleBar.swift`: V2 상단 바 컴포넌트 스타일 참고 가능 +- `SodaLive/Sources/Extensions/FontModifier.swift`: `SodaTypography.heading3` 및 `.appFont(_:)` 제공 +- `SodaLive/Sources/UI/Theme/Spacing.swift`: `SodaSpacing.s20` 제공 +- `SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset`: 우측 chevron 에셋 존재 + +--- + +## 3. Goals + +- Figma 시안과 동일한 높이, 여백, 타이포그래피를 SwiftUI로 구현한다. +- 기존 V2 컴포넌트 관례와 디자인 토큰을 사용한다. +- 제목 텍스트와 우측 chevron 탭 동작을 호출부에서 제어할 수 있게 한다. +- 네비게이션 상단 바인 `TitleBar`와 책임을 분리한다. + +--- + +## 4. Non-Goals + +- 화면 단위 라우팅이나 API 연동은 포함하지 않는다. +- 신규 디자인 토큰을 추가하지 않는다. +- Figma의 React/Tailwind 생성 코드를 그대로 이식하지 않는다. +- 기존 `TitleBar`, `DefaultTitleBar`, `HomeTitleBar`의 동작을 변경하지 않는다. + +--- + +## 5. Core Requirements + +### 5.1 컴포넌트 API + +- 컴포넌트 이름은 `SectionTitle`로 둔다. +- `title: String`을 호출부에서 전달한다. +- `action: (() -> Void)?`는 기본값 `nil`을 사용한다. +- `action`이 있으면 우측 chevron만 `Button`으로 렌더링한다. +- `action`이 없으면 chevron을 표시하지 않고 정적 `HStack`을 표시한다. + +### 5.2 레이아웃 + +- Width: full +- Height: `42` +- Horizontal padding: `SodaSpacing.s20` +- Alignment: 세로 가운데 정렬 +- 좌측 제목, `Spacer(minLength: 0)`, 우측 chevron의 `HStack` 구조를 사용한다. +- 우측 chevron은 `action != nil`일 때만 24x24 크기로 표시한다. + +### 5.3 스타일 + +- 제목은 `Text(title).appFont(.heading3)`를 사용한다. +- 제목 색상은 `Color.white`를 사용한다. +- 제목은 한 줄로 제한하고 말줄임 처리한다. +- 컴포넌트 본문에는 배경색을 지정하지 않는다. + +### 5.4 아이콘 및 접근성 + +- 우측 chevron은 기존 에셋 `ic_chevron_right`를 사용한다. +- SwiftUI `Image(systemName: "chevron.right")`는 앱 에셋 스타일과 다를 수 있으므로 사용하지 않는다. +- `action`이 있을 때 chevron 버튼의 접근성 라벨은 `title`을 사용한다. +- 제목 영역은 탭해도 `action`을 실행하지 않는다. + +--- + +## 6. Recommended Approach + +전용 `SectionTitle` 컴포넌트를 신규 추가한다. 기존 `TitleBar`를 확장하지 않는 이유는 `TitleBar`가 화면 상단 네비게이션 바 책임을 갖고 있고 높이도 60pt라, Figma의 42pt 섹션 헤더와 의미 및 치수가 다르기 때문이다. + +단순 호출부 인라인 구현도 가능하지만 재사용성과 디자인 일관성이 떨어진다. 따라서 `SodaLive/Sources/V2/Component/SectionTitle.swift`에 작고 독립적인 View로 구현한다. + +--- + +## 7. Technical Constraints + +- SwiftUI 기반으로 작성한다. +- 신규 파일은 구현 시 `SodaLive/Sources/V2/Component/**` 아래에 둔다. +- 색상과 spacing은 기존 토큰을 우선 사용한다. +- 폰트는 기존 `.appFont(.heading3)` 사용을 우선한다. +- 프로젝트 설정 변경은 필요한 경우에만 수행한다. + +--- + +## 8. Success Criteria + +- 문서 기준 구현 후 `SectionTitle`은 높이 42, full width, 좌우 20pt 여백을 갖는다. +- 제목은 20pt bold에 해당하는 `.appFont(.heading3)`와 흰색으로 표시된다. +- 긴 제목은 한 줄 말줄임 처리된다. +- `action`이 있으면 우측 24x24 chevron이 표시되고, 없으면 표시되지 않는다. +- `action`은 chevron을 탭했을 때만 실행되고, 제목 영역을 탭했을 때는 실행되지 않는다. +- 기존 `TitleBar`, `DefaultTitleBar`, `HomeTitleBar` 동작은 바뀌지 않는다. + +--- + +## 9. Decisions + +- 파일 생성 위치는 `SodaLive/Sources/V2/Component/SectionTitle.swift`로 결정한다. +- 우측 chevron은 `action`이 있을 때만 `ic_chevron_right`를 사용한다. +- 신규 디자인 토큰은 추가하지 않는다. +- 이번 PRD는 Figma 노드 `20:3614` 단일 컴포넌트만 다룬다. + +--- + +## 10. Verification Notes + +- Figma 노드 `20:3614`의 스크린샷과 생성 코드에서 높이 42, 좌우 20, 20pt bold title, 우측 24pt chevron 구조를 확인했다. +- `SodaLive/Sources/V2/Component/TitleBar.swift`를 확인해 V2 컴포넌트의 SwiftUI 스타일을 확인했다. +- `SodaLive/Sources/Extensions/FontModifier.swift`를 확인해 `.appFont(.heading3)`가 20pt bold임을 확인했다. +- `SodaLive/Sources/UI/Theme/Spacing.swift`를 확인해 `SodaSpacing.s20` 사용 가능성을 확인했다. +- `SodaLive/Resources/Assets.xcassets/v2/ic_chevron_right.imageset/Contents.json`을 확인해 `ic_chevron_right` 에셋 존재를 확인했다.