Skip to content

Design/#13 : UI component 구현 - #19

Merged
doyeonk429 merged 12 commits into
developfrom
design/#13/ui-component-구현
May 24, 2026

Hidden character warning

The head ref may contain hidden characters: "design/#13/ui-component-\uad6c\ud604"
Merged

Design/#13 : UI component 구현#19
doyeonk429 merged 12 commits into
developfrom
design/#13/ui-component-구현

Conversation

@doyeonk429

@doyeonk429 doyeonk429 commented May 24, 2026

Copy link
Copy Markdown
Contributor

✨ What's this PR?

📌 관련 이슈 (Related Issue)


🧶 주요 변경 내용 (Summary)

신규 컴포넌트 8개 추가 (DVDesign/Sources/Components/)

컴포넌트 설명
DVCategory 카테고리 필터 버튼 (All / Star / Expired / Deleted)
DVProjectContainer 사이드바 프로젝트 행
DVVaultContainer Secret 목록 행 (만료 상태 TrailingIcon 포함)
DVButton Primary / Secondary 스타일 버튼
DVCheckBox 체크박스
DVTitleBar 타이틀 + 정렬 버튼 + 검색 필드
DVSecretType Secret 타입 아이콘 + 레이블
DVPageControl 온보딩 스텝 인디케이터

SampleApp 프리뷰 연동

  • 각 컴포넌트별 PreviewView 추가 (Interactive / 상태별 확인 가능)
  • ContentView 라우팅 업데이트

구현 상세

  • 코딩 가이드라인 준수: // MARK: - 섹션 구분, body 단일 컨테이너, 하위뷰는 extension private var/func 분리
  • 모든 인터랙티브 컴포넌트에 .contentShape(Rectangle()) label 내부 적용 → 빈 영역 클릭 이슈 해결
  • DVVaultContainer.TrailingIcon enum으로 만료 상태 타입 안전하게 관리 (.expiringSoon / .expired)
  • DVButton.Style enum에 cornerRadius / height / width / horizontalPadding / font 토큰화
  • DVTitleBar 검색 필드 .regularMaterial 배경 적용 (Liquid Glass 대비)

📸 스크린샷 (Optional)


🧪 테스트 / 검증 내역

  • DVDesignSampleApp 빌드 및 각 컴포넌트 동작 확인
  • Hover / Selected / Disabled 상태 확인
  • My Mac 환경에서 정상 동작

💬 기타 공유 사항

  • 컬러 토큰은 현재 light/dark 동일값 — 다크모드 스펙 확정 시 별도 이슈로 처리 예정
  • 접근성 트레이트 (.accessibilityLabel, .accessibilityAddTraits) 미적용 — 후속 PR 예정
  • DVPageControl은 온보딩 전용으로 현재 DVDesign에 위치, 추후 DVPresentation 이동 검토 필요

🙇🏻‍♀️ 리뷰 가이드

  • DVButton.Style enum의 토큰 구조 (cornerRadius / height / width / font 일괄 관리 방식)
  • DVVaultContainer.TrailingIcon enum 설계
  • .contentShape 위치를 Button label 내부에 적용한 이유

Summary by CodeRabbit

  • 새 기능

    • DVButton, DVCategory, DVCheckBox, DVProjectContainer, DVSecretType, DVTitleBar, DVVaultContainer, DVStepIndicator 등 8개 UI 컴포넌트 추가
    • 각 컴포넌트별 인터랙티브 프리뷰 화면 추가
  • 변경

    • 샘플 앱 내 상세 뷰 매핑이 확장되어 여러 컴포넌트가 개별 프리뷰로 노출되도록 개선
    • 일부 내비게이션 섹션 구성 변경(항목 추가/제거) 및 소유자 표기 업데이트

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@doyeonk429, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 38 minutes and 56 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c6dc6495-d606-4292-9c1b-c3e5ab5b7477

📥 Commits

Reviewing files that changed from the base of the PR and between 024e957 and 43b5eae.

📒 Files selected for processing (18)
  • Projects/DVDesign/SampleApp/Sources/ContentView.swift
  • Projects/DVDesign/SampleApp/Sources/DVButtonPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVCategoryPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVCheckBoxPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVSecretTypePreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVStepIndicatorPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVTitleBarPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/PreviewHelpers.swift
  • Projects/DVDesign/Sources/Components/DVButton.swift
  • Projects/DVDesign/Sources/Components/DVCategory.swift
  • Projects/DVDesign/Sources/Components/DVCheckBox.swift
  • Projects/DVDesign/Sources/Components/DVProjectContainer.swift
  • Projects/DVDesign/Sources/Components/DVSecretType.swift
  • Projects/DVDesign/Sources/Components/DVStepIndicator.swift
  • Projects/DVDesign/Sources/Components/DVTitleBar.swift
  • Projects/DVDesign/Sources/Components/DVVaultContainer.swift

Walkthrough

신규 DVDesign SwiftUI 컴포넌트 8종과 각 컴포넌트 전시용 PreviewView, previewSection 헬퍼, ContentView 라우팅 확장을 추가합니다.

Changes

DVDesign UI 컴포넌트 및 프리뷰 시스템

Layer / File(s) Summary
기본 상호작용 컴포넌트
Projects/DVDesign/Sources/Components/DVButton.swift, DVCheckBox.swift, DVStepIndicator.swift, Projects/DVDesign/SampleApp/Sources/DVButtonPreviewView.swift, DVCheckBoxPreviewView.swift, DVStepIndicatorPreviewView.swift
DVButton은 primary/secondary 스타일과 hover·press 상태 조합으로 배경/레이아웃을 결정합니다. DVCheckBox는 isChecked/hover 기반 표시를 처리하고, DVStepIndicator는 활성 단계 너비·색상과 스프링 애니메이션을 적용합니다. 각 컴포넌트에 대응하는 PreviewView가 추가되었습니다.
선택 및 목록 컨테이너
Projects/DVDesign/Sources/Components/DVCategory.swift, DVProjectContainer.swift, DVVaultContainer.swift, Projects/DVDesign/SampleApp/Sources/DVCategoryPreviewView.swift, DVProjectContainerPreviewView.swift, DVVaultContainerPreviewView.swift
DVCategory는 isSelected/hover에 따른 스타일 분기를 제공하고, DVProjectContainer/DVVaultContainer는 리스트 행 렌더링(카운트 상한 처리 포함)과 선택 바인딩/컨텍스트 메뉴를 제공하는 프리뷰로 전시됩니다.
특화된 UI 컴포넌트
Projects/DVDesign/Sources/Components/DVSecretType.swift, DVTitleBar.swift, Projects/DVDesign/SampleApp/Sources/DVSecretTypePreviewView.swift, DVTitleBarPreviewView.swift
DVSecretType은 원형 아이콘+라벨 구성, DVTitleBar는 제목·정렬 버튼·바인딩된 검색 필드를 통합한 헤더 구현을 포함합니다. 각각의 상태를 보여주는 PreviewView가 추가되었습니다.
프리뷰 인프라 및 라우팅
Projects/DVDesign/SampleApp/Sources/PreviewHelpers.swift, ContentView.swift
previewSection 제네릭 헬퍼를 추가했고, ContentView의 detailView(for:)에 새 컴포넌트 케이스를 매핑하여 해당 PreviewView를 반환하도록 라우팅을 확장했습니다. ComponentSection 목록도 일부 항목을 교체/갱신했습니다.

Sequence Diagram

sequenceDiagram
  participant User
  participant ContentView
  participant Router as detailView(for:)
  participant Preview as PreviewView

  User->>ContentView: 컴포넌트 선택
  ContentView->>Router: component.name 전달
  Router->>Preview: 해당 PreviewView() 반환
  Preview->>Preview: `@State로` 인터랙션 처리 (토글/선택/스텝 변경)
  Preview-->>ContentView: UI 상태 반영(뷰 내부)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • yeseonglee
  • dlguszoo
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 issue #13의 주요 목표인 8개 UI 컴포넌트 구현을 명확히 반영하고 있으며, 변경사항의 핵심을 간결하게 전달합니다.
Linked Issues check ✅ Passed PR에서 구현된 8개 컴포넌트(DVButton, DVCategory, DVCheckBox, DVPageControl, DVProjectContainer, DVSecretType, DVTitleBar, DVVaultContainer)가 모두 issue #13의 요구사항과 완벽하게 일치하며, 각 컴포넌트에 대한 Preview 통합도 완료되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 issue #13의 8개 UI 컴포넌트 구현 및 Preview 통합 범위 내에 있으며, 추가적인 범위 외 변경사항은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch design/#13/ui-component-구현

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@doyeonk429
doyeonk429 requested a review from dlguszoo May 24, 2026 08:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
Projects/DVDesign/Sources/Components/DVVaultContainer.swift (1)

107-115: ⚡ Quick win

fileprivateprivate로 축소해주세요.

현재 범위에서는 파일 단위 공개가 필요 없어 private가 더 안전하고 의도도 명확합니다.

제안 수정안
 extension DVVaultContainer.TrailingIcon {
 
-    fileprivate var iconName: String {
+    private var iconName: String {
         switch self {
         case .expiringSoon: return "clock"
         case .expired:      return "exclamationmark.circle"
         }
     }
 
-    fileprivate var iconColor: Color {
+    private var iconColor: Color {
         switch self {
         case .expiringSoon: return Color.dv(.warning)
         case .expired:      return Color.dv(.danger)
         }
     }
 }
As per coding guidelines, "접근 제어가 가능한 가장 엄격한 수준인지 확인하세요. (`private` > `fileprivate` > `internal`)".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/Sources/Components/DVVaultContainer.swift` around lines 107
- 115, Change the access level of the computed properties from fileprivate to
private to tighten encapsulation: replace the fileprivate declarations for
iconName and iconColor with private in the type where they are defined (the
computed properties named iconName and iconColor), and ensure any usages within
the same file still compile (they should, since private allows access within the
enclosing declaration); no other visibility changes are needed.
Projects/DVDesign/SampleApp/Sources/DVCategoryPreviewView.swift (1)

9-33: ⚡ Quick win

body 내부 중첩 레이아웃을 private 서브뷰로 분리해 주세요.

섹션 블록이 body에 직접 쌓여 있어 확장 시 가독성이 빠르게 떨어집니다. interactiveSection, selectedSection, unselectedSection 같은 private var로 분리하고 private extension으로 이동하는 편이 좋습니다.

♻️ 제안 예시
 var body: some View {
     ScrollView {
-        VStack(alignment: .leading, spacing: 32) {
-            previewSection("Interactive") { ... }
-            previewSection("Selected") { ... }
-            previewSection("Unselected") { ... }
-        }
+        content
         .padding(24)
         .frame(maxWidth: .infinity, alignment: .leading)
     }
     .navigationTitle("DVCategory")
 }
+
+private extension DVCategoryPreviewView {
+    var content: some View {
+        VStack(alignment: .leading, spacing: 32) {
+            interactiveSection
+            selectedSection
+            unselectedSection
+        }
+    }
+}

As per coding guidelines "var body 안에 중첩 레이아웃이 직접 구현되어 있으면 extension의 private var/func로 분리를 제안하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/DVCategoryPreviewView.swift` around lines
9 - 33, The body currently contains multiple nested layout blocks
(previewSection with HStack and DVCategory rows) which hurts readability;
refactor each into private computed subviews such as private var
interactiveSection, private var selectedSection, and private var
unselectedSection that return the corresponding View (using the existing
previewSection, HStack and DVCategory usages), move those private vars into a
private extension of DVCategoryPreviewView, and have body simply compose those
subviews (along with the existing padding/frame/navigationTitle).
Projects/DVDesign/SampleApp/Sources/DVTitleBarPreviewView.swift (1)

23-34: ⚡ Quick win

프리뷰 배경의 Color.white 고정은 다크 모드 확인을 왜곡할 수 있습니다.

프리뷰 정확도를 위해 고정 흰색 대신 시스템 배경색을 쓰는 편이 안전합니다.

♻️ 제안 수정안
-    .background(Color.white)
+    .background(Color(uiColor: .systemBackground))
@@
-    .background(Color.white)
+    .background(Color(uiColor: .systemBackground))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/DVTitleBarPreviewView.swift` around lines
23 - 34, The previews currently hardcode Color.white which skews dark-mode
rendering; update the preview background usage in the previewSection blocks that
wrap DVTitleBar (the DVTitleBar preview instances) to use the system background
color instead (e.g., use SwiftUI's system/background color provider such as
Color(.systemBackground) or Color(UIColor.systemBackground)) so previews reflect
light/dark appearances correctly.
Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift (1)

16-55: ⚡ Quick win

body의 중첩 레이아웃을 private 서브뷰로 분리해 주세요.

현재 body에 섹션 구성과 상태 토글 UI가 한 번에 들어가 있어 수정 시 영향 범위가 큽니다. 섹션 단위로 private var/func로 분리하면 유지보수성이 좋아집니다.

♻️ 제안 diff
 struct DVVaultContainerPreviewView: View {
@@
-    var body: some View {
-        ScrollView {
-            VStack(alignment: .leading, spacing: 32) {
-                ...
-            }
-            .padding(24)
-            .frame(maxWidth: .infinity, alignment: .leading)
-        }
-        .navigationTitle("DVVaultContainer")
-    }
+    var body: some View {
+        ScrollView { content }
+            .navigationTitle("DVVaultContainer")
+    }
 }
+
+private extension DVVaultContainerPreviewView {
+    var content: some View {
+        VStack(alignment: .leading, spacing: 32) {
+            interactiveSection
+            selectedSection
+            unselectedSection
+            expiringSoonSection
+            expiredSection
+        }
+        .padding(24)
+        .frame(maxWidth: .infinity, alignment: .leading)
+    }
+}
As per coding guidelines `var body 안에 중첩 레이아웃이 직접 구현되어 있으면 extension의 private var/func로 분리를 제안하세요.`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift` around
lines 16 - 55, The body currently embeds multiple nested layout blocks (the
previewSection stacks, ForEach over vaults, and DVVaultContainer instances)
making maintenance hard; extract each logical preview into private computed vars
or private funcs in a file-local extension (e.g., private var
interactivePreview: some View, private var selectedPreview: some View, private
func expiringPreview() -> some View) and have body simply compose those vars;
ensure you reference existing symbols (body, previewSection, vaults,
selectedIndex, DVVaultContainer) when moving code and preserve the selection
toggle closure semantics (updating selectedIndex) and any trailingIcon
parameters.
Projects/DVDesign/SampleApp/Sources/ContentView.swift (1)

35-57: ⚡ Quick win

라우팅 스위치가 이중화되어 이미 분기 드리프트가 생겼습니다.

detailView(for:)destination(for:)가 같은 책임을 중복하고 있고, 현재도 DVRadioButton 계열 케이스가 한쪽에만 있어 매핑 불일치가 있습니다. 라우팅 함수를 하나로 합쳐 단일 소스로 유지해 주세요.

♻️ 제안 diff
-                            NavigationLink(component.name) {
-                                detailView(for: component)
-                            }
+                            NavigationLink(component.name) {
+                                destination(for: component)
+                            }

-    private func detailView(for component: Component) -> some View {
+    private func destination(for component: Component) -> some View {
         switch component.name {
         case "DVPageControl":
             DVPageControlPreviewView()
@@
         case "DVSecretType":
             DVSecretTypePreviewView()
         case "DVRadioButton", "DVRadioButtonGroup":
             RadioButtonPreviewView()
         default:
             ComponentPlaceholderView(name: component.name, owner: component.owner)
         }
     }
 }
 
-// MARK: - Routing
-
-@ViewBuilder
-private func destination(for component: Component) -> some View {
-    ...
-}

Also applies to: 64-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/ContentView.swift` around lines 35 - 57,
The switch-based routing for components is duplicated between detailView(for:)
and destination(for:) causing drift (e.g., the DVRadioButton case exists in only
one); refactor by extracting a single mapping function (e.g.,
componentView(for:) or mapComponentToView) that returns the appropriate View for
a Component and include every current case (DVPageControl, DVCategory,
DVProjectContainer, DVVaultContainer, DVButton, DVCheckBox, DVTitleBar,
DVSecretType, DVRadioButton/DVRadioButtonGroup, and default
ComponentPlaceholderView) and then have both detailView(for:) and
destination(for:) call that single function (or replace them with thin wrappers)
so there is one authoritative switch to maintain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Projects/DVDesign/SampleApp/Sources/DVPageControlPreviewView.swift`:
- Around line 19-43: 현재 프리뷰 코드에서 currentStep 경계값(2)이 하드코딩되어 있어 totalSteps 변경 시
동작이 깨질 수 있으니, 각 previewSection 블록과 버튼 핸들러에서 하드코딩된 2를 사용하지 말고 totalSteps에서 파생한
lastStepIndex = totalSteps - 1 값을 사용하도록 변경하세요; 즉 DVPageControl(totalSteps: X,
currentStep: currentStep)를 사용하는 영역에서 totalSteps 값을 변수로 선언하고 DVButton의 증감 가드(if
currentStep < lastStepIndex / if currentStep > 0)와 .disabled
조건(.disabled(currentStep == lastStepIndex) 등)에 lastStepIndex를 참조하도록 업데이트하세요.

In `@Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift`:
- Around line 20-26: ForEach is using id: \.0 which can collide when project
names duplicate; change the iteration to use a stable unique identifier (e.g.,
use the element index or an explicit ID field) when iterating over projects so
each DVProjectContainer has a unique id; locate the ForEach(projects, id: \.0)
block and switch to ForEach(Array(projects.enumerated()), id: \.0 or use \.1.id)
or otherwise bind the index in the closure and use that index as the id while
keeping DVProjectContainer(name:count:isSelected:){...} and the selected logic
intact.

In `@Projects/DVDesign/Sources/Components/DVCategory.swift`:
- Around line 40-41: The clip shape uses RoundedRectangle(cornerRadius: 16) but
the border overlay uses a 12 radius causing the stroke to appear misaligned on
hover; update the border drawing to use the same corner radius (replace the 12
radius in the borderOverlay definition with 16) so the overlay stroke and
clipShape (RoundedRectangle(cornerRadius: 16)) match; apply the same change for
the second occurrence where overlay(borderOverlay) is paired with clipShape to
ensure both instances use cornerRadius: 16.

In `@Projects/DVDesign/Sources/Components/DVCheckBox.swift`:
- Around line 26-33: Update DVCheckBox's body so the tappable area is at least
the platform minimum and screen readers get state info: keep checkboxShape sized
to 16x16 (e.g., checkboxShape.frame(width:16, height:16)), but wrap the Button
content with extra hit area (e.g., add .padding(8) or set the Button view to
.frame(minWidth:44, minHeight:44) and .contentShape(Rectangle())) to ensure a
minimum tappable target; also add accessibility modifiers on the Button (or
checkboxShape) such as .accessibilityLabel("…"), .accessibilityValue(isChecked ?
"Checked" : "Unchecked") and .accessibilityAddTraits(isChecked ? .isSelected :
[]) so VoiceOver conveys the control role and state. Make these changes in the
DVCheckBox.body where Button(action:) and checkboxShape are defined.

In `@Projects/DVDesign/Sources/Components/DVProjectContainer.swift`:
- Around line 40-42: The hover border's corner radius doesn't match the clipped
background (clipShape(RoundedRectangle(cornerRadius: 6)) vs the border overlay's
cornerRadius 8), causing a visible gap when hovered; update the rounded corner
radii so both use the same value (either change clipShape's cornerRadius to 8 or
change the borderOverlay's cornerRadius to 6) wherever you set these (e.g., the
clipShape call and the borderOverlay definition used in DVProjectContainer.swift
and the same pattern at the other occurrence around lines 80-82) so the
background clipping and the overlay border share an identical cornerRadius.

In `@Projects/DVDesign/Sources/Components/DVTitleBar.swift`:
- Around line 55-62: The sortButton is an icon-only .plain Button with no
accessibility label and a small tap target; update the Button created in the
sortButton computed property (the one using onSortTapped and Image(systemName:
"arrow.up.arrow.down")) to add an accessibility label (use a localized string,
e.g. "Sort" or the appropriate Korean label) and ensure a minimum tappable area
by increasing its hit area (for example via a minimum frame size like 44x44 or
adding padding and contentShape(Rectangle())), and keep accessibility traits
(e.g., .accessibilityAddTraits(.isButton)) so VoiceOver announces it properly
while preserving the existing .buttonStyle(.plain).

---

Nitpick comments:
In `@Projects/DVDesign/SampleApp/Sources/ContentView.swift`:
- Around line 35-57: The switch-based routing for components is duplicated
between detailView(for:) and destination(for:) causing drift (e.g., the
DVRadioButton case exists in only one); refactor by extracting a single mapping
function (e.g., componentView(for:) or mapComponentToView) that returns the
appropriate View for a Component and include every current case (DVPageControl,
DVCategory, DVProjectContainer, DVVaultContainer, DVButton, DVCheckBox,
DVTitleBar, DVSecretType, DVRadioButton/DVRadioButtonGroup, and default
ComponentPlaceholderView) and then have both detailView(for:) and
destination(for:) call that single function (or replace them with thin wrappers)
so there is one authoritative switch to maintain.

In `@Projects/DVDesign/SampleApp/Sources/DVCategoryPreviewView.swift`:
- Around line 9-33: The body currently contains multiple nested layout blocks
(previewSection with HStack and DVCategory rows) which hurts readability;
refactor each into private computed subviews such as private var
interactiveSection, private var selectedSection, and private var
unselectedSection that return the corresponding View (using the existing
previewSection, HStack and DVCategory usages), move those private vars into a
private extension of DVCategoryPreviewView, and have body simply compose those
subviews (along with the existing padding/frame/navigationTitle).

In `@Projects/DVDesign/SampleApp/Sources/DVTitleBarPreviewView.swift`:
- Around line 23-34: The previews currently hardcode Color.white which skews
dark-mode rendering; update the preview background usage in the previewSection
blocks that wrap DVTitleBar (the DVTitleBar preview instances) to use the system
background color instead (e.g., use SwiftUI's system/background color provider
such as Color(.systemBackground) or Color(UIColor.systemBackground)) so previews
reflect light/dark appearances correctly.

In `@Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift`:
- Around line 16-55: The body currently embeds multiple nested layout blocks
(the previewSection stacks, ForEach over vaults, and DVVaultContainer instances)
making maintenance hard; extract each logical preview into private computed vars
or private funcs in a file-local extension (e.g., private var
interactivePreview: some View, private var selectedPreview: some View, private
func expiringPreview() -> some View) and have body simply compose those vars;
ensure you reference existing symbols (body, previewSection, vaults,
selectedIndex, DVVaultContainer) when moving code and preserve the selection
toggle closure semantics (updating selectedIndex) and any trailingIcon
parameters.

In `@Projects/DVDesign/Sources/Components/DVVaultContainer.swift`:
- Around line 107-115: Change the access level of the computed properties from
fileprivate to private to tighten encapsulation: replace the fileprivate
declarations for iconName and iconColor with private in the type where they are
defined (the computed properties named iconName and iconColor), and ensure any
usages within the same file still compile (they should, since private allows
access within the enclosing declaration); no other visibility changes are
needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d4b3f68-df31-45cd-996d-c563e97a20c1

📥 Commits

Reviewing files that changed from the base of the PR and between afe263a and 2fb835b.

📒 Files selected for processing (18)
  • Projects/DVDesign/SampleApp/Sources/ContentView.swift
  • Projects/DVDesign/SampleApp/Sources/DVButtonPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVCategoryPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVCheckBoxPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVPageControlPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVSecretTypePreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVTitleBarPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/PreviewHelpers.swift
  • Projects/DVDesign/Sources/Components/DVButton.swift
  • Projects/DVDesign/Sources/Components/DVCategory.swift
  • Projects/DVDesign/Sources/Components/DVCheckBox.swift
  • Projects/DVDesign/Sources/Components/DVPageControl.swift
  • Projects/DVDesign/Sources/Components/DVProjectContainer.swift
  • Projects/DVDesign/Sources/Components/DVSecretType.swift
  • Projects/DVDesign/Sources/Components/DVTitleBar.swift
  • Projects/DVDesign/Sources/Components/DVVaultContainer.swift

Comment on lines +19 to +43
DVPageControl(totalSteps: 3, currentStep: currentStep)
HStack(spacing: 8) {
DVButton(titleText: "이전", style: .secondary) {
if currentStep > 0 { currentStep -= 1 }
}
.frame(width: 80)
.disabled(currentStep == 0)

DVButton(titleText: "다음", style: .secondary) {
if currentStep < 2 { currentStep += 1 }
}
.frame(width: 80)
.disabled(currentStep == 2)
}
}
}
previewSection("Step 1 / 3") {
DVPageControl(totalSteps: 3, currentStep: 0)
}
previewSection("Step 2 / 3") {
DVPageControl(totalSteps: 3, currentStep: 1)
}
previewSection("Step 3 / 3") {
DVPageControl(totalSteps: 3, currentStep: 2)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

currentStep 경계값(2) 하드코딩을 제거해 주세요.

totalSteps와 경계 로직이 분리되어 있어 단계 수 변경 시 프리뷰 동작이 어긋날 수 있습니다. lastStepIndex = totalSteps - 1로 통일해 주세요.

🐛 제안 수정안
 struct DVPageControlPreviewView: View {
@@
-    `@State` private var currentStep = 0
+    `@State` private var currentStep = 0
+    private let totalSteps = 3
@@
-                        DVPageControl(totalSteps: 3, currentStep: currentStep)
+                        DVPageControl(totalSteps: totalSteps, currentStep: currentStep)
@@
-                            DVButton(titleText: "다음", style: .secondary) {
-                                if currentStep < 2 { currentStep += 1 }
+                            DVButton(titleText: "다음", style: .secondary) {
+                                if currentStep < totalSteps - 1 { currentStep += 1 }
                             }
                             .frame(width: 80)
-                            .disabled(currentStep == 2)
+                            .disabled(currentStep == totalSteps - 1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/DVPageControlPreviewView.swift` around
lines 19 - 43, 현재 프리뷰 코드에서 currentStep 경계값(2)이 하드코딩되어 있어 totalSteps 변경 시 동작이 깨질
수 있으니, 각 previewSection 블록과 버튼 핸들러에서 하드코딩된 2를 사용하지 말고 totalSteps에서 파생한
lastStepIndex = totalSteps - 1 값을 사용하도록 변경하세요; 즉 DVPageControl(totalSteps: X,
currentStep: currentStep)를 사용하는 영역에서 totalSteps 값을 변수로 선언하고 DVButton의 증감 가드(if
currentStep < lastStepIndex / if currentStep > 0)와 .disabled
조건(.disabled(currentStep == lastStepIndex) 등)에 lastStepIndex를 참조하도록 업데이트하세요.

Comment thread Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift Outdated
Comment thread Projects/DVDesign/Sources/Components/DVCategory.swift Outdated
Comment on lines +26 to +33
public var body: some View {
Button(action: action) {
checkboxShape
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.onHover { isHovered = $0 }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

체크박스에 접근성 상태와 최소 탭 영역을 보강해주세요.

현재 16x16 시각 크기 그대로 클릭 영역이 잡혀 조작이 어렵고, 보이스오버에서 체크 상태 의미 전달이 부족합니다. 최소 탭 영역과 접근성 라벨/값을 추가하는 게 안전합니다.

제안 수정안
     public var body: some View {
         Button(action: action) {
             checkboxShape
+                .frame(width: 44, height: 44)
                 .contentShape(Rectangle())
         }
         .buttonStyle(.plain)
         .onHover { isHovered = $0 }
+        .accessibilityLabel("체크박스")
+        .accessibilityValue(isChecked ? "선택됨" : "선택 안 됨")
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/Sources/Components/DVCheckBox.swift` around lines 26 - 33,
Update DVCheckBox's body so the tappable area is at least the platform minimum
and screen readers get state info: keep checkboxShape sized to 16x16 (e.g.,
checkboxShape.frame(width:16, height:16)), but wrap the Button content with
extra hit area (e.g., add .padding(8) or set the Button view to
.frame(minWidth:44, minHeight:44) and .contentShape(Rectangle())) to ensure a
minimum tappable target; also add accessibility modifiers on the Button (or
checkboxShape) such as .accessibilityLabel("…"), .accessibilityValue(isChecked ?
"Checked" : "Unchecked") and .accessibilityAddTraits(isChecked ? .isSelected :
[]) so VoiceOver conveys the control role and state. Make these changes in the
DVCheckBox.body where Button(action:) and checkboxShape are defined.

Comment thread Projects/DVDesign/Sources/Components/DVProjectContainer.swift Outdated
Comment on lines +55 to +62
private var sortButton: some View {
Button(action: onSortTapped) {
Image(systemName: "arrow.up.arrow.down")
.dvFont(.bodyXL)
.foregroundStyle(Color.dv(.gray600))
}
.buttonStyle(.plain)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

정렬 버튼에 접근성 이름과 최소 탭 영역을 추가해주세요.

아이콘-only .plain 버튼은 보이스오버 의미 전달이 약하고, 클릭 타깃도 작아 조작성이 떨어집니다.

제안 수정안
     private var sortButton: some View {
         Button(action: onSortTapped) {
             Image(systemName: "arrow.up.arrow.down")
                 .dvFont(.bodyXL)
                 .foregroundStyle(Color.dv(.gray600))
+                .frame(width: 44, height: 44)
+                .contentShape(Rectangle())
         }
         .buttonStyle(.plain)
+        .accessibilityLabel("정렬")
+        .accessibilityHint("정렬 옵션을 엽니다")
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/Sources/Components/DVTitleBar.swift` around lines 55 - 62,
The sortButton is an icon-only .plain Button with no accessibility label and a
small tap target; update the Button created in the sortButton computed property
(the one using onSortTapped and Image(systemName: "arrow.up.arrow.down")) to add
an accessibility label (use a localized string, e.g. "Sort" or the appropriate
Korean label) and ensure a minimum tappable area by increasing its hit area (for
example via a minimum frame size like 44x44 or adding padding and
contentShape(Rectangle())), and keep accessibility traits (e.g.,
.accessibilityAddTraits(.isButton)) so VoiceOver announces it properly while
preserving the existing .buttonStyle(.plain).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interaction 없는 ui component이므로 DVStepIndicator 로 네이밍 수정

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. .listStyle(.sidebar) 그대로 + .tint() 만 바꾸는 경우

가장 단순한 방법:
List(selection: $selected) { ... }
.listStyle(.sidebar)
.tint(Color.dv(.vaultGreen))

✅ 커스텀 가능

  • 선택 배경 색: .tint()로 vaultGreen 지정 가능
  • 행 내부 텍스트/아이콘 색, 폰트, 레이아웃
  • 아이콘 종류 (SF Symbol, 커스텀 이미지)

❌ 커스텀 불가 / 제한적

  • 선택 배경의 corner radius (시스템이 ~6pt로 결정)
  • 선택 배경의 vibrancy 합성 — 순수 vaultGreen이 아니라 사이드바 머티리얼과 섞인 톤으로 렌더링됨. 디자인 시안의 색과 픽셀 단위로는 다르게 보임
  • 윈도우 비활성 시 자동 dim — 끌 수 없음 (보통 끄지 않는 게 맞음)
  • 호버 시 미세 배경 틴트 — 시스템이 강제
  • 행 높이/패딩 — .controlSize 정도로만 조절
  • .badge(count) 모양 — 시스템 회색 pill 고정. 색·폰트·모양 변경 불가

→ 한마디로: "기본 macOS 사이드바 룩 + 브랜드 컬러로 살짝 도색" 수준. Mail, Notes, Reminders가 다 이 방법

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. .listStyle(.sidebar) 포기, .listStyle(.plain) + selection 활용

List(selection: $selected) { ... }
.listStyle(.plain)

✅ 새로 얻는 자유도

  • 행 배경/보더/corner radius 완전 자유
  • 선택 배경 자체를 직접 그릴 수 있음 (시스템이 안 그려줌)
  • 호버/프레스 시각 효과 직접 정의
  • 배지 모양 직접 그리기

✅ 그래도 유지되는 네이티브 기능

  • 키보드 ↑/↓ 네비게이션
  • Return 선택, Space, 다중 선택 (⌘/⇧)
  • 포커스 링
  • selection 바인딩 자동 처리
  • VoiceOver 기본 통합

❌ 잃는 것

  • 사이드바 vibrancy 머티리얼 (배경이 평면 색이 됨)
  • 시스템 사이드바 룩 (외부 앱과 일관성)
  • 자동 윈도우 비활성 dim → 직접 @Environment(.controlActiveState) 처리 필요

→ 브랜드 컬러를 정확히 vaultGreen으로 칠하고, 디자인 시안대로 corner radius/패딩을 살리고 싶다면 이 길. 우리 케이스에 가장 맞을 가능성 높음.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. 비슷한 시스템 컴포넌트 / 패턴

macOS는 행(row)의 비주얼을 직접 그리되, 컨테이너로 List를 쓰는
것이 시스템 표준
이에요. 즉 "List + 우리가 그린 row"가 정답.

참고할 만한 네이티브 앱의 패턴:

패턴 DVVaultContainer와 매핑
Mail.app 메시지 리스트 아바타 + (보낸이/제목/미리보기) + 우측 날짜 + 우측 첨부/플래그 아이콘 가장 가까움
Messages.app 대화 리스트 아바타 + (이름/미리보기) + 우측 시간 + unread dot 거의 동일 구조
Notes.app 노트 리스트 (제목/미리보기/날짜) + 우측 첨부 아이콘 아바타 없는 변형
Reminders.app 리마인더 체크박스 + (제목/노트/날짜) + 우측 플래그 트레일링 아이콘 = 만료 아이콘
Finder 리스트 뷰 파일 아이콘 + 이름 + (수정일 / 크기 / 종류) 비슷한 골격

→ 셋 다 List + 커스텀 row 입니다. 직접 ViewBuilder로 row를 만들고, 컨테이너만 List로 감싸요.

Table<Value>도 macOS에 있지만 이건 다중 컬럼 + 정렬 가능한 스프레드시트 류라서 (Finder의 컬럼 헤더 리스트 같은 거) Vault 같은 풍부한 단일 컬럼 row에는
과합니다. List가 맞는 선택.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. List로 옮겼을 때의 모습

호출부 (DVPresentation 같은 곳):

List(selection: $selectedVaultID) {
    ForEach(vaults) { vault in
        DVVaultRow(vault: vault)
            .tag(vault.id)
            .contextMenu { ... }      // 한 줄
            .draggable(vault)         // 한 줄
    }
}
.listStyle(.inset)                    // 또는 .plain
.tint(Color.dv(.vaultGreen))          // .sidebar/.inset일 때 선택색

DVVaultRow현재 DVVaultContainer의 비주얼과 거의 동일 — 다만:

현재 (Button) 변경 후 (List row)
action: () -> Void 받음 제거. 선택은 List가 관리
isSelected: Bool 인자로 받음 부모가 selection을 알고 있으면 row가 그 값을 비교, 또는 .listRowBackground 사용
@State isHovered 직접 관리 List의 호버를 받거나, 필요하면 그대로 유지
Button(action:) { ... }.buttonStyle(.plain) 래핑 제거. 그냥 HStack { ... }
키보드/포커스 직접 처리 안 됨 List가 처리

코드량은 오히려 줄어들고, 잃는 거 없이 ↑/↓ 키, Return, ⌘/⇧ 다중 선택, 포커스 링이 다 들어옵니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.searchable(text:) 모디파이어
.searchable(text: $query, placement: .sidebar, prompt: "Search vaults")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.searchable()은 검색 필드 위치를 시스템이 강제(주로 툴바). 시안의 '컬럼 헤더 안 인라인'을 못 살림.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude 의견은 이러하니 실제로 시스템 컴포넌트 살린 코드로 테스트해보고 figma랑 너무 다르면 폐기합시다

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DVCheckBox는 macOS에서 시스템 컴포넌트(Toggle(...).toggleStyle(.checkbox))가 강하게 권장되는 영역입니다.

.tint(vaultGreen)으로 브랜드 색은 유지되고, Space 키 토글·포커스 링·라벨 클릭·VoiceOver checkbox trait·Mixed 상태가 전부 무료로 따라옵니다.

잃는 건 corner radius 0.5pt와 크기 2pt 차이 정도. 디자인팀과 '시스템 룩 수용 가능?'만 합의되면 전환 권장.

시스템 전환 없이 현재 구조를 유지한다면 최소한:

  • L42/L60 cornerRadius 5.5/5 불일치 수정
  • API를 @binding isOn으로 (action 콜백은 macOS답지 않음)
  • 라벨까지 받는 API로 확장 + 라벨 영역도 hit target
  • VoiceOver 라벨/트레이트 명시

가 필요합니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ButtonStyle 아키텍처는 정석이라 골격은 macOS-native. 가장 큰 문제는 Primary 스펙(pill·고정폭·40pt)이 iOS 모달 CTA에서 그대로 가져온 듯한 점. 다이얼로그/시트 컨텍스트에 들어가면 macOS Save/Cancel 컨벤션과 충돌하므로 디자인팀과 재검토 필요. 시스템 .borderedProminent로 갈지 커스텀 유지할지의 결정도 그 합의 위에서 내려야 함.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. borderOverlay cornerRadius mismatch (L40 16 vs L82 12)
  • 채움 16, stroke 12 → 4pt 어긋남. DVProjectContainer(6/8, 2pt)·DVCheckBox(5.5/5, 0.5pt)보다 가장 큰 차이.
  • hover 시 보더가 채움보다 안쪽 4pt에 그어져 시각적으로 눈에 띔.
  • 둘 다 16으로 통일.
  1. 고정 크기 108×72 (L38)
  • 사이드바 폭이 사용자 조절 가능한 macOS 컨텍스트에서 부서짐.
  • 사이드바를 좁히면 2×2가 1×4로 wrap되지도 않고 잘림.
  • 그리드 컨테이너가 셀 크기를 결정하도록 maxWidth: .infinity 또는 GridItem(.flexible()) 검토.
  1. iconCircle이 placeholder (L63-67)
  • 회색/흰색 빈 원. 시안에서도 동일. 디자인 미완성 or API 누락.
  • 각 카테고리에 의미 있는 SF Symbol이 들어가야 자연스러움 (tray/star.fill/clock/trash).
  • API에 icon: Image 또는 systemName: String? 추가.
  1. 호버가 stroke 방식 (L82)
  • 다른 사이드바 컴포넌트와 같은 이슈. macOS 사이드바 호버는 옅은 배경 fill이 표준.
  • 다만 이미 미선택 상태가 gray100 fill이라 호버 시 gray200으로 살짝 진해지는 방식이 자연스러움.
  1. press 피드백 없음
  • .buttonStyle(.plain)이라 mousedown 색 변화 없음. ButtonStyle 만들어 configuration.isPressed 처리.
  1. 호버/선택 전환 무애니메이션
  • .animation(.easeOut(duration: 0.12), value: isHovered) + value: isSelected.

@doyeonk429 doyeonk429 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yeseonglee 구현하느라 고생했고 디자인 자체가 애매한 부분이 약간 있어서 그 부분은 별도로 같이 회의하면서 정리하면 더 좋을 것 같아.

my claude의 후기

전반적으로 SwiftUI 사용은 깔끔하지만, 시안 차원에서 iOS 패턴이 macOS 컴포넌트로 그대로 옮겨진 부분들이 코드에 반영돼 있습니다. 컴포넌트 구현 자체의 즉시 수정 항목(cornerRadius 불일치 3건, 고정 width)은 이번 PR에서 정리하고, 레이아웃·디자인 패턴 차원의 부채(2×2 카테고리 그리드, FAB, 가로 라디오, Primary pill)는 디자인팀과 별도 트랙으로 정리하는 것을 권장합니다. 또한 DVProjectContainer/DVVaultContainer는 시스템 List(selection:)로 흡수 가능한지, DVCheckBox는 Toggle.toggleStyle(.checkbox)로 갈 수 있는지가 가장 큰 결정 포인트입니다.

@doyeonk429 doyeonk429 added the 🎨 Design UI 디자인 작업 label May 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
Projects/DVDesign/Sources/Components/DVTitleBar.swift (1)

54-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

정렬 아이콘 버튼의 접근성 라벨/탭 영역을 보강해주세요.

아이콘-only .plain 버튼은 의미 전달이 약하고 클릭 영역도 작습니다. 최소 히트 타깃과 접근성 라벨을 함께 추가하는 편이 안전합니다.

개선 예시
     private var sortButton: some View {
         Button(action: onSortTapped) {
             Image(systemName: "arrow.up.arrow.down")
                 .dvFont(.bodyXL)
                 .foregroundStyle(Color.dv(.gray800))
+                .frame(width: 44, height: 44)
+                .contentShape(Rectangle())
         }
         .buttonStyle(.plain)
+        .accessibilityLabel("정렬")
+        .accessibilityHint("정렬 옵션을 엽니다")
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/Sources/Components/DVTitleBar.swift` around lines 54 - 61,
The sortButton (in DVTitleBar.swift) is an icon-only .plain Button with a small
tap area and no accessibility label; update the Button that calls onSortTapped
to provide an explicit accessibility label (e.g., .accessibilityLabel("Sort"))
and increase the hit target (wrap the Image with additional tappable padding or
apply a .contentShape(Rectangle()) plus padding) so the control meets minimum
touch size; keep the visual appearance by retaining .buttonStyle(.plain) after
applying padding and ensure VoiceOver reads the label (optionally add
.accessibilityHint if needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@Projects/DVDesign/Sources/Components/DVTitleBar.swift`:
- Around line 54-61: The sortButton (in DVTitleBar.swift) is an icon-only .plain
Button with a small tap area and no accessibility label; update the Button that
calls onSortTapped to provide an explicit accessibility label (e.g.,
.accessibilityLabel("Sort")) and increase the hit target (wrap the Image with
additional tappable padding or apply a .contentShape(Rectangle()) plus padding)
so the control meets minimum touch size; keep the visual appearance by retaining
.buttonStyle(.plain) after applying padding and ensure VoiceOver reads the label
(optionally add .accessibilityHint if needed).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b36d05aa-f73c-42ac-8744-62aedb65070a

📥 Commits

Reviewing files that changed from the base of the PR and between 2fb835b and 19df99b.

📒 Files selected for processing (7)
  • Projects/DVDesign/SampleApp/Sources/DVCategoryPreviewView.swift
  • Projects/DVDesign/Sources/Components/DVCategory.swift
  • Projects/DVDesign/Sources/Components/DVCheckBox.swift
  • Projects/DVDesign/Sources/Components/DVProjectContainer.swift
  • Projects/DVDesign/Sources/Components/DVSecretType.swift
  • Projects/DVDesign/Sources/Components/DVTitleBar.swift
  • Projects/DVDesign/Sources/Components/DVVaultContainer.swift

@doyeonk429
doyeonk429 requested a review from yeseonglee May 24, 2026 13:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Projects/DVDesign/Sources/Components/DVProjectContainer.swift`:
- Around line 97-107: The rightMouseDown implementation overwrites menuObserver
without removing any previously-registered observer and lacks cleanup in deinit,
causing lost tokens and duplicate callbacks; before calling
NotificationCenter.default.addObserver in rightMouseDown (in DVProjectContainer
and DVVaultContainer) remove the existing observer token if non-nil (using
NotificationCenter.default.removeObserver(menuObserver!) or the Swift API
matching your stored type) then assign the new token, and implement deinit to
call resetAndRemoveObserver() or explicitly remove menuObserver and nil it so
any remaining observer is unregistered when the object is deallocated; ensure
resetAndRemoveObserver clears the token after removal to avoid repeated removal
attempts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aa2c5b01-bc18-48f7-af85-93f37248bbe5

📥 Commits

Reviewing files that changed from the base of the PR and between 19df99b and f1fe24b.

📒 Files selected for processing (7)
  • Projects/DVDesign/SampleApp/Sources/ContentView.swift
  • Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVStepIndicatorPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift
  • Projects/DVDesign/Sources/Components/DVProjectContainer.swift
  • Projects/DVDesign/Sources/Components/DVStepIndicator.swift
  • Projects/DVDesign/Sources/Components/DVVaultContainer.swift

Comment thread Projects/DVDesign/Sources/Components/DVProjectContainer.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift (1)

3-4: 💤 Low value

시스템 프레임워크와 프로젝트 모듈 사이에 빈 줄을 추가하세요.

내장 프레임워크(SwiftUI)와 프로젝트 모듈(DVDesign) 임포트 사이에 빈 줄을 넣으면 가독성이 향상됩니다.

♻️ 제안 수정안
 import SwiftUI
+
 import DVDesign

As per coding guidelines: 모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요. (내장 프레임워크 먼저, 빈 줄로 서드파티 구분)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift`
around lines 3 - 4, 파일 상단의 import 정렬과 포맷을 개선하세요: 내장 프레임워크(import SwiftUI)를 프로젝트
모듈(import DVDesign)보다 먼저 유지하되 두 그룹 사이에 빈 줄을 추가해 시각적으로 구분하고, 모듈들이 알파벳 순서로 정렬되어
있는지 확인하세요 (참조 심볼: import SwiftUI, import DVDesign).
Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift (2)

3-4: 💤 Low value

시스템 프레임워크와 프로젝트 모듈 사이에 빈 줄을 추가하세요.

내장 프레임워크(SwiftUI)와 프로젝트 모듈(DVDesign) 임포트 사이에 빈 줄을 넣으면 가독성이 향상됩니다.

♻️ 제안 수정안
 import SwiftUI
+
 import DVDesign

As per coding guidelines: 모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요. (내장 프레임워크 먼저, 빈 줄로 서드파티 구분)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift` around
lines 3 - 4, 현재 파일의 import 목록에서 시스템 프레임워크(import SwiftUI)와 프로젝트 모듈(import
DVDesign) 사이에 빈 줄이 없으니, import SwiftUI를 먼저 두고 그 다음 줄에 빈 줄을 추가한 후 import
DVDesign를 배치하여 시스템 프레임워크와 프로젝트/서드파티 모듈을 분리하고, 전체 import 블록이 알파벳 순으로 정렬되어 있는지
확인하세요 (참조 심볼: import SwiftUI, import DVDesign).

10-19: 💤 Low value

튜플 대신 명시적 struct 사용을 고려하세요.

3개 필드를 가진 튜플은 가독성 경계선상에 있습니다. 프리뷰 데이터지만 VaultPreviewItem 같은 struct로 추출하면 코드가 더 명확해집니다.

♻️ 제안 수정안
+    private struct VaultPreviewItem {
+        let name: String
+        let date: String
+        let trailingIcon: DVVaultContainer.TrailingIcon?
+    }
+
-    private let vaults: [(String, String, DVVaultContainer.TrailingIcon?)] = [
-        ("내가 설정한 이름",         "2026.04.01", nil),
+    private let vaults: [VaultPreviewItem] = [
+        VaultPreviewItem(name: "내가 설정한 이름", date: "2026.04.01", trailingIcon: nil),
         ...
     ]

ForEach 부분도 수정:

 DVVaultContainer(
-    name: vaults[index].0,
-    date: vaults[index].1,
-    trailingIcon: vaults[index].2,
+    name: vaults[index].name,
+    date: vaults[index].date,
+    trailingIcon: vaults[index].trailingIcon,
     isSelected: selectedIndex == index
 )

As per coding guidelines: 튜플 반환 시 필드가 3개를 초과하면 struct 사용을 제안하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift` around
lines 10 - 19, Extract the tuple type into a clear struct (e.g.,
VaultPreviewItem) with fields title: String, date: String, trailingIcon:
DVVaultContainer.TrailingIcon?; replace the private let vaults: [(String,
String, DVVaultContainer.TrailingIcon?)] declaration with a [VaultPreviewItem]
literal; make VaultPreviewItem conform to Identifiable (or add an id: UUID) so
the ForEach that currently iterates over vaults can use each item's id and named
properties (title, date, trailingIcon) instead of tuple indexes; update any
usages in DVVaultContainerPreviewView and the ForEach block to reference the
struct properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift`:
- Around line 3-4: 파일 상단의 import 정렬과 포맷을 개선하세요: 내장 프레임워크(import SwiftUI)를 프로젝트
모듈(import DVDesign)보다 먼저 유지하되 두 그룹 사이에 빈 줄을 추가해 시각적으로 구분하고, 모듈들이 알파벳 순서로 정렬되어
있는지 확인하세요 (참조 심볼: import SwiftUI, import DVDesign).

In `@Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift`:
- Around line 3-4: 현재 파일의 import 목록에서 시스템 프레임워크(import SwiftUI)와 프로젝트 모듈(import
DVDesign) 사이에 빈 줄이 없으니, import SwiftUI를 먼저 두고 그 다음 줄에 빈 줄을 추가한 후 import
DVDesign를 배치하여 시스템 프레임워크와 프로젝트/서드파티 모듈을 분리하고, 전체 import 블록이 알파벳 순으로 정렬되어 있는지
확인하세요 (참조 심볼: import SwiftUI, import DVDesign).
- Around line 10-19: Extract the tuple type into a clear struct (e.g.,
VaultPreviewItem) with fields title: String, date: String, trailingIcon:
DVVaultContainer.TrailingIcon?; replace the private let vaults: [(String,
String, DVVaultContainer.TrailingIcon?)] declaration with a [VaultPreviewItem]
literal; make VaultPreviewItem conform to Identifiable (or add an id: UUID) so
the ForEach that currently iterates over vaults can use each item's id and named
properties (title, date, trailingIcon) instead of tuple indexes; update any
usages in DVVaultContainerPreviewView and the ForEach block to reference the
struct properties.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d3622c2-a3dd-4014-b088-62d7fcaf38b8

📥 Commits

Reviewing files that changed from the base of the PR and between f1fe24b and 024e957.

📒 Files selected for processing (5)
  • Projects/DVDesign/SampleApp/Sources/ContentView.swift
  • Projects/DVDesign/SampleApp/Sources/DVProjectContainerPreviewView.swift
  • Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swift
  • Projects/DVDesign/Sources/Components/DVProjectContainer.swift
  • Projects/DVDesign/Sources/Components/DVVaultContainer.swift

@dlguszoo dlguszoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다👍🏻

yeseonglee and others added 3 commits May 24, 2026 23:25
- 우클릭 감지 NSViewRepresentable / hover stroke / border overlay 제거
- 가변 폭 지원: width 고정 제거, minWidth 200 / Spacer(minLength: 8) / 이름·날짜 줄임표
- isSelected 파라미터 추가 - 선택 시 trailing icon 색을 dv.white로 전환
- 프리뷰를 HSplitView 2-column(list + detail) 구조로 재구성
- 컬럼 단위 controlBackgroundColor + scrollContentBackground(.hidden)으로 배경 정상화
- 우클릭 감지 NSViewRepresentable / hover stroke / border overlay 제거
- 가변 폭 지원: width 고정 제거, minWidth 120 / Spacer(minLength: 8) / 이름 줄임표
- 프리뷰를 HSplitView 2-column(list + detail) 구조로 재구성
- controlBackgroundColor + scrollContentBackground(.hidden)로 배경 정상화
- SampleApp 컴포넌트 owner 교체
@doyeonk429
doyeonk429 force-pushed the design/#13/ui-component-구현 branch from 17a59d1 to 43b5eae Compare May 24, 2026 14:26
@doyeonk429
doyeonk429 requested review from dlguszoo and yeseonglee May 24, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎨 Design UI 디자인 작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design: UI Component 구현

3 participants