@momo-kits/native-kits 0.167.1-duo.8-debug → 0.167.1-duo.9

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 (254) hide show
  1. package/CLAUDE.md +4 -1
  2. package/ios/Application/ApplicationEnvironment.swift +11 -0
  3. package/ios/Application/ComposeApi.swift +2 -1
  4. package/ios/Application/{Components.swift → LegacyHeader.swift} +6 -62
  5. package/ios/Application/Screen.swift +3 -2
  6. package/ios/Avatar/Avatar.swift +4 -3
  7. package/ios/Badge/Badge.swift +6 -4
  8. package/ios/Badge/BadgeDot.swift +1 -1
  9. package/ios/Badge/BadgeRibbon.swift +0 -1
  10. package/ios/Button/Button.swift +15 -12
  11. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +12 -20
  12. package/ios/Carousel/Carousel.swift +29 -12
  13. package/ios/Checkbox/Checkbox.swift +7 -3
  14. package/ios/Chip/Chip.swift +5 -4
  15. package/ios/Collapse/Collapse.swift +70 -19
  16. package/ios/Colors+Radius+Spacing/Colors.swift +9 -0
  17. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  18. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  19. package/ios/Divider/Divider.swift +37 -4
  20. package/ios/Extensions/Color++.swift +18 -0
  21. package/ios/Icon/Icon.swift +7 -1
  22. package/ios/IconButton/IconButton.swift +15 -7
  23. package/ios/Image/Image.swift +7 -34
  24. package/ios/Information/Information.swift +27 -9
  25. package/ios/Input/ErrorView.swift +2 -2
  26. package/ios/Input/Input.swift +160 -147
  27. package/ios/Input/InputPhoneNumber.swift +12 -9
  28. package/ios/Input/InputSearch.swift +102 -75
  29. package/ios/Input/InputTextArea.swift +128 -145
  30. package/ios/InputDropDown/InputDropDown.swift +10 -7
  31. package/ios/InputMoney/InputMoney.swift +33 -11
  32. package/ios/InputOTP/InputOTP.swift +22 -12
  33. package/ios/Layout/Card.swift +50 -0
  34. package/ios/Layout/GridContext.swift +123 -3
  35. package/ios/Layout/Item.swift +5 -4
  36. package/ios/Layout/Section.swift +4 -100
  37. package/ios/Loader/Loader.swift +5 -9
  38. package/ios/Lottie/LottieView.swift +10 -0
  39. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +61 -9
  40. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTab.swift +60 -34
  41. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +11 -6
  42. package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
  43. package/ios/Navigation/Component/Header.swift +493 -0
  44. package/ios/Navigation/Component/HeaderBackground.swift +134 -0
  45. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  46. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  47. package/ios/Navigation/Component/HeaderRight.swift +386 -0
  48. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  49. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +20 -23
  50. package/ios/{Application/Navigation/component → Navigation/Component}/SideBar.swift +49 -28
  51. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -5
  52. package/ios/Navigation/HostBackGestureBridge.swift +57 -0
  53. package/ios/Navigation/Navigation.swift +190 -0
  54. package/ios/{Application/Navigation → Navigation}/NavigationContainer.swift +46 -41
  55. package/ios/{Application/Navigation → Navigation}/Navigator.swift +65 -9
  56. package/ios/{Application → Navigation}/StackScreen.swift +236 -103
  57. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  58. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  59. package/ios/Pagination/PaginationScroll.swift +2 -2
  60. package/ios/Popup/PopupDisplay.swift +79 -159
  61. package/ios/Popup/PopupInput.swift +10 -13
  62. package/ios/Popup/PopupLayout.swift +97 -2
  63. package/ios/Popup/PopupNotify.swift +27 -55
  64. package/ios/Popup/PopupPromotion.swift +22 -50
  65. package/ios/PopupView/FullscreenPopup.swift +13 -19
  66. package/ios/PopupView/PopupView.swift +1 -1
  67. package/ios/PopupView/Utils++.swift +0 -8
  68. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  69. package/ios/Radio/Radio.swift +2 -1
  70. package/ios/Rating/Rating.swift +9 -1
  71. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  72. package/ios/Skeleton/Skeleton.swift +22 -27
  73. package/ios/Slider/Slider.swift +16 -18
  74. package/ios/Stepper/Stepper.swift +6 -1
  75. package/ios/Steps/Steps.swift +8 -6
  76. package/ios/SuggestAction/SuggestAction.swift +5 -0
  77. package/ios/Swipeable/SwipeCell.swift +10 -6
  78. package/ios/Switch/Switch.swift +22 -20
  79. package/ios/TabView/TabView.swift +102 -21
  80. package/ios/Tag/Tag.swift +15 -4
  81. package/ios/Template/Logo/Logo.swift +2 -1
  82. package/ios/Template/TrustBanner/TrustBanner.swift +41 -27
  83. package/ios/Title/Title.swift +6 -17
  84. package/ios/Tooltip/Tooltip.swift +29 -14
  85. package/ios/Typography/Text.swift +61 -19
  86. package/ios/Uploader/Uploader.swift +3 -1
  87. package/ios/native-kits.podspec +1 -0
  88. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  89. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  90. package/package.json +1 -1
  91. package/build.gradle.kts +0 -11
  92. package/compose/build.gradle.kts +0 -204
  93. package/compose/build.gradle.kts.backup +0 -204
  94. package/compose/compose.podspec +0 -47
  95. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  96. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  97. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  98. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  99. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
  100. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +0 -6
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  140. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  141. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  142. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  143. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  144. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  145. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  146. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
  147. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
  148. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
  149. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  150. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  151. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  152. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  153. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
  154. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  155. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  156. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  157. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
  158. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  159. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
  160. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
  161. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  162. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  163. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
  164. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  168. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
  169. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
  170. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  171. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
  172. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  173. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  174. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  175. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  176. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
  177. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
  178. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  179. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  180. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -232
  181. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  182. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  183. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  184. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  185. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  186. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  187. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  188. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
  189. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  190. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
  191. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  192. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
  193. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  194. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  195. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -708
  196. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  197. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  198. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -203
  199. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -232
  200. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  201. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +0 -98
  202. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -312
  203. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  204. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -408
  205. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -92
  206. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -105
  207. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  208. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -366
  209. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
  210. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  211. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -185
  212. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
  213. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  214. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  215. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  216. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  217. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
  218. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +0 -13
  219. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  220. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  221. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  222. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  223. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
  224. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  225. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  226. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  227. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  228. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  229. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +0 -86
  230. package/gradle/libs.versions.toml +0 -69
  231. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  232. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  233. package/gradle.properties +0 -26
  234. package/gradlew +0 -252
  235. package/gradlew.bat +0 -94
  236. package/ios/Application/HeaderRight.swift +0 -324
  237. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  238. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  239. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  240. package/ios/Application/Navigation/NavigatorManager.swift +0 -29
  241. package/ios/Application/Navigation/component/DuoScrollGradient.swift +0 -54
  242. package/ios/Application/Navigation/component/Header.swift +0 -183
  243. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -84
  244. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  245. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  246. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  247. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -280
  248. package/ios-demo/README.md +0 -120
  249. package/settings.gradle.kts +0 -64
  250. /package/ios/{Application/Layout → Layout}/ScreenLayout.swift +0 -0
  251. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
  252. /package/ios/{Application/Navigation → Navigation}/BottomTab/DuoBarCompression.swift +0 -0
  253. /package/ios/{Application/Navigation → Navigation}/BottomTab/DuoTabBar.swift +0 -0
  254. /package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +0 -0
@@ -2,7 +2,6 @@
2
2
 
3
3
  import Foundation
4
4
  import SDWebImageSwiftUI
5
- import SkeletonUI
6
5
  import SwiftUI
7
6
 
8
7
  public enum ImageState {
@@ -39,22 +38,23 @@ public struct ImageView: View {
39
38
  .resizable()
40
39
  .renderingMode(color != nil ? .template : .original)
41
40
  .placeholder {
42
- VStack(alignment: .center, content: {
41
+ VStack(alignment: .center) {
43
42
  if error {
44
- ImageErrorIcon(color: theme.colors.text.disable)
45
- } else if placeholder != nil {
43
+ Icon(source: "media_fail", color: theme.colors.text.disable)
44
+ } else if let placeholder {
46
45
  placeholder
46
+ } else if loading {
47
+ Skeleton()
47
48
  }
48
- })
49
+ }
49
50
  .frame(maxWidth: .infinity, maxHeight: .infinity)
50
- .skeleton(with: loading && !error && placeholder == nil)
51
- .shape(type: .rectangle)
52
51
  }
53
52
  .scaledToFit()
54
53
  .transition(.fade)
55
54
  .aspectRatio(aspectRatio, contentMode: contentMode)
56
55
  .foregroundColor(color)
57
56
  .onAppear { onStateChanged?(.loading) }
57
+ .onChange(of: url) { _ in error = false }
58
58
  }
59
59
 
60
60
  // MARK: Internal
@@ -69,30 +69,3 @@ public struct ImageView: View {
69
69
  @State var error: Bool = false
70
70
  var url: String
71
71
  }
72
-
73
- // A bare WebImage on purpose: going through `Icon` nests another `ImageView`, whose own
74
- // failure would render this placeholder again, without end while offline.
75
- private struct ImageErrorIcon: View {
76
- let color: Color
77
-
78
- var body: some View {
79
- WebImage(url: URL(string: iconSources["media_fail"] ?? ""))
80
- .resizable()
81
- .renderingMode(.template)
82
- .scaledToFit()
83
- .foregroundColor(color)
84
- .frame(width: 24, height: 24)
85
- }
86
- }
87
-
88
- private struct ColorMultiplyModifier: ViewModifier {
89
- let color: Color?
90
-
91
- func body(content: Content) -> some View {
92
- if let color {
93
- content.colorMultiply(color)
94
- } else {
95
- content
96
- }
97
- }
98
- }
@@ -19,6 +19,7 @@ public struct Information: View {
19
19
  private let showIconClose: Bool
20
20
  private let onPressClose: (() -> Void)?
21
21
  private let highlightText: [String]
22
+ private let accessibilityLabel: String?
22
23
 
23
24
  public init(
24
25
  title: String? = nil,
@@ -30,7 +31,8 @@ public struct Information: View {
30
31
  action: String? = nil,
31
32
  showIconClose: Bool = true,
32
33
  onPressClose: (() -> Void)? = nil,
33
- highlightText: [String] = []
34
+ highlightText: [String] = [],
35
+ accessibilityLabel: String? = nil
34
36
  ) {
35
37
  self.title = title
36
38
  self.description = description
@@ -42,6 +44,11 @@ public struct Information: View {
42
44
  self.showIconClose = showIconClose
43
45
  self.onPressClose = onPressClose
44
46
  self.highlightText = highlightText
47
+ self.accessibilityLabel = accessibilityLabel
48
+ }
49
+
50
+ private var automationId: String {
51
+ accessibilityLabel ?? "Information\(title.map { "/\($0)" } ?? "")"
45
52
  }
46
53
 
47
54
  private var borderColor: Color {
@@ -80,20 +87,23 @@ public struct Information: View {
80
87
  VStack(alignment: .leading, spacing: 0) {
81
88
  HStack(alignment: .top, spacing: 0) {
82
89
  if let image {
83
- ImageView(image)
90
+ ImageView(image, contentMode: .fill)
84
91
  .frame(width: 40, height: 40)
85
92
  .clipShape(RoundedRectangle(cornerRadius: Radius.S))
93
+ .accessibilityIdentifier("\(automationId)|img")
86
94
  .padding(.trailing, Spacing.S)
87
95
  } else if showIcon {
88
96
  Icon(source: iconSource, size: 16, color: accentColor)
97
+ .accessibilityIdentifier("\(automationId)|icon")
89
98
  .padding(.trailing, Spacing.S)
90
99
  }
91
100
 
92
101
  VStack(alignment: .leading, spacing: 0) {
93
102
  if let title {
94
- MomoText(title, typography: .labelDefaultMedium)
103
+ MomoText(title, typography: .labelDefaultMedium, accessibilityId: "\(automationId)|title-text")
95
104
  }
96
105
  MomoText(description, typography: .descriptionDefaultRegular, highlightText: highlightText)
106
+ .accessibilityIdentifier("\(automationId)|description-text")
97
107
  }
98
108
  .frame(maxWidth: .infinity, alignment: .leading)
99
109
 
@@ -101,15 +111,21 @@ public struct Information: View {
101
111
  Icon(source: "navigation_close", size: 16, color: theme.colors.text.default)
102
112
  .contentShape(Rectangle())
103
113
  .onTapGesture(perform: onPressClose)
114
+ .accessibilityAddTraits(.isButton)
115
+ .accessibilityIdentifier("\(automationId)|close-icon-touch")
104
116
  }
105
117
  }
106
118
 
107
119
  if let action, let onPressAction {
108
- MomoText(action, typography: .actionXsBold, color: accentColor)
109
- .frame(maxWidth: .infinity, alignment: .trailing)
110
- .contentShape(Rectangle())
111
- .onTapGesture(perform: onPressAction)
112
- .padding(.top, Spacing.S)
120
+ HStack(spacing: 0) {
121
+ MomoText(action, typography: .actionXsBold, color: accentColor, accessibilityId: "\(automationId)|cta-text")
122
+ }
123
+ .frame(maxWidth: .infinity, alignment: .trailing)
124
+ .contentShape(Rectangle())
125
+ .onTapGesture(perform: onPressAction)
126
+ .accessibilityElement(children: .contain)
127
+ .accessibilityIdentifier("\(automationId)|cta-touch")
128
+ .padding(.top, Spacing.S)
113
129
  }
114
130
  }
115
131
  .padding(Spacing.M)
@@ -119,8 +135,10 @@ public struct Information: View {
119
135
  )
120
136
  .overlay(
121
137
  RoundedRectangle(cornerRadius: Radius.S)
122
- .stroke(borderColor, lineWidth: 0.5)
138
+ .strokeBorder(borderColor, lineWidth: 0.5)
123
139
  )
140
+ .accessibilityElement(children: .contain)
141
+ .accessibilityIdentifier(automationId)
124
142
  }
125
143
  }
126
144
 
@@ -37,9 +37,9 @@ public struct ErrorView: View {
37
37
  }
38
38
  .padding(.top, Spacing.XS)
39
39
  } else if errorSpacing {
40
- Spacer()
41
- .padding(.top, Spacing.XS)
40
+ Color.clear
42
41
  .frame(height: 18)
42
+ .padding(.top, Spacing.XS)
43
43
  }
44
44
  }
45
45
 
@@ -37,8 +37,7 @@ public struct Input: View {
37
37
  @State private var isFocused: Bool = false
38
38
  @State private var isPasswordHidden: Bool = true
39
39
  @FocusState private var isFieldFocused: Bool
40
- @State private var floatingLabelHeight: CGFloat = 16
41
-
40
+
42
41
  public init(
43
42
  text: Binding<String>,
44
43
  placeholder: String = "",
@@ -112,142 +111,115 @@ public struct Input: View {
112
111
  }
113
112
  )
114
113
 
115
- VStack(alignment: .leading, spacing: 4) {
116
- ZStack(alignment: .topLeading) {
117
- // Floating label
118
- if !floatingValue.isEmpty || !floatingIcon.isEmpty {
119
- HStack(spacing: Spacing.XS) {
120
- MomoText(floatingValue, typography: .labelSMedium, color: getFloatingColor())
121
- if required {
122
- MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
123
- }
124
- if !floatingIcon.isEmpty {
125
- Icon(source: floatingIcon, size: 16, color: getFloatingIconColor())
126
- }
127
- }
128
- .padding(.horizontal, Spacing.S)
129
- .background(
130
- GeometryReader { geo in
131
- Color.clear.onAppear { floatingLabelHeight = geo.size.height }
132
- }
133
- )
134
- .background(theme.colors.background.surface)
135
- // Kotlin centers the label within the (box-height-tall) decorationBox
136
- // by default, then applies `offsetY = -size.values.height / 2` — net
137
- // effect: the label ends up centered exactly on the box's top border
138
- // (spanning -labelHeight/2 ... +labelHeight/2 around y=0). Our ZStack
139
- // anchors top-leading instead, so reproducing that requires offsetting
140
- // by half the label's OWN measured height, not half the box height.
141
- .offset(x: Spacing.S, y: -floatingLabelHeight / 2)
142
- .zIndex(10)
114
+ VStack(alignment: .leading, spacing: 0) {
115
+ // Input container
116
+ HStack(alignment: .center, spacing: 0) {
117
+ // Leading icon
118
+ if !leadingIcon.isEmpty {
119
+ Icon(source: leadingIcon, size: size == .small ? 24 : 32, color: leadingIconColor ?? theme.colors.text.hint)
120
+ .padding(.trailing, Spacing.M)
143
121
  }
144
-
145
- // Input container
146
- HStack(alignment: .center, spacing: 0) {
147
- // Leading icon
148
- if !leadingIcon.isEmpty {
149
- Icon(source: leadingIcon, size: size == .small ? 24 : 32, color: getLeadingIconColor())
150
- .padding(.trailing, Spacing.M)
122
+
123
+ // Text input field
124
+ ZStack(alignment: .leading) {
125
+ if text.isEmpty {
126
+ MomoText(
127
+ placeholder + (required && floatingValue.isEmpty ? " *" : ""),
128
+ typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
129
+ color: getPlaceholderColor()
130
+ )
151
131
  }
152
-
153
- // Text input field
154
- ZStack(alignment: .leading) {
155
- if text.isEmpty {
156
- MomoText(
157
- placeholder + (required && floatingValue.isEmpty ? " *" : ""),
158
- typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
159
- color: getPlaceholderColor()
160
- )
161
- }
162
-
163
- if secureTextEntry && isPasswordHidden {
164
- SecureInputField(
165
- text: $text,
166
- keyboardType: keyboardType,
167
- fontSize: fontScale(14),
168
- fontWeight: fontWeight == .bold ? .bold : .regular,
169
- textColor: UIColor(getTextColor()),
170
- cursorColor: UIColor(theme.colors.primary),
171
- isDisabled: disabled || readOnly,
172
- maxLength: maxLength,
173
- autofocus: autofocus,
174
- onFocusChange: { focused in
175
- handleFocusChange(focused)
176
- },
177
- onChangeText: onChangeText
178
- )
179
- } else {
180
- TextField("", text: textBinding, onEditingChanged: { focused in
132
+
133
+ // One UITextField for both states: swapping views on the eye toggle would drop focus.
134
+ if secureTextEntry {
135
+ SecureInputField(
136
+ text: $text,
137
+ isSecure: isPasswordHidden,
138
+ keyboardType: keyboardType,
139
+ fontSize: fontScale(14),
140
+ fontWeight: fontWeight == .bold ? .bold : .regular,
141
+ textColor: UIColor(getTextColor()),
142
+ cursorColor: UIColor(theme.colors.primary),
143
+ isDisabled: disabled || readOnly,
144
+ maxLength: maxLength,
145
+ autofocus: autofocus,
146
+ onFocusChange: { focused in
181
147
  handleFocusChange(focused)
182
- })
183
- .keyboardType(keyboardType)
184
- .font(fontWeight == .bold ? .action_s_bold : .body_default_regular)
185
- .ignoreBoldTextSetting()
186
- .foregroundColor(getTextColor())
187
- .disabled(disabled || readOnly)
188
- .applyCursorColor(theme.colors.primary)
189
- .focused($isFieldFocused)
190
- .onAppear {
191
- if autofocus { isFieldFocused = true }
192
- }
193
- // SwiftUI reads a TextField's UIKit input traits only when it
194
- // builds the backing UITextField, so a later `keyboardType`
195
- // change is silently dropped. The secure path can call
196
- // `reloadInputViews()` in `updateUIView`; here the only lever
197
- // is to force a rebuild.
198
- .id(keyboardType.rawValue)
148
+ },
149
+ onChangeText: onChangeText
150
+ )
151
+ } else {
152
+ TextField("", text: textBinding, onEditingChanged: { focused in
153
+ handleFocusChange(focused)
154
+ })
155
+ .keyboardType(keyboardType)
156
+ .font(fontScale.font(size: 14, weight: fontWeight == .bold ? .bold : .regular))
157
+ .ignoreBoldTextSetting()
158
+ .foregroundColor(getTextColor())
159
+ .disabled(disabled || readOnly)
160
+ .applyCursorColor(theme.colors.primary)
161
+ .focused($isFieldFocused)
162
+ .onAppear {
163
+ if autofocus { isFieldFocused = true }
199
164
  }
165
+ // SwiftUI reads a TextField's UIKit input traits only when it
166
+ // builds the backing UITextField, so a later `keyboardType`
167
+ // change is silently dropped. The secure path can call
168
+ // `reloadInputViews()` in `updateUIView`; here the only lever
169
+ // is to force a rebuild.
170
+ .id(keyboardType.rawValue)
200
171
  }
201
-
202
- // Clear button (only show when focused and has text)
203
- if isFocused && !text.isEmpty {
204
- SwiftUI.Button(action: {
205
- text = ""
206
- onChangeText?("")
207
- }) {
208
- Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
209
- .padding(.leading, Spacing.S).accessibility(identifier: "ic_clear")
210
- }
172
+ }
173
+
174
+ // Clear button (only show when focused and has text)
175
+ if isFocused && !text.isEmpty {
176
+ SwiftUI.Button(action: {
177
+ text = ""
178
+ onChangeText?("")
179
+ }) {
180
+ Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
181
+ .padding(.horizontal, Spacing.XS)
211
182
  }
212
-
213
- // Loading indicator
214
- if loading {
215
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
216
- .frame(width: 16, height: 16)
217
- .padding(.leading, Spacing.S)
183
+ .accessibilityIdentifier("ic_clear")
184
+ }
185
+
186
+ // Loading indicator
187
+ if loading {
188
+ ActivityIndicator(isAnimating: .constant(true), style: .medium, color: getRightIconColor())
189
+ .frame(width: 16, height: 16)
190
+ .padding(.leading, Spacing.S)
191
+ }
192
+
193
+ // Right icon: password toggle once there is text, otherwise the custom icon (as Compose)
194
+ if secureTextEntry && !text.isEmpty {
195
+ SwiftUI.Button(action: togglePasswordVisibility) {
196
+ Icon(
197
+ source: isPasswordHidden ? "24_security_eye_off" : "24_security_eye_open",
198
+ size: 24,
199
+ color: getRightIconColor()
200
+ )
201
+ .padding(.leading, Spacing.S)
218
202
  }
219
-
220
- // Right icon (password toggle or custom icon)
221
- if secureTextEntry {
222
- if !text.isEmpty {
223
- SwiftUI.Button(action: togglePasswordVisibility) {
224
- Icon(
225
- source: isPasswordHidden ? "24_security_eye_off" : "24_security_eye_open",
226
- size: 24,
227
- color: getRightIconColor()
228
- )
229
- .padding(.leading, Spacing.S).accessibility(identifier: "ic_show_hide")
230
- }
231
- }
232
- } else if !rightIcon.isEmpty {
233
- SwiftUI.Button(action: { onRightIconPressed?() }) {
234
- Icon(source: rightIcon, size: 24, color: getRightIconColor())
235
- .padding(.leading, Spacing.S)
236
- }
203
+ .accessibilityIdentifier("ic_show_hide")
204
+ } else if !rightIcon.isEmpty {
205
+ SwiftUI.Button(action: { onRightIconPressed?() }) {
206
+ Icon(source: rightIcon, size: 24, color: getRightIconColor())
207
+ .padding(.leading, Spacing.S)
237
208
  }
238
209
  }
239
- .padding(.horizontal, Spacing.M)
240
- .frame(height: fontScale(size == .small ? 48 : 56))
241
- .background(
242
- RoundedRectangle(cornerRadius: Radius.S)
243
- .fill(theme.colors.background.surface)
244
- )
245
- .overlay(
246
- RoundedRectangle(cornerRadius: Radius.S)
247
- .stroke(borderColor(), lineWidth: 1)
248
- )
249
210
  }
250
-
211
+ .padding(.horizontal, Spacing.M)
212
+ .frame(height: fontScale(size == .small ? 48 : 56))
213
+ .background(
214
+ RoundedRectangle(cornerRadius: Radius.S)
215
+ .fill(theme.colors.background.surface)
216
+ )
217
+ .overlay(
218
+ RoundedRectangle(cornerRadius: Radius.S)
219
+ .stroke(borderColor(), lineWidth: 1)
220
+ )
221
+ .overlay(alignment: .topLeading) { floatingLabel }
222
+
251
223
  // Error or hint
252
224
  ErrorView(
253
225
  errorMessage: error,
@@ -263,10 +235,33 @@ public struct Input: View {
263
235
  }
264
236
  }
265
237
  }
266
-
238
+
239
+ /// Centered on the box's top border, like Compose's `FloatingLabel` (offset by -height / 2).
240
+ @ViewBuilder
241
+ private var floatingLabel: some View {
242
+ if !floatingValue.isEmpty || !floatingIcon.isEmpty {
243
+ HStack(spacing: 0) {
244
+ MomoText(floatingValue, typography: .labelSMedium, color: getFloatingColor())
245
+ if required {
246
+ MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
247
+ }
248
+ if !floatingIcon.isEmpty {
249
+ Icon(source: floatingIcon, size: 16, color: getFloatingIconColor())
250
+ .padding(.leading, Spacing.XS)
251
+ }
252
+ }
253
+ .padding(.horizontal, Spacing.S)
254
+ .background(theme.colors.background.surface)
255
+ .alignmentGuide(.top) { $0[VerticalAlignment.center] }
256
+ .offset(x: Spacing.S)
257
+ }
258
+ }
259
+
267
260
  // MARK: - Helpers
268
-
261
+
269
262
  private func handleFocusChange(_ focused: Bool) {
263
+ // The keyboard-hide fallback can report a blur the field already reported.
264
+ guard focused != isFocused else { return }
270
265
  isFocused = focused
271
266
  if focused {
272
267
  onFocus?()
@@ -316,10 +311,6 @@ public struct Input: View {
316
311
  return disabled ? theme.colors.text.disable : (floatingIconColor ?? theme.colors.text.default)
317
312
  }
318
313
 
319
- private func getLeadingIconColor() -> Color {
320
- return disabled ? theme.colors.text.disable : (leadingIconColor ?? theme.colors.text.hint)
321
- }
322
-
323
314
  private func getRightIconColor() -> Color {
324
315
  return disabled ? theme.colors.text.disable : (rightIconColor ?? theme.colors.text.default)
325
316
  }
@@ -329,6 +320,7 @@ public struct Input: View {
329
320
  // MARK: - Secure Input Field (UIViewRepresentable)
330
321
  private struct SecureInputField: UIViewRepresentable {
331
322
  @Binding var text: String
323
+ var isSecure: Bool
332
324
  var keyboardType: UIKeyboardType
333
325
  var fontSize: CGFloat
334
326
  var fontWeight: UIFont.Weight
@@ -346,7 +338,11 @@ private struct SecureInputField: UIViewRepresentable {
346
338
 
347
339
  func makeUIView(context: Context) -> UITextField {
348
340
  let textField = UITextField()
349
- textField.isSecureTextEntry = true
341
+ textField.isSecureTextEntry = isSecure
342
+ // Stays a password field while revealed: keep it out of autocorrect / QuickType learning.
343
+ textField.autocorrectionType = .no
344
+ textField.autocapitalizationType = .none
345
+ textField.spellCheckingType = .no
350
346
  textField.delegate = context.coordinator
351
347
  textField.keyboardType = keyboardType
352
348
  textField.font = UIFont.systemFont(ofSize: fontSize, weight: fontWeight)
@@ -374,6 +370,10 @@ private struct SecureInputField: UIViewRepresentable {
374
370
  if textField.text != text {
375
371
  textField.text = text
376
372
  }
373
+ let font = UIFont.systemFont(ofSize: fontSize, weight: fontWeight)
374
+ if textField.font != font {
375
+ textField.font = font
376
+ }
377
377
  textField.isEnabled = !isDisabled
378
378
  textField.textColor = textColor
379
379
  textField.tintColor = cursorColor
@@ -381,6 +381,12 @@ private struct SecureInputField: UIViewRepresentable {
381
381
  textField.keyboardType = keyboardType
382
382
  textField.reloadInputViews()
383
383
  }
384
+ if textField.isSecureTextEntry != isSecure {
385
+ textField.isSecureTextEntry = isSecure
386
+ if textField.isFirstResponder {
387
+ context.coordinator.reinsertText(in: textField)
388
+ }
389
+ }
384
390
  }
385
391
 
386
392
  class Coordinator: NSObject, UITextFieldDelegate {
@@ -391,19 +397,21 @@ private struct SecureInputField: UIViewRepresentable {
391
397
  self.parent = parent
392
398
  }
393
399
 
394
- func textFieldDidBeginEditing(_ textField: UITextField) {
400
+ // When a secure text field regains focus (or turns secure while editing),
401
+ // iOS marks the existing text as "committed" and the next keystroke would
402
+ // replace ALL of it. Re-inserting via insertText() puts it back into an
403
+ // "active editing" state (and moves the caret to the end).
404
+ func reinsertText(in textField: UITextField) {
395
405
  let existingText = textField.text ?? ""
396
- if !existingText.isEmpty {
397
- // When a secure text field regains focus, iOS marks the
398
- // existing text as "committed". The next keystroke would
399
- // replace ALL committed text with the new character.
400
- // Prevent this by clearing and re-inserting via insertText(),
401
- // which puts the text into an "active editing" state.
402
- isResettingText = true
403
- textField.text = ""
404
- textField.insertText(existingText)
405
- isResettingText = false
406
- }
406
+ guard !existingText.isEmpty else { return }
407
+ isResettingText = true
408
+ textField.text = ""
409
+ textField.insertText(existingText)
410
+ isResettingText = false
411
+ }
412
+
413
+ func textFieldDidBeginEditing(_ textField: UITextField) {
414
+ reinsertText(in: textField)
407
415
  parent.onFocusChange(true)
408
416
  }
409
417
 
@@ -460,12 +468,17 @@ private extension View {
460
468
  struct ActivityIndicator: UIViewRepresentable {
461
469
  @Binding var isAnimating: Bool
462
470
  let style: UIActivityIndicatorView.Style
471
+ /// Compose tints the spinner with the right-icon color; nil keeps the system gray.
472
+ var color: Color? = nil
463
473
 
464
474
  func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
465
475
  return UIActivityIndicatorView(style: style)
466
476
  }
467
477
 
468
478
  func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
479
+ if let color {
480
+ uiView.color = UIColor(color)
481
+ }
469
482
  isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
470
483
  }
471
484
  }
@@ -76,16 +76,15 @@ public struct InputPhoneNumber: View {
76
76
  }
77
77
  )
78
78
 
79
- VStack(alignment: .leading, spacing: 4) {
79
+ VStack(alignment: .leading, spacing: 0) {
80
80
  HStack(spacing: 0) {
81
81
  // 🇻🇳 Flag
82
82
  ImageView("https://static.momocdn.net/app/img/icon/ic-qrcode-package/ic_vn_flag.png")
83
83
  .frame(width: 24, height: 24)
84
84
  .padding(.trailing, Spacing.XS)
85
-
86
- MomoText("+84", typography: size == .small ? .headerSSemibold : .headerMBold)
87
- .foregroundColor(theme.colors.text.default)
88
-
85
+
86
+ MomoText("+84", typography: size == .small ? .headerSSemibold : .headerMBold, color: theme.colors.text.default)
87
+
89
88
  Rectangle()
90
89
  .fill(theme.colors.border.default)
91
90
  .frame(width: 1, height: size == .small ? 24 : 32)
@@ -105,7 +104,9 @@ public struct InputPhoneNumber: View {
105
104
  handleFocusChange(focused)
106
105
  })
107
106
  .keyboardType(.numberPad)
108
- .font(size == .small ? .header_s_semibold : .header_m_bold)
107
+ .font(size == .small
108
+ ? fontScale.font(size: 14, weight: .semibold)
109
+ : fontScale.font(size: 18, weight: .bold))
109
110
  .ignoreBoldTextSetting()
110
111
  .foregroundColor(theme.colors.text.default)
111
112
  .applyCursorColor(theme.colors.primary)
@@ -125,13 +126,14 @@ public struct InputPhoneNumber: View {
125
126
  onChangeText?("")
126
127
  }) {
127
128
  Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
128
- .padding(.leading, Spacing.S).accessibility(identifier: "ic_clear")
129
+ .padding(.horizontal, Spacing.S)
129
130
  }
131
+ .accessibilityIdentifier("ic_clear")
130
132
  }
131
-
133
+
132
134
  // Loading indicator
133
135
  if loading {
134
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
136
+ ActivityIndicator(isAnimating: .constant(true), style: .medium, color: rightIconColor ?? theme.colors.text.default)
135
137
  .frame(width: 16, height: 16)
136
138
  }
137
139
 
@@ -166,6 +168,7 @@ public struct InputPhoneNumber: View {
166
168
  // MARK: - Helpers
167
169
 
168
170
  private func handleFocusChange(_ focused: Bool) {
171
+ guard focused != isFocused else { return }
169
172
  isFocused = focused
170
173
  if focused {
171
174
  onFocus?()