Skip to content

Design/#14 - DVRadioButton/Group 컴포넌트 구현#15

Open
doyeonk429 wants to merge 3 commits into
developfrom
design/#14
Open

Design/#14 - DVRadioButton/Group 컴포넌트 구현#15
doyeonk429 wants to merge 3 commits into
developfrom
design/#14

Conversation

@doyeonk429
Copy link
Copy Markdown
Contributor

@doyeonk429 doyeonk429 commented May 21, 2026

✨ What’s this PR?

📌 관련 이슈 (Related Issue)


🧶 주요 변경 내용 (Summary)

  • DVFont.swift의 토큰 네이밍 컨벤션 ({category}{Size} / {category}{Size}{Weight})
  • View+DVFont.swift의 line-height 적용 방식 (.lineSpacing + 수직 padding 보정) Projects/DVDesign/Sources/Components/RadioButton/ 하위에 라디오 버튼 컴포넌트 2종과 SampleApp 프리뷰 추가.

컴포넌트

  • DVRadioButton<Label> — 16pt 원형 인디케이터 + 라벨, Default/Selected 2상태
  • 텍스트 라벨 편의 init: DVRadioButton("Dev", isSelected:, action:)DVFont.bodyMD + DVColor.gray900 자동 적용
  • 커스텀 라벨 init: @ViewBuilder label 클로저로 아이콘/혼합 콘텐츠 지원
  • DVRadioButtonGroup<Value: Hashable> — 가로 정렬, 단일 선택 바인딩
  • Size 변형: xs(spacing 8 / minWidth 180), sm(20 / 330), md(56 / 380)
  • Item(value:title:) Identifiable nested struct로 옵션 정의

macOS 인터랙션

  • Hit target 24pt 확대 (시각은 16pt 원 유지) — 패딩된 HStack 전체(원/갭/텍스트 모두) 클릭 가능
  • 키보드: Tab 포커스, ←/→ 그룹 내 라디오 이동(NSMatrix 규약, wrap 없음), Space 활성화
  • 시스템 파란 focus 링 비활성화(.focusEffectDisabled()), hover 효과 미적용 — native NSButton 라디오 외관과 일치
  • VoiceOver: .isButton + isSelected.isSelected 트레이트

문서화

  • Swift DocC 한국어 상세 주석 — Xcode Quick Help에서 시각 상태 표, 인터랙션 가이드, 코드 예제 즉시 열람 가능
  • 토큰/타입 간 심볼 링크(DVColor/vaultGreen, DVFont/bodyMD)로 크로스 레퍼런스

SampleApp

  • RadioButtonPreviewView 신설 — Default/Selected/Interactive + XS/SM/MD 6 케이스
  • ContentView 라우팅: 도연 섹션의 DVRadioButton/DVRadioButtonGroup 항목 → 실제 프리뷰 화면

📸 스크린샷 (Optional)

2026-05-21.11.20.58.mov

🧪 테스트 / 검증 내역

  • tuist generate 정상 완료
  • xcodebuild -scheme DVDesign 빌드 성공
  • xcodebuild -scheme DVDesignSampleApp 빌드 성공
  • 6개 #Preview 케이스(DVRadioButton 3 + DVRadioButtonGroup 3) Xcode Canvas 렌더링 확인
  • SampleApp 실행 → ContentView 사이드바의 DVRadioButton/DVRadioButtonGroup 진입 시 인터랙션(마우스 클릭/Tab/화살표 키/Space) 정상 동작
  • My Mac, macOS 14+ 환경에서 정상 동작

💬 기타 공유 사항 & 🙇🏻‍♀️ 리뷰 가이드

  • DVRadioButton.swift
    • Button 없이 HStack + .onTapGesture + .onKeyPress(.space) 조합으로 hit target을 텍스트 영역까지 일관되게 확장한 부분
  • DVRadioButtonGroup.swift
    • @FocusState<Value?> + .onKeyPress(.leftArrow/.rightArrow) 기반 화살표 키 네비게이션의 selection/focus 동기화 흐름(moveSelection(by:))

Summary by CodeRabbit

  • New Features
    • 라디오버튼 컴포넌트 추가: 단일 선택 기능 및 선택/미선택 상태 시각화
    • 라디오버튼 그룹 컴포넌트 추가: 여러 라디오버튼을 그룹화하여 한 번에 관리
    • 3가지 크기(XS/SM/MD) 레이아웃 옵션 지원
    • 키보드 네비게이션 및 접근성 기능 구현

Review Change Stack

- DVRadioButton: 16pt 원형 인디케이터, 기본/선택 상태, 텍스트 라벨 편의 init
- DVRadioButtonGroup: XS/SM/MD 사이즈 변형(spacing 8/20/56, minWidth 180/330/380)
- macOS 키보드 네비게이션: Tab 포커스, ←/→ 그룹 내 이동, Space 활성화
- Hit target 24pt 확대(시각은 16pt 유지), 시스템 포커스 링 비활성화
- DVColor 토큰만 사용, Swift DocC 상세 주석 작성
DVRadioButton(Default/Selected/Interactive) 및 DVRadioButtonGroup(XS/SM/MD)
케이스를 ContentView 라우팅에 연결
@doyeonk429 doyeonk429 self-assigned this May 21, 2026
@doyeonk429 doyeonk429 linked an issue May 21, 2026 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Warning

Rate limit exceeded

@doyeonk429 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 52 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 749604b2-c230-4c8b-990f-5694f381a6ab

📥 Commits

Reviewing files that changed from the base of the PR and between 18eb7ba and 4dfeae8.

📒 Files selected for processing (3)
  • Projects/DVDesign/SampleApp/Sources/RadioButtonPreviewView.swift
  • Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift
  • Projects/DVDesign/Sources/Components/RadioButton/DVRadioButtonGroup.swift

Walkthrough

DVRadioButton과 DVRadioButtonGroup 컴포넌트를 신규 구현하고 샘플 앱에 통합했습니다. 라디오 버튼은 16pt 인디케이터, 탭/키보드 상호작용, 포커싱을 지원하며, 그룹은 화살표 키 네비게이션과 크기 제어를 제공합니다.

Changes

라디오 버튼 컴포넌트 개발 및 통합

레이어 / 파일 요약
DVRadioButton 컴포넌트
Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift
isSelected 상태에 따라 16pt 원형 인디케이터(선택 시 흰 점)를 렌더링하고, 탭과 Space 키 입력으로 action을 호출합니다. contentShape(Rectangle())로 탭 영역을 확장하고, macOS 포커싱(focusable(true))과 접근성 트레이트를 적용합니다. Label == Text extension으로 String 편의 생성자를 제공하며, 정적/인터랙티브 프리뷰를 포함합니다.
DVRadioButtonGroup 컴포넌트 및 API
Projects/DVDesign/Sources/Components/RadioButton/DVRadioButtonGroup.swift
items 배열을 HStack으로 렌더링하고 selection: Binding<Value>로 선택 상태를 동기화합니다. 좌/우 화살표 키로 현재 포커스(또는 선택)를 기준으로 인접 항목을 선택합니다. Item(Identifiable) 타입과 Size(.xs/.sm/.md) enum으로 데이터 및 레이아웃을 제어하며, 세 가지 크기 변형 프리뷰를 제공합니다.
샘플 앱 라우팅 및 프리뷰
Projects/DVDesign/SampleApp/Sources/ContentView.swift, Projects/DVDesign/SampleApp/Sources/RadioButtonPreviewView.swift
ContentView에서 component.name에 따라 RadioButtonPreviewView()로 동적 라우팅합니다. RadioButtonPreviewView는 단일 선택(singleSelected)과 환경별 그룹 선택(xsSelection, smSelection, mdSelection)을 @State로 관리하고, section(title:content:)labeled(caption:content:) 헬퍼 뷰로 공통 레이아웃을 재사용합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 DVRadioButton/Group 컴포넌트 구현이라는 주요 변경 사항을 정확하게 요약하고 있으며, 이슈 #14를 참조하여 명확합니다.
Linked Issues check ✅ Passed 모든 코딩 요구사항을 충족: DVRadioButton(원형 인디케이터, 기본/선택 상태, 라벨 지원) [#14], DVRadioButtonGroup(가로 배치, 단일 선택, Item 구조체) [#14], 디자인 가이드 준수(xs/sm/md 변형, 간격·너비 지정) [#14], 접근성·키보드 네비게이션(포커스, ←/→ 이동, Space) [#14], SampleApp 프리뷰 제공 [#14].
Out of Scope Changes check ✅ Passed 변경사항이 모두 DVRadioButton/Group 컴포넌트 구현 범위 내: ContentView 라우팅 추가는 SampleApp 프리뷰 제공 범위 내이며, 외부 스코프 변경 없습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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/#14

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

12-14: 💤 Low value

중첩 타입 이름 Environment — SwiftUI 표준 심볼과 혼동 우려

이 파일에선 @Environment 프로퍼티 래퍼를 쓰지 않아 컴파일은 무방하지만, SwiftUI의 표준 Environment와 동명이라 추후 같은 파일에 @Environment(\.foo) var foo 같은 코드가 들어오면 의도치 않은 모호성이 생길 수 있어요. Env 정도로 짧게 두는 편이 의도가 더 분명합니다.

♻️ 제안 diff
-    `@State` private var xsSelection: Environment = .dev
-    `@State` private var smSelection: Environment = .staging
-    `@State` private var mdSelection: Environment = .prod
+    `@State` private var xsSelection: Env = .dev
+    `@State` private var smSelection: Env = .staging
+    `@State` private var mdSelection: Env = .prod

-    private enum Environment: Hashable {
+    private enum Env: Hashable {
         case dev, staging, prod
     }

-    private var items: [DVRadioButtonGroup<Environment>.Item] {
+    private var items: [DVRadioButtonGroup<Env>.Item] {
🤖 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/RadioButtonPreviewView.swift` around
lines 12 - 14, The nested enum named Environment conflicts semantically with
SwiftUI's Environment symbol; rename the enum Environment (in
RadioButtonPreviewView.swift) to a less ambiguous name like Env (or RadioEnv)
and update all references (e.g., usages in RadioButtonPreviewView and any
associated initializers or switch/case) to the new identifier, keeping the same
conformance (Hashable) and cases (dev, staging, prod) to avoid behavioral
changes.
Projects/DVDesign/SampleApp/Sources/ContentView.swift (1)

34-42: 💤 Low value

문자열 기반 라우팅 — 타입 안전한 매핑 고려

component.name 문자열 매칭은 오타 한 글자에 조용히 placeholder로 빠지는 구조라 장기적으로는 위험합니다. 컴포넌트 수가 늘어나기 전에 enum 라우트 또는 Component@ViewBuilder destination 핸들을 들고 다니게 하면 더 안전합니다. 지금 단계에선 우선순위 낮은 개선 제안입니다.

🤖 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 34 - 42,
The switch in detailView(for:) relies on string matching of component.name which
is fragile; refactor to a type-safe mapping by adding either an enum (e.g.,
ComponentType) used by Component or a computed `@ViewBuilder` property on
Component (e.g., var destinationView: some View) and update detailView(for:) to
use that instead of string literals; locate Component, the detailView(for:)
function, RadioButtonPreviewView and ComponentPlaceholderView and implement the
enum or computed view property, then replace the switch over component.name with
a switch on the enum or simply return component.destinationView so new
components won’t silently fall back to placeholders due to typos.
Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift (1)

93-109: 💤 Low value

focusable(true)focusable() 정리

기본 인자가 true라 명시는 군더더기에요. 작은 정리 한 줄 제안드립니다.

♻️ 제안 diff
-        .focusable(true)
+        .focusable()
🤖 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/RadioButton/DVRadioButton.swift` around
lines 93 - 109, The call to focusable(true) in DVRadioButton.body is redundant
because the default parameter is true; update the view modifier to use
focusable() instead to clean up the code (locate the focusable(true) inside the
DVRadioButton struct's body where HStack modifiers are applied and replace it
with focusable()).
🤖 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/RadioButtonPreviewView.swift`:
- Line 62: 캡션 텍스트가 잘못 표기되어 있습니다: in the labeled(...) call the caption "spacing
28" should read "spacing 56" to match DVRadioButtonGroup.Size.md.spacing (and
the case in DVRadioButtonGroup.swift). Update the labeled("MD (spacing 28)")
invocation to labeled("MD (spacing 56)") so the preview text accurately reflects
DVRadioButtonGroup.Size.md.spacing.

In `@Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift`:
- Around line 125-154: Replace the module‑level access modifier on the extension
with explicit access on the initializer: remove "public" from the "public
extension DVRadioButton where Label == Text" declaration and mark the
init(_:isSelected:action:) initializer itself as public (i.e., add the public
modifier to that init in DVRadioButton where Label == Text) so the access
control follows the guideline of declaring visibility on each member rather than
on the extension.

In `@Projects/DVDesign/Sources/Components/RadioButton/DVRadioButtonGroup.swift`:
- Around line 120-186: Remove the `public` modifier from the extension
declaration and instead explicitly mark each exposed type/member as public: add
`public` to `struct Item` and its members (`id`, `title`, and `init`), and add
`public` to `enum Size` and its public computed properties (`spacing`,
`minWidth`) so the visibility is explicit on each declaration within
`DVRadioButtonGroup`.

---

Nitpick comments:
In `@Projects/DVDesign/SampleApp/Sources/ContentView.swift`:
- Around line 34-42: The switch in detailView(for:) relies on string matching of
component.name which is fragile; refactor to a type-safe mapping by adding
either an enum (e.g., ComponentType) used by Component or a computed
`@ViewBuilder` property on Component (e.g., var destinationView: some View) and
update detailView(for:) to use that instead of string literals; locate
Component, the detailView(for:) function, RadioButtonPreviewView and
ComponentPlaceholderView and implement the enum or computed view property, then
replace the switch over component.name with a switch on the enum or simply
return component.destinationView so new components won’t silently fall back to
placeholders due to typos.

In `@Projects/DVDesign/SampleApp/Sources/RadioButtonPreviewView.swift`:
- Around line 12-14: The nested enum named Environment conflicts semantically
with SwiftUI's Environment symbol; rename the enum Environment (in
RadioButtonPreviewView.swift) to a less ambiguous name like Env (or RadioEnv)
and update all references (e.g., usages in RadioButtonPreviewView and any
associated initializers or switch/case) to the new identifier, keeping the same
conformance (Hashable) and cases (dev, staging, prod) to avoid behavioral
changes.

In `@Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift`:
- Around line 93-109: The call to focusable(true) in DVRadioButton.body is
redundant because the default parameter is true; update the view modifier to use
focusable() instead to clean up the code (locate the focusable(true) inside the
DVRadioButton struct's body where HStack modifiers are applied and replace it
with focusable()).
🪄 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: cdf90ea8-04af-45c3-98d5-faad73c4113f

📥 Commits

Reviewing files that changed from the base of the PR and between 12c8009 and 18eb7ba.

📒 Files selected for processing (4)
  • Projects/DVDesign/SampleApp/Sources/ContentView.swift
  • Projects/DVDesign/SampleApp/Sources/RadioButtonPreviewView.swift
  • Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift
  • Projects/DVDesign/Sources/Components/RadioButton/DVRadioButtonGroup.swift

Comment thread Projects/DVDesign/SampleApp/Sources/RadioButtonPreviewView.swift Outdated
Comment thread Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift Outdated
Comment thread Projects/DVDesign/Sources/Components/RadioButton/DVRadioButtonGroup.swift Outdated
- public extension 블록 대신 멤버별 접근 제어 명시
- SampleApp MD spacing 캡션 28 → 56 정정
@doyeonk429 doyeonk429 requested review from dlguszoo and yeseonglee May 21, 2026 14:35
@doyeonk429 doyeonk429 added the 🎨 Design UI 디자인 작업 label May 21, 2026
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: DVRadioButton/Group 개발

1 participant