Skip to content

Commit 39166d6

Browse files
merge with dev
2 parents f70ae3b + 4d66c7f commit 39166d6

File tree

6 files changed

+282
-0
lines changed

6 files changed

+282
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import ComponentsKit
2+
import SwiftUI
3+
import UIKit
4+
5+
struct BadgePreview: View {
6+
@State private var model = BadgeVM {
7+
$0.title = "Badge"
8+
}
9+
10+
var body: some View {
11+
VStack {
12+
PreviewWrapper(title: "UIKit") {
13+
UKBadge(model: self.model)
14+
.preview
15+
}
16+
PreviewWrapper(title: "SwiftUI") {
17+
SUBadge(model: self.model)
18+
}
19+
Form {
20+
Picker("Font", selection: self.$model.font) {
21+
Text("Default").tag(Optional<UniversalFont>.none)
22+
Text("Small").tag(UniversalFont.smButton)
23+
Text("Medium").tag(UniversalFont.mdButton)
24+
Text("Large").tag(UniversalFont.lgButton)
25+
Text("Custom: system bold of size 16").tag(UniversalFont.system(size: 16, weight: .bold))
26+
}
27+
ComponentOptionalColorPicker(selection: self.$model.color)
28+
ComponentRadiusPicker(selection: self.$model.cornerRadius) {
29+
Text("Custom: 4px").tag(ComponentRadius.custom(4))
30+
}
31+
Picker("Style", selection: self.$model.style) {
32+
Text("Filled").tag(BadgeVM.Style.filled)
33+
Text("Light").tag(BadgeVM.Style.light)
34+
}
35+
Picker("Paddings", selection: self.$model.paddings) {
36+
Text("8px; 6px")
37+
.tag(Paddings(top: 6, leading: 8, bottom: 6, trailing: 8))
38+
Text("10px; 8px")
39+
.tag(Paddings(top: 8, leading: 10, bottom: 8, trailing: 10))
40+
Text("12px; 10px")
41+
.tag(Paddings(top: 10, leading: 12, bottom: 10, trailing: 12))
42+
}
43+
}
44+
}
45+
}
46+
}
47+
48+
#Preview {
49+
BadgePreview()
50+
}

Examples/DemosApp/DemosApp/Core/App.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ struct App: View {
1010
}
1111
NavigationLinkWithTitle("Avatar") {
1212
AvatarPreview()
13+
NavigationLinkWithTitle("Badge") {
14+
BadgePreview()
1315
}
1416
NavigationLinkWithTitle("Button") {
1517
ButtonPreview()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
3+
extension BadgeVM {
4+
/// Defines the available visual styles for a badge.
5+
public enum Style: Equatable {
6+
case filled
7+
case light
8+
}
9+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import SwiftUI
2+
3+
/// A model that defines the appearance properties for a badge component.
4+
public struct BadgeVM: ComponentVM {
5+
/// The text displayed on the badge.
6+
public var title: String = ""
7+
8+
/// The color of the badge.
9+
public var color: ComponentColor?
10+
11+
/// The visual style of the badge.
12+
///
13+
/// Defaults to `.filled`.
14+
public var style: Style = .filled
15+
16+
/// The font used for the badge's text.
17+
///
18+
/// Defaults to `.smButton`.
19+
public var font: UniversalFont = .smButton
20+
21+
/// The corner radius of the badge.
22+
///
23+
/// Defaults to `.medium`.
24+
public var cornerRadius: ComponentRadius = .medium
25+
26+
/// Paddings for the badge.
27+
public var paddings: Paddings = .init(horizontal: 10, vertical: 8)
28+
29+
/// Initializes a new instance of `BadgeVM` with default values.
30+
public init() {}
31+
}
32+
33+
// MARK: Helpers
34+
35+
extension BadgeVM {
36+
/// Returns the background color of the badge based on its style.
37+
var backgroundColor: UniversalColor {
38+
switch self.style {
39+
case .filled:
40+
return self.color?.main ?? .content2
41+
case .light:
42+
return self.color?.background ?? .content1
43+
}
44+
}
45+
46+
/// Returns the foreground color of the badge based on its style.
47+
var foregroundColor: UniversalColor {
48+
switch self.style {
49+
case .filled:
50+
return self.color?.contrast ?? .foreground
51+
case .light:
52+
return self.color?.main ?? .foreground
53+
}
54+
}
55+
}
56+
57+
// MARK: UIKit Helpers
58+
59+
extension BadgeVM {
60+
func shouldUpdateLayout(_ oldModel: Self?) -> Bool {
61+
return self.font != oldModel?.font
62+
|| self.paddings != oldModel?.paddings
63+
}
64+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import SwiftUI
2+
3+
/// A SwiftUI component that displays a badge.
4+
public struct SUBadge: View {
5+
// MARK: Properties
6+
7+
/// A model that defines the appearance properties.
8+
public var model: BadgeVM
9+
10+
// MARK: Initialization
11+
12+
/// Initializes a new instance of `SUBadge`.
13+
/// - Parameter model: A model that defines the appearance properties.
14+
public init(model: BadgeVM) {
15+
self.model = model
16+
}
17+
18+
// MARK: Body
19+
20+
public var body: some View {
21+
Text(self.model.title)
22+
.font(self.model.font.font)
23+
.padding(self.model.paddings.edgeInsets)
24+
.foregroundStyle(self.model.foregroundColor.color)
25+
.background(self.model.backgroundColor.color)
26+
.clipShape(
27+
RoundedRectangle(cornerRadius: self.model.cornerRadius.value())
28+
)
29+
}
30+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import AutoLayout
2+
import UIKit
3+
4+
/// A UIKit component that displays a badge.
5+
open class UKBadge: UIView, UKComponent {
6+
// MARK: - Properties
7+
8+
/// A model that defines the appearance properties.
9+
public var model: BadgeVM {
10+
didSet {
11+
self.update(oldValue)
12+
}
13+
}
14+
15+
private var titleLabelConstraints: LayoutConstraints = .init()
16+
17+
// MARK: - Subviews
18+
19+
/// A label that displays the title from the model.
20+
public var titleLabel = UILabel()
21+
22+
// MARK: - UIView Properties
23+
24+
open override var intrinsicContentSize: CGSize {
25+
return self.sizeThatFits(UIView.layoutFittingExpandedSize)
26+
}
27+
28+
// MARK: - Initialization
29+
30+
/// Initializes a new instance of `UKBadge`.
31+
/// - Parameter model: A model that defines the appearance properties for the badge.
32+
public init(model: BadgeVM = .init()) {
33+
self.model = model
34+
super.init(frame: .zero)
35+
36+
self.setup()
37+
self.style()
38+
self.layout()
39+
}
40+
41+
public required init?(coder: NSCoder) {
42+
fatalError("init(coder:) has not been implemented")
43+
}
44+
45+
// MARK: - Setup
46+
47+
private func setup() {
48+
self.addSubview(self.titleLabel)
49+
}
50+
51+
// MARK: - Style
52+
53+
private func style() {
54+
Self.Style.mainView(self, model: self.model)
55+
Self.Style.titleLabel(self.titleLabel, model: self.model)
56+
}
57+
58+
// MARK: - Layout
59+
60+
private func layout() {
61+
self.titleLabelConstraints = .merged {
62+
self.titleLabel.top(self.model.paddings.top)
63+
self.titleLabel.leading(self.model.paddings.leading)
64+
self.titleLabel.bottom(self.model.paddings.bottom)
65+
self.titleLabel.trailing(self.model.paddings.trailing)
66+
}
67+
68+
self.titleLabelConstraints.allConstraints.forEach { $0?.priority = .defaultHigh }
69+
}
70+
71+
open override func layoutSubviews() {
72+
super.layoutSubviews()
73+
74+
self.layer.cornerRadius = self.model.cornerRadius.value(for: self.bounds.height)
75+
}
76+
77+
// MARK: - Update
78+
79+
public func update(_ oldModel: BadgeVM) {
80+
guard self.model != oldModel else { return }
81+
82+
self.style()
83+
if self.model.shouldUpdateLayout(oldModel) {
84+
self.titleLabelConstraints.leading?.constant = self.model.paddings.leading
85+
self.titleLabelConstraints.top?.constant = self.model.paddings.top
86+
self.titleLabelConstraints.bottom?.constant = -self.model.paddings.bottom
87+
self.titleLabelConstraints.trailing?.constant = -self.model.paddings.trailing
88+
89+
self.invalidateIntrinsicContentSize()
90+
self.setNeedsLayout()
91+
}
92+
}
93+
94+
// MARK: - UIView Methods
95+
96+
open override func sizeThatFits(_ size: CGSize) -> CGSize {
97+
let contentSize = self.titleLabel.sizeThatFits(size)
98+
99+
let totalWidthPadding = self.model.paddings.leading + self.model.paddings.trailing
100+
let totalHeightPadding = self.model.paddings.top + self.model.paddings.bottom
101+
102+
let width = contentSize.width + totalWidthPadding
103+
let height = contentSize.height + totalHeightPadding
104+
105+
return CGSize(
106+
width: min(width, size.width),
107+
height: min(height, size.height)
108+
)
109+
}
110+
}
111+
112+
// MARK: - Style Helpers
113+
114+
extension UKBadge {
115+
fileprivate enum Style {
116+
static func mainView(_ view: UIView, model: BadgeVM) {
117+
view.backgroundColor = model.backgroundColor.uiColor
118+
view.layer.cornerRadius = model.cornerRadius.value(for: view.bounds.height)
119+
}
120+
static func titleLabel(_ label: UILabel, model: BadgeVM) {
121+
label.textAlignment = .center
122+
label.text = model.title
123+
label.font = model.font.uiFont
124+
label.textColor = model.foregroundColor.uiColor
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)