Skip to content

Commit da2b2a4

Browse files
committed
[BOOK-347] feat: BKTextButton component 추가
1 parent c2270fa commit da2b2a4

File tree

2 files changed

+226
-33
lines changed

2 files changed

+226
-33
lines changed

src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright © 2025 Booket. All rights reserved
1+
// Copyright © 2026 Booket. All rights reserved
22

33
import BKDesign
44
import SnapKit
@@ -52,6 +52,9 @@ public final class BKButtonTestViewController: UIViewController {
5252
setupButtons(for: .medium)
5353
setupButtons(for: .small)
5454
setupButtons(for: .rounded)
55+
56+
addSectionSeparator()
57+
setupTextButtons()
5558
}
5659

5760
private func setupButtons(for size: BKButtonSize) {
@@ -66,6 +69,39 @@ public final class BKButtonTestViewController: UIViewController {
6669
addIconButton("양쪽 아이콘", style: .primary, size: size, left: BKImage.Icon.apple, right: BKImage.Icon.kakao)
6770
}
6871

72+
// MARK: - Text Button Tests
73+
74+
private func setupTextButtons() {
75+
addSectionHeader("Text Buttons")
76+
77+
addTextButton("텍스트 버튼1", size: .small)
78+
addTextButton("텍스트 버튼2", size: .medium)
79+
addTextButton("텍스트 버튼3", size: .large)
80+
addTextButton("아주 긴 내용의 텍스트 버튼을 만들어봅시다", size: .large)
81+
addDisabledTextButton("비활성화 텍스트", size: .small)
82+
}
83+
84+
private func addTextButton(_ title: String, size: BKButtonSize) {
85+
let button = BKTextButton(title: "[\(size.label)] \(title)", size: size)
86+
containerView.addArrangedSubview(wrapForLeftAlign(button))
87+
}
88+
89+
private func addDisabledTextButton(_ title: String, size: BKButtonSize) {
90+
let button = BKTextButton(title: "[\(size.label)] \(title)", size: size)
91+
button.isDisabled = true
92+
containerView.addArrangedSubview(wrapForLeftAlign(button))
93+
}
94+
95+
/// 텍스트 버튼은 intrinsic size를 사용하므로 왼쪽 정렬 래퍼 필요
96+
private func wrapForLeftAlign(_ view: UIView) -> UIView {
97+
let wrapper = UIView()
98+
wrapper.addSubview(view)
99+
view.snp.makeConstraints {
100+
$0.leading.verticalEdges.equalToSuperview()
101+
}
102+
return wrapper
103+
}
104+
69105
// MARK: - Button Builders
70106

71107
private func addButton(_ title: String, style: BKButtonStyle, size: BKButtonSize) {
@@ -95,41 +131,26 @@ public final class BKButtonTestViewController: UIViewController {
95131
containerView.addArrangedSubview(button)
96132
}
97133

98-
private func setupIndependentButtons() {
99-
let sampleView = UIView()
100-
scrollView.addSubview(sampleView)
101-
sampleView.snp.makeConstraints {
102-
$0.top.equalTo(containerView.snp.bottom).offset(40)
103-
$0.centerX.equalToSuperview()
104-
$0.bottom.lessThanOrEqualToSuperview()
105-
}
106-
107-
let buttons: [BKButton] = [
108-
.primary(title: "[Free] Apple 로그인", size: .large),
109-
.secondary(title: "[Free] Secondary", size: .large),
110-
.tertiary(title: "[Free] Tertiary", size: .large),
111-
.primary(title: "[Free] Apple 로그인", size: .medium),
112-
.secondary(title: "[Free] Secondary", size: .small),
113-
.tertiary(title: "[Free] Tertiary", size: .rounded)
114-
]
115-
116-
buttons[0].leftIcon = BKImage.Icon.apple
117-
buttons[2].rightIcon = BKImage.Icon.kakao
118-
119-
var last: UIView?
120-
for button in buttons {
121-
sampleView.addSubview(button)
122-
button.snp.makeConstraints {
123-
$0.centerX.equalToSuperview()
124-
$0.top.equalTo(last?.snp.bottom ?? sampleView.snp.top).offset(16)
125-
}
126-
last = button
127-
}
134+
// MARK: - Section Helpers
135+
136+
private func addSectionHeader(_ title: String) {
137+
let label = UILabel()
138+
label.text = title
139+
label.font = .systemFont(ofSize: 18, weight: .bold)
140+
label.textColor = .darkGray
141+
containerView.addArrangedSubview(label)
142+
}
143+
144+
private func addSectionSeparator() {
145+
let separator = UIView()
146+
separator.backgroundColor = .systemGray4
147+
separator.snp.makeConstraints { $0.height.equalTo(1) }
148+
containerView.addArrangedSubview(separator)
149+
containerView.setCustomSpacing(24, after: separator)
128150
}
129-
130151
}
131152

132-
153+
// MARK: - BKButtonSize Extension
133154
public extension BKButtonSize {
134155
var label: String {
135156
switch self {
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright © 2025 Booket. All rights reserved
2+
3+
import SnapKit
4+
import UIKit
5+
6+
/// 밑줄이 있는 텍스트 버튼 컴포넌트입니다.
7+
///
8+
/// 링크 스타일의 텍스트 버튼으로, 배경 없이 텍스트와 밑줄만 표시됩니다.
9+
/// `BKButtonGroup`과 함께 사용할 수 있습니다.
10+
public final class BKTextButton: UIControl {
11+
12+
// MARK: - Public Properties
13+
14+
/// 버튼에 표시될 텍스트
15+
public var title: String? {
16+
didSet {
17+
titleLabel.text = title
18+
invalidateIntrinsicContentSize()
19+
}
20+
}
21+
22+
/// 버튼의 비활성화 여부
23+
public var isDisabled: Bool = false {
24+
didSet {
25+
isEnabled = !isDisabled
26+
updateColors()
27+
}
28+
}
29+
30+
/// 버튼 크기 (폰트 크기에 영향)
31+
public var size: BKButtonSize = .small {
32+
didSet {
33+
titleLabel.font = size.font
34+
invalidateIntrinsicContentSize()
35+
}
36+
}
37+
38+
/// 텍스트 및 밑줄 색상 설정
39+
public var foregroundColors: BKButtonColorSet = BKButtonColorSet(
40+
normal: .bkContentColor(.brand),
41+
pressed: .bkContentColor(.brand),
42+
disabled: .bkContentColor(.disable)
43+
) {
44+
didSet {
45+
updateColors()
46+
}
47+
}
48+
49+
// MARK: - Override Properties
50+
51+
public override var isHighlighted: Bool {
52+
didSet {
53+
updateColors()
54+
animatePressedState()
55+
}
56+
}
57+
58+
public override var isEnabled: Bool {
59+
didSet {
60+
updateColors()
61+
}
62+
}
63+
64+
// MARK: - Private Properties
65+
66+
private let titleLabel = UILabel()
67+
private let underlineView = UIView()
68+
69+
private let animationDuration: TimeInterval = 0.15
70+
71+
private var currentState: BKButtonState {
72+
BKButtonState(isEnabled: isEnabled, isHighlighted: isHighlighted)
73+
}
74+
75+
// MARK: - Initialization
76+
77+
public init(title: String? = nil, size: BKButtonSize = .small) {
78+
super.init(frame: .zero)
79+
self.title = title
80+
self.size = size
81+
setupViews()
82+
updateColors()
83+
}
84+
85+
public required init?(coder: NSCoder) {
86+
super.init(coder: coder)
87+
setupViews()
88+
updateColors()
89+
}
90+
91+
// MARK: - Setup
92+
93+
private func setupViews() {
94+
setupTitleLabel()
95+
setupUnderlineView()
96+
}
97+
98+
private func setupTitleLabel() {
99+
addSubview(titleLabel)
100+
101+
titleLabel.text = title
102+
titleLabel.font = size.font
103+
titleLabel.textAlignment = .center
104+
titleLabel.numberOfLines = 1
105+
106+
titleLabel.snp.makeConstraints {
107+
$0.top.leading.trailing.equalToSuperview()
108+
}
109+
}
110+
111+
private func setupUnderlineView() {
112+
addSubview(underlineView)
113+
114+
underlineView.snp.makeConstraints {
115+
$0.top.equalTo(titleLabel.snp.bottom).offset(1)
116+
$0.leading.trailing.equalTo(titleLabel)
117+
$0.height.equalTo(1)
118+
$0.bottom.equalToSuperview()
119+
}
120+
}
121+
122+
// MARK: - Update Methods
123+
124+
private func updateColors() {
125+
let color = foregroundColors.color(for: currentState)
126+
titleLabel.textColor = color
127+
underlineView.backgroundColor = color
128+
}
129+
130+
// MARK: - Animation
131+
132+
private func animatePressedState() {
133+
let targetAlpha: CGFloat = isHighlighted ? 0.6 : 1.0
134+
UIView.animate(
135+
withDuration: animationDuration,
136+
delay: 0,
137+
options: [.allowUserInteraction, .beginFromCurrentState],
138+
animations: {
139+
self.titleLabel.alpha = targetAlpha
140+
self.underlineView.alpha = targetAlpha
141+
}
142+
)
143+
}
144+
145+
// MARK: - Intrinsic Content Size
146+
147+
public override var intrinsicContentSize: CGSize {
148+
let labelSize = titleLabel.intrinsicContentSize
149+
// 라벨 높이 + 밑줄 오프셋(1) + 밑줄 높이(1)
150+
return CGSize(
151+
width: labelSize.width,
152+
height: labelSize.height + 2
153+
)
154+
}
155+
}
156+
157+
// MARK: - Factory Methods
158+
extension BKTextButton {
159+
/// 텍스트 버튼 생성
160+
public static func text(title: String, size: BKButtonSize = .small) -> BKTextButton {
161+
BKTextButton(title: title, size: size)
162+
}
163+
}
164+
165+
/* 커스텀 색상세트 사용 시
166+
let customButton = BKTextButton(title: "커스텀", size: .small)
167+
customButton.foregroundColors = BKButtonColorSet(
168+
normal: .systemBlue,
169+
pressed: .systemBlue,
170+
disabled: .systemGray
171+
)
172+
*/

0 commit comments

Comments
 (0)