@@ -2,9 +2,6 @@ import UIKit
22
33/// A model that defines the appearance properties for a button component.
44public struct ButtonVM : ComponentVM {
5- /// The text displayed on the button.
6- public var title : String = " "
7-
85 /// The scaling factor for the button's press animation, with a value between 0 and 1.
96 ///
107 /// Defaults to `.medium`.
@@ -13,6 +10,11 @@ public struct ButtonVM: ComponentVM {
1310 /// The color of the button.
1411 public var color : ComponentColor ?
1512
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+
1618 /// The corner radius of the button.
1719 ///
1820 /// Defaults to `.medium`.
@@ -23,6 +25,14 @@ public struct ButtonVM: ComponentVM {
2325 /// If not provided, the font is automatically calculated based on the button's size.
2426 public var font : UniversalFont ?
2527
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+
2636 /// A Boolean value indicating whether the button is enabled or disabled.
2737 ///
2838 /// Defaults to `true`.
@@ -33,6 +43,16 @@ public struct ButtonVM: ComponentVM {
3343 /// Defaults to `false`.
3444 public var isFullWidth : Bool = false
3545
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+
3656 /// The predefined size of the button.
3757 ///
3858 /// Defaults to `.medium`.
@@ -43,21 +63,36 @@ public struct ButtonVM: ComponentVM {
4363 /// Defaults to `.filled`.
4464 public var style : ButtonStyle = . filled
4565
66+ /// The text displayed on the button.
67+ public var title : String = " "
68+
4669 /// Initializes a new instance of `ButtonVM` with default values.
4770 public init ( ) { }
4871}
4972
5073// MARK: Shared Helpers
5174
5275extension 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+ }
5388 var backgroundColor : UniversalColor ? {
5489 switch self . style {
5590 case . filled:
5691 let color = self . color? . main ?? . content2
57- return color. enabled ( self . isEnabled )
92+ return color. enabled ( self . isInteractive )
5893 case . light:
5994 let color = self . color? . background ?? . content1
60- return color. enabled ( self . isEnabled )
95+ return color. enabled ( self . isInteractive )
6196 case . plain, . bordered:
6297 return nil
6398 }
@@ -69,7 +104,7 @@ extension ButtonVM {
69104 case . plain, . light, . bordered:
70105 self . color? . main ?? . foreground
71106 }
72- return color. enabled ( self . isEnabled )
107+ return color. enabled ( self . isInteractive )
73108 }
74109 var borderWidth : CGFloat {
75110 switch self . style {
@@ -85,7 +120,7 @@ extension ButtonVM {
85120 return nil
86121 case . bordered:
87122 if let color {
88- return color. main. enabled ( self . isEnabled )
123+ return color. main. enabled ( self . isInteractive )
89124 } else {
90125 return . divider
91126 }
@@ -112,6 +147,13 @@ extension ButtonVM {
112147 case . large: 52
113148 }
114149 }
150+ var imageSide : CGFloat {
151+ switch self . size {
152+ case . small: 20
153+ case . medium: 24
154+ case . large: 28
155+ }
156+ }
115157 var horizontalPadding : CGFloat {
116158 return switch self . size {
117159 case . small: 16
@@ -121,6 +163,21 @@ extension ButtonVM {
121163 }
122164}
123165
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+
124181// MARK: UIKit Helpers
125182
126183extension ButtonVM {
@@ -141,10 +198,23 @@ extension ButtonVM {
141198
142199 return . init( width: width, height: self . height)
143200 }
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
148218 }
149219}
150220
0 commit comments