@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,215 @@
1
+ //
2
+ // BottomTabBar.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `BottomTabBar` (navigation/bottomtab/BottomTabBar.kt).
6
+ // Renders the row of tab items, an animated selection indicator, a top shadow,
7
+ // badges/dots, and an optional center floating button (with the curved notch).
8
+ //
9
+
10
+ import SwiftUI
11
+
12
+ // MARK: - Models
13
+
14
+ /// Mirrors Compose's `BottomTabItem`.
15
+ public struct BottomTabItem: Identifiable {
16
+ public let id = UUID()
17
+ public let name: String
18
+ public let label: String
19
+ public let icon: String
20
+ public let showDot: Bool
21
+ public let badgeLabel: String?
22
+ public let options: NavigationOptions?
23
+ public let screen: () -> AnyView
24
+
25
+ public init(
26
+ name: String,
27
+ label: String,
28
+ icon: String,
29
+ showDot: Bool = false,
30
+ badgeLabel: String? = nil,
31
+ options: NavigationOptions? = nil,
32
+ @ViewBuilder screen: @escaping () -> some View
33
+ ) {
34
+ self.name = name
35
+ self.label = label
36
+ self.icon = icon
37
+ self.showDot = showDot
38
+ self.badgeLabel = badgeLabel
39
+ self.options = options
40
+ self.screen = { AnyView(screen()) }
41
+ }
42
+ }
43
+
44
+ /// Mirrors Compose's `BottomTabFloatingButton`.
45
+ public struct BottomTabFloatingButton {
46
+ public let icon: String
47
+ public let label: String
48
+ public let onPress: () -> Void
49
+
50
+ public init(icon: String, label: String, onPress: @escaping () -> Void) {
51
+ self.icon = icon
52
+ self.label = label
53
+ self.onPress = onPress
54
+ }
55
+ }
56
+
57
+ let bottomTabFloatingButtonWidth: CGFloat = 75
58
+ let BOTTOM_TAB_BAR_HEIGHT: CGFloat = 64
59
+
60
+ // MARK: - Tab bar
61
+
62
+ struct BottomTabBar: View {
63
+ let items: [BottomTabItem]
64
+ let floatingButton: BottomTabFloatingButton?
65
+ let selectedIndex: Int
66
+ let onTabSelected: (Int) -> Void
67
+
68
+ private var screenWidth: CGFloat { UIScreen.main.bounds.width }
69
+ private var mid: Int { items.count / 2 }
70
+
71
+ private var tabWidth: CGFloat {
72
+ if floatingButton != nil {
73
+ return (screenWidth - bottomTabFloatingButtonWidth) / CGFloat(items.count)
74
+ }
75
+ return screenWidth / CGFloat(items.count)
76
+ }
77
+
78
+ private var indicatorOffset: CGFloat {
79
+ let adjust: CGFloat = (floatingButton != nil && selectedIndex >= mid) ? bottomTabFloatingButtonWidth : 0
80
+ return tabWidth * CGFloat(selectedIndex) + tabWidth / 2 - screenWidth / 2 + adjust
81
+ }
82
+
83
+ var body: some View {
84
+ ZStack(alignment: .bottom) {
85
+ HStack(spacing: 0) {
86
+ if floatingButton == nil {
87
+ tabItems(from: 0, to: items.count)
88
+ } else {
89
+ tabItems(from: 0, to: mid)
90
+ Spacer()
91
+ .frame(width: bottomTabFloatingButtonWidth)
92
+ .padding(.horizontal, Spacing.XXS)
93
+ tabItems(from: mid, to: items.count)
94
+ }
95
+ }
96
+ .frame(maxWidth: .infinity)
97
+ .frame(height: BOTTOM_TAB_BAR_HEIGHT)
98
+ .background(Colors.black01)
99
+
100
+ // Animated selection indicator.
101
+ RoundedRectangle(cornerRadius: 2)
102
+ .fill(Colors.primary)
103
+ .frame(width: 44, height: 2)
104
+ .offset(x: indicatorOffset, y: -BOTTOM_TAB_BAR_HEIGHT + 2)
105
+ .animation(.easeInOut(duration: 0.25), value: selectedIndex)
106
+
107
+ // Top shadow.
108
+ LinearGradient(
109
+ colors: [Color.black.opacity(0.05), .clear],
110
+ startPoint: .bottom,
111
+ endPoint: .top
112
+ )
113
+ .frame(height: 6)
114
+ .frame(maxWidth: .infinity)
115
+ .offset(y: -BOTTOM_TAB_BAR_HEIGHT)
116
+
117
+ if let floatingButton {
118
+ CenterFloatingButton(data: floatingButton)
119
+ }
120
+ }
121
+ }
122
+
123
+ @ViewBuilder
124
+ private func tabItems(from: Int, to: Int) -> some View {
125
+ ForEach(from..<to, id: \.self) { index in
126
+ TabBarItem(
127
+ item: items[index],
128
+ selected: index == selectedIndex,
129
+ onClick: { onTabSelected(index) }
130
+ )
131
+ .frame(maxWidth: .infinity)
132
+ }
133
+ }
134
+ }
135
+
136
+ // MARK: - Tab item
137
+
138
+ private struct TabBarItem: View {
139
+ let item: BottomTabItem
140
+ let selected: Bool
141
+ let onClick: () -> Void
142
+
143
+ private var tintColor: Color { selected ? Colors.primary : Colors.black10 }
144
+
145
+ /// Mirrors Compose's `formatLabel`: hide a badge that is the number "0".
146
+ private var badgeLabel: String? {
147
+ guard let label = item.badgeLabel else { return nil }
148
+ if let n = Int(label), n == 0 { return nil }
149
+ return label
150
+ }
151
+
152
+ var body: some View {
153
+ ZStack(alignment: .topTrailing) {
154
+ VStack(spacing: 0) {
155
+ Icon(source: item.icon, size: 28, color: tintColor)
156
+ .padding(Spacing.XS)
157
+ MomoText(
158
+ item.label,
159
+ typography: selected ? .labelXsMedium : .descriptionXsRegular,
160
+ color: tintColor,
161
+ textAlign: .center,
162
+ maxLines: 1
163
+ )
164
+ .truncationMode(.tail)
165
+ }
166
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
167
+ .padding(.horizontal, Spacing.S)
168
+ .padding(.vertical, Spacing.S)
169
+
170
+ if let badgeLabel {
171
+ if badgeLabel.isEmpty {
172
+ BadgeDot(size: .small)
173
+ .offset(x: -Spacing.S, y: Spacing.XS)
174
+ } else {
175
+ Badge(label: badgeLabel)
176
+ .offset(x: -Spacing.XS, y: 0)
177
+ }
178
+ } else if item.showDot {
179
+ BadgeDot(size: .small)
180
+ .offset(x: -Spacing.S, y: Spacing.XS)
181
+ }
182
+ }
183
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
184
+ .padding(.horizontal, Spacing.XXS)
185
+ .contentShape(Rectangle())
186
+ .onTapGesture { onClick() }
187
+ }
188
+ }
189
+
190
+ // MARK: - Center floating button
191
+
192
+ private struct CenterFloatingButton: View {
193
+ let data: BottomTabFloatingButton
194
+
195
+ var body: some View {
196
+ VStack(spacing: 0) {
197
+ CurvedContainer()
198
+ Circle()
199
+ .fill(Colors.primary)
200
+ .frame(width: 48, height: 48)
201
+ .overlay(Icon(source: data.icon, size: 28, color: .white))
202
+ Spacer().frame(height: 4)
203
+ MomoText(data.label, typography: .labelXsMedium, color: Colors.black01, textAlign: .center, maxLines: 1)
204
+ .frame(maxWidth: .infinity)
205
+ .frame(height: 14)
206
+ .background(Colors.primary)
207
+ .clipShape(RoundedRectangle(cornerRadius: Radius.XL))
208
+ }
209
+ .frame(width: bottomTabFloatingButtonWidth)
210
+ .padding(.horizontal, Spacing.XXS)
211
+ .padding(.vertical, Spacing.S)
212
+ .contentShape(Rectangle())
213
+ .onTapGesture { data.onPress() }
214
+ }
215
+ }
@@ -0,0 +1,65 @@
1
+ //
2
+ // CurvedContainer.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `CurvedContainer` (navigation/bottomtab/CurvedContainer.kt).
6
+ // Draws the surface-colored notch that sits behind the center floating button,
7
+ // carving a circular cut-out into the tab bar's top edge.
8
+ //
9
+
10
+ import SwiftUI
11
+
12
+ struct CurvedContainer: View {
13
+ var circleSize: CGFloat = 48
14
+ var tabBarItemIconSize: CGFloat = 28
15
+ var circleOverSize: CGFloat = 2
16
+ var width: CGFloat = 71
17
+ var height: CGFloat = 16
18
+ var color: Color = Colors.black01
19
+
20
+ var body: some View {
21
+ let circlePositionTop = circleSize - tabBarItemIconSize - 10
22
+ let circleOverTop = circlePositionTop + circleOverSize
23
+ let circleR = circleSize / 2
24
+ let circleOverR = circleR + circleOverSize
25
+ let centerX = width / 2
26
+
27
+ CurvedShape(circleOverTop: circleOverTop, centerX: centerX, circleOverR: circleOverR, width: width)
28
+ .fill(color)
29
+ .frame(width: width, height: height)
30
+ .offset(y: 24)
31
+ .zIndex(-1)
32
+ }
33
+ }
34
+
35
+ /// Mirrors Compose's `getPath`: a flat bar with a smooth circular dip in the
36
+ /// middle, sized to cradle the floating button.
37
+ private struct CurvedShape: Shape {
38
+ let circleOverTop: CGFloat
39
+ let centerX: CGFloat
40
+ let circleOverR: CGFloat
41
+ let width: CGFloat
42
+
43
+ func path(in rect: CGRect) -> Path {
44
+ var path = Path()
45
+ path.move(to: CGPoint(x: 0, y: circleOverTop))
46
+ path.addLine(to: CGPoint(x: centerX - circleOverR - 40, y: circleOverTop))
47
+
48
+ path.addCurve(
49
+ to: CGPoint(x: centerX, y: 0),
50
+ control1: CGPoint(x: centerX - circleOverR, y: circleOverTop),
51
+ control2: CGPoint(x: centerX - circleOverR, y: 0)
52
+ )
53
+ path.addCurve(
54
+ to: CGPoint(x: centerX + circleOverR + 40, y: circleOverTop),
55
+ control1: CGPoint(x: centerX + circleOverR, y: 0),
56
+ control2: CGPoint(x: centerX + circleOverR, y: circleOverTop)
57
+ )
58
+
59
+ path.addLine(to: CGPoint(x: width, y: circleOverTop))
60
+ path.addLine(to: CGPoint(x: width, y: 60))
61
+ path.addLine(to: CGPoint(x: 0, y: 60))
62
+ path.closeSubpath()
63
+ return path
64
+ }
65
+ }
@@ -0,0 +1,66 @@
1
+ //
2
+ // HeaderBackProps.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `HeaderBackProps` + `PopupNotifyProps`/`PopupAction`.
6
+ // When `preventBack` is set, `Navigator.onBackSafe` shows a confirm popup
7
+ // before actually leaving the screen — same behavior as Compose.
8
+ //
9
+
10
+ import SwiftUI
11
+
12
+ /// Mirrors Compose's `PopupAction` (a titled button with an optional handler).
13
+ public struct PopupAction {
14
+ public let title: String
15
+ public let onPress: (() -> Void)?
16
+
17
+ public init(title: String, onPress: (() -> Void)? = nil) {
18
+ self.title = title
19
+ self.onPress = onPress
20
+ }
21
+ }
22
+
23
+ /// Mirrors Compose's `PopupNotifyProps` as consumed by `headerBackProps.preventBack`.
24
+ public struct PreventBackProps {
25
+ public var image: String
26
+ public var title: String
27
+ public var description: String
28
+ public var error: String
29
+ public var primary: PopupAction?
30
+ public var secondary: PopupAction?
31
+ public var buttonDirection: PopupButtonDirection
32
+ public var isShowCloseIcon: Bool
33
+ public var onIconClose: (() -> Void)?
34
+
35
+ public init(
36
+ image: String = "",
37
+ title: String = "",
38
+ description: String = "",
39
+ error: String = "",
40
+ primary: PopupAction? = nil,
41
+ secondary: PopupAction? = nil,
42
+ buttonDirection: PopupButtonDirection = .row,
43
+ isShowCloseIcon: Bool = true,
44
+ onIconClose: (() -> Void)? = nil
45
+ ) {
46
+ self.image = image
47
+ self.title = title
48
+ self.description = description
49
+ self.error = error
50
+ self.primary = primary
51
+ self.secondary = secondary
52
+ self.buttonDirection = buttonDirection
53
+ self.isShowCloseIcon = isShowCloseIcon
54
+ self.onIconClose = onIconClose
55
+ }
56
+ }
57
+
58
+ /// Mirrors Compose's `HeaderBackProps`.
59
+ public struct HeaderBackProps {
60
+ /// When non-nil, a confirm popup is shown before the back navigation runs.
61
+ public var preventBack: PreventBackProps?
62
+
63
+ public init(preventBack: PreventBackProps? = nil) {
64
+ self.preventBack = preventBack
65
+ }
66
+ }
@@ -0,0 +1,57 @@
1
+ //
2
+ // HeaderTitle.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `sealed class HeaderTitle` (navigation/component/Header.kt).
6
+ // The header can show either a plain title (`.default`) or a rich "user" title
7
+ // with an avatar group, subtitle, inline icons and a status dot (`.user`).
8
+ // (Compose's `Location`/`Journey` variants are not rendered yet, so they are
9
+ // omitted here for parity with the implemented `HeaderUser`.)
10
+ //
11
+
12
+ import SwiftUI
13
+
14
+ /// Data backing a `.user` header title (mirrors `HeaderTitle.User`).
15
+ public struct HeaderUserData {
16
+ public var title: String
17
+ public var subTitle: String?
18
+ public var image: [String]?
19
+ public var dotColor: Color?
20
+ public var tintColor: Color?
21
+ public var onPress: (() -> Void)?
22
+ public var icons: [String]
23
+ public var isLoading: Bool
24
+
25
+ public init(
26
+ title: String,
27
+ subTitle: String? = nil,
28
+ image: [String]? = nil,
29
+ dotColor: Color? = nil,
30
+ tintColor: Color? = nil,
31
+ onPress: (() -> Void)? = nil,
32
+ icons: [String] = [],
33
+ isLoading: Bool = false
34
+ ) {
35
+ self.title = title
36
+ self.subTitle = subTitle
37
+ self.image = image
38
+ self.dotColor = dotColor
39
+ self.tintColor = tintColor
40
+ self.onPress = onPress
41
+ self.icons = icons
42
+ self.isLoading = isLoading
43
+ }
44
+ }
45
+
46
+ /// Mirrors Compose's `HeaderTitle` sealed class.
47
+ public enum HeaderTitle {
48
+ case `default`(String)
49
+ case user(HeaderUserData)
50
+
51
+ /// Plain title text, when this is a `.default` title (used by the legacy
52
+ /// string-based `Header`/`NavigationOptions` paths).
53
+ public var defaultTitle: String? {
54
+ if case let .default(text) = self { return text }
55
+ return nil
56
+ }
57
+ }
@@ -0,0 +1,209 @@
1
+ //
2
+ // HeaderUser.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `HeaderUser` (navigation/component/HeaderUser.kt).
6
+ // Renders a `HeaderTitle.User`: an avatar group (1..n avatars with overflow),
7
+ // a title with optional inline icons, and an optional subtitle. Shows a
8
+ // skeleton shimmer placeholder while `isLoading`.
9
+ //
10
+
11
+ import SwiftUI
12
+
13
+ @available(iOS 16.0, *)
14
+ public struct HeaderUserView: View {
15
+ private let data: HeaderUserData
16
+
17
+ public init(_ data: HeaderUserData) {
18
+ self.data = data
19
+ }
20
+
21
+ private let avatarSize: CGFloat = Spacing.XXL // 32
22
+
23
+ private var maxWidth: CGFloat {
24
+ UIScreen.main.bounds.width - 172
25
+ }
26
+
27
+ public var body: some View {
28
+ if data.isLoading {
29
+ shimmer
30
+ } else {
31
+ content
32
+ }
33
+ }
34
+
35
+ private var content: some View {
36
+ HStack(spacing: 0) {
37
+ HStack(spacing: 0) {
38
+ ZStack(alignment: .bottomTrailing) {
39
+ AvatarGroup(urls: data.image ?? [], title: data.title, size: avatarSize)
40
+ if let dotColor = data.dotColor {
41
+ Circle()
42
+ .fill(dotColor)
43
+ .frame(width: 10, height: 10)
44
+ }
45
+ }
46
+ .frame(width: avatarSize, height: avatarSize)
47
+
48
+ Spacer().frame(width: 6)
49
+
50
+ VStack(alignment: .leading, spacing: 0) {
51
+ HStack(spacing: 0) {
52
+ MomoText(data.title, typography: .actionXsBold, color: data.tintColor ?? Colors.black17, maxLines: 1)
53
+ .truncationMode(.tail)
54
+
55
+ if !data.icons.isEmpty {
56
+ Spacer().frame(width: 6)
57
+ HStack(spacing: 0) {
58
+ ForEach(Array(data.icons.enumerated()), id: \.offset) { _, icon in
59
+ ImageView(icon)
60
+ .frame(width: 16, height: 16)
61
+ .clipShape(RoundedRectangle(cornerRadius: 4))
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ if let subTitle = data.subTitle, !subTitle.isEmpty {
68
+ Spacer().frame(height: 2)
69
+ MomoText(subTitle, typography: .descriptionXsRegular, color: data.tintColor ?? Colors.black17, maxLines: 1)
70
+ .truncationMode(.tail)
71
+ }
72
+ }
73
+ }
74
+ .frame(maxWidth: maxWidth, alignment: .leading)
75
+
76
+ Spacer(minLength: 0)
77
+ }
78
+ .frame(maxWidth: .infinity, alignment: .leading)
79
+ .padding(.vertical, Spacing.XS)
80
+ .contentShape(Rectangle())
81
+ .onTapGesture { data.onPress?() }
82
+ .allowsHitTesting(data.onPress != nil)
83
+ }
84
+
85
+ private var shimmer: some View {
86
+ HStack(spacing: Spacing.M / 2) {
87
+ Circle()
88
+ .fill(Colors.black05)
89
+ .frame(width: avatarSize, height: avatarSize)
90
+ VStack(alignment: .leading, spacing: Spacing.XXS) {
91
+ RoundedRectangle(cornerRadius: 8)
92
+ .fill(Colors.black05)
93
+ .frame(width: 120, height: 18)
94
+ RoundedRectangle(cornerRadius: 8)
95
+ .fill(Colors.black05)
96
+ .frame(width: 120, height: 12)
97
+ }
98
+ }
99
+ .frame(maxWidth: .infinity, alignment: .leading)
100
+ .padding(.vertical, Spacing.XS)
101
+ }
102
+ }
103
+
104
+ // MARK: - Avatar group
105
+
106
+ /// Mirrors Compose's `AvatarGroup`: lays out 1..4 avatars in a fixed collage,
107
+ /// falls back to initials when empty, and shows a "+n" overflow tile beyond 4.
108
+ @available(iOS 16.0, *)
109
+ private struct AvatarGroup: View {
110
+ let urls: [String]
111
+ let title: String
112
+ let size: CGFloat
113
+
114
+ private var child: CGFloat {
115
+ switch urls.count {
116
+ case 0, 1, 2: return 24
117
+ default: return 16
118
+ }
119
+ }
120
+
121
+ var body: some View {
122
+ switch urls.count {
123
+ case 0:
124
+ InitialsAvatar(name: title, size: size)
125
+ case 1:
126
+ avatar(urls[0]).frame(width: size, height: size)
127
+ case 2:
128
+ ZStack {
129
+ avatar(urls[0]).frame(width: child, height: child)
130
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
131
+ avatar(urls[1]).frame(width: child, height: child)
132
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
133
+ }
134
+ .frame(width: size, height: size)
135
+ case 3:
136
+ ZStack {
137
+ avatar(urls[0]).frame(width: child, height: child)
138
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
139
+ avatar(urls[1]).frame(width: child, height: child)
140
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
141
+ avatar(urls[2]).frame(width: child, height: child)
142
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
143
+ }
144
+ .frame(width: size, height: size)
145
+ .padding(2)
146
+ case 4:
147
+ ZStack {
148
+ avatar(urls[0]).frame(width: child, height: child)
149
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
150
+ avatar(urls[1]).frame(width: child, height: child)
151
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
152
+ avatar(urls[2]).frame(width: child, height: child)
153
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
154
+ avatar(urls[3]).frame(width: child, height: child)
155
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
156
+ }
157
+ .frame(width: size, height: size)
158
+ .padding(1)
159
+ default:
160
+ ZStack {
161
+ avatar(urls[0]).frame(width: child, height: child)
162
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
163
+ avatar(urls[1]).frame(width: child, height: child)
164
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
165
+ avatar(urls[2]).frame(width: child, height: child)
166
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
167
+ overflowTile(more: urls.count - 3)
168
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
169
+ }
170
+ .frame(width: size, height: size)
171
+ .padding(1)
172
+ }
173
+ }
174
+
175
+ private func avatar(_ url: String) -> some View {
176
+ ImageView(url)
177
+ .aspectRatio(contentMode: .fill)
178
+ .clipShape(Circle())
179
+ .overlay(Circle().stroke(Colors.black05, lineWidth: 0.5))
180
+ }
181
+
182
+ private func overflowTile(more: Int) -> some View {
183
+ MomoText("+\(more)", typography: .descriptionXsRegular, color: Colors.pink03, textAlign: .center, maxLines: 1)
184
+ .frame(width: child, height: child)
185
+ .background(Colors.pink09)
186
+ .clipShape(Circle())
187
+ }
188
+ }
189
+
190
+ @available(iOS 16.0, *)
191
+ private struct InitialsAvatar: View {
192
+ let name: String
193
+ let size: CGFloat
194
+
195
+ private var initials: String {
196
+ let words = name.trimmingCharacters(in: .whitespaces)
197
+ .split(whereSeparator: { $0.isWhitespace })
198
+ .suffix(2)
199
+ return words.compactMap { $0.first.map { String($0).uppercased() } }.joined()
200
+ }
201
+
202
+ var body: some View {
203
+ MomoText(initials, typography: .descriptionXsRegular, color: Colors.pink03, textAlign: .center, maxLines: 1)
204
+ .frame(width: size, height: size)
205
+ .background(Colors.pink09)
206
+ .clipShape(Circle())
207
+ .overlay(Circle().stroke(Colors.black05, lineWidth: 0.5))
208
+ }
209
+ }