@@ -2,9 +2,6 @@ import UIKit
2
2
3
3
/// A model that defines the appearance properties for a button component.
4
4
public struct ButtonVM : ComponentVM {
5
- /// The text displayed on the button.
6
- public var title : String = " "
7
-
8
5
/// The scaling factor for the button's press animation, with a value between 0 and 1.
9
6
///
10
7
/// Defaults to `.medium`.
@@ -13,6 +10,11 @@ public struct ButtonVM: ComponentVM {
13
10
/// The color of the button.
14
11
public var color : ComponentColor ?
15
12
13
+ /// The spacing between the button's title and its image or loading indicator.
14
+ ///
15
+ /// Defaults to `8.0`.
16
+ public var contentSpacing : CGFloat = 8.0
17
+
16
18
/// The corner radius of the button.
17
19
///
18
20
/// Defaults to `.medium`.
@@ -23,6 +25,14 @@ public struct ButtonVM: ComponentVM {
23
25
/// If not provided, the font is automatically calculated based on the button's size.
24
26
public var font : UniversalFont ?
25
27
28
+ /// The position of the image relative to the button's title.
29
+ ///
30
+ /// Defaults to `.leading`.
31
+ public var imageLocation : ImageLocation = . leading
32
+
33
+ /// The source of the image to be displayed.
34
+ public var imageSrc : ImageSource ?
35
+
26
36
/// A Boolean value indicating whether the button is enabled or disabled.
27
37
///
28
38
/// Defaults to `true`.
@@ -33,6 +43,16 @@ public struct ButtonVM: ComponentVM {
33
43
/// Defaults to `false`.
34
44
public var isFullWidth : Bool = false
35
45
46
+ /// A Boolean value indicating whether the button is currently in a loading state.
47
+ ///
48
+ /// Defaults to `false`.
49
+ public var isLoading : Bool = false
50
+
51
+ /// The loading VM used for the loading indicator.
52
+ ///
53
+ /// If not provided, a default loading view model is used.
54
+ public var loadingVM : LoadingVM ?
55
+
36
56
/// The predefined size of the button.
37
57
///
38
58
/// Defaults to `.medium`.
@@ -43,21 +63,36 @@ public struct ButtonVM: ComponentVM {
43
63
/// Defaults to `.filled`.
44
64
public var style : ButtonStyle = . filled
45
65
66
+ /// The text displayed on the button.
67
+ public var title : String = " "
68
+
46
69
/// Initializes a new instance of `ButtonVM` with default values.
47
70
public init ( ) { }
48
71
}
49
72
50
73
// MARK: Shared Helpers
51
74
52
75
extension ButtonVM {
76
+ var isInteractive : Bool {
77
+ self . isEnabled && !self . isLoading
78
+ }
79
+ var preferredLoadingVM : LoadingVM {
80
+ return self . loadingVM ?? . init {
81
+ $0. color = . init(
82
+ main: foregroundColor,
83
+ contrast: self . color? . main ?? . background
84
+ )
85
+ $0. size = . small
86
+ }
87
+ }
53
88
var backgroundColor : UniversalColor ? {
54
89
switch self . style {
55
90
case . filled:
56
91
let color = self . color? . main ?? . content2
57
- return color. enabled ( self . isEnabled )
92
+ return color. enabled ( self . isInteractive )
58
93
case . light:
59
94
let color = self . color? . background ?? . content1
60
- return color. enabled ( self . isEnabled )
95
+ return color. enabled ( self . isInteractive )
61
96
case . plain, . bordered:
62
97
return nil
63
98
}
@@ -69,7 +104,7 @@ extension ButtonVM {
69
104
case . plain, . light, . bordered:
70
105
self . color? . main ?? . foreground
71
106
}
72
- return color. enabled ( self . isEnabled )
107
+ return color. enabled ( self . isInteractive )
73
108
}
74
109
var borderWidth : CGFloat {
75
110
switch self . style {
@@ -85,7 +120,7 @@ extension ButtonVM {
85
120
return nil
86
121
case . bordered:
87
122
if let color {
88
- return color. main. enabled ( self . isEnabled )
123
+ return color. main. enabled ( self . isInteractive )
89
124
} else {
90
125
return . divider
91
126
}
@@ -112,6 +147,13 @@ extension ButtonVM {
112
147
case . large: 52
113
148
}
114
149
}
150
+ var imageSide : CGFloat {
151
+ switch self . size {
152
+ case . small: 20
153
+ case . medium: 24
154
+ case . large: 28
155
+ }
156
+ }
115
157
var horizontalPadding : CGFloat {
116
158
return switch self . size {
117
159
case . small: 16
@@ -121,6 +163,21 @@ extension ButtonVM {
121
163
}
122
164
}
123
165
166
+ extension ButtonVM {
167
+ var image : UIImage ? {
168
+ guard let imageSrc else { return nil }
169
+ switch imageSrc {
170
+ case . sfSymbol( let name) :
171
+ return UIImage ( systemName: name) ? . withTintColor (
172
+ self . foregroundColor. uiColor,
173
+ renderingMode: . alwaysOriginal
174
+ )
175
+ case . local( let name, let bundle) :
176
+ return UIImage ( named: name, in: bundle, compatibleWith: nil )
177
+ }
178
+ }
179
+ }
180
+
124
181
// MARK: UIKit Helpers
125
182
126
183
extension ButtonVM {
@@ -141,10 +198,23 @@ extension ButtonVM {
141
198
142
199
return . init( width: width, height: self . height)
143
200
}
144
- func shouldUpdateSize( _ oldModel: Self ? ) -> Bool {
145
- return self . size != oldModel? . size
146
- || self . font != oldModel? . font
147
- || self . isFullWidth != oldModel? . isFullWidth
201
+ func shouldUpdateImagePosition( _ oldModel: Self ? ) -> Bool {
202
+ guard let oldModel else { return true }
203
+ return self . imageLocation != oldModel. imageLocation
204
+ }
205
+ func shouldUpdateImageSize( _ oldModel: Self ? ) -> Bool {
206
+ guard let oldModel else { return true }
207
+ return self . imageSide != oldModel. imageSide
208
+ }
209
+ func shouldRecalculateSize( _ oldModel: Self ? ) -> Bool {
210
+ guard let oldModel else { return true }
211
+ return self . size != oldModel. size
212
+ || self . font != oldModel. font
213
+ || self . isFullWidth != oldModel. isFullWidth
214
+ || self . isLoading != oldModel. isLoading
215
+ || self . imageSrc != oldModel. imageSrc
216
+ || self . contentSpacing != oldModel. contentSpacing
217
+ || self . title != oldModel. title
148
218
}
149
219
}
150
220
0 commit comments