Design/#17 - DVTextField/DVTextContainer 컴포넌트 구현#20
Conversation
- DVTextField/DVTextContainer 등 입력·표시 계열 컴포넌트가 공유하는 사이즈 enum - xs/sm/md/lg 4단계, 각각 180/330/380/700pt width 매핑 (Figma 스펙) - Foundations/Sizing 디렉토리로 분리하여 향후 다른 컴포넌트 확장 대비
- DVTextField: 시스템 SwiftUI TextField + .textFieldStyle(.plain) + .tint(.vaultGreen) 조합 - DVTextContainer: read-only 박스 + 인터랙티브 액세서리 지원 - 편의 init 3종 — secured(마스킹+눈 토글), copyable(복사), onTap:icon:(커스텀 단일) - .textSelection(.enabled)로 마우스 드래그 선택·복사 지원 - 액세서리 없음: 좌우 8pt 대칭 padding / 있음: trailing 4pt - Swift DocC 한국어 상세 주석
- TextFieldPreviewView: Placeholder/Active 상태 + XS/SM/MD/LG 사이즈 케이스 - TextContainerPreviewView: Filled/Empty + Interactive(secured/copyable/onTap/멀티 액세서리) + 사이즈 - 가로 스크롤 동작 확인용 긴 텍스트 케이스 포함 - copyable 케이스는 NSPasteboard 직접 호출 + 피드백 라벨 (컨테이너별 state 분리) - onTap 케이스는 SwiftUI openURL environment로 실제 브라우저 호출 - ContentView에 DVTextField/DVTextContainer 라우팅 추가
Walkthrough텍스트 입출력 컴포넌트 시스템을 추가합니다. 공유 사이징 토큰 DVComponentSize를 정의한 뒤, SwiftUI 기반 DVTextField(입력)와 DVTextContainer(표시)를 구현하고, 각각의 상태/상호작용을 보여주는 프리뷰 뷰를 SampleApp에 작성하며, ContentView에서 라우팅하도록 통합합니다. Changes텍스트 입출력 컴포넌트 시스템
Sequence Diagram(s)(지표 기준: 새로운 컴포넌트 추가, 단순한 초기화 및 스타일 적용으로 제어 흐름이 자명해 다이어그램 생략) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
Projects/DVDesign/SampleApp/Sources/ContentView.swift (1)
3-4: 💤 Low value임포트 그룹화 개선 제안
시스템 프레임워크(
SwiftUI)와 로컬 모듈(DVDesign) 사이에 빈 줄을 추가하면 가독성이 향상됩니다.♻️ 제안하는 수정
import SwiftUI + import DVDesign코딩 가이드라인 기준: "모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요. (내장 프레임워크 먼저, 빈 줄로 서드파티 구분)"
🤖 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 3 - 4, Add a blank line between the system framework import and the local module import so imports are grouped and more readable: ensure "import SwiftUI" appears first (system/framework imports), then a blank line, then "import DVDesign" (local/third-party), and verify imports remain alphabetized within their groups.Projects/DVDesign/Sources/Foundations/Sizing/DVComponentSize.swift (1)
3-3: ⚡ Quick winFoundations 토큰 파일의 UI 프레임워크 의존성을 줄이세요.
DVComponentSize는CGFloat만 사용하므로SwiftUI대신CoreGraphics를 import하면 레이어 결합도를 낮출 수 있습니다.♻️ 제안 diff
-import SwiftUI +import CoreGraphics🤖 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/Foundations/Sizing/DVComponentSize.swift` at line 3, DVComponentSize currently imports SwiftUI unnecessarily; replace the SwiftUI dependency with CoreGraphics since DVComponentSize only uses CGFloat. Open the file containing DVComponentSize, remove the "import SwiftUI" statement and add "import CoreGraphics" instead, and verify there are no SwiftUI types referenced elsewhere in the DVComponentSize declaration or related helpers.Projects/DVDesign/Sources/Components/DVTextContainer.swift (1)
118-139: ⚡ Quick win
body의 중첩 레이아웃을private var/func로 분리해 주세요.현재
body에 텍스트 스크롤/스타일/컨테이너 스타일이 한 번에 모여 있어 변경 지점 추적이 어렵습니다.textContent,containerBackground같은 private 멤버로 분리하면 유지보수가 쉬워집니다.As per coding guidelines "var body 안에 중첩 레이아웃이 직접 구현되어 있으면 extension의 private var/func로 분리를 제안하세요."♻️ 제안 diff
public var body: some View { - // spacing 8 — 텍스트(또는 가로 스크롤된 콘텐츠)와 액세서리 사이 최소 간격. - // 텍스트가 오버플로우되어 ScrollView 우측 끝까지 스크롤되더라도 이 8pt가 보장됨. HStack(spacing: 8) { - ScrollView(.horizontal, showsIndicators: false) { - Text(text) - .font(DVFont.bodyLG.font) - .foregroundStyle(Color.dv(.gray900)) - .lineLimit(1) - .fixedSize(horizontal: true, vertical: false) - .textSelection(.enabled) - } + textContent accessories() } .padding(.leading, 8) .padding(.trailing, trailingPadding) .frame(width: size.width, height: 28, alignment: .leading) - .background { - RoundedRectangle(cornerRadius: 6) - .fill(Color.dv(.gray300)) - } + .background { containerBackground } } + +private var textContent: some View { + ScrollView(.horizontal, showsIndicators: false) { + Text(text) + .font(DVFont.bodyLG.font) + .foregroundStyle(Color.dv(.gray900)) + .lineLimit(1) + .fixedSize(horizontal: true, vertical: false) + .textSelection(.enabled) + } +} + +private var containerBackground: some View { + RoundedRectangle(cornerRadius: 6) + .fill(Color.dv(.gray300)) +}🤖 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/DVTextContainer.swift` around lines 118 - 139, Split the nested layout inside the public var body into small private members: extract the ScrollView+Text block into a private var/func named textContent (preserve Text modifiers: .font(DVFont.bodyLG.font), .foregroundStyle(Color.dv(.gray900)), .lineLimit(1), .fixedSize(horizontal: true, vertical: false), .textSelection(.enabled)), extract the RoundedRectangle background into a private var/func containerBackground, and compose the HStack (HStack(spacing: 8) { textContent; accessories() }) plus paddings/ frame into a private var/func (e.g., mainContent) so body simply returns mainContent.background(containerBackground()); keep references to accessories(), trailingPadding, and size unchanged. Ensure accessibility and modifiers are preserved exactly when moving code to the private members.Projects/DVDesign/SampleApp/Sources/TextFieldPreviewView.swift (1)
14-63: ⚡ Quick win
body의 중첩 레이아웃은 섹션 단위private var/func로 분리해 주세요.Line 14-35의 본문이 길고, Line 42-63 하위뷰 헬퍼가 본 타입 내부에 있어 읽기 흐름이 무거워졌습니다.
statusSection,sizeSection같은private var로 쪼개고, 헬퍼는extension TextFieldPreviewView로 이동하면 유지보수가 쉬워집니다.As per coding guidelines "
var body안에 중첩 레이아웃이 직접 구현되어 있으면extension의private var/func로 분리를 제안하세요." 및 "하위뷰가extension밖에 선언되어 있으면 이동을 제안하세요."🤖 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/TextFieldPreviewView.swift` around lines 14 - 63, Extract the large nested layout inside var body into separate private computed properties like private var statusSection and private var sizeSection returning some View, move the helper view builders section(...) and labeled(...) out of the main type into an extension TextFieldPreviewView as private functions, and update body to compose using statusSection and sizeSection; this keeps the DVTextField rows and VStack structure unchanged but relocates section and labeled to the extension and replaces the inline blocks in body with the new private vars.Projects/DVDesign/SampleApp/Sources/TextContainerPreviewView.swift (2)
15-140: ⚡ Quick win프리뷰 본문과 하위뷰 헬퍼를 extension 기반으로 분리해 주세요.
Line 15-113의 레이아웃이 길고, Line 120-140 하위뷰 헬퍼가 본 타입 내부에 있어 탐색 비용이 큽니다. 섹션별
private var/func분리 +extension TextContainerPreviewView이동을 권장합니다.As per coding guidelines "
var body안에 중첩 레이아웃이 직접 구현되어 있으면extension의private var/func로 분리를 제안하세요." 및 "하위뷰가extension밖에 선언되어 있으면 이동을 제안하세요."🤖 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/TextContainerPreviewView.swift` around lines 15 - 140, The body contains large nested layout and helper methods (section(_:content:), labeled(_:content:), copyToClipboard(_:)) defined inside the main type; extract the helper view builders and clipboard helper into a private extension of TextContainerPreviewView to reduce noise in var body. Move the implementations of section(title:content:), labeled(_:content:) and copyToClipboard(_:) out of the main type body and into an extension TextContainerPreviewView { private func/var ... } (keeping their signatures and access control), and update any references in var body to use these moved helpers.
10-11: ⚡ Quick winBool 상태명은
is접두사로 통일해 주세요.
manualRevealed,securedRevealed는 Bool 의미가 즉시 드러나지 않습니다.isManualRevealed,isSecuredRevealed로 바꾸면 가독성이 좋아집니다.As per coding guidelines "Bool 프로퍼티에
is,has,can등의 접두사가 있는지 확인하세요."Also applies to: 52-53, 72-73, 83-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/TextContainerPreviewView.swift` around lines 10 - 11, Rename the Bool `@State` properties manualRevealed and securedRevealed to isManualRevealed and isSecuredRevealed respectively, and update all references/usages (bindings, view logic, toggles) to the new names; also apply the same is-prefixed rename to the other Bool properties noted in the review (the ones referenced at 52-53, 72-73, 83-85) so all boolean state names follow the is/has/can convention and preserve existing behavior.
🤖 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/TextContainerPreviewView.swift`:
- Line 13: The stored URL is force-unwrapped via the externalURL constant which
risks crashes; change externalURL to be Optional (e.g., private let externalURL:
URL?) and then safely unwrap it at its usage sites (the preview/initialization
code that constructs the preview) using guard let / if let or provide a sensible
fallback (??) before passing into TextContainerPreviewView or any initializer;
update references to externalURL (and any preview construction) to handle the
optional safely rather than using !.
In `@Projects/DVDesign/SampleApp/Sources/TextFieldPreviewView.swift`:
- Around line 7-25: The preview state variables are being reused across multiple
DVTextField samples (e.g., emptyValue is bound at lines where the placeholder,
focus and other samples are rendered), causing interactions to bleed between
examples; introduce distinct `@State` properties for each preview instance (e.g.,
emptyPlaceholderValue, focusValue, activeValue, xsValue, smValue, mdValue,
lgValue) and update the DVTextField bindings to use those new state names so
each labeled/section sample has its own independent state and no longer
interferes with others.
In `@Projects/DVDesign/Sources/Components/DVTextContainer.swift`:
- Around line 195-237: The copyable initializer's accessory Button (public
init(copyable:text:size:onCopy:)) has no accessibility label, making VoiceOver
unclear; add an explicit accessibility label to the Button (or its Image) such
as .accessibilityLabel(Text("Copy")) or a localized string to describe the
action, and apply the same pattern to the analogous secured initializer's
reveal/toggle Button (the Button that toggles isRevealed / shows
"eye"/"eye.slash") so screen readers announce the purpose.
---
Nitpick comments:
In `@Projects/DVDesign/SampleApp/Sources/ContentView.swift`:
- Around line 3-4: Add a blank line between the system framework import and the
local module import so imports are grouped and more readable: ensure "import
SwiftUI" appears first (system/framework imports), then a blank line, then
"import DVDesign" (local/third-party), and verify imports remain alphabetized
within their groups.
In `@Projects/DVDesign/SampleApp/Sources/TextContainerPreviewView.swift`:
- Around line 15-140: The body contains large nested layout and helper methods
(section(_:content:), labeled(_:content:), copyToClipboard(_:)) defined inside
the main type; extract the helper view builders and clipboard helper into a
private extension of TextContainerPreviewView to reduce noise in var body. Move
the implementations of section(title:content:), labeled(_:content:) and
copyToClipboard(_:) out of the main type body and into an extension
TextContainerPreviewView { private func/var ... } (keeping their signatures and
access control), and update any references in var body to use these moved
helpers.
- Around line 10-11: Rename the Bool `@State` properties manualRevealed and
securedRevealed to isManualRevealed and isSecuredRevealed respectively, and
update all references/usages (bindings, view logic, toggles) to the new names;
also apply the same is-prefixed rename to the other Bool properties noted in the
review (the ones referenced at 52-53, 72-73, 83-85) so all boolean state names
follow the is/has/can convention and preserve existing behavior.
In `@Projects/DVDesign/SampleApp/Sources/TextFieldPreviewView.swift`:
- Around line 14-63: Extract the large nested layout inside var body into
separate private computed properties like private var statusSection and private
var sizeSection returning some View, move the helper view builders section(...)
and labeled(...) out of the main type into an extension TextFieldPreviewView as
private functions, and update body to compose using statusSection and
sizeSection; this keeps the DVTextField rows and VStack structure unchanged but
relocates section and labeled to the extension and replaces the inline blocks in
body with the new private vars.
In `@Projects/DVDesign/Sources/Components/DVTextContainer.swift`:
- Around line 118-139: Split the nested layout inside the public var body into
small private members: extract the ScrollView+Text block into a private var/func
named textContent (preserve Text modifiers: .font(DVFont.bodyLG.font),
.foregroundStyle(Color.dv(.gray900)), .lineLimit(1), .fixedSize(horizontal:
true, vertical: false), .textSelection(.enabled)), extract the RoundedRectangle
background into a private var/func containerBackground, and compose the HStack
(HStack(spacing: 8) { textContent; accessories() }) plus paddings/ frame into a
private var/func (e.g., mainContent) so body simply returns
mainContent.background(containerBackground()); keep references to accessories(),
trailingPadding, and size unchanged. Ensure accessibility and modifiers are
preserved exactly when moving code to the private members.
In `@Projects/DVDesign/Sources/Foundations/Sizing/DVComponentSize.swift`:
- Line 3: DVComponentSize currently imports SwiftUI unnecessarily; replace the
SwiftUI dependency with CoreGraphics since DVComponentSize only uses CGFloat.
Open the file containing DVComponentSize, remove the "import SwiftUI" statement
and add "import CoreGraphics" instead, and verify there are no SwiftUI types
referenced elsewhere in the DVComponentSize declaration or related helpers.
🪄 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: 68d3e034-94db-4884-964a-e297f986fe08
📒 Files selected for processing (6)
Projects/DVDesign/SampleApp/Sources/ContentView.swiftProjects/DVDesign/SampleApp/Sources/TextContainerPreviewView.swiftProjects/DVDesign/SampleApp/Sources/TextFieldPreviewView.swiftProjects/DVDesign/Sources/Components/DVTextContainer.swiftProjects/DVDesign/Sources/Components/DVTextField.swiftProjects/DVDesign/Sources/Foundations/Sizing/DVComponentSize.swift
yeseonglee
left a comment
There was a problem hiding this comment.
구현하느라 수고하셨습니다~ link 요소까지 넣어서 디테일이 더 추가된 것 같아요
✨ What’s this PR?
📌 관련 이슈 (Related Issue)
🧶 주요 변경 내용 (Summary)
공통 토큰 —
Projects/DVDesign/Sources/Foundations/Sizing/DVComponentSize.swiftxs / sm / md / lg4단계 enum,width프로퍼티로 Figma 스펙 너비 노출 (180 / 330 / 380 / 700pt)DVTextField— 시스템TextField최대 활용TextField+.textFieldStyle(.plain)위에 디자인 토큰만 덧입힘 — IME(한글), 접근성, 키보드 단축키,.onSubmit/.focused/.disabled등모든 시스템 modifier 호환
.tint(Color.dv(.vaultGreen))으로 커서 색만 디자인 시스템에 맞춤prompt:파라미터로 placeholder 색(gray400) 적용gray300유지)DVTextContainer— read-only 표시 + 인터랙티브 액세서리init(_:size:)init(secured:isRevealed:size:)PW_Visability)init(copyable:size:onCopy:)init(_:size:onTap:icon:)init(_:size:accessories:)ScrollView(.horizontal, showsIndicators: false)) — 트랙패드/마우스 휠로 가려진 부분 확인 가능.textSelection(.enabled)— 마우스 드래그로 텍스트 선택 + ⌘+C 복사SampleApp 프리뷰
TextFieldPreviewView— Placeholder/Active 상태 + XS/SM/MD/LG 사이즈TextContainerPreviewView— Filled/Empty + 인터랙티브(secured/copyable/onTap/멀티) + 가로 스크롤 케이스 + 사이즈NSPasteboard호출 + 피드백 라벨(컨테이너별 state 분리)openURLenvironment로 실제 브라우저 호출DVTextField/DVTextContainer연결문서화
DVColor/gray900,DVFont/bodyLG,DVComponentSize/md)📸 스크린샷 (Optional)
2026-05-24.5.03.58.mov
https://youtu.be/pNY_c4QOW0Q
🧪 테스트 / 검증 내역
tuist generate정상 완료xcodebuild -scheme DVDesign빌드 성공xcodebuild -scheme DVDesignSampleApp빌드 성공💬 기타 공유 사항
TextField가 그대로 제공하도록 두고, 시각 속성만 디자인 토큰으로 덧입혔습니다. 호출자는.onSubmit,.focused,.disabled등 모든 SwiftUI modifier를 그대로 사용할 수 있습니다.DVTextContainer(copyable:)는doc.on.doc아이콘과 버튼 styling만 제공하고, 실제NSPasteboard호출은 호출자 책임으로 둬서 DVDesign 모듈이 AppKit에 의존하지 않도록 했습니다.🙇🏻♀️ 리뷰 가이드 (선택)
DVComponentSize.swift: 다른 컴포넌트에서도 재사용할 공통 토큰. 추후fontSize/height등 다른 메트릭이 추가될 때 확장 방향에 대한 의견DVTextField.swift:.textFieldStyle(.plain)+ 외부 시각 wrapping 패턴이 시스템 modifier 호환성을 해치지 않는지 (특히.font/.foregroundStyle은내부에서 강제 적용되는 점)
DVTextContainer.swift: 4가지 init 분기 구조(AnyView기반 편의 init vs generic 기본 init). 그리고 trailing padding 분기(액세서리 유무에 따른 4/8pt)가stored property로 처리되는 방식
.textSelection조합이 의도대로 동작하는지 (SampleApp의 긴 텍스트 케이스 참고)Summary by CodeRabbit
릴리스 노트