Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2025 Booket. All rights reserved
// Copyright © 2026 Booket. All rights reserved

import BKDesign
import SnapKit
Expand Down Expand Up @@ -52,6 +52,9 @@ public final class BKButtonTestViewController: UIViewController {
setupButtons(for: .medium)
setupButtons(for: .small)
setupButtons(for: .rounded)

addSectionSeparator()
setupTextButtons()
}

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

// MARK: - Text Button Tests

private func setupTextButtons() {
addSectionHeader("Text Buttons")

addTextButton("텍스트 버튼1", size: .small)
addTextButton("텍스트 버튼2", size: .medium)
addTextButton("텍스트 버튼3", size: .large)
addTextButton("아주 긴 내용의 텍스트 버튼을 만들어봅시다", size: .large)
addDisabledTextButton("비활성화 텍스트", size: .small)
}

private func addTextButton(_ title: String, size: BKButtonSize) {
let button = BKTextButton(title: "[\(size.label)] \(title)", size: size)
containerView.addArrangedSubview(wrapForLeftAlign(button))
}

private func addDisabledTextButton(_ title: String, size: BKButtonSize) {
let button = BKTextButton(title: "[\(size.label)] \(title)", size: size)
button.isDisabled = true
containerView.addArrangedSubview(wrapForLeftAlign(button))
}

/// 텍스트 버튼은 intrinsic size를 사용하므로 왼쪽 정렬 래퍼 필요
private func wrapForLeftAlign(_ view: UIView) -> UIView {
let wrapper = UIView()
wrapper.addSubview(view)
view.snp.makeConstraints {
$0.leading.verticalEdges.equalToSuperview()
}
return wrapper
}

// MARK: - Button Builders

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

private func setupIndependentButtons() {
let sampleView = UIView()
scrollView.addSubview(sampleView)
sampleView.snp.makeConstraints {
$0.top.equalTo(containerView.snp.bottom).offset(40)
$0.centerX.equalToSuperview()
$0.bottom.lessThanOrEqualToSuperview()
}

let buttons: [BKButton] = [
.primary(title: "[Free] Apple 로그인", size: .large),
.secondary(title: "[Free] Secondary", size: .large),
.tertiary(title: "[Free] Tertiary", size: .large),
.primary(title: "[Free] Apple 로그인", size: .medium),
.secondary(title: "[Free] Secondary", size: .small),
.tertiary(title: "[Free] Tertiary", size: .rounded)
]

buttons[0].leftIcon = BKImage.Icon.apple
buttons[2].rightIcon = BKImage.Icon.kakao

var last: UIView?
for button in buttons {
sampleView.addSubview(button)
button.snp.makeConstraints {
$0.centerX.equalToSuperview()
$0.top.equalTo(last?.snp.bottom ?? sampleView.snp.top).offset(16)
}
last = button
}
// MARK: - Section Helpers

private func addSectionHeader(_ title: String) {
let label = UILabel()
label.text = title
label.font = .systemFont(ofSize: 18, weight: .bold)
label.textColor = .darkGray
containerView.addArrangedSubview(label)
}

private func addSectionSeparator() {
let separator = UIView()
separator.backgroundColor = .systemGray4
separator.snp.makeConstraints { $0.height.equalTo(1) }
containerView.addArrangedSubview(separator)
containerView.setCustomSpacing(24, after: separator)
}

}


// MARK: - BKButtonSize Extension
public extension BKButtonSize {
var label: String {
switch self {
Expand Down
172 changes: 172 additions & 0 deletions src/Projects/BKDesign/Sources/Components/Button/BKTextButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright © 2025 Booket. All rights reserved

import SnapKit
import UIKit

/// 밑줄이 있는 텍스트 버튼 컴포넌트입니다.
///
/// 링크 스타일의 텍스트 버튼으로, 배경 없이 텍스트와 밑줄만 표시됩니다.
/// `BKButtonGroup`과 함께 사용할 수 있습니다.
public final class BKTextButton: UIControl {

// MARK: - Public Properties

/// 버튼에 표시될 텍스트
public var title: String? {
didSet {
titleLabel.text = title
invalidateIntrinsicContentSize()
}
}

/// 버튼의 비활성화 여부
public var isDisabled: Bool = false {
didSet {
isEnabled = !isDisabled
updateColors()
}
}

/// 버튼 크기 (폰트 크기에 영향)
public var size: BKButtonSize = .small {
didSet {
titleLabel.font = size.font
invalidateIntrinsicContentSize()
}
}

/// 텍스트 및 밑줄 색상 설정
public var foregroundColors: BKButtonColorSet = BKButtonColorSet(
normal: .bkContentColor(.brand),
pressed: .bkContentColor(.brand),
disabled: .bkContentColor(.disable)
) {
didSet {
updateColors()
}
}

// MARK: - Override Properties

public override var isHighlighted: Bool {
didSet {
updateColors()
animatePressedState()
}
}

public override var isEnabled: Bool {
didSet {
updateColors()
}
}

// MARK: - Private Properties

private let titleLabel = UILabel()
private let underlineView = UIView()

private let animationDuration: TimeInterval = 0.15

private var currentState: BKButtonState {
BKButtonState(isEnabled: isEnabled, isHighlighted: isHighlighted)
}

// MARK: - Initialization

public init(title: String? = nil, size: BKButtonSize = .small) {
super.init(frame: .zero)
self.title = title
self.size = size
setupViews()
updateColors()
}

public required init?(coder: NSCoder) {
super.init(coder: coder)
setupViews()
updateColors()
}

// MARK: - Setup

private func setupViews() {
setupTitleLabel()
setupUnderlineView()
}

private func setupTitleLabel() {
addSubview(titleLabel)

titleLabel.text = title
titleLabel.font = size.font
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 1

titleLabel.snp.makeConstraints {
$0.top.leading.trailing.equalToSuperview()
}
}

private func setupUnderlineView() {
addSubview(underlineView)

underlineView.snp.makeConstraints {
$0.top.equalTo(titleLabel.snp.bottom).offset(1)
$0.leading.trailing.equalTo(titleLabel)
$0.height.equalTo(1)
$0.bottom.equalToSuperview()
}
}

// MARK: - Update Methods

private func updateColors() {
let color = foregroundColors.color(for: currentState)
titleLabel.textColor = color
underlineView.backgroundColor = color
}

// MARK: - Animation

private func animatePressedState() {
let targetAlpha: CGFloat = isHighlighted ? 0.6 : 1.0
UIView.animate(
withDuration: animationDuration,
delay: 0,
options: [.allowUserInteraction, .beginFromCurrentState],
animations: {
self.titleLabel.alpha = targetAlpha
self.underlineView.alpha = targetAlpha
}
)
}

// MARK: - Intrinsic Content Size

public override var intrinsicContentSize: CGSize {
let labelSize = titleLabel.intrinsicContentSize
// 라벨 높이 + 밑줄 오프셋(1) + 밑줄 높이(1)
return CGSize(
width: labelSize.width,
height: labelSize.height + 2
)
}
}

// MARK: - Factory Methods
extension BKTextButton {
/// 텍스트 버튼 생성
public static func text(title: String, size: BKButtonSize = .small) -> BKTextButton {
BKTextButton(title: title, size: size)
}
}

/* 커스텀 색상세트 사용 시
let customButton = BKTextButton(title: "커스텀", size: .small)
customButton.foregroundColors = BKButtonColorSet(
normal: .systemBlue,
pressed: .systemBlue,
disabled: .systemGray
)
*/