@momo-kits/native-kits 0.162.2-beta.1 → 0.162.2-sp.1-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 (295) hide show
  1. package/.claude/settings.local.json +55 -0
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +186 -0
  4. package/compose/build.gradle.kts.backup +186 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +123 -0
  8. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  9. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  10. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  11. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  12. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +109 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +305 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +921 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +121 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +403 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +85 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +340 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +198 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +123 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +94 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +136 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +224 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +148 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +188 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +116 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +452 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +172 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +255 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +233 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +259 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +241 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +92 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +352 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +103 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +338 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +70 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +96 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +348 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +256 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +494 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +131 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +215 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +96 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +531 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +92 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +135 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +214 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +590 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +177 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +192 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +205 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +239 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +57 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +14 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +50 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +253 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +133 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +104 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +145 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +345 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +556 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +161 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +243 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +187 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +315 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +34 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +370 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +131 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +167 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +72 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +66 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +70 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +105 -0
  127. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +33 -0
  128. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +186 -0
  129. package/gradle/libs.versions.toml +67 -0
  130. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  131. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  132. package/gradle.properties +26 -0
  133. package/gradlew +252 -0
  134. package/gradlew.bat +94 -0
  135. package/ios/Application/Components.swift +34 -43
  136. package/ios/Application/FloatingButton.swift +10 -8
  137. package/ios/Application/Navigation/BottomTab/BottomTab.swift +102 -0
  138. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  139. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  140. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  141. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  142. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  143. package/ios/Application/Navigation/NavigationContainer.swift +151 -0
  144. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  145. package/ios/Application/Navigation/Navigator.swift +360 -0
  146. package/ios/Application/Navigation/Overplay/BottomSheet.swift +176 -0
  147. package/ios/Application/Navigation/Overplay/ModalScreen.swift +68 -0
  148. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  149. package/ios/Application/Navigation/component/Header.swift +178 -0
  150. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  151. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  152. package/ios/Application/StackScreen.swift +449 -0
  153. package/ios/Avatar/Avatar.swift +193 -0
  154. package/ios/Badge/Badge.swift +11 -13
  155. package/ios/Badge/BadgeRibbon.swift +12 -19
  156. package/ios/BaselineView/BaselineView.swift +163 -0
  157. package/ios/Button/Button.swift +62 -23
  158. package/ios/Carousel/Carousel.swift +181 -0
  159. package/ios/Checkbox/Checkbox.swift +14 -12
  160. package/ios/Chip/Chip.swift +23 -9
  161. package/ios/Collapse/Collapse.swift +182 -0
  162. package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
  163. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  164. package/ios/DateTimePicker/DateTimePicker.swift +210 -0
  165. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  166. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  167. package/ios/DateTimePicker/WheelPicker.swift +161 -0
  168. package/ios/Divider/Divider.swift +16 -0
  169. package/ios/Icon/Icon.swift +6 -1
  170. package/ios/IconButton/IconButton.swift +112 -0
  171. package/ios/Information/Information.swift +151 -0
  172. package/ios/Input/Input.swift +42 -25
  173. package/ios/Input/InputPhoneNumber.swift +21 -19
  174. package/ios/Input/InputSearch.swift +101 -45
  175. package/ios/Input/InputTextArea.swift +67 -40
  176. package/ios/InputDropDown/InputDropDown.swift +198 -0
  177. package/ios/InputMoney/InputMoney.swift +335 -0
  178. package/ios/InputOTP/InputOTP.swift +210 -0
  179. package/ios/Loader/Loader.swift +113 -0
  180. package/ios/MoMoUIKits.podspec +5 -0
  181. package/ios/OTPKeyboard/KeyboardButton.swift +1 -1
  182. package/ios/Pagination/PaginationDot.swift +61 -0
  183. package/ios/Pagination/PaginationNumber.swift +35 -0
  184. package/ios/Pagination/PaginationScroll.swift +101 -0
  185. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  186. package/ios/Popup/PopupDisplay.swift +10 -12
  187. package/ios/Popup/PopupInput.swift +2 -6
  188. package/ios/Popup/PopupNotify.swift +219 -0
  189. package/ios/ProgressInfo/ProgressInfo.swift +294 -0
  190. package/ios/Radio/Radio.swift +70 -0
  191. package/ios/Rating/Rating.swift +82 -0
  192. package/ios/Skeleton/Skeleton.swift +99 -0
  193. package/ios/Slider/Slider.swift +237 -0
  194. package/ios/Stepper/Stepper.swift +220 -0
  195. package/ios/Steps/Steps.swift +451 -0
  196. package/ios/SuggestAction/SuggestAction.swift +97 -0
  197. package/ios/Swipeable/SwipeCell.swift +185 -0
  198. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  199. package/ios/TabView/TabView.swift +530 -0
  200. package/ios/Tag/Tag.swift +98 -0
  201. package/ios/Template/TrustBanner/TrustBanner.swift +2 -0
  202. package/ios/Title/Title.swift +269 -0
  203. package/ios/Tooltip/Tooltip.swift +482 -0
  204. package/ios/Typography/Text.swift +86 -24
  205. package/ios/Typography/Typography.swift +31 -6
  206. package/ios/Uploader/Uploader.swift +183 -0
  207. package/ios/native-kits.podspec +58 -8
  208. package/ios-demo/MoMoUIKitsDemo.podspec +18 -0
  209. package/ios-demo/README.md +120 -0
  210. package/ios-demo/Resources/dot_loading.json +558 -0
  211. package/ios-demo/Resources/icon.json +4052 -0
  212. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  213. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  214. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  215. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  216. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  217. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  218. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  219. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  220. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  221. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  222. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  223. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +72 -0
  224. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  225. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
  226. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
  227. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  228. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  229. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
  230. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
  231. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
  232. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
  233. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
  234. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  235. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
  236. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
  237. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  238. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +261 -0
  239. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
  240. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
  241. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
  242. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
  243. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
  244. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
  245. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
  246. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
  247. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
  248. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
  249. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
  250. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +227 -0
  251. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
  252. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  253. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
  254. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
  255. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
  256. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
  257. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
  258. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
  259. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
  260. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
  261. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
  262. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
  263. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
  264. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
  265. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
  266. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
  267. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
  268. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
  269. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
  270. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
  271. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  272. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
  273. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +50 -0
  274. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  275. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  276. package/local.properties +8 -0
  277. package/package.json +1 -1
  278. package/publish.sh +50 -0
  279. package/scripts/gen-ios-color.mjs +136 -0
  280. package/settings.gradle.kts +64 -0
  281. package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme +0 -32
  282. package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  283. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  284. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/WorkspaceSettings.xcsettings +0 -16
  285. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
  286. package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
  287. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
  288. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
  289. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
  290. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
  291. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
  292. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +0 -58
  293. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios.xcscheme +0 -58
  294. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -60
  295. package/ios/Typography/MomoFontScale.swift +0 -36
@@ -0,0 +1,99 @@
1
+ import Foundation
2
+ import SwiftUI
3
+
4
+ /// Native SwiftUI port of the Compose `Skeleton` component.
5
+ ///
6
+ /// Fills the available space with a base placeholder color and overlays a
7
+ /// horizontal shimmer gradient that animates left-to-right on an infinite loop.
8
+ ///
9
+ /// Mirrors the Compose surface: `Skeleton()` takes no required arguments and
10
+ /// fills its container. The shimmer animation parameters match the Compose
11
+ /// defaults (`durationMillis = 1000`, shimmer band = 60% of container width).
12
+ public struct Skeleton: View {
13
+ // Matches Compose `DEFAULT_DURATION_MILLIS = 1000`.
14
+ private let durationMillis: Int
15
+ // Matches Compose `angleOfAxisY = 0f` (horizontal gradient).
16
+ private let angleOfAxisY: CGFloat
17
+
18
+ public init(
19
+ durationMillis: Int = 1000,
20
+ angleOfAxisY: CGFloat = 0
21
+ ) {
22
+ self.durationMillis = durationMillis
23
+ self.angleOfAxisY = angleOfAxisY
24
+ }
25
+
26
+ public var body: some View {
27
+ GeometryReader { proxy in
28
+ ShimmerLoading(
29
+ size: proxy.size,
30
+ durationMillis: durationMillis,
31
+ angleOfAxisY: angleOfAxisY
32
+ )
33
+ }
34
+ // Compose base background (`Colors.black_05`).
35
+ .background(Colors.black05)
36
+ }
37
+ }
38
+
39
+ /// Internal shimmer overlay. Reproduces Compose's `Modifier.shimmerLoadingAnimation`:
40
+ /// a linear gradient band (60% of the container width) translated across the
41
+ /// surface on an infinite, restarting, linearly-eased loop.
42
+ private struct ShimmerLoading: View {
43
+ let size: CGSize
44
+ let durationMillis: Int
45
+ let angleOfAxisY: CGFloat
46
+
47
+ @State private var phase: CGFloat = 0
48
+
49
+ // Matches Compose `SHADOW_BRUSH_PERCENTAGE = 0.6f`.
50
+ private static let shadowBrushPercentage: CGFloat = 0.6
51
+
52
+ // Pre-computed shimmer colors (Colors.black_05, black_03, black_05).
53
+ private static let shimmerColors: [Color] = [
54
+ Colors.black05,
55
+ Colors.black03,
56
+ Colors.black05,
57
+ ]
58
+
59
+ private var widthOfShadowBrush: CGFloat {
60
+ (size.width * Self.shadowBrushPercentage).rounded(.towardZero)
61
+ }
62
+
63
+ // Compose animates from 0 to (durationMillis + widthOfShadowBrush). Since the
64
+ // band is anchored at the animated value, the meaningful travel range spans
65
+ // from off-screen-left to off-screen-right: -widthOfShadowBrush ... maxWidth.
66
+ private var animationTarget: CGFloat {
67
+ size.width + widthOfShadowBrush
68
+ }
69
+
70
+ var body: some View {
71
+ let startX = phase - widthOfShadowBrush
72
+ let endX = phase
73
+
74
+ Rectangle()
75
+ .fill(
76
+ LinearGradient(
77
+ gradient: Gradient(colors: Self.shimmerColors),
78
+ startPoint: .init(
79
+ x: size.width > 0 ? startX / size.width : 0,
80
+ y: 0
81
+ ),
82
+ endPoint: .init(
83
+ x: size.width > 0 ? endX / size.width : 0,
84
+ y: size.height > 0 ? angleOfAxisY / size.height : 0
85
+ )
86
+ )
87
+ )
88
+ .onAppear {
89
+ guard size.width > 0 else { return }
90
+ phase = 0
91
+ withAnimation(
92
+ .linear(duration: Double(durationMillis) / 1000.0)
93
+ .repeatForever(autoreverses: false)
94
+ ) {
95
+ phase = animationTarget
96
+ }
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,237 @@
1
+ import SwiftUI
2
+
3
+ // ── Design constants ─────────────────────────────────────────────────────────
4
+
5
+ private let THUMB_SIZE: CGFloat = 20
6
+ private let THUMB_BORDER: CGFloat = 3
7
+ private let TRACK_HEIGHT: CGFloat = 4
8
+ private let LABEL_GAP: CGFloat = 4 // space between label and thumb top (Spacing.XS)
9
+
10
+ // ── Pure helpers (mirrors Compose / RN helpers) ──────────────────────────────
11
+
12
+ private func clampF(_ value: CGFloat, _ minV: CGFloat, _ maxV: CGFloat) -> CGFloat {
13
+ Swift.min(Swift.max(value, minV), maxV)
14
+ }
15
+
16
+ /// Converts a raw touch position into a snapped slider value.
17
+ /// Direct port of Compose getValueForPosition().
18
+ private func getValueForPosition(
19
+ positionInView: CGFloat,
20
+ containerWidth: CGFloat,
21
+ thumbWidth: CGFloat,
22
+ minV: CGFloat,
23
+ maxV: CGFloat,
24
+ step: CGFloat
25
+ ) -> CGFloat {
26
+ let availableSpace = containerWidth - thumbWidth
27
+ if availableSpace <= 0 { return minV }
28
+ let relStepUnit = step / (maxV - minV)
29
+ var relPosition = (positionInView - thumbWidth / 2) / availableSpace
30
+ let relOffset = relPosition.truncatingRemainder(dividingBy: relStepUnit)
31
+ relPosition -= relOffset
32
+ if relStepUnit != 0, relOffset / relStepUnit >= 0.5 { relPosition += relStepUnit }
33
+ let steps = (relPosition / relStepUnit).rounded()
34
+ return clampF(minV + steps * step, minV, maxV)
35
+ }
36
+
37
+ /// Direct port of Compose isLowCloser().
38
+ private func isLowCloser(_ downX: CGFloat, _ lowPosition: CGFloat, _ highPosition: CGFloat) -> Bool {
39
+ if lowPosition == highPosition { return downX < lowPosition }
40
+ return abs(downX - lowPosition) < abs(downX - highPosition)
41
+ }
42
+
43
+ private func valueToFraction(_ value: CGFloat, _ minV: CGFloat, _ maxV: CGFloat) -> CGFloat {
44
+ if maxV == minV { return 0 }
45
+ return clampF((value - minV) / (maxV - minV), 0, 1)
46
+ }
47
+
48
+ private func formatLabel(_ value: CGFloat) -> String {
49
+ if value == value.rounded() {
50
+ return String(Int(value))
51
+ }
52
+ return String(format: "%.1f", value)
53
+ }
54
+
55
+ // ── Component ─────────────────────────────────────────────────────────────────
56
+
57
+ /// Range slider (or single-thumb when `disableRange` = true), ported from Compose.
58
+ ///
59
+ /// SwiftUI ships a built-in `Slider`; this custom type is named `MomoSlider`
60
+ /// (convention, like `MomoText`) to avoid module-wide shadowing.
61
+ public struct MomoSlider: View {
62
+ @Environment(\.appTheme) private var theme
63
+
64
+ private let minV: CGFloat
65
+ private let maxV: CGFloat
66
+ private let step: CGFloat
67
+ private let initLow: CGFloat
68
+ private let initHigh: CGFloat
69
+ private let minRange: CGFloat
70
+ private let disableRange: Bool
71
+ private let floatingLabel: Bool
72
+ private let disabled: Bool
73
+ private let onValueChanged: (CGFloat, CGFloat, Bool) -> Void
74
+ private let onSliderTouchStart: (CGFloat, CGFloat) -> Void
75
+ private let onSliderTouchEnd: (CGFloat, CGFloat) -> Void
76
+
77
+ @State private var lowValue: CGFloat = 0
78
+ @State private var highValue: CGFloat = 0
79
+ @State private var draggingLow: Bool = true
80
+ @State private var isDragging: Bool = false
81
+ @State private var didInit: Bool = false
82
+
83
+ public init(
84
+ min: CGFloat,
85
+ max: CGFloat,
86
+ step: CGFloat,
87
+ low: CGFloat? = nil,
88
+ high: CGFloat? = nil,
89
+ minRange: CGFloat = 0,
90
+ disableRange: Bool = false,
91
+ floatingLabel: Bool = false,
92
+ disabled: Bool = false,
93
+ onValueChanged: @escaping (CGFloat, CGFloat, Bool) -> Void = { _, _, _ in },
94
+ onSliderTouchStart: @escaping (CGFloat, CGFloat) -> Void = { _, _ in },
95
+ onSliderTouchEnd: @escaping (CGFloat, CGFloat) -> Void = { _, _ in }
96
+ ) {
97
+ self.minV = min
98
+ self.maxV = max
99
+ self.step = step
100
+ self.minRange = minRange
101
+ self.disableRange = disableRange
102
+ self.floatingLabel = floatingLabel
103
+ self.disabled = disabled
104
+ self.onValueChanged = onValueChanged
105
+ self.onSliderTouchStart = onSliderTouchStart
106
+ self.onSliderTouchEnd = onSliderTouchEnd
107
+
108
+ self.initLow = clampF(low ?? min, min, max)
109
+ self.initHigh = disableRange ? max : clampF(high ?? max, min, max)
110
+ }
111
+
112
+ private var thumbColor: Color { disabled ? theme.colors.text.disable : theme.colors.primary }
113
+ private var trackActiveColor: Color { disabled ? theme.colors.text.disable : theme.colors.primary }
114
+ private var trackInactiveColor: Color { theme.colors.background.default }
115
+
116
+ public var body: some View {
117
+ GeometryReader { geo in
118
+ let trackWidth = geo.size.width
119
+ let availableWidth = max(trackWidth - THUMB_SIZE, 0)
120
+ let topPad: CGFloat = floatingLabel ? (THUMB_SIZE + LABEL_GAP) : 0
121
+
122
+ let lowFraction = valueToFraction(lowValue, minV, maxV)
123
+ let highFraction = valueToFraction(highValue, minV, maxV)
124
+
125
+ let activeStartFraction = disableRange ? 0 : lowFraction
126
+ let activeEndFraction = disableRange ? lowFraction : highFraction
127
+ let activeStartX = THUMB_SIZE / 2 + availableWidth * activeStartFraction
128
+ let activeWidth = availableWidth * (activeEndFraction - activeStartFraction)
129
+
130
+ ZStack(alignment: .topLeading) {
131
+ // ── Floating label ────────────────────────────────────────────
132
+ if floatingLabel && isDragging {
133
+ let activeFraction = draggingLow ? lowFraction : highFraction
134
+ let activeValue = draggingLow ? lowValue : highValue
135
+ MomoText(formatLabel(activeValue), typography: .descriptionXsRegular, color: theme.colors.text.default)
136
+ .padding(.horizontal, Spacing.XS)
137
+ .padding(.vertical, Spacing.XXS)
138
+ .background(Colors.black01)
139
+ .clipShape(RoundedRectangle(cornerRadius: Radius.XS))
140
+ .offset(x: availableWidth * activeFraction, y: 0)
141
+ }
142
+
143
+ // ── Inactive track (full width) ───────────────────────────────
144
+ Capsule()
145
+ .fill(trackInactiveColor)
146
+ .frame(width: max(trackWidth - THUMB_SIZE, 0), height: TRACK_HEIGHT)
147
+ .offset(x: THUMB_SIZE / 2, y: topPad + (THUMB_SIZE - TRACK_HEIGHT) / 2)
148
+
149
+ // ── Active track (selected range / single value) ──────────────
150
+ if activeWidth > 0 {
151
+ Capsule()
152
+ .fill(trackActiveColor)
153
+ .frame(width: activeWidth, height: TRACK_HEIGHT)
154
+ .offset(x: activeStartX, y: topPad + (THUMB_SIZE - TRACK_HEIGHT) / 2)
155
+ }
156
+
157
+ // ── Low thumb ─────────────────────────────────────────────────
158
+ thumb
159
+ .offset(x: availableWidth * lowFraction, y: topPad)
160
+
161
+ // ── High thumb (range mode only) ──────────────────────────────
162
+ if !disableRange {
163
+ thumb
164
+ .offset(x: availableWidth * highFraction, y: topPad)
165
+ }
166
+ }
167
+ .frame(width: trackWidth, height: topPad + THUMB_SIZE, alignment: .topLeading)
168
+ .contentShape(Rectangle())
169
+ .gesture(dragGesture(trackWidth: trackWidth))
170
+ }
171
+ .frame(height: (floatingLabel ? (THUMB_SIZE + LABEL_GAP) : 0) + THUMB_SIZE)
172
+ .onAppear {
173
+ if !didInit {
174
+ lowValue = initLow
175
+ highValue = initHigh
176
+ didInit = true
177
+ }
178
+ }
179
+ }
180
+
181
+ private var thumb: some View {
182
+ Circle()
183
+ .fill(thumbColor)
184
+ .frame(width: THUMB_SIZE, height: THUMB_SIZE)
185
+ .overlay(
186
+ Circle().strokeBorder(theme.colors.background.surface, lineWidth: THUMB_BORDER)
187
+ )
188
+ }
189
+
190
+ private func dragGesture(trackWidth: CGFloat) -> some Gesture {
191
+ DragGesture(minimumDistance: 0)
192
+ .onChanged { gesture in
193
+ if disabled { return }
194
+ let touchX = gesture.location.x
195
+
196
+ if !isDragging {
197
+ // Finger down: pick the closer thumb
198
+ let lowCenterX = THUMB_SIZE / 2 +
199
+ valueToFraction(lowValue, minV, maxV) * (trackWidth - THUMB_SIZE)
200
+ let highCenterX = THUMB_SIZE / 2 +
201
+ valueToFraction(highValue, minV, maxV) * (trackWidth - THUMB_SIZE)
202
+ draggingLow = disableRange || isLowCloser(touchX, lowCenterX, highCenterX)
203
+ isDragging = true
204
+ onSliderTouchStart(lowValue, highValue)
205
+ }
206
+
207
+ applyDrag(positionX: touchX, trackWidthPx: trackWidth)
208
+ }
209
+ .onEnded { _ in
210
+ if disabled { return }
211
+ isDragging = false
212
+ onSliderTouchEnd(lowValue, highValue)
213
+ }
214
+ }
215
+
216
+ private func applyDrag(positionX: CGFloat, trackWidthPx: CGFloat) {
217
+ let snapped = getValueForPosition(
218
+ positionInView: positionX,
219
+ containerWidth: trackWidthPx,
220
+ thumbWidth: THUMB_SIZE,
221
+ minV: minV, maxV: maxV, step: step
222
+ )
223
+ if draggingLow {
224
+ let newLow = clampF(snapped, minV, highValue - minRange)
225
+ if newLow != lowValue {
226
+ lowValue = newLow
227
+ onValueChanged(newLow, highValue, true)
228
+ }
229
+ } else {
230
+ let newHigh = clampF(snapped, lowValue + minRange, maxV)
231
+ if newHigh != highValue {
232
+ highValue = newHigh
233
+ onValueChanged(lowValue, newHigh, true)
234
+ }
235
+ }
236
+ }
237
+ }
@@ -0,0 +1,220 @@
1
+ import SwiftUI
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Size variant
5
+ // ---------------------------------------------------------------------------
6
+
7
+ public enum StepperSize {
8
+ case large
9
+ case small
10
+ }
11
+
12
+ private struct StepperSizeSpec {
13
+ let buttonSize: CGFloat
14
+ let iconSize: CGFloat
15
+ let numberMinWidth: CGFloat
16
+ let numberHeight: CGFloat
17
+ }
18
+
19
+ private func stepperSizeSpec(_ size: StepperSize) -> StepperSizeSpec {
20
+ switch size {
21
+ case .large:
22
+ return StepperSizeSpec(buttonSize: 36, iconSize: 22, numberMinWidth: 32, numberHeight: 28)
23
+ case .small:
24
+ return StepperSizeSpec(buttonSize: 28, iconSize: 18, numberMinWidth: 28, numberHeight: 24)
25
+ }
26
+ }
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Disabled spec — mirrors Compose `disabled: Any` (Bool or per-part array)
30
+ // ---------------------------------------------------------------------------
31
+
32
+ public struct StepperDisabled {
33
+ let minus: Bool
34
+ let plus: Bool
35
+
36
+ fileprivate var number: Bool { minus && plus }
37
+
38
+ public init(minus: Bool, plus: Bool) {
39
+ self.minus = minus
40
+ self.plus = plus
41
+ }
42
+
43
+ /// All parts share the same disabled state.
44
+ public init(_ all: Bool) {
45
+ self.minus = all
46
+ self.plus = all
47
+ }
48
+ }
49
+
50
+ extension StepperDisabled: ExpressibleByBooleanLiteral {
51
+ public init(booleanLiteral value: Bool) {
52
+ self.init(value)
53
+ }
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Private sub-views
58
+ // ---------------------------------------------------------------------------
59
+
60
+ private struct StepperButton: View {
61
+ @Environment(\.appTheme) private var theme
62
+
63
+ var sign: String
64
+ var size: StepperSize
65
+ var disabled: Bool
66
+ var onPress: () -> Void
67
+
68
+ var body: some View {
69
+ let specs = stepperSizeSpec(size)
70
+ let iconColor: Color = disabled ? theme.colors.text.disable : theme.colors.primary
71
+ let iconName = sign == "-" ? "24_navigation_minus_circle" : "24_navigation_plus_circle"
72
+
73
+ SwiftUI.Button(action: { if !disabled { onPress() } }) {
74
+ ZStack {
75
+ Circle().fill(theme.colors.background.disable)
76
+ Icon(source: iconName, size: specs.iconSize, color: iconColor)
77
+ }
78
+ .frame(width: specs.buttonSize, height: specs.buttonSize)
79
+ }
80
+ .buttonStyle(.plain)
81
+ .disabled(disabled)
82
+ }
83
+ }
84
+
85
+ private struct StepperNumberView: View {
86
+ @Environment(\.appTheme) private var theme
87
+
88
+ @Binding var text: String
89
+ var size: StepperSize
90
+ var disabled: Bool
91
+ var editable: Bool
92
+
93
+ var body: some View {
94
+ let specs = stepperSizeSpec(size)
95
+ let textColor: Color = disabled ? theme.colors.text.disable : theme.colors.text.default
96
+ let borderColor: Color = disabled ? theme.colors.border.disable : theme.colors.border.default
97
+
98
+ Group {
99
+ if editable {
100
+ TextField("", text: $text)
101
+ .keyboardType(.numberPad)
102
+ .multilineTextAlignment(.center)
103
+ .disabled(disabled)
104
+ } else {
105
+ MomoText(text, typography: .descriptionDefaultRegular, color: textColor)
106
+ }
107
+ }
108
+ .font(.system(size: scaleSize(12)))
109
+ .ignoreBoldTextSetting()
110
+ .foregroundColor(textColor)
111
+ .frame(minWidth: specs.numberMinWidth)
112
+ .frame(height: specs.numberHeight)
113
+ .padding(.horizontal, Spacing.XS)
114
+ .overlay(
115
+ RoundedRectangle(cornerRadius: Radius.S)
116
+ .stroke(borderColor, lineWidth: 1)
117
+ )
118
+ }
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Public API — MomoStepper
123
+ // ---------------------------------------------------------------------------
124
+
125
+ /// MomoStepper — quantity +/- control. Layout: [- button] [value] [+ button].
126
+ ///
127
+ /// Named `MomoStepper` to avoid shadowing SwiftUI's built-in `Stepper`.
128
+ ///
129
+ /// - Parameters:
130
+ /// - value: Binding to the current value. Clamped to `min...max`.
131
+ /// - min: Minimum allowed value (default 0). Minus auto-disables at this bound.
132
+ /// - max: Maximum allowed value (default 100). Plus auto-disables at this bound.
133
+ /// - step: Amount added/subtracted per press (default 1).
134
+ /// - size: Visual size variant (default `.large`).
135
+ /// - disabled: Disable state — `true` for everything, or `StepperDisabled(minus:plus:)`.
136
+ /// - editable: When `true` the number display becomes a keyboard input (default false).
137
+ /// - onValueChange: Callback invoked with the new value on every change.
138
+ public struct MomoStepper: View {
139
+ @Binding private var value: Int
140
+ private let min: Int
141
+ private let max: Int
142
+ private let step: Int
143
+ private let size: StepperSize
144
+ private let disabled: StepperDisabled
145
+ private let editable: Bool
146
+ private let onValueChange: (Int) -> Void
147
+
148
+ @State private var editingText: String = ""
149
+
150
+ public init(
151
+ value: Binding<Int>,
152
+ min: Int = 0,
153
+ max: Int = 100,
154
+ step: Int = 1,
155
+ size: StepperSize = .large,
156
+ disabled: StepperDisabled = false,
157
+ editable: Bool = false,
158
+ onValueChange: @escaping (Int) -> Void = { _ in }
159
+ ) {
160
+ self._value = value
161
+ self.min = min
162
+ self.max = max
163
+ self.step = step
164
+ self.size = size
165
+ self.disabled = disabled
166
+ self.editable = editable
167
+ self.onValueChange = onValueChange
168
+ }
169
+
170
+ private func clamp(_ raw: Int) -> Int {
171
+ Swift.max(min, Swift.min(max, raw))
172
+ }
173
+
174
+ private func applyValue(_ raw: Int) {
175
+ let parsed = clamp(raw)
176
+ value = parsed
177
+ editingText = String(parsed)
178
+ onValueChange(parsed)
179
+ }
180
+
181
+ public var body: some View {
182
+ let effectiveMinusDisabled = disabled.minus || value <= min
183
+ let effectivePlusDisabled = disabled.plus || value >= max
184
+
185
+ HStack(spacing: Spacing.S) {
186
+ StepperButton(
187
+ sign: "-",
188
+ size: size,
189
+ disabled: effectiveMinusDisabled,
190
+ onPress: { applyValue(value - step) }
191
+ )
192
+
193
+ StepperNumberView(
194
+ text: Binding(
195
+ get: { editable ? editingText : String(value) },
196
+ set: { newText in
197
+ editingText = newText
198
+ let parsed = Int(newText.filter { $0.isNumber }) ?? 0
199
+ applyValue(parsed)
200
+ }
201
+ ),
202
+ size: size,
203
+ disabled: disabled.number,
204
+ editable: editable
205
+ )
206
+
207
+ StepperButton(
208
+ sign: "+",
209
+ size: size,
210
+ disabled: effectivePlusDisabled,
211
+ onPress: { applyValue(value + step) }
212
+ )
213
+ }
214
+ .onAppear {
215
+ let clamped = clamp(value)
216
+ if clamped != value { value = clamped }
217
+ editingText = String(clamped)
218
+ }
219
+ }
220
+ }