-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MOB-945] 체크박스 컴포넌트 구현 #79
base: feature/redesign_bezier
Are you sure you want to change the base?
Changes from all commits
4880de2
f0c589a
0ba12c6
c9bd8de
8060486
03c5b71
112554c
e9513c1
e07ce5d
4f64477
04b83e0
76d524a
0bb52cb
2af0d1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// | ||
// BezierCheckboxSource.swift | ||
// | ||
// | ||
// Created by 구본욱 on 8/16/24. | ||
// | ||
|
||
import SwiftUI | ||
|
||
struct BezierCheckboxSource: View { | ||
private let needStroke: Bool | ||
private let icon: BezierIcon? | ||
private let strokeColor: BezierColor | ||
private let backgroundColor: BezierColor | ||
private let iconColor: BezierColor | ||
|
||
init( | ||
needStroke: Bool, | ||
icon: BezierIcon?, | ||
strokeColor: BezierColor, | ||
backgroundColor: BezierColor, | ||
iconColor: BezierColor | ||
) { | ||
self.needStroke = needStroke | ||
self.icon = icon | ||
self.strokeColor = strokeColor | ||
self.backgroundColor = backgroundColor | ||
self.iconColor = iconColor | ||
} | ||
|
||
var body: some View { | ||
ZStack { | ||
Circle() | ||
.fill(self.backgroundColor.color) | ||
.applyBezierBorder( | ||
shape: Circle(), | ||
style: self.strokeColor.color, | ||
lineWidth: 2, | ||
alignment: .inner | ||
) | ||
|
||
self.icon?.image | ||
.frame(length: 16) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Metric 추가하면 좋을 것 같아요! |
||
.foregroundColor(self.iconColor.color) | ||
} | ||
.frame(length: 20) | ||
.padding(.all, 2) | ||
.compositingGroup() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// | ||
// BezierCheckboxTypes.swift | ||
// | ||
// | ||
// Created by 구본욱 on 8/23/24. | ||
// | ||
|
||
import Foundation | ||
|
||
// MARK: - BezierCheckboxColor | ||
public enum BezierCheckboxColor { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Color, Checked 는 BezierCheckbox 내부에서만 사용하고 있어서 안으로 들어가면 좋을 것 같아요! |
||
case green | ||
case blue | ||
} | ||
|
||
// MARK: - BezierCheckboxChecked | ||
public enum BezierCheckboxChecked { | ||
case checked | ||
case unchecked | ||
case indeterminate | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,217 @@ | ||||||
// | ||||||
// BezierPrimaryCheckbox.swift | ||||||
// | ||||||
// | ||||||
// Created by 구본욱 on 8/23/24. | ||||||
// | ||||||
|
||||||
import SwiftUI | ||||||
|
||||||
// MARK: - Metric | ||||||
private enum Metric { | ||||||
static let minHeight: CGFloat = 40 | ||||||
static let labelTop: CGFloat = 11 | ||||||
} | ||||||
|
||||||
/// 최소 1가지 이상의 옵션을 선택 또는 해제할 수 있는 컨트롤 컴포넌트로, 일반적으로 사용하는 체크박스 아이템입니다. | ||||||
// MARK: - BezierPrimaryCheckbox | ||||||
public struct BezierPrimaryCheckbox<Nested>: View where Nested: View { | ||||||
public typealias Color = BezierCheckboxColor | ||||||
public typealias Checked = BezierCheckboxChecked | ||||||
public typealias NestedBuilder = () -> Nested | ||||||
|
||||||
@Environment(\.isEnabled) var isEnabled | ||||||
|
||||||
private let label: AttributedString? | ||||||
private let color: Color | ||||||
private let checked: Checked | ||||||
private let showRequired: Bool | ||||||
private let nestedBuilder: NestedBuilder | ||||||
|
||||||
/// Nested가 존재하는 체크박스를 생성합니다. | ||||||
/// - Parameters: | ||||||
/// - label: 체크박스에 함께 표기될 텍스트를 지정합니다. | ||||||
/// - color: 체크박스 소스가 표기될 색상을 지정합니다. blue와 green을 지정할 수 있습니다. | ||||||
/// - checked: 체크박스의 체크 상태를 지정합니다. checked, unchecked, indeterminate를 지정할 수 있습니다. | ||||||
/// - showRequired: * 표시(Asterisk)를 사용해서 필수 항목임을 표현할지 지정합니다. | ||||||
/// - nestedBuilder: 체크박스의 하위 영역에 표시될 내용을 지정하는 뷰를 생성합니다. | ||||||
public init( | ||||||
label: String?, | ||||||
color: Color, | ||||||
checked: Checked, | ||||||
showRequired: Bool, | ||||||
@ViewBuilder nestedBuilder: @escaping NestedBuilder | ||||||
) { | ||||||
if let label { | ||||||
self.label = AttributedString(label) | ||||||
} else { | ||||||
self.label = nil | ||||||
} | ||||||
self.color = color | ||||||
self.checked = checked | ||||||
self.showRequired = showRequired | ||||||
self.nestedBuilder = nestedBuilder | ||||||
} | ||||||
|
||||||
/// Nested가 없는 체크박스를 생성합니다. | ||||||
/// - Parameters: | ||||||
/// - label: 체크박스에 함께 표기될 텍스트를 지정합니다. | ||||||
/// - color: 체크박스 소스가 표기될 색상을 지정합니다. blue와 green을 지정할 수 있습니다. | ||||||
/// - checked: 체크박스의 체크 상태를 지정합니다. checked, unchecked, indeterminate를 지정할 수 있습니다. | ||||||
/// - showRequired: * 표시(Asterisk)를 사용해서 필수 항목임을 표현할지 지정합니다. | ||||||
public init( | ||||||
label: String?, | ||||||
color: Color, | ||||||
checked: Checked, | ||||||
showRequired: Bool | ||||||
) where Nested == EmptyView { | ||||||
if let label { | ||||||
self.label = AttributedString(label) | ||||||
} else { | ||||||
self.label = nil | ||||||
} | ||||||
self.color = color | ||||||
self.checked = checked | ||||||
self.showRequired = showRequired | ||||||
self.nestedBuilder = { EmptyView() } | ||||||
} | ||||||
|
||||||
/// Nested가 존재하는 체크박스를 생성합니다. | ||||||
/// - Parameters: | ||||||
/// - label: 체크박스에 함께 표기될 AttributedString을 지정합니다. | ||||||
/// - color: 체크박스 소스가 표기될 색상을 지정합니다. blue와 green을 지정할 수 있습니다. | ||||||
/// - checked: 체크박스의 체크 상태를 지정합니다. checked, unchecked, indeterminate를 지정할 수 있습니다. | ||||||
/// - showRequired: * 표시(Asterisk)를 사용해서 필수 항목임을 표현할지 지정합니다. | ||||||
/// - nestedBuilder: 체크박스의 하위 영역에 표시될 내용을 지정하는 뷰를 생성합니다. | ||||||
public init( | ||||||
label: AttributedString?, | ||||||
color: Color, | ||||||
checked: Checked, | ||||||
showRequired: Bool, | ||||||
@ViewBuilder nestedBuilder: @escaping NestedBuilder | ||||||
) { | ||||||
self.label = label | ||||||
self.color = color | ||||||
self.checked = checked | ||||||
self.showRequired = showRequired | ||||||
self.nestedBuilder = nestedBuilder | ||||||
} | ||||||
|
||||||
/// Nested가 없는 체크박스를 생성합니다. | ||||||
/// - Parameters: | ||||||
/// - label: 체크박스에 함께 표기될 AttributedString을 지정합니다. | ||||||
/// - color: 체크박스 소스가 표기될 색상을 지정합니다. blue와 green을 지정할 수 있습니다. | ||||||
/// - checked: 체크박스의 체크 상태를 지정합니다. checked, unchecked, indeterminate를 지정할 수 있습니다. | ||||||
/// - showRequired: * 표시(Asterisk)를 사용해서 필수 항목임을 표현할지 지정합니다. | ||||||
public init( | ||||||
label: AttributedString?, | ||||||
color: Color, | ||||||
checked: Checked, | ||||||
showRequired: Bool | ||||||
) where Nested == EmptyView { | ||||||
self.label = label | ||||||
self.color = color | ||||||
self.checked = checked | ||||||
self.showRequired = showRequired | ||||||
self.nestedBuilder = { EmptyView() } | ||||||
} | ||||||
|
||||||
public var body: some View { | ||||||
VStack(spacing: 0) { | ||||||
HStack(alignment: .top, spacing: 0) { | ||||||
ZStack(alignment: .center) { | ||||||
BezierCheckboxSource( | ||||||
needStroke: self.sourceNeedStroke, | ||||||
icon: self.sourceIcon, | ||||||
strokeColor: self.sourceStrokeColor, | ||||||
backgroundColor: self.sourceBackgroundColor, | ||||||
iconColor: self.sourceIconColor | ||||||
) | ||||||
} | ||||||
.frame(length: Metric.minHeight) | ||||||
|
||||||
HStack(alignment: .center, spacing: 0) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
채널 엑스에서는 spacing 0이 들어가는 경우가 많아서 이때마다 Metric에 추가하기 어려워서 0 일 때만 .zero로 구분해주자 했었어요. 이 컨벤션을 베지어에서도 가져갈지 논의 해보면 좋을 것 같아요 |
||||||
Text(self.label ?? "") | ||||||
.font(BezierFont.caption1Regular.font) | ||||||
.lineLimit(nil) | ||||||
|
||||||
if self.showRequired { | ||||||
Text("*") | ||||||
.font(BezierFont.caption1Regular.font) | ||||||
.foregroundColor(BezierColor.fgRedNormal.color) | ||||||
} | ||||||
|
||||||
Spacer() | ||||||
} | ||||||
.padding(.vertical, Metric.labelTop) | ||||||
} | ||||||
|
||||||
VStack(alignment: .leading, spacing: 0) { | ||||||
self.nestedBuilder() | ||||||
}.padding(.leading, Metric.minHeight) | ||||||
} | ||||||
.frame(minHeight: Metric.minHeight) | ||||||
.compositingGroup() | ||||||
.applyDisabledStyle() | ||||||
} | ||||||
} | ||||||
|
||||||
extension BezierPrimaryCheckbox { | ||||||
private var sourceBackgroundColor: BezierColor { | ||||||
switch self.checked { | ||||||
case .checked, .indeterminate: | ||||||
return self.color == .blue ? .primaryBgNormal : .bgGreenNormal | ||||||
case .unchecked: | ||||||
return self.isEnabled ? .bgWhiteNormal : .bgBlackDark | ||||||
} | ||||||
} | ||||||
|
||||||
private var sourceNeedStroke: Bool { | ||||||
return self.checked == .unchecked && self.isEnabled | ||||||
} | ||||||
|
||||||
private var sourceStrokeColor: BezierColor { | ||||||
guard self.sourceNeedStroke else { return .bgWhiteAlphaTransparent } | ||||||
|
||||||
return .bgBlackDark | ||||||
} | ||||||
|
||||||
private var sourceIcon: BezierIcon? { | ||||||
switch self.checked { | ||||||
case .checked: | ||||||
return .checkBold | ||||||
case .unchecked: | ||||||
return nil | ||||||
case .indeterminate: | ||||||
return .hyphenBold | ||||||
} | ||||||
} | ||||||
|
||||||
private var sourceIconColor: BezierColor { | ||||||
return self.checked == .unchecked ? .bgWhiteAlphaTransparent : .fgAbsoluteWhiteDark | ||||||
} | ||||||
} | ||||||
|
||||||
#Preview { | ||||||
VStack { | ||||||
BezierPrimaryCheckbox( | ||||||
label: "Hello", | ||||||
color: .blue, | ||||||
checked: .indeterminate, | ||||||
showRequired: true | ||||||
) { | ||||||
BezierSecondaryCheckbox( | ||||||
label: "Secondary", | ||||||
color: .blue, | ||||||
checked: false | ||||||
) | ||||||
} | ||||||
BezierPrimaryCheckbox( | ||||||
label: "Hello", | ||||||
color: .green, | ||||||
checked: .indeterminate, | ||||||
showRequired: true | ||||||
) | ||||||
} | ||||||
.padding(.horizontal, 20) | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// | ||
// BezierSecondaryCheckbox.swift | ||
// | ||
// | ||
// Created by 구본욱 on 8/23/24. | ||
// | ||
|
||
import SwiftUI | ||
|
||
// MARK: - Metric | ||
private enum Metric { | ||
static let minHeight: CGFloat = 36 | ||
static let labelTop: CGFloat = 9 | ||
} | ||
|
||
/// 최소 1가지 이상의 옵션을 선택 또는 해제할 수 있는 컨트롤 컴포넌트입니다. 위계상 primary 에 속할 때만 사용해야하며 단독으로 사용할 수 없고, Indent 되어 표시됩니다. | ||
// MARK: - BezierSecondaryCheckbox | ||
public struct BezierSecondaryCheckbox: View { | ||
public typealias Color = BezierCheckboxColor | ||
|
||
private let label: AttributedString? | ||
private let color: Color | ||
private let checked: Bool | ||
|
||
/// BezierPrimaryCheckbox의 하위(Nested)에 추가될 수 있는 서브 체크박스를 생성합니다. | ||
/// - Parameters: | ||
/// - label: 체크박스에 함께 표기될 텍스트를 지정합니다. | ||
/// - color: 체크박스 소스가 표기될 색상을 지정합니다. blue와 green을 지정할 수 있습니다. | ||
/// - checked: 체크박스의 체크 상태를 지정합니다. | ||
public init(label: String?, color: Color, checked: Bool) { | ||
if let label { | ||
self.label = AttributedString(label) | ||
} else { | ||
self.label = nil | ||
} | ||
self.color = color | ||
self.checked = checked | ||
} | ||
|
||
/// BezierPrimaryCheckbox의 하위(Nested)에 추가될 수 있는 서브 체크박스를 생성합니다. | ||
/// - Parameters: | ||
/// - label: 체크박스에 함께 표기될 AttributedString를 지정합니다. | ||
/// - color: 체크박스 소스가 표기될 색상을 지정합니다. blue와 green을 지정할 수 있습니다. | ||
/// - checked: 체크박스의 체크 상태를 지정합니다. | ||
public init(label: AttributedString?, color: Color, checked: Bool) { | ||
self.label = label | ||
self.color = color | ||
self.checked = checked | ||
} | ||
|
||
public var body: some View { | ||
HStack(alignment: .top, spacing: 0) { | ||
ZStack(alignment: .center) { | ||
BezierCheckboxSource( | ||
needStroke: false, | ||
icon: .checkBold, | ||
strokeColor: .bgWhiteAlphaTransparent, | ||
backgroundColor: .bgWhiteAlphaTransparent, | ||
iconColor: self.sourceIconColor | ||
) | ||
} | ||
.frame(length: Metric.minHeight) | ||
|
||
Text(self.label ?? "") | ||
.font(BezierFont.caption1Regular.font) | ||
.lineLimit(nil) | ||
.padding(.vertical, Metric.labelTop) | ||
|
||
Spacer() | ||
} | ||
.frame(minHeight: Metric.minHeight) | ||
.compositingGroup() | ||
.applyDisabledStyle() | ||
} | ||
} | ||
|
||
extension BezierSecondaryCheckbox { | ||
private var sourceIconColor: BezierColor { | ||
if self.checked { | ||
return self.color == .blue ? .primaryBgNormal : .fgGreenNormal | ||
} else { | ||
return .fgBlackDark | ||
} | ||
} | ||
} | ||
|
||
#Preview { | ||
BezierSecondaryCheckbox( | ||
label: "hello", | ||
color: .green, | ||
checked: true | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요거 사용처가 있을까요?