@momo-kits/native-kits 0.167.1-duo.2-debug → 0.167.1-duo.20

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 (266) hide show
  1. package/CLAUDE.md +36 -8
  2. package/README.md +2 -2
  3. package/ios/Application/ApplicationEnvironment.swift +17 -0
  4. package/ios/Application/MaxApi.swift +12 -3
  5. package/ios/Avatar/Avatar.swift +4 -3
  6. package/ios/Badge/Badge.swift +6 -4
  7. package/ios/Badge/BadgeDot.swift +1 -1
  8. package/ios/Badge/BadgeRibbon.swift +0 -1
  9. package/ios/Button/Button.swift +15 -12
  10. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  11. package/ios/Carousel/Carousel.swift +29 -12
  12. package/ios/Checkbox/Checkbox.swift +7 -3
  13. package/ios/Chip/Chip.swift +5 -4
  14. package/ios/Collapse/Collapse.swift +70 -19
  15. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  16. package/ios/Colors+Radius+Spacing/Radius.swift +6 -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 +38 -9
  21. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  22. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  23. package/ios/Icon/Icon.swift +21 -23
  24. package/ios/Icon/Icons.swift +1329 -0
  25. package/ios/IconButton/IconButton.swift +15 -7
  26. package/ios/Image/Image.swift +7 -23
  27. package/ios/Information/Information.swift +27 -9
  28. package/ios/Input/ErrorView.swift +2 -2
  29. package/ios/Input/Input.swift +160 -147
  30. package/ios/Input/InputPhoneNumber.swift +12 -9
  31. package/ios/Input/InputSearch.swift +102 -75
  32. package/ios/Input/InputTextArea.swift +128 -145
  33. package/ios/InputDropDown/InputDropDown.swift +10 -7
  34. package/ios/InputMoney/InputMoney.swift +33 -11
  35. package/ios/InputOTP/InputOTP.swift +22 -12
  36. package/ios/Layout/Card.swift +50 -0
  37. package/ios/Layout/GridContext.swift +187 -3
  38. package/ios/Layout/Item.swift +5 -4
  39. package/ios/Layout/ScreenLayout.swift +380 -0
  40. package/ios/Layout/Section.swift +4 -100
  41. package/ios/Loader/Loader.swift +5 -9
  42. package/ios/Lottie/LottieView.swift +14 -2
  43. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +128 -9
  44. package/ios/Navigation/BottomTab/BottomTab.swift +185 -0
  45. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
  46. package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
  47. package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
  48. package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
  49. package/ios/Navigation/Component/Header.swift +531 -0
  50. package/ios/Navigation/Component/HeaderBackground.swift +133 -0
  51. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  52. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  53. package/ios/Navigation/Component/HeaderRight.swift +377 -0
  54. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  55. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  56. package/ios/Navigation/Component/SideBar.swift +183 -0
  57. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +5 -6
  58. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  59. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -2
  60. package/ios/Navigation/Navigation.swift +204 -0
  61. package/ios/Navigation/NavigationContainer.swift +194 -0
  62. package/ios/{Application/Navigation → Navigation}/Navigator.swift +177 -91
  63. package/ios/Navigation/StackScreen.swift +820 -0
  64. package/ios/Navigation/StackTransition.swift +400 -0
  65. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  66. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  67. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  68. package/ios/Pagination/PaginationScroll.swift +2 -2
  69. package/ios/Popup/PopupDisplay.swift +72 -136
  70. package/ios/Popup/PopupInput.swift +10 -13
  71. package/ios/Popup/PopupLayout.swift +140 -0
  72. package/ios/Popup/PopupNotify.swift +56 -73
  73. package/ios/Popup/PopupPromotion.swift +31 -47
  74. package/ios/PopupView/FullscreenPopup.swift +13 -19
  75. package/ios/PopupView/PopupView.swift +1 -1
  76. package/ios/PopupView/Utils++.swift +0 -8
  77. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  78. package/ios/Radio/Radio.swift +2 -1
  79. package/ios/Rating/Rating.swift +9 -1
  80. package/ios/Resources/dot_loading.json +558 -0
  81. package/ios/Resources/lottie_circle_loader.json +1 -0
  82. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  83. package/ios/Skeleton/Skeleton.swift +22 -27
  84. package/ios/Slider/Slider.swift +16 -18
  85. package/ios/Stepper/Stepper.swift +6 -1
  86. package/ios/Steps/Steps.swift +8 -6
  87. package/ios/SuggestAction/SuggestAction.swift +5 -0
  88. package/ios/Swipeable/SwipeCell.swift +10 -6
  89. package/ios/Switch/Switch.swift +22 -20
  90. package/ios/TabView/TabView.swift +102 -21
  91. package/ios/Tag/Tag.swift +15 -4
  92. package/ios/Template/Logo/Logo.swift +2 -1
  93. package/ios/Template/TrustBanner/TrustBanner.swift +106 -54
  94. package/ios/Title/Title.swift +6 -17
  95. package/ios/Tooltip/Tooltip.swift +29 -14
  96. package/ios/Typography/Text.swift +61 -19
  97. package/ios/Uploader/Uploader.swift +3 -1
  98. package/ios/native-kits.podspec +2 -1
  99. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  100. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  101. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  102. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +1 -0
  103. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  104. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  105. package/package.json +1 -1
  106. package/scripts/gen-ios-icons.mjs +36 -0
  107. package/build.gradle.kts +0 -11
  108. package/compose/build.gradle.kts +0 -190
  109. package/compose/build.gradle.kts.backup +0 -190
  110. package/compose/compose.podspec +0 -47
  111. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  112. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  113. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  114. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  115. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -151
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  140. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  141. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  142. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  143. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  144. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
  145. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  146. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  147. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  148. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
  149. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  150. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
  151. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
  152. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  153. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
  154. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  155. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  156. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  157. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  158. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  159. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  160. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  161. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -334
  162. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -98
  163. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
  164. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  168. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
  169. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  170. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  171. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  172. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
  173. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  174. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
  175. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
  176. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  177. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  178. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
  179. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  180. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  181. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  182. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  183. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
  184. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
  185. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  186. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
  187. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  188. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  189. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  190. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  191. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
  192. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
  193. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  194. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  195. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -157
  196. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  197. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  198. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  199. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  200. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  201. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  202. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  203. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -299
  204. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  205. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -129
  206. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  207. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
  208. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  209. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  210. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -622
  211. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  212. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  213. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -175
  214. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -232
  215. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  216. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  217. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -381
  218. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -83
  219. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -105
  220. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  221. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -347
  222. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
  223. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  224. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -169
  225. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
  226. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  227. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  228. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  229. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  230. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -71
  231. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  232. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  233. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  234. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  235. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
  236. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  237. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  238. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  239. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  240. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -213
  241. package/gradle/libs.versions.toml +0 -69
  242. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  243. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  244. package/gradle.properties +0 -26
  245. package/gradlew +0 -252
  246. package/gradlew.bat +0 -94
  247. package/ios/Application/Components.swift +0 -270
  248. package/ios/Application/ComposeApi.swift +0 -22
  249. package/ios/Application/HeaderRight.swift +0 -306
  250. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  251. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  252. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  253. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  254. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  255. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  256. package/ios/Application/Navigation/component/Header.swift +0 -178
  257. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  258. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  259. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  260. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  261. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  262. package/ios/Application/Screen.swift +0 -250
  263. package/ios/Application/StackScreen.swift +0 -492
  264. package/ios-demo/README.md +0 -120
  265. package/settings.gradle.kts +0 -64
  266. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -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?()
@@ -18,16 +18,17 @@ struct RenderRightIconSearch: View {
18
18
  var body: some View {
19
19
  HStack(spacing: 0) {
20
20
  if loading {
21
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
21
+ ActivityIndicator(isAnimating: .constant(true), style: .medium, color: color)
22
22
  .frame(width: 16, height: 16)
23
23
  .padding(.horizontal, Spacing.S)
24
24
  }
25
25
  if !icon.isEmpty {
26
- Divider()
27
- .frame(width: 1)
28
- .background(theme.colors.primary)
26
+ // Compose: a 1pt rule inset S from the top and bottom of the 36pt bar.
27
+ Rectangle()
28
+ .fill(theme.colors.primary)
29
+ .frame(width: 1, height: 36 - Spacing.S * 2)
29
30
  .padding(.leading, Spacing.S)
30
-
31
+
31
32
  Icon(source: icon, size: 24, color: color)
32
33
  .padding(.leading, Spacing.S)
33
34
  .onTapGesture(perform: onClick)
@@ -58,6 +59,10 @@ public struct InputSearchProps {
58
59
  var searchIcon: (() -> AnyView)? = nil
59
60
  // Mirrors Compose's InputSearchProps.placeholderCustomRender — a custom placeholder view.
60
61
  var placeholderCustomRender: (() -> AnyView)? = nil
62
+ var backgroundColor: Color? = nil
63
+ var clearCondition: (() -> Bool)? = nil
64
+ var onClearPress: () -> Void = {}
65
+ var onSubmit: () -> Void = {}
61
66
 
62
67
  public init(text: Binding<String>,
63
68
  buttonText: String = "",
@@ -77,7 +82,11 @@ public struct InputSearchProps {
77
82
  fontWeight: Font.Weight = .regular,
78
83
  keyboardType: UIKeyboardType = .default,
79
84
  searchIcon: (() -> AnyView)? = nil,
80
- placeholderCustomRender: (() -> AnyView)? = nil) {
85
+ placeholderCustomRender: (() -> AnyView)? = nil,
86
+ backgroundColor: Color? = nil,
87
+ clearCondition: (() -> Bool)? = nil,
88
+ onClearPress: @escaping () -> Void = {},
89
+ onSubmit: @escaping () -> Void = {}) {
81
90
  self._text = text
82
91
  self.buttonText = buttonText
83
92
  self.showButtonText = showButtonText
@@ -97,6 +106,10 @@ public struct InputSearchProps {
97
106
  self.keyboardType = keyboardType
98
107
  self.searchIcon = searchIcon
99
108
  self.placeholderCustomRender = placeholderCustomRender
109
+ self.backgroundColor = backgroundColor
110
+ self.clearCondition = clearCondition
111
+ self.onClearPress = onClearPress
112
+ self.onSubmit = onSubmit
100
113
  }
101
114
  }
102
115
 
@@ -124,6 +137,10 @@ public struct InputSearch: View {
124
137
  var keyboardType: UIKeyboardType = .default
125
138
  var searchIcon: (() -> AnyView)? = nil
126
139
  var placeholderCustomRender: (() -> AnyView)? = nil
140
+ var backgroundColor: Color? = nil
141
+ var clearCondition: (() -> Bool)? = nil
142
+ var onClearPress: () -> Void = {}
143
+ var onSubmit: () -> Void = {}
127
144
 
128
145
  @State private var isFocused = false
129
146
  @State private var isBlurred = false
@@ -146,7 +163,11 @@ public struct InputSearch: View {
146
163
  fontWeight: Font.Weight = .regular,
147
164
  keyboardType: UIKeyboardType = .default,
148
165
  searchIcon: (() -> AnyView)? = nil,
149
- placeholderCustomRender: (() -> AnyView)? = nil) {
166
+ placeholderCustomRender: (() -> AnyView)? = nil,
167
+ backgroundColor: Color? = nil,
168
+ clearCondition: (() -> Bool)? = nil,
169
+ onClearPress: @escaping () -> Void = {},
170
+ onSubmit: @escaping () -> Void = {}) {
150
171
  self._text = text
151
172
  self.buttonText = buttonText
152
173
  self.showButtonText = showButtonText
@@ -166,6 +187,10 @@ public struct InputSearch: View {
166
187
  self.keyboardType = keyboardType
167
188
  self.searchIcon = searchIcon
168
189
  self.placeholderCustomRender = placeholderCustomRender
190
+ self.backgroundColor = backgroundColor
191
+ self.clearCondition = clearCondition
192
+ self.onClearPress = onClearPress
193
+ self.onSubmit = onSubmit
169
194
  }
170
195
 
171
196
  /// Convenience initializer mirroring Compose's `InputSearch(inputSearchProps)`.
@@ -189,88 +214,90 @@ public struct InputSearch: View {
189
214
  fontWeight: props.fontWeight,
190
215
  keyboardType: props.keyboardType,
191
216
  searchIcon: props.searchIcon,
192
- placeholderCustomRender: props.placeholderCustomRender
217
+ placeholderCustomRender: props.placeholderCustomRender,
218
+ backgroundColor: props.backgroundColor,
219
+ clearCondition: props.clearCondition,
220
+ onClearPress: props.onClearPress,
221
+ onSubmit: props.onSubmit
193
222
  )
194
223
  }
195
224
 
196
225
 
197
226
  public var body: some View {
198
227
  let iconColor: Color = disabled ? theme.colors.text.disable : (self.iconColor ?? theme.colors.text.default)
199
- VStack(alignment: .leading, spacing: 0) {
228
+ HStack(spacing: 0) {
200
229
  HStack(spacing: 0) {
201
- HStack {
202
- if let searchIcon = searchIcon {
203
- searchIcon()
204
- } else {
205
- Image(systemName: "magnifyingglass")
206
- .foregroundColor(theme.colors.text.hint)
207
- .frame(width: 24, height: 24)
208
- .padding(.trailing, Spacing.XS)
209
- }
230
+ if let searchIcon = searchIcon {
231
+ searchIcon()
232
+ } else {
233
+ Icon(source: "navigation_search", size: 24, color: theme.colors.text.hint)
234
+ .padding(.trailing, Spacing.XS)
235
+ }
210
236
 
211
- ZStack(alignment: .leading) {
212
- if text.isEmpty {
213
- if let placeholderCustomRender = placeholderCustomRender {
214
- placeholderCustomRender()
215
- } else {
216
- Text(placeholder)
217
- .foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.hint)
218
- .font(.system(size: fontScale(14), weight: fontWeight))
219
- .ignoreBoldTextSetting()
220
- }
237
+ ZStack(alignment: .leading) {
238
+ if text.isEmpty {
239
+ if let placeholderCustomRender = placeholderCustomRender {
240
+ placeholderCustomRender()
241
+ } else {
242
+ MomoText(
243
+ placeholder,
244
+ typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
245
+ color: disabled ? theme.colors.text.disable : theme.colors.text.hint,
246
+ maxLines: 1
247
+ )
221
248
  }
222
- TextField("", text: $text, onEditingChanged: { editing in
223
- isFocused = editing
224
- if editing {
225
- onFocus()
226
- } else if isBlurred {
227
- onBlur()
228
- }
229
- if editing && !isBlurred {
230
- isBlurred = true
231
- }
232
- }, onCommit: {})
233
- .disabled(disabled)
234
- .foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.default)
235
- .font(.system(size: fontScale(14), weight: fontWeight))
236
- .ignoreBoldTextSetting()
237
- .keyboardType(keyboardType)
238
- .applyCursorColor(theme.colors.primary)
239
249
  }
240
-
241
- if isFocused && !text.isEmpty {
242
- SwiftButton(action: {
243
- text = ""
244
- // Kotlin's clear icon fires a callback distinct from
245
- // onChangeText (`onClearPress`, not ported here); route
246
- // through onChangeText so clearing isn't a silent no-op.
247
- onChangeText("")
248
- }) {
249
- Image(systemName: "xmark.circle.fill")
250
- .foregroundColor(theme.colors.text.hint)
251
- .frame(width: 16, height: 16)
250
+ TextField("", text: $text, onEditingChanged: { editing in
251
+ isFocused = editing
252
+ if editing {
253
+ onFocus()
254
+ } else if isBlurred {
255
+ onBlur()
252
256
  }
253
- .padding(.leading, Spacing.S)
254
- }
255
-
256
- RenderRightIconSearch(loading: loading, icon: icon, color: iconColor, onClick: onRightIconPressed)
257
+ if editing && !isBlurred {
258
+ isBlurred = true
259
+ }
260
+ }, onCommit: onSubmit)
261
+ .disabled(disabled)
262
+ .foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.default)
263
+ .font(.system(size: fontScale(14), weight: fontWeight))
264
+ .ignoreBoldTextSetting()
265
+ .keyboardType(keyboardType)
266
+ .applyCursorColor(theme.colors.primary)
257
267
  }
258
- .padding(.horizontal, Spacing.M)
259
- .padding(.vertical, Spacing.S)
260
- .background(theme.colors.background.surface)
261
- .cornerRadius(Radius.XL)
262
- .overlay(
263
- RoundedRectangle(cornerRadius: Radius.XL)
264
- .stroke(getBorderColor(isFocused: isFocused, error: error, disabled: disabled), lineWidth: showBorder ? 1 : 0)
265
- )
266
- .frame(maxWidth: showButtonText ? .infinity : nil, alignment: .leading)
267
-
268
- if showButtonText {
269
- SwiftButton(action: onPressButtonText) {
270
- MomoText(buttonText, typography: .actionDefaultBold, color: theme.colors.text.default)
268
+
269
+ if clearCondition?() == true || (isFocused && !text.isEmpty) {
270
+ SwiftButton(action: {
271
+ text = ""
272
+ // Compose only fires onClearPress; onChangeText("") is kept so existing
273
+ // SwiftUI callers that filter on it still see the clear.
274
+ onChangeText("")
275
+ onClearPress()
276
+ }) {
277
+ Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
271
278
  }
272
- .padding(.leading, Spacing.L)
279
+ .padding(.leading, Spacing.XS)
280
+ }
281
+
282
+ RenderRightIconSearch(loading: loading, icon: icon, color: iconColor, onClick: onRightIconPressed)
283
+ }
284
+ .padding(.horizontal, Spacing.M)
285
+ .frame(maxWidth: .infinity, alignment: .leading)
286
+ .frame(height: 36)
287
+ .background(
288
+ RoundedRectangle(cornerRadius: Radius.XL)
289
+ .fill(backgroundColor ?? theme.colors.background.surface)
290
+ )
291
+ .overlay(
292
+ RoundedRectangle(cornerRadius: Radius.XL)
293
+ .stroke(showBorder ? getBorderColor(isFocused: isFocused, error: error, disabled: disabled) : .clear, lineWidth: 1)
294
+ )
295
+
296
+ if showButtonText {
297
+ SwiftButton(action: onPressButtonText) {
298
+ MomoText(buttonText, typography: .actionDefaultBold, color: theme.colors.text.default)
273
299
  }
300
+ .padding(.leading, Spacing.L)
274
301
  }
275
302
  }
276
303
  }
@@ -16,22 +16,24 @@ struct UITextViewWrapper: UIViewRepresentable {
16
16
  var textColor: Color
17
17
  var cursorColor: Color
18
18
  var fontWeight: Font.Weight
19
+ var keyboardType: UIKeyboardType = .default
19
20
  var disabled: Bool
20
21
  var onChangeText: (String) -> Void
21
- var onDone: (() -> Void)?
22
22
  var onFocus: (() -> Void)?
23
23
  var onBlur: (() -> Void)?
24
-
24
+
25
25
  func makeUIView(context: Context) -> UITextView {
26
26
  let textView = UITextView()
27
27
  textView.delegate = context.coordinator
28
28
  textView.backgroundColor = .clear
29
29
  textView.textContainerInset = .zero
30
30
  textView.textContainer.lineFragmentPadding = 0
31
+ textView.keyboardType = keyboardType
31
32
  return textView
32
33
  }
33
-
34
+
34
35
  func updateUIView(_ uiView: UITextView, context: Context) {
36
+ context.coordinator.parent = self
35
37
  if uiView.text != text {
36
38
  uiView.text = text
37
39
  }
@@ -44,19 +46,23 @@ struct UITextViewWrapper: UIViewRepresentable {
44
46
  weight: fontWeight == .bold ? .bold : .regular
45
47
  )
46
48
  uiView.isEditable = !disabled
49
+ if uiView.keyboardType != keyboardType {
50
+ uiView.keyboardType = keyboardType
51
+ uiView.reloadInputViews()
52
+ }
47
53
  }
48
-
54
+
49
55
  func makeCoordinator() -> Coordinator {
50
56
  Coordinator(self)
51
57
  }
52
-
58
+
53
59
  class Coordinator: NSObject, UITextViewDelegate {
54
60
  var parent: UITextViewWrapper
55
-
61
+
56
62
  init(_ parent: UITextViewWrapper) {
57
63
  self.parent = parent
58
64
  }
59
-
65
+
60
66
  func textViewDidChange(_ textView: UITextView) {
61
67
  let limited = String(textView.text.prefix(parent.maxLength))
62
68
  if textView.text != limited {
@@ -65,20 +71,17 @@ struct UITextViewWrapper: UIViewRepresentable {
65
71
  parent.text = limited
66
72
  parent.onChangeText(limited)
67
73
  }
68
-
74
+
69
75
  func textViewDidBeginEditing(_ textView: UITextView) {
70
76
  parent.onFocus?()
71
77
  }
72
-
78
+
73
79
  func textViewDidEndEditing(_ textView: UITextView) {
74
80
  parent.onBlur?()
75
81
  }
76
-
82
+
83
+ // Like Compose's `onValueChange`, an edit that would exceed maxLength is rejected whole.
77
84
  func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
78
- if text == "\n" {
79
- parent.onDone?()
80
- return false
81
- }
82
85
  let current = textView.text ?? ""
83
86
  guard let range = Range(range, in: current) else { return false }
84
87
  let proposed = current.replacingCharacters(in: range, with: text)
@@ -90,6 +93,7 @@ struct UITextViewWrapper: UIViewRepresentable {
90
93
  // Make the struct public
91
94
  public struct InputTextArea: View {
92
95
  @Environment(\.appTheme) private var theme
96
+ @Environment(\.momoFontScale) private var fontScale
93
97
  @Binding var text: String
94
98
  var maxLength: Int = MAX_LENGTH
95
99
  var height: CGFloat = DEFAULT_HEIGHT
@@ -111,11 +115,9 @@ public struct InputTextArea: View {
111
115
  var required: Bool = false
112
116
  var fontWeight: Font.Weight = .regular
113
117
  var keyboardType: UIKeyboardType = .default
114
-
118
+
115
119
  @State private var isFocused = false
116
- @State private var isBlurred = false
117
- @State private var floatingLabelHeight: CGFloat = 16
118
-
120
+
119
121
  // Make the initializer public
120
122
  public init(
121
123
  text: Binding<String>,
@@ -162,147 +164,128 @@ public struct InputTextArea: View {
162
164
  self.fontWeight = fontWeight
163
165
  self.keyboardType = keyboardType
164
166
  }
165
-
167
+
166
168
  public var body: some View {
167
169
  VStack(alignment: .leading, spacing: 0) {
170
+ // Compose: `height` is the whole box (text, counter and paddings), scaled.
171
+ VStack(spacing: 0) {
172
+ HStack(alignment: .top, spacing: 0) {
168
173
  ZStack(alignment: .topLeading) {
169
- if !floatingValue.isEmpty || !floatingIcon.isEmpty {
170
- HStack(spacing: 4) {
171
- MomoText(
172
- floatingValue,
173
- typography: .labelSMedium,
174
- color: disabled ? theme.colors.text.disable : (floatingValueColor ?? theme.colors.text.hint)
175
- )
176
-
177
- if required {
178
- MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
179
- }
180
-
181
- if !floatingIcon.isEmpty {
182
- Icon(
183
- source: floatingIcon,
184
- size: 16,
185
- color: disabled ? theme.colors.text.disable : (floatingIconColor ?? theme.colors.text.default)
186
- )
187
- .padding(.leading, Spacing.XS)
188
- }
189
- }
190
- .padding(.horizontal, Spacing.S)
191
- .background(
192
- GeometryReader { geo in
193
- Color.clear.onAppear { floatingLabelHeight = geo.size.height }
194
- }
174
+ if text.isEmpty {
175
+ MomoText(
176
+ placeholder,
177
+ typography: fontWeight == .bold ? .actionSBold : .bodyDefaultRegular,
178
+ color: disabled ? theme.colors.text.disable : theme.colors.text.hint
195
179
  )
196
- .background(theme.colors.background.surface)
197
- // Kotlin centers the label within the box-height-tall decorationBox
198
- // by default, then applies `offsetY = -scaleHeight / 2` — net effect:
199
- // the label ends up centered exactly on the box's top border. Our
200
- // ZStack anchors top-leading instead, so matching that requires
201
- // offsetting by half the label's OWN measured height, not half the
202
- // (104pt-tall) text-area box height.
203
- .offset(x: Spacing.S, y: -floatingLabelHeight / 2)
204
- .zIndex(10)
180
+ .fontSize(16, lineHeight: 24)
205
181
  }
206
182
 
207
- VStack {
208
- HStack(alignment: .top, spacing: 0) {
209
- ZStack(alignment: .topLeading) {
210
- if text.isEmpty {
211
- Text(placeholder)
212
- .foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.hint)
213
- .font(.system(size: 16, weight: fontWeight))
214
- .padding(.horizontal, 16)
215
- .padding(.vertical, 12)
216
- }
217
-
218
- UITextViewWrapper(
219
- text: $text,
220
- maxLength: maxLength,
221
- textColor: disabled ? theme.colors.text.disable : theme.colors.text.default,
222
- cursorColor: theme.colors.primary,
223
- fontWeight: fontWeight,
224
- disabled: disabled,
225
- onChangeText: onChangeText,
226
- onDone: {
227
- // Handle done action if needed
228
- },
229
- onFocus: {
230
- isFocused = true
231
- onFocus()
232
- }, onBlur: {
233
- isFocused = false
234
- isBlurred = true
235
- onBlur()
236
- })
237
- .frame(height: height)
238
- .padding(.horizontal, 12)
239
- .padding(.vertical, 8)
240
- .disabled(disabled)
241
- }
242
- .frame(maxWidth: .infinity)
243
-
244
- // Clear button (mirrors Compose: focused + non-empty).
245
- if isFocused && !text.isEmpty {
246
- SwiftUI.Button(action: {
247
- text = ""
248
- onChangeText("")
249
- }) {
250
- Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
251
- }
252
- .padding(.leading, Spacing.XS)
253
- .padding(.top, 12)
254
- }
255
-
256
- // Loading indicator and right icon (independent, mirrors
257
- // Compose's RenderRightIcon: both can render at once).
258
- if loading {
259
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
260
- .frame(width: 16, height: 16)
261
- .padding(.leading, Spacing.XS)
262
- .padding(.top, 12)
263
- }
264
- if !icon.isEmpty {
265
- SwiftUI.Button(action: onRightIconPressed) {
266
- Icon(source: icon, size: 24, color: disabled ? theme.colors.text.disable : (iconColor ?? theme.colors.text.default))
267
- }
268
- .padding(.leading, Spacing.XS)
269
- .padding(.top, 12)
270
- }
183
+ UITextViewWrapper(
184
+ text: $text,
185
+ maxLength: maxLength,
186
+ textColor: disabled ? theme.colors.text.disable : theme.colors.text.default,
187
+ cursorColor: theme.colors.primary,
188
+ fontWeight: fontWeight,
189
+ keyboardType: keyboardType,
190
+ disabled: disabled,
191
+ onChangeText: onChangeText,
192
+ onFocus: {
193
+ isFocused = true
194
+ onFocus()
195
+ },
196
+ onBlur: {
197
+ isFocused = false
198
+ onBlur()
271
199
  }
200
+ )
201
+ .disabled(disabled)
202
+ }
203
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
272
204
 
273
- HStack {
274
- Spacer()
275
- MomoText("\(text.count)/\(maxLength)", typography: .descriptionXsRegular, color: theme.colors.text.hint)
276
- }
277
- .padding(.horizontal, 16)
278
- .padding(.bottom, 12)
205
+ // Clear button (mirrors Compose: focused + non-empty).
206
+ if isFocused && !text.isEmpty {
207
+ SwiftUI.Button(action: {
208
+ text = ""
209
+ onChangeText("")
210
+ }) {
211
+ Icon(source: "24_navigation_close_circle_full", size: 16, color: theme.colors.text.hint)
279
212
  }
280
- .background(theme.colors.background.surface)
281
- .cornerRadius(Radius.S)
282
- .overlay(
283
- RoundedRectangle(cornerRadius: Radius.S)
284
- .stroke(borderColor, lineWidth: 1)
285
- )
213
+ .padding(.leading, Spacing.XS)
286
214
  }
287
-
288
- ErrorView(errorMessage: error, errorSpacing: errorSpacing, hintText: "")
289
- }
290
- .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
291
- if !isBlurred {
292
- isFocused = true
293
- onFocus()
215
+
216
+ // Loading indicator and right icon (independent, mirrors
217
+ // Compose's RenderRightIcon: both can render at once).
218
+ if loading {
219
+ ActivityIndicator(isAnimating: .constant(true), style: .medium, color: iconTintColor)
220
+ .frame(width: 16, height: 16)
221
+ .padding(.leading, Spacing.XS)
294
222
  }
295
- }
296
- .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
297
- if isFocused {
298
- isFocused = false
299
- isBlurred = true
300
- onBlur()
223
+ if !icon.isEmpty {
224
+ SwiftUI.Button(action: onRightIconPressed) {
225
+ Icon(source: icon, size: 24, color: iconTintColor)
226
+ }
227
+ .padding(.leading, Spacing.XS)
301
228
  }
302
229
  }
303
- .ignoreBoldTextSetting()
230
+ .padding([.horizontal, .top], Spacing.M)
231
+ .frame(maxHeight: .infinity, alignment: .top)
232
+
233
+ MomoText("\(text.count)/\(maxLength)", typography: .descriptionXsRegular, color: theme.colors.text.hint)
234
+ .frame(maxWidth: .infinity, alignment: .trailing)
235
+ .padding(.trailing, Spacing.M)
236
+ .padding(.bottom, Spacing.M)
237
+ }
238
+ .frame(height: fontScale(height))
239
+ .background(
240
+ RoundedRectangle(cornerRadius: Radius.S)
241
+ .fill(theme.colors.background.surface)
242
+ )
243
+ .overlay(
244
+ RoundedRectangle(cornerRadius: Radius.S)
245
+ .stroke(borderColor, lineWidth: 1)
246
+ )
247
+ .overlay(alignment: .topLeading) { floatingLabel }
248
+
249
+ ErrorView(errorMessage: error, errorSpacing: errorSpacing, hintText: "")
250
+ }
251
+ .ignoreBoldTextSetting()
304
252
  }
305
-
253
+
254
+ /// Centered on the box's top border, like Compose's `FloatingLabel` (offset by -height / 2).
255
+ @ViewBuilder
256
+ private var floatingLabel: some View {
257
+ if !floatingValue.isEmpty || !floatingIcon.isEmpty {
258
+ HStack(spacing: 0) {
259
+ MomoText(
260
+ floatingValue,
261
+ typography: .labelSMedium,
262
+ color: disabled ? theme.colors.text.disable : (floatingValueColor ?? theme.colors.text.hint)
263
+ )
264
+
265
+ if required {
266
+ MomoText("*", typography: .labelSMedium, color: theme.colors.error.primary)
267
+ }
268
+
269
+ if !floatingIcon.isEmpty {
270
+ Icon(
271
+ source: floatingIcon,
272
+ size: 16,
273
+ color: disabled ? theme.colors.text.disable : (floatingIconColor ?? theme.colors.text.default)
274
+ )
275
+ .padding(.leading, Spacing.XS)
276
+ }
277
+ }
278
+ .padding(.horizontal, Spacing.S)
279
+ .background(theme.colors.background.surface)
280
+ .alignmentGuide(.top) { $0[VerticalAlignment.center] }
281
+ .offset(x: Spacing.S)
282
+ }
283
+ }
284
+
285
+ private var iconTintColor: Color {
286
+ disabled ? theme.colors.text.disable : (iconColor ?? theme.colors.text.default)
287
+ }
288
+
306
289
  private var borderColor: Color {
307
290
  if disabled {
308
291
  return theme.colors.border.disable