@momo-kits/native-kits 0.152.4-beta.2 → 0.152.4-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/CODE_OF_CONDUCT.md +133 -0
  2. package/CONTRIBUTING.md +114 -0
  3. package/LICENSE +20 -0
  4. package/README.md +7 -0
  5. package/build.gradle.kts +32 -0
  6. package/compose/MoMoComposeKits.podspec +54 -0
  7. package/compose/build.gradle.kts +149 -0
  8. package/compose/src/androidMain/AndroidManifest.xml +2 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +105 -0
  10. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +1 -0
  11. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  12. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  23. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  24. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +306 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +715 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +214 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +236 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +77 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +27 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +334 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +345 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +90 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +131 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +69 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +143 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +179 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +111 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +384 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +160 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +234 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +223 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +232 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +236 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +228 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +50 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +34 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +85 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +33 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +338 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +95 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +64 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +89 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +84 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +208 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +172 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +199 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +237 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +13 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +191 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +258 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +59 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +49 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +232 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +111 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +94 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +159 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +232 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +17 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +459 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +169 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +216 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +180 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +251 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +31 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +385 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +38 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  108. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +144 -0
  109. package/gradle.properties +19 -0
  110. package/gradlew +240 -0
  111. package/gradlew.bat +91 -0
  112. package/ios/Application/ApplicationEnvironment.swift +50 -0
  113. package/ios/Application/Components.swift +263 -0
  114. package/ios/Application/ComposeApi.swift +22 -0
  115. package/ios/Application/FloatingButton.swift +172 -0
  116. package/ios/Application/HeaderRight.swift +271 -0
  117. package/ios/Application/Screen.swift +249 -0
  118. package/ios/Badge/BadgeDot.swift +31 -0
  119. package/ios/Button/Button.swift +211 -0
  120. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
  121. package/ios/Checkbox/Checkbox.swift +81 -0
  122. package/ios/Chip/Chip.swift +96 -0
  123. package/ios/Colors+Radius+Spacing/Colors.swift +172 -0
  124. package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
  125. package/ios/Colors+Radius+Spacing/Spacing.swift +12 -0
  126. package/ios/Extensions/Color++.swift +25 -0
  127. package/ios/Icon/Icon.swift +51 -0
  128. package/ios/Image/Image.swift +70 -0
  129. package/ios/Input/Input.swift +207 -0
  130. package/ios/Input/InputPhoneNumber.swift +176 -0
  131. package/ios/Input/InputSearch.swift +238 -0
  132. package/ios/Input/InputTextArea.swift +242 -0
  133. package/ios/Lottie/LottieView.swift +86 -0
  134. package/ios/OTPKeyboard/KeyboardButton.swift +41 -0
  135. package/ios/OTPKeyboard/OTPKeyboard.swift +145 -0
  136. package/ios/Popup/PopupDisplay.swift +284 -0
  137. package/ios/Popup/PopupInput.swift +96 -0
  138. package/ios/Popup/PopupPromotion.swift +73 -0
  139. package/ios/PopupView/FullscreenPopup.swift +251 -0
  140. package/ios/PopupView/Modifiers.swift +158 -0
  141. package/ios/PopupView/PopupView.swift +289 -0
  142. package/ios/PopupView/Utils++.swift +281 -0
  143. package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
  144. package/ios/Swipeable/SwipeCell.swift +278 -0
  145. package/ios/Swipeable/SwipeCellModel.swift +86 -0
  146. package/ios/Switch/Switch.swift +44 -0
  147. package/ios/Template/Logo/Logo.swift +75 -0
  148. package/ios/Template/TrustBanner/TrustBanner.swift +120 -0
  149. package/ios/Theme.md +18 -0
  150. package/ios/Typography/Text.swift +140 -0
  151. package/ios/Typography/Typography.swift +95 -0
  152. package/ios/native-kits.podspec +18 -0
  153. package/package.json +6 -7
  154. package/settings.gradle.kts +25 -0
  155. package/shared/build.gradle.kts +0 -74
@@ -0,0 +1,22 @@
1
+ import SwiftUI
2
+ import Foundation
3
+
4
+ public enum Radius {
5
+ public static let XXS: CGFloat = 2
6
+ public static let XS: CGFloat = 4
7
+ public static let S: CGFloat = 8
8
+ public static let M: CGFloat = 12
9
+ public static let L: CGFloat = 16
10
+ public static let XL: CGFloat = 24
11
+ }
12
+
13
+ public struct RoundedCorner: Shape {
14
+
15
+ var radius: CGFloat = .infinity
16
+ var corners: UIRectCorner = .allCorners
17
+
18
+ public func path(in rect: CGRect) -> Path {
19
+ let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
20
+ return Path(path.cgPath)
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ import Foundation
2
+
3
+ public enum Spacing {
4
+ public static let XXS: CGFloat = 2
5
+ public static let XS: CGFloat = 4
6
+ public static let S: CGFloat = 8
7
+ public static let M: CGFloat = 12
8
+ public static let L: CGFloat = 16
9
+ public static let XL: CGFloat = 24
10
+ public static let XXL: CGFloat = 32
11
+ public static let XXXL: CGFloat = 48
12
+ }
@@ -0,0 +1,25 @@
1
+ import SwiftUI
2
+
3
+ public extension Color {
4
+ init(hex: UInt, alpha: Double = 1) {
5
+ self.init(
6
+ .sRGB,
7
+ red: Double((hex >> 16) & 0xFF) / 255,
8
+ green: Double((hex >> 08) & 0xFF) / 255,
9
+ blue: Double((hex >> 00) & 0xFF) / 255,
10
+ opacity: alpha
11
+ )
12
+ }
13
+
14
+ init(hex: String) {
15
+ let scanner = Scanner(string: hex)
16
+ var rgbValue: UInt64 = 0
17
+ scanner.scanHexInt64(&rgbValue)
18
+
19
+ let r = (rgbValue & 0xff0000) >> 16
20
+ let g = (rgbValue & 0xff00) >> 8
21
+ let b = rgbValue & 0xff
22
+
23
+ self.init(red: Double(r) / 0xff, green: Double(g) / 0xff, blue: Double(b) / 0xff)
24
+ }
25
+ }
@@ -0,0 +1,51 @@
1
+ import SwiftUI
2
+
3
+ struct IconSource: Codable {
4
+ let uri: String
5
+ }
6
+
7
+ func loadIconSources() -> [String: IconSource] {
8
+ guard let url = Bundle.main.url(forResource: "icon", withExtension: "json") else {
9
+ print("Error: icon.json not found")
10
+ return [:]
11
+ }
12
+
13
+ do {
14
+ let data = try Data(contentsOf: url)
15
+ let decoded = try JSONDecoder().decode([String: IconSource].self, from: data)
16
+ return decoded
17
+ } catch {
18
+ print("Error decoding icon.json: \(error)")
19
+ return [:]
20
+ }
21
+ }
22
+
23
+ // Load the JSON data into a global variable
24
+ let iconSources: [String: IconSource] = loadIconSources()
25
+
26
+ // Icon ViewModel
27
+ public struct Icon: View {
28
+ var source: String
29
+ var size: CGFloat
30
+ var color: Color?
31
+ var accessibilityLabel: String?
32
+
33
+ public init(
34
+ source: String,
35
+ size: CGFloat = 24,
36
+ color: Color? = nil,
37
+ accessibilityLabel: String? = nil
38
+ ) {
39
+ self.source = source
40
+ self.size = size
41
+ self.color = color
42
+ self.accessibilityLabel = accessibilityLabel
43
+ }
44
+
45
+ public var body: some View {
46
+ if let uri = iconSources[source]?.uri {
47
+ ImageView(uri, color: color).frame(width: size, height: size)
48
+ }
49
+ }
50
+ }
51
+
@@ -0,0 +1,70 @@
1
+
2
+
3
+ import Foundation
4
+ import SDWebImageSwiftUI
5
+ import SkeletonUI
6
+ import SwiftUI
7
+
8
+ public struct ImageView: View {
9
+ // MARK: Lifecycle
10
+
11
+ public init(_ url: String, aspectRatio: CGFloat? = nil, contentMode: ContentMode = ContentMode.fit, placeholder: AnyView? = nil, color: Color? = nil) {
12
+ self.url = url
13
+ self.aspectRatio = aspectRatio
14
+ self.contentMode = contentMode
15
+ self.placeholder = placeholder
16
+ self.color = color
17
+ }
18
+
19
+ // MARK: Public
20
+
21
+ public var body: some View {
22
+ return WebImage(url: URL(string: url), isAnimating: .constant(true))
23
+ .onFailure { _ in
24
+ error = true
25
+ }
26
+ .onSuccess { _, _, _ in
27
+ error = false
28
+ }
29
+ .resizable()
30
+ .renderingMode(color != nil ? .template : .original)
31
+ .placeholder {
32
+ VStack(alignment: .center, content: {
33
+ if error {
34
+ Image(systemName: "photo.badge.exclamationmark").frame(width: 24, height: 24)
35
+ } else if placeholder != nil {
36
+ placeholder
37
+ }
38
+ })
39
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
40
+ .skeleton(with: !error && placeholder == nil)
41
+ .shape(type: .rectangle)
42
+ }
43
+ .scaledToFit()
44
+ .transition(.fade)
45
+ .aspectRatio(aspectRatio, contentMode: contentMode)
46
+ .foregroundColor(color)
47
+ }
48
+
49
+ // MARK: Internal
50
+
51
+ var aspectRatio: CGFloat?
52
+ var contentMode: ContentMode
53
+ var placeholder: AnyView?
54
+ var color: Color?
55
+
56
+ @State var error: Bool = false
57
+ var url: String
58
+ }
59
+
60
+ private struct ColorMultiplyModifier: ViewModifier {
61
+ let color: Color?
62
+
63
+ func body(content: Content) -> some View {
64
+ if let color {
65
+ content.colorMultiply(color)
66
+ } else {
67
+ content
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,207 @@
1
+ import Foundation
2
+ import SwiftUI
3
+
4
+
5
+ fileprivate struct FloatingComponent: View {
6
+ var floatingValue: String
7
+ var floatingIcon: AnyView?
8
+
9
+ public init (_ floatingValue: String = "", floatingIcon: AnyView? = nil){
10
+ self.floatingValue = floatingValue
11
+ self.floatingIcon = floatingIcon
12
+ }
13
+
14
+ public var body: some View {
15
+ return HStack {
16
+ Text(floatingValue)
17
+ .font(.paragraph)
18
+ .foregroundColor(Colors.black09)
19
+ .font(.system(size: 12))
20
+ .padding(.leading, 8)
21
+ .padding(.trailing, 8)
22
+ floatingIcon
23
+ }
24
+ .background(Colors.black01)
25
+ .offset(x: 16, y:-8)
26
+ .zIndex(10)
27
+ }
28
+ }
29
+
30
+ struct DotTextField: View{
31
+ @Binding var value: String
32
+ @State var isHide: Bool = true
33
+
34
+
35
+ func onToggle(){
36
+ isHide.toggle()
37
+ }
38
+
39
+ public var body: some View {
40
+ return HStack {
41
+ if (isHide){
42
+ SecureField("", text: $value)
43
+ }
44
+ else {
45
+ TextField("", text: $value)
46
+ }
47
+
48
+ SwiftButton(action: onToggle){
49
+ Image(systemName: isHide ? "eye.slash" : "eye")
50
+ .foregroundColor(Colors.text)
51
+ .frame(width: 16, height: 16)
52
+ .padding(.leading, -4)
53
+ }
54
+
55
+
56
+ }
57
+
58
+ }
59
+ }
60
+
61
+ public struct Input: View {
62
+ @Binding var value: String
63
+ @State var focused: Bool = false
64
+
65
+ var placeholder: String
66
+ var floatingValue: String
67
+ var errorMessage: String
68
+ var isSecure: Bool
69
+ var keyboardType: UIKeyboardType
70
+ var floatingIcon: AnyView?
71
+ var rightIcon: AnyView?
72
+ var onChange: ((String) -> Void)?
73
+ var onBlur: (() -> Void)?
74
+ var floatingIconURL: String?
75
+ var floatingIconColor: Color?
76
+ var size: InputSize
77
+ var fontWeight: InputFontWeight
78
+ var disabled: Bool
79
+ var leadingIcon: String?
80
+ var leadingIconColor: Color?
81
+ var loading: Bool
82
+ var required: Bool
83
+ var hintText: String?
84
+
85
+ func onCancel(){
86
+ self.value.removeAll()
87
+ }
88
+
89
+ public init(_ value: Binding<String>, placeholder: String = "", floatingValue: String = "", multilines: Bool = false, errorMessage: String = "", isSecure: Bool = false, keyboardType: UIKeyboardType = .default, floatingIcon: AnyView? = nil, rightIcon: AnyView? = nil, onChange: ((String)->Void)? = nil, onBlur: (()->Void)? = nil, floatingIconURL: String? = nil, floatingIconColor: Color? = Colors.pink03, size: InputSize = .small, fontWeight: InputFontWeight = .regular, disabled: Bool = false, leadingIcon: String? = nil, leadingIconColor: Color? = Colors.pink03, loading: Bool = false, required: Bool = false, hintText: String? = nil){
90
+ self.placeholder = placeholder
91
+ self.floatingValue = floatingValue
92
+ self.errorMessage = errorMessage
93
+ self.isSecure = isSecure
94
+ self._value = value
95
+ self.keyboardType = keyboardType
96
+ self.rightIcon = rightIcon
97
+ self.floatingIcon = floatingIcon
98
+ self.onChange = onChange
99
+ self.floatingIconURL = floatingIconURL
100
+ self.floatingIconColor = floatingIconColor
101
+ self.size = size
102
+ self.fontWeight = fontWeight
103
+ self.disabled = disabled
104
+ self.leadingIcon = leadingIcon
105
+ self.leadingIconColor = leadingIconColor
106
+ self.loading = loading
107
+ self.required = required
108
+ self.hintText = hintText
109
+ }
110
+
111
+ func onChangeText(value: String){
112
+ onChange?(value)
113
+ }
114
+
115
+ public var body: some View {
116
+ VStack(alignment: .leading, spacing: 4) {
117
+ ZStack(alignment: .topLeading) {
118
+ HStack(alignment: .center) {
119
+ if let leadingIcon = leadingIcon {
120
+ Icon(source: leadingIcon, size: 16, color: leadingIconColor)
121
+ }
122
+
123
+ if !isSecure {
124
+ TextField("", text: $value, onEditingChanged: { isChange in
125
+ self.focused = isChange
126
+ })
127
+ .disabled(disabled)
128
+ .font(fontWeight == .bold ? .headline : .body)
129
+ } else {
130
+ DotTextField(value: $value)
131
+ .disabled(disabled)
132
+ }
133
+
134
+ if loading {
135
+ ActivityIndicator(isAnimating: .constant(true), style: .medium)
136
+ .frame(width: 20, height: 20)
137
+ } else if !value.isEmpty {
138
+ SwiftButton(action: onCancel){
139
+ Image(systemName: "x.circle.fill")
140
+ .foregroundColor(Colors.text)
141
+ .frame(width: 16, height: 16)
142
+ .padding(.leading, -4)
143
+ .padding(.trailing, 8)
144
+ }
145
+ }
146
+
147
+ rightIcon
148
+ }
149
+ .padding(.horizontal, 12)
150
+ .frame(height: size == .small ? 40 : 48)
151
+ .background(disabled ? Colors.black01 : Color.white)
152
+ .overlay(RoundedRectangle(cornerRadius: 8).stroke(focused ? Colors.pink03 : Colors.border, lineWidth: 1))
153
+
154
+ if value.isEmpty {
155
+ Text(placeholder + (required ? " *" : ""))
156
+ .foregroundColor(Colors.black09)
157
+ .padding(.horizontal, leadingIcon == nil ? 12 : 32)
158
+ .padding(.top, 12)
159
+ .allowsHitTesting(false)
160
+ }
161
+
162
+ if !floatingValue.isEmpty {
163
+ FloatingComponent(floatingValue, floatingIcon: floatingIconURL.map { url in
164
+ AnyView(
165
+ Icon(source: url, size: 16, color: floatingIconColor)
166
+ )
167
+ })
168
+
169
+ }
170
+ }
171
+
172
+ if !errorMessage.isEmpty {
173
+ Text(errorMessage)
174
+ .foregroundColor(.red)
175
+ .font(.caption)
176
+ } else if let hint = hintText {
177
+ Text(hint)
178
+ .foregroundColor(Colors.black09)
179
+ .font(.caption)
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+
186
+ struct ActivityIndicator: UIViewRepresentable {
187
+ @Binding var isAnimating: Bool
188
+ let style: UIActivityIndicatorView.Style
189
+
190
+ func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
191
+ return UIActivityIndicatorView(style: style)
192
+ }
193
+
194
+ func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
195
+ isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
196
+ }
197
+ }
198
+
199
+ public enum InputSize {
200
+ case small
201
+ case large
202
+ }
203
+
204
+ public enum InputFontWeight {
205
+ case regular
206
+ case bold
207
+ }
@@ -0,0 +1,176 @@
1
+ //
2
+ // InputPhoneNumber.swift
3
+ // Pods
4
+ //
5
+ // Created by sophia on 20/10/25.
6
+ //
7
+
8
+ import SwiftUI
9
+
10
+ public struct InputPhoneNumber: View {
11
+ @Binding public var text: String
12
+
13
+ public var placeholder: String
14
+ public var size: InputSize
15
+ public var hintText: String
16
+ public var error: String
17
+ public var loading: Bool
18
+ public var rightIcon: String
19
+ public var rightIconColor: Color
20
+ public var onFocus: (() -> Void)?
21
+ public var onBlur: (() -> Void)?
22
+ public var onRightIconPressed: (() -> Void)?
23
+
24
+ @State private var isFocused: Bool = false
25
+
26
+ public init(
27
+ text: Binding<String>,
28
+ placeholder: String = "0123456789",
29
+ size: InputSize = .small,
30
+ hintText: String = "",
31
+ error: String = "",
32
+ loading: Bool = false,
33
+ required: Bool = false,
34
+ rightIcon: String = "",
35
+ rightIconColor: Color = Colors.black12,
36
+ onFocus: (() -> Void)? = nil,
37
+ onBlur: (() -> Void)? = nil,
38
+ onRightIconPressed: (() -> Void)? = nil
39
+ ) {
40
+ self._text = text
41
+ self.placeholder = placeholder
42
+ self.size = size
43
+ self.hintText = hintText
44
+ self.error = error
45
+ self.loading = loading
46
+ self.rightIcon = rightIcon
47
+ self.rightIconColor = rightIconColor
48
+ self.onFocus = onFocus
49
+ self.onBlur = onBlur
50
+ self.onRightIconPressed = onRightIconPressed
51
+ }
52
+
53
+ // MARK: - Body
54
+ public var body: some View {
55
+ VStack(alignment: .leading, spacing: 4) {
56
+ HStack(spacing: 0) {
57
+ // 🇻🇳 Flag
58
+ ImageView("https://static.momocdn.net/app/img/icon/ic-qrcode-package/ic_vn_flag.png")
59
+ .frame(width: 24, height: 24)
60
+ .padding(.trailing, Spacing.XS)
61
+
62
+ MomoText("+84", typography: size == .small ? .headerSSemibold : .headerMBold)
63
+ .foregroundColor(Colors.black17)
64
+
65
+ Rectangle()
66
+ .fill(Colors.black04)
67
+ .frame(width: 1, height: size == .small ? 24 : 32)
68
+ .padding(.horizontal, Spacing.M)
69
+
70
+ // Text input
71
+ ZStack(alignment: .leading) {
72
+ if text.isEmpty {
73
+ Text(placeholder)
74
+ .foregroundColor(Colors.black12)
75
+ .font(size == .small ? .header_s_semibold : .header_m_bold)
76
+ }
77
+
78
+ TextField("", text: $text, onEditingChanged: { focused in
79
+ handleFocusChange(focused)
80
+ })
81
+ .keyboardType(.numberPad)
82
+ .font(size == .small ? .header_s_semibold : .header_m_bold)
83
+ .foregroundColor(Colors.black17)
84
+ .applyPrimaryCursorColor()
85
+ .lineLimit(1)
86
+
87
+ }
88
+
89
+ // Clear button
90
+ if isFocused && !text.isEmpty {
91
+ SwiftUI.Button(action: { text = "" }) {
92
+ Icon(source: "24_navigation_close_circle_full", size: 16, color: Colors.black12)
93
+ .padding(.leading, Spacing.S)
94
+ }
95
+ }
96
+
97
+ // Loading indicator
98
+ if loading {
99
+ if #available(iOS 14.0, *) {
100
+ ProgressView()
101
+ .progressViewStyle(CircularProgressViewStyle(tint: Colors.black17))
102
+ .frame(width: 16, height: 16)
103
+ .padding(.leading, Spacing.S)
104
+ } else {
105
+ Image(systemName: "clock")
106
+ .rotationEffect(.degrees(isFocused ? 360 : 0))
107
+ .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
108
+ .frame(width: 16, height: 16)
109
+ .padding(.leading, Spacing.S)
110
+ }
111
+ }
112
+
113
+ // ✅ Right icon
114
+ if !rightIcon.isEmpty {
115
+ SwiftUI.Button(action: { onRightIconPressed?() }) {
116
+ Icon(source: rightIcon, size: 24, color: rightIconColor)
117
+ .padding(.leading, Spacing.S)
118
+ }
119
+ }
120
+ }
121
+ .padding(.horizontal, Spacing.M)
122
+ .frame(height: size == .small ? 48 : 56)
123
+ .overlay(
124
+ RoundedRectangle(cornerRadius: Radius.S)
125
+ .stroke(borderColor(), lineWidth: isFocused ? 1.5 : 1)
126
+ )
127
+
128
+ // Error or hint
129
+ if (!error.isEmpty || !hintText.isEmpty) {
130
+ HStack(spacing: 4) {
131
+ Icon(source: "ic_error", size: 16, color: errorColor())
132
+ MomoText(
133
+ error.isEmpty ? (hintText.isEmpty ? "Không thể chỉnh sửa" : hintText) : error,
134
+ typography: .descriptionDefaultRegular,
135
+ color: errorColor()
136
+ )
137
+ }
138
+ .padding(.top, Spacing.XS)
139
+ }
140
+ }
141
+ }
142
+
143
+ // MARK: - Helpers
144
+
145
+ private func handleFocusChange(_ focused: Bool) {
146
+ isFocused = focused
147
+ if focused {
148
+ onFocus?()
149
+ } else {
150
+ onBlur?()
151
+ }
152
+ }
153
+
154
+ private func borderColor() -> Color {
155
+ if !error.isEmpty { return Colors.red03 }
156
+ if isFocused { return Colors.primary }
157
+ return Colors.black04
158
+ }
159
+
160
+ private func errorColor() -> Color {
161
+ !error.isEmpty ? Colors.red03 : Colors.black12
162
+ }
163
+ }
164
+
165
+ private extension View {
166
+ func applyPrimaryCursorColor() -> some View {
167
+ if #available(iOS 15.0, *) {
168
+ // Modern SwiftUI: use .tint() for cursor
169
+ return self.tint(Colors.primary)
170
+ } else {
171
+ // iOS 13–14: return unchanged (no tint support)
172
+ return self
173
+ }
174
+ }
175
+ }
176
+