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

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/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +204 -0
  4. package/compose/build.gradle.kts.backup +204 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +19 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +124 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +293 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +831 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +110 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +16 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +18 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +297 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +232 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +106 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +205 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +393 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +708 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +209 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +231 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +408 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +92 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +366 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +37 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +185 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +127 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
  134. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +40 -0
  135. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  136. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  137. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  138. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  139. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
  140. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +86 -0
  141. package/gradle/libs.versions.toml +69 -0
  142. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  143. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  144. package/gradle.properties +26 -0
  145. package/gradlew +252 -0
  146. package/gradlew.bat +94 -0
  147. package/ios/Application/ApplicationEnvironment.swift +11 -0
  148. package/ios/Application/ComposeApi.swift +2 -1
  149. package/ios/Application/{Components.swift → LegacyHeader.swift} +6 -62
  150. package/ios/Application/Screen.swift +3 -2
  151. package/ios/Avatar/Avatar.swift +4 -3
  152. package/ios/Badge/Badge.swift +6 -4
  153. package/ios/Badge/BadgeDot.swift +1 -1
  154. package/ios/Badge/BadgeRibbon.swift +0 -1
  155. package/ios/Button/Button.swift +15 -12
  156. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +12 -20
  157. package/ios/Carousel/Carousel.swift +29 -12
  158. package/ios/Checkbox/Checkbox.swift +7 -3
  159. package/ios/Chip/Chip.swift +5 -4
  160. package/ios/Collapse/Collapse.swift +70 -19
  161. package/ios/Colors+Radius+Spacing/Colors.swift +9 -0
  162. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  163. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  164. package/ios/Divider/Divider.swift +37 -4
  165. package/ios/Extensions/Color++.swift +18 -0
  166. package/ios/Icon/Icon.swift +7 -1
  167. package/ios/IconButton/IconButton.swift +15 -7
  168. package/ios/Image/Image.swift +7 -34
  169. package/ios/Information/Information.swift +27 -9
  170. package/ios/Input/ErrorView.swift +2 -2
  171. package/ios/Input/Input.swift +160 -147
  172. package/ios/Input/InputPhoneNumber.swift +12 -9
  173. package/ios/Input/InputSearch.swift +102 -75
  174. package/ios/Input/InputTextArea.swift +128 -145
  175. package/ios/InputDropDown/InputDropDown.swift +10 -7
  176. package/ios/InputMoney/InputMoney.swift +33 -11
  177. package/ios/InputOTP/InputOTP.swift +22 -12
  178. package/ios/Layout/Card.swift +50 -0
  179. package/ios/Layout/GridContext.swift +123 -3
  180. package/ios/Layout/Item.swift +5 -4
  181. package/ios/Layout/Section.swift +4 -100
  182. package/ios/Loader/Loader.swift +5 -9
  183. package/ios/Lottie/LottieView.swift +10 -0
  184. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +61 -9
  185. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTab.swift +60 -34
  186. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +11 -6
  187. package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
  188. package/ios/Navigation/Component/Header.swift +493 -0
  189. package/ios/Navigation/Component/HeaderBackground.swift +134 -0
  190. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  191. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  192. package/ios/Navigation/Component/HeaderRight.swift +386 -0
  193. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  194. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +20 -23
  195. package/ios/{Application/Navigation/component → Navigation/Component}/SideBar.swift +49 -28
  196. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -5
  197. package/ios/Navigation/HostBackGestureBridge.swift +57 -0
  198. package/ios/Navigation/Navigation.swift +190 -0
  199. package/ios/{Application/Navigation → Navigation}/NavigationContainer.swift +46 -41
  200. package/ios/{Application/Navigation → Navigation}/Navigator.swift +65 -9
  201. package/ios/{Application → Navigation}/StackScreen.swift +236 -103
  202. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  203. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  204. package/ios/Pagination/PaginationScroll.swift +2 -2
  205. package/ios/Popup/PopupDisplay.swift +79 -159
  206. package/ios/Popup/PopupInput.swift +10 -13
  207. package/ios/Popup/PopupLayout.swift +97 -2
  208. package/ios/Popup/PopupNotify.swift +27 -55
  209. package/ios/Popup/PopupPromotion.swift +22 -50
  210. package/ios/PopupView/FullscreenPopup.swift +13 -19
  211. package/ios/PopupView/PopupView.swift +1 -1
  212. package/ios/PopupView/Utils++.swift +0 -8
  213. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  214. package/ios/Radio/Radio.swift +2 -1
  215. package/ios/Rating/Rating.swift +9 -1
  216. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  217. package/ios/Skeleton/Skeleton.swift +22 -27
  218. package/ios/Slider/Slider.swift +16 -18
  219. package/ios/Stepper/Stepper.swift +6 -1
  220. package/ios/Steps/Steps.swift +8 -6
  221. package/ios/SuggestAction/SuggestAction.swift +5 -0
  222. package/ios/Swipeable/SwipeCell.swift +10 -6
  223. package/ios/Switch/Switch.swift +22 -20
  224. package/ios/TabView/TabView.swift +102 -21
  225. package/ios/Tag/Tag.swift +15 -4
  226. package/ios/Template/Logo/Logo.swift +2 -1
  227. package/ios/Template/TrustBanner/TrustBanner.swift +41 -27
  228. package/ios/Title/Title.swift +6 -17
  229. package/ios/Tooltip/Tooltip.swift +29 -14
  230. package/ios/Typography/Text.swift +61 -19
  231. package/ios/Uploader/Uploader.swift +3 -1
  232. package/ios/native-kits.podspec +1 -0
  233. package/ios-demo/README.md +120 -0
  234. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  235. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  236. package/package.json +1 -1
  237. package/settings.gradle.kts +64 -0
  238. package/ios/Application/HeaderRight.swift +0 -324
  239. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  240. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  241. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  242. package/ios/Application/Navigation/NavigatorManager.swift +0 -29
  243. package/ios/Application/Navigation/component/DuoScrollGradient.swift +0 -54
  244. package/ios/Application/Navigation/component/Header.swift +0 -183
  245. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -84
  246. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  247. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  248. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  249. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -280
  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
@@ -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?()