@momo-kits/native-kits 0.0.1-beta.4 → 0.0.2-alpha.1

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 (296) hide show
  1. package/CLAUDE.md +99 -0
  2. package/PUBLISH_GUIDE.md +675 -0
  3. package/README.md +184 -5
  4. package/ios/Application/ApplicationEnvironment.swift +71 -0
  5. package/ios/Application/Components.swift +270 -0
  6. package/ios/Application/ComposeApi.swift +22 -0
  7. package/ios/Application/FloatingButton.swift +174 -0
  8. package/ios/Application/HeaderRight.swift +306 -0
  9. package/ios/Application/LanguageSource.swift +93 -0
  10. package/ios/Application/Localize.swift +378 -0
  11. package/ios/Application/LocalizeModifier.swift +30 -0
  12. package/ios/Application/MaxApi.swift +18 -0
  13. package/ios/Application/Navigation/BottomTab/BottomTab.swift +104 -0
  14. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  15. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  16. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  17. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  18. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  19. package/ios/Application/Navigation/NavigationContainer.swift +213 -0
  20. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  21. package/ios/Application/Navigation/Navigator.swift +453 -0
  22. package/ios/Application/Navigation/NavigatorManager.swift +22 -0
  23. package/ios/Application/Navigation/Overplay/BottomSheet.swift +256 -0
  24. package/ios/Application/Navigation/Overplay/ModalScreen.swift +74 -0
  25. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  26. package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
  27. package/ios/Application/Navigation/component/Header.swift +178 -0
  28. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  29. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  30. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +11 -0
  31. package/ios/Application/Navigation/component/NavigationFooter.swift +90 -0
  32. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +196 -0
  33. package/ios/Application/Screen.swift +250 -0
  34. package/ios/Application/StackScreen.swift +492 -0
  35. package/ios/Avatar/Avatar.swift +193 -0
  36. package/ios/Badge/Badge.swift +84 -0
  37. package/ios/Badge/BadgeDot.swift +31 -0
  38. package/ios/Badge/BadgeRibbon.swift +319 -0
  39. package/ios/BaselineView/BaselineView.swift +163 -0
  40. package/ios/Button/Button.swift +232 -84
  41. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
  42. package/ios/Carousel/Carousel.swift +196 -0
  43. package/ios/Checkbox/Checkbox.swift +77 -0
  44. package/ios/Chip/Chip.swift +120 -0
  45. package/ios/Collapse/Collapse.swift +192 -0
  46. package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Colors.swift +42 -37
  47. package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
  48. package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Spacing.swift +3 -1
  49. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  50. package/ios/DateTimePicker/DateTimePicker.swift +212 -0
  51. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  52. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  53. package/ios/DateTimePicker/WheelPicker.swift +165 -0
  54. package/ios/Divider/Divider.swift +16 -0
  55. package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
  56. package/ios/Extensions/Color++.swift +32 -0
  57. package/ios/Icon/Icon.swift +69 -0
  58. package/ios/IconButton/IconButton.swift +140 -0
  59. package/ios/Image/Image.swift +87 -0
  60. package/ios/Information/Information.swift +156 -0
  61. package/ios/Input/ErrorView.swift +58 -0
  62. package/ios/Input/Input.swift +461 -77
  63. package/ios/Input/InputPhoneNumber.swift +192 -0
  64. package/ios/Input/InputSearch.swift +301 -0
  65. package/ios/Input/InputTextArea.swift +318 -0
  66. package/ios/InputDropDown/InputDropDown.swift +201 -0
  67. package/ios/InputMoney/InputMoney.swift +405 -0
  68. package/ios/InputOTP/InputOTP.swift +211 -0
  69. package/ios/Layout/GridContext.swift +30 -0
  70. package/ios/Layout/Item.swift +42 -0
  71. package/ios/Layout/Section.swift +134 -0
  72. package/ios/Loader/Loader.swift +113 -0
  73. package/ios/Lottie/LottieView.swift +86 -0
  74. package/ios/MoMoUIKits.podspec +5 -0
  75. package/ios/OTPKeyboard/KeyboardButton.swift +13 -5
  76. package/ios/OTPKeyboard/OTPKeyboard.swift +1 -1
  77. package/ios/Pagination/PaginationDot.swift +61 -0
  78. package/ios/Pagination/PaginationNumber.swift +35 -0
  79. package/ios/Pagination/PaginationScroll.swift +101 -0
  80. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  81. package/ios/Popup/PopupDisplay.swift +279 -0
  82. package/ios/Popup/PopupInput.swift +92 -0
  83. package/ios/Popup/PopupNotify.swift +219 -0
  84. package/ios/Popup/PopupPromotion.swift +74 -0
  85. package/ios/PopupView/FullscreenPopup.swift +251 -0
  86. package/ios/{Popup → PopupView}/Modifiers.swift +6 -20
  87. package/ios/{Popup → PopupView}/PopupView.swift +38 -146
  88. package/ios/PopupView/Utils++.swift +281 -0
  89. package/ios/ProgressInfo/ProgressInfo.swift +297 -0
  90. package/ios/Radio/Radio.swift +69 -0
  91. package/ios/Rating/Rating.swift +83 -0
  92. package/{example/ios/Example/Preview Content/Preview Assets.xcassets/Contents.json → ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json} +5 -0
  93. package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Text.colorset/Contents.json +3 -3
  94. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
  95. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
  96. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
  97. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
  98. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
  99. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
  100. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
  101. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
  102. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
  103. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
  104. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
  105. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
  106. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
  107. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
  108. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
  109. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
  110. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
  111. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
  112. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
  113. package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
  114. package/ios/Skeleton/Skeleton.swift +99 -0
  115. package/ios/Slider/Slider.swift +237 -0
  116. package/ios/StatusBarTap/StatusBarTap.h +13 -0
  117. package/ios/StatusBarTap/StatusBarTap.m +75 -0
  118. package/ios/Stepper/Stepper.swift +221 -0
  119. package/ios/Steps/Steps.swift +467 -0
  120. package/ios/SuggestAction/SuggestAction.swift +100 -0
  121. package/ios/Swipeable/SwipeCell.swift +226 -31
  122. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  123. package/ios/Switch/Switch.swift +69 -0
  124. package/ios/TabView/TabView.swift +534 -0
  125. package/ios/Tag/Tag.swift +99 -0
  126. package/ios/Template/Logo/Logo.swift +75 -0
  127. package/ios/Template/TrustBanner/TrustBanner.swift +123 -0
  128. package/ios/Title/Title.swift +270 -0
  129. package/ios/Tooltip/Tooltip.swift +486 -0
  130. package/ios/Typography/FontScale.swift +103 -0
  131. package/ios/Typography/FontScaleStore.swift +58 -0
  132. package/ios/Typography/KitDesignSystem.swift +17 -0
  133. package/ios/Typography/Text.swift +262 -0
  134. package/ios/Typography/Typography.swift +80 -23
  135. package/ios/Uploader/Uploader.swift +174 -0
  136. package/ios/native-kits.podspec +73 -13
  137. package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
  138. package/ios-demo/Resources/dot_loading.json +558 -0
  139. package/ios-demo/Resources/icon.json +4052 -0
  140. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  141. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  142. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  143. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  144. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  145. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  146. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  147. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  148. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  149. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  150. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  151. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
  152. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  153. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +107 -0
  154. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +75 -0
  155. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  156. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  157. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +108 -0
  158. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +96 -0
  159. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +60 -0
  160. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +76 -0
  161. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +102 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  163. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +100 -0
  164. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DemoScreen.swift +420 -0
  165. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +56 -0
  166. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  167. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +276 -0
  168. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +93 -0
  169. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +81 -0
  170. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +65 -0
  171. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +96 -0
  172. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +117 -0
  173. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +94 -0
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +94 -0
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +88 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +120 -0
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +85 -0
  178. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +87 -0
  179. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +73 -0
  180. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +235 -0
  181. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +241 -0
  182. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +68 -0
  183. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PlaygroundData.swift +76 -0
  184. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  185. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +95 -0
  186. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +67 -0
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +101 -0
  188. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +64 -0
  189. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +68 -0
  190. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +83 -0
  191. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +61 -0
  192. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +90 -0
  193. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +82 -0
  194. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +93 -0
  195. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +114 -0
  196. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +90 -0
  197. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +107 -0
  198. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +58 -0
  199. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +114 -0
  200. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +86 -0
  201. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +181 -0
  202. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +104 -0
  203. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +118 -0
  204. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  205. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +85 -0
  206. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +53 -0
  207. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  208. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  209. package/package.json +6 -6
  210. package/publish.sh +554 -20
  211. package/scripts/gen-ios-color.mjs +136 -0
  212. package/CODE_OF_CONDUCT.md +0 -133
  213. package/CONTRIBUTING.md +0 -114
  214. package/LICENSE +0 -20
  215. package/android/build.gradle +0 -82
  216. package/android/gradle.properties +0 -5
  217. package/android/src/main/AndroidManifest.xml +0 -4
  218. package/android/src/main/java/com/momoplatform/nativekits/NativeKitsPackage.kt +0 -17
  219. package/android/src/main/java/com/momoplatform/nativekits/NativeKitsViewManager.kt +0 -20
  220. package/example/ios/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -63
  221. package/example/ios/Example/ComponentDetail.swift +0 -35
  222. package/example/ios/Example/ComponentItem.swift +0 -24
  223. package/example/ios/Example/ComponentModel.swift +0 -33
  224. package/example/ios/Example/ContentView.swift +0 -53
  225. package/example/ios/Example/Example.entitlements +0 -10
  226. package/example/ios/Example/Foundation/ButtonDemo.swift +0 -80
  227. package/example/ios/Example/Foundation/Popup/ActionSheets.swift +0 -136
  228. package/example/ios/Example/Foundation/Popup/Floats.swift +0 -127
  229. package/example/ios/Example/Foundation/Popup/Popups.swift +0 -101
  230. package/example/ios/Example/Foundation/PopupDemo.swift +0 -366
  231. package/example/ios/Example/Foundation/TextInputDemo.swift +0 -31
  232. package/example/ios/Example/Foundation/TypographyDemo.swift +0 -53
  233. package/example/ios/Example/Foundation.swift +0 -26
  234. package/example/ios/Example/Info.plist +0 -18
  235. package/example/ios/Example/Library/SwipeableDemo.swift +0 -105
  236. package/example/ios/Example/Library.swift +0 -26
  237. package/example/ios/Example/Main.swift +0 -11
  238. package/example/ios/Example/Overview.swift +0 -20
  239. package/example/ios/Example/Resource/SFProText-Black.ttf +0 -0
  240. package/example/ios/Example/Resource/SFProText-Bold.ttf +0 -0
  241. package/example/ios/Example/Resource/SFProText-Heavy.ttf +0 -0
  242. package/example/ios/Example/Resource/SFProText-Light.ttf +0 -0
  243. package/example/ios/Example/Resource/SFProText-Medium.ttf +0 -0
  244. package/example/ios/Example/Resource/SFProText-Regular.ttf +0 -0
  245. package/example/ios/Example/Resource/SFProText-Semibold.ttf +0 -0
  246. package/example/ios/Example/Resource/SFProText-Thin.ttf +0 -0
  247. package/example/ios/Example/Resource/SFProText-Ultralight.ttf +0 -0
  248. package/example/ios/Example/Resource/foundation.json +0 -33
  249. package/example/ios/Example/Resource/library.json +0 -8
  250. package/example/ios/Example/Utils.swift +0 -30
  251. package/example/ios/Example/ViewModifier/DarkViewModifier.swift +0 -22
  252. package/example/ios/Example.xcodeproj/project.pbxproj +0 -570
  253. package/example/ios/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  254. package/example/ios/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  255. package/example/ios/Example.xcodeproj/project.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  256. package/example/ios/Example.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  257. package/example/ios/Example.xcworkspace/contents.xcworkspacedata +0 -10
  258. package/example/ios/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  259. package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  260. package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
  261. package/example/ios/Podfile +0 -6
  262. package/example/ios/Podfile.lock +0 -16
  263. package/example/ios/Pods/Local Podspecs/MoMoUIKits.podspec.json +0 -29
  264. package/example/ios/Pods/Manifest.lock +0 -16
  265. package/example/ios/Pods/Pods.xcodeproj/project.pbxproj +0 -661
  266. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
  267. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
  268. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -25
  269. package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-dummy.m +0 -5
  270. package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-prefix.pch +0 -12
  271. package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-umbrella.h +0 -16
  272. package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.debug.xcconfig +0 -12
  273. package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.modulemap +0 -6
  274. package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.release.xcconfig +0 -12
  275. package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown +0 -3
  276. package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist +0 -29
  277. package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m +0 -5
  278. package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h +0 -16
  279. package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig +0 -14
  280. package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap +0 -6
  281. package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig +0 -14
  282. package/ios/Colors+Spacing/Radius.swift +0 -11
  283. package/ios/Popup/Utils++.swift +0 -163
  284. package/ios/Theme.md +0 -18
  285. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Background.colorset/Contents.json +0 -0
  286. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Border.colorset/Contents.json +0 -0
  287. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Card.colorset/Contents.json +0 -0
  288. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Contents.json +0 -0
  289. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Error.colorset/Contents.json +0 -0
  290. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Primary.colorset/Contents.json +0 -0
  291. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/PrimaryDark.colorset/Contents.json +0 -0
  292. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/PrimaryLight.colorset/Contents.json +0 -0
  293. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Secondary.colorset/Contents.json +0 -0
  294. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Success.colorset/Contents.json +0 -0
  295. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/TextSecondary.colorset/Contents.json +0 -0
  296. /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Warning.colorset/Contents.json +0 -0
@@ -1,15 +1,16 @@
1
-
2
1
  import Foundation
2
+ import Lottie
3
3
  import SwiftUI
4
4
 
5
5
  // MARK: - ButtonType
6
6
 
7
7
  public enum ButtonType {
8
8
  case primary
9
- case danger
10
- case tonal
11
9
  case secondary
10
+ case tonal
12
11
  case outline
12
+ case danger
13
+ case text
13
14
  case disabled
14
15
  }
15
16
 
@@ -21,111 +22,258 @@ public enum ButtonSize {
21
22
  case small
22
23
  }
23
24
 
24
- // MARK: - Button
25
+ public struct ButtonStyleData {
26
+ let background: Color
27
+ let content: Color
28
+ let border: Color?
29
+ let borderWidth: CGFloat
30
+ }
31
+
32
+ public extension ButtonType {
33
+ func backgroundColor(loading: Bool) -> Color {
34
+ backgroundColor(loading: loading, theme: defaultTheme)
35
+ }
36
+
37
+ func backgroundColor(loading: Bool, theme: Theme) -> Color {
38
+ switch self {
39
+ case .disabled: return theme.colors.background.disable.opacity(loading ? 0.75 : 1)
40
+ case .primary: return theme.colors.primary.opacity(loading ? 0.75 : 1)
41
+ case .secondary: return theme.colors.background.surface.opacity(loading ? 0.75 : 1)
42
+ case .outline: return theme.colors.background.surface.opacity(loading ? 0.75 : 1)
43
+ case .tonal: return theme.colors.background.tonal.opacity(loading ? 0.75 : 1)
44
+ case .danger: return theme.colors.error.primary.opacity(loading ? 0.75 : 1)
45
+ case .text: return .clear
46
+ }
47
+ }
48
+
49
+ func contentColor(loading: Bool) -> Color {
50
+ contentColor(loading: loading, theme: defaultTheme)
51
+ }
52
+
53
+ func contentColor(loading: Bool, theme: Theme) -> Color {
54
+ switch self {
55
+ case .disabled: return theme.colors.text.disable.opacity(loading ? 0.75 : 1)
56
+ case .primary: return Colors.black01
57
+ case .secondary: return theme.colors.text.default
58
+ case .outline: return theme.colors.primary
59
+ case .tonal: return theme.colors.primary
60
+ case .danger: return Colors.black01
61
+ case .text: return theme.colors.primary
62
+ }
63
+ }
64
+
65
+ var borderColor: Color? {
66
+ borderColor(theme: defaultTheme)
67
+ }
68
+
69
+ func borderColor(theme: Theme) -> Color? {
70
+ switch self {
71
+ case .outline: return theme.colors.primary
72
+ case .secondary: return theme.colors.border.default
73
+ default: return nil
74
+ }
75
+ }
76
+
77
+ var borderWidth: CGFloat {
78
+ switch self {
79
+ case .outline: return 1
80
+ case .secondary: return 1
81
+ default: return 0
82
+ }
83
+ }
84
+ }
85
+
86
+ extension ButtonSize {
87
+ var height: CGFloat {
88
+ switch self {
89
+ case .large: return 48
90
+ case .medium: return 36
91
+ case .small: return 28
92
+ }
93
+ }
94
+
95
+ var padding: CGFloat {
96
+ switch self {
97
+ case .large: return Spacing.L
98
+ case .medium: return Spacing.M
99
+ case .small: return Spacing.S
100
+ }
101
+ }
102
+
103
+ var radius: CGFloat {
104
+ Radius.S
105
+ }
106
+
107
+ var iconSize: CGFloat {
108
+ switch self {
109
+ case .large: return 24
110
+ case .medium, .small: return 16
111
+ }
112
+ }
113
+
114
+ var iconSpacing: CGFloat {
115
+ switch self {
116
+ case .small: return Spacing.XS
117
+ case .medium, .large: return Spacing.S
118
+ }
119
+ }
120
+
121
+ var typographyStyle: TypographyStyle {
122
+ switch self {
123
+ case .large: return .actionDefaultBold
124
+ case .medium: return .actionSBold
125
+ case .small: return .actionXsBold
126
+ }
127
+ }
128
+ }
25
129
 
26
130
  public struct Button: View {
27
- // MARK: Lifecycle
131
+ @Environment(\.appTheme) private var theme
132
+ var title: String
133
+ var action: () -> Void
134
+ var type: ButtonType
135
+ var size: ButtonSize
136
+ var iconLeft: String
137
+ var iconRight: String
138
+ var isFull: Bool
139
+ var loading: Bool
140
+ var useTintColor: Bool
28
141
 
29
- public init(title: String = "", action: @escaping () -> Void, type: ButtonType = .primary, size: ButtonSize = .large, iconLeft: AnyView? = nil, iconRight: AnyView? = nil) {
142
+ public init(
143
+ title: String = "Button",
144
+ action: @escaping () -> Void,
145
+ type: ButtonType = .primary,
146
+ size: ButtonSize = .large,
147
+ iconLeft: String = "",
148
+ iconRight: String = "",
149
+ isFull: Bool = true,
150
+ loading: Bool = false,
151
+ useTintColor: Bool = true
152
+ ) {
30
153
  self.title = title
31
154
  self.action = action
32
155
  self.type = type
33
156
  self.size = size
34
157
  self.iconLeft = iconLeft
35
158
  self.iconRight = iconRight
159
+ self.isFull = isFull
160
+ self.loading = loading
161
+ self.useTintColor = useTintColor
36
162
  }
37
163
 
38
- // MARK: Public
164
+ private func shouldLoadingOnLeft(_ left: String, _ right: String) -> Bool {
165
+ let hasLeft = !left.isEmpty
166
+ let hasRight = !right.isEmpty
167
+
168
+ switch (hasLeft, hasRight) {
169
+ case (false, false): return true
170
+ case (true, false): return true
171
+ case (false, true): return false
172
+ case (true, true): return false
173
+ }
174
+ }
39
175
 
40
176
  public var body: some View {
41
- SwiftUI.Button(action: action) {
42
- HStack {
43
- iconLeft
44
- renderTitle(title: title, type: type, size: size)
45
- iconRight
177
+ let loadingOnLeft = shouldLoadingOnLeft(iconLeft, iconRight)
178
+ let isEnabled = type != .disabled && !loading
179
+
180
+ let bg = type.backgroundColor(loading: loading, theme: theme)
181
+ let fg = type.contentColor(loading: loading, theme: theme)
182
+ let border = type.borderColor(theme: theme)
183
+ let borderWidth = type.borderWidth
184
+
185
+ SwiftUI.Button(action: {
186
+ if isEnabled {
187
+ action()
46
188
  }
47
- .buttonSize(size)
48
- .buttonType(type)
49
- }.disabled(type == .disabled)
189
+ }) {
190
+ HStack(spacing: size.iconSpacing) {
191
+ renderIcon(icon: iconLeft, forceLoading: loading && loadingOnLeft, textColor: fg)
192
+
193
+ MomoText(title, typography: size.typographyStyle, color: fg)
194
+ .lineLimit(1)
195
+ .truncationMode(.tail)
196
+
197
+ renderIcon(icon: iconRight, forceLoading: loading && !loadingOnLeft, textColor: fg)
198
+ }
199
+ .frame(maxWidth: isFull ? .infinity : nil)
200
+ .padding(.horizontal, size.padding)
201
+ .frame(height: size.height)
202
+ }
203
+ .buttonStyle(ButtonPressFeedbackStyle(
204
+ isEnabled: isEnabled,
205
+ background: bg,
206
+ border: border,
207
+ borderWidth: borderWidth,
208
+ radius: size.radius,
209
+ height: size.height,
210
+ isFull: isFull
211
+ ))
212
+ .disabled(!isEnabled)
50
213
  }
51
214
 
52
- // MARK: Internal
215
+ /// Mirrors Kotlin `RenderIcon`: the loader replaces the icon slot entirely, and
216
+ /// `useTintColor == false` leaves the icon at its own colors.
217
+ @ViewBuilder
218
+ private func renderIcon(icon: String, forceLoading: Bool, textColor: Color) -> some View {
219
+ let color: Color? = useTintColor ? textColor : nil
53
220
 
54
- var title: String
55
- var action: () -> Void = {}
56
- var type: ButtonType = .primary
57
- var size: ButtonSize = .large
58
- var iconLeft: AnyView?
59
- var iconRight: AnyView?
60
-
61
- func renderTitle(title: String, type: ButtonType, size: ButtonSize) -> Text {
62
- switch size {
63
- case .large:
64
- return Text(title).font(.system(size: 16, weight: Font.Weight.bold))
65
- case .medium:
66
- return Text(title).font(.system(size: 14, weight: Font.Weight.bold))
67
- case .small:
68
- return Text(title).font(.system(size: 12, weight: Font.Weight.bold))
221
+ if forceLoading {
222
+ LottieView(name: "lottie_circle_loader", loopMode: .loop)
223
+ .frame(width: size.iconSize, height: size.iconSize)
224
+ .colorMultiply(color ?? Colors.black01)
225
+ } else if !icon.isEmpty {
226
+ Icon(source: icon, size: size.iconSize, color: color)
69
227
  }
70
228
  }
71
229
  }
72
230
 
73
- private extension View {
74
- func buttonType(_ type: ButtonType) -> some View {
75
- switch type {
76
- case .primary:
77
- return AnyView(self
78
- .background(Colors.primary)
79
- .foregroundColor(Colors.black01)
80
- .clipShape(RoundedRectangle(cornerRadius: Radius.S)))
81
- case .danger:
82
- return AnyView(self
83
- .background(Colors.error)
84
- .foregroundColor(Colors.black01)
85
- .clipShape(RoundedRectangle(cornerRadius: Radius.S)))
86
- case .tonal:
87
- return AnyView(self
88
- .background(Colors.primaryLight)
89
- .foregroundColor(Colors.primary)
90
- .clipShape(RoundedRectangle(cornerRadius: Radius.S)))
91
- case .secondary:
92
- return AnyView(self
93
- .background(Colors.card)
94
- .foregroundColor(Colors.text)
95
- .cornerRadius(8)
96
- .overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.black06, lineWidth: 1))
97
- .padding(2)
98
- )
99
- case .outline:
100
- return AnyView(self
101
- .background(Colors.card)
102
- .foregroundColor(Colors.primary)
103
- .cornerRadius(8)
104
- .overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.primary, lineWidth: 1))
105
- .padding(2)
231
+ /// Mirrors Compose's press feedback: the background shrinks via an animated
232
+ /// horizontal inset while the content fades, rather than a plain opacity dim on
233
+ /// the whole button (matching `animatedPadding`/`alpha` in Button.kt).
234
+ private struct ButtonPressFeedbackStyle: ButtonStyle {
235
+ let isEnabled: Bool
236
+ let background: Color
237
+ let border: Color?
238
+ let borderWidth: CGFloat
239
+ let radius: CGFloat
240
+ let height: CGFloat
241
+ let isFull: Bool
242
+
243
+ func makeBody(configuration: Configuration) -> some View {
244
+ let pressed = configuration.isPressed && isEnabled
245
+
246
+ configuration.label
247
+ .modifier(HugContentOnLegacyOS(isFull: isFull))
248
+ .opacity(pressed ? 0.5 : 1)
249
+ .background(
250
+ RoundedRectangle(cornerRadius: radius)
251
+ .fill(background)
252
+ .overlay(
253
+ RoundedRectangle(cornerRadius: radius)
254
+ .strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
255
+ )
256
+ .padding(.horizontal, pressed ? 2 : 0)
106
257
  )
107
- case .disabled:
108
- return AnyView(self
109
- .background(Colors.black05)
110
- .foregroundColor(Colors.black06)
111
- .clipShape(RoundedRectangle(cornerRadius: Radius.S)))
112
- }
258
+ .frame(height: height)
259
+ .clipShape(RoundedRectangle(cornerRadius: radius))
260
+ .animation(.easeInOut(duration: 0.1), value: pressed)
113
261
  }
262
+ }
263
+
264
+ /// `ButtonStyleConfiguration.Label` is greedy on iOS 16 and earlier — it accepts
265
+ /// the full proposed width, so the background drawn in `makeBody` stretches past
266
+ /// the content even when `isFull == false`. iOS 17 fixed the label to hug its
267
+ /// content, so only legacy OSes need the `fixedSize` clamp (kept off on 17+ to
268
+ /// preserve tail truncation of long titles).
269
+ private struct HugContentOnLegacyOS: ViewModifier {
270
+ let isFull: Bool
114
271
 
115
- func buttonSize(_ size: ButtonSize) -> some View {
116
- switch size {
117
- case .large:
118
- return self
119
- .frame(maxWidth: .infinity, minHeight: 48, maxHeight: 48)
120
- .padding([.leading, .trailing], Spacing.XS)
121
- case .medium:
122
- return self
123
- .frame(minWidth: 80, maxWidth: 96, minHeight: 36, maxHeight: 36)
124
- .padding([.leading, .trailing], Spacing.XS)
125
- case .small:
126
- return self
127
- .frame(minWidth: 56, maxWidth: 76, minHeight: 28, maxHeight: 28)
128
- .padding([.leading, .trailing], Spacing.XS)
272
+ func body(content: Content) -> some View {
273
+ if #available(iOS 17.0, *) {
274
+ content
275
+ } else {
276
+ content.fixedSize(horizontal: !isFull, vertical: false)
129
277
  }
130
278
  }
131
279
  }
@@ -0,0 +1,126 @@
1
+
2
+ import Foundation
3
+ import SwiftUI
4
+
5
+ // MARK: - CalculatorKeyboard
6
+
7
+
8
+ extension String {
9
+ var isNumber: Bool {
10
+ let digitsCharacters = CharacterSet(charactersIn: "0123456789")
11
+ return CharacterSet(charactersIn: self).isSubset(of: digitsCharacters)
12
+ }
13
+ }
14
+
15
+
16
+ public struct CalculatorKeyboard: View {
17
+ // MARK: Lifecycle
18
+ @Binding var value: String
19
+ @State var tempValue: String = ""
20
+
21
+ public init(_ value: Binding<String>) {
22
+ self._value = value
23
+ }
24
+
25
+ func onPressBack(){
26
+ if (value.count > 0 ){
27
+ value.removeLast()
28
+ }
29
+
30
+ }
31
+
32
+ func onPressClear(key: String){
33
+ value = ""
34
+ tempValue = ""
35
+ }
36
+
37
+ func onPressEqual(key: String){
38
+ if (tempValue == "") {
39
+ return
40
+ }
41
+ if (tempValue.last?.isNumber == false){
42
+ tempValue.removeLast()
43
+ }
44
+ let expression = NSExpression(format: tempValue)
45
+
46
+ value = String(expression.expressionValue(with: nil, context: nil) as! Int)
47
+ if (Int(value) ?? 0 < 0){
48
+ value = ""
49
+ }
50
+ }
51
+
52
+ func onPressKey(key:String){
53
+ if (tempValue.last?.isNumber == false){
54
+ if (key.isNumber == false){
55
+ return
56
+ }
57
+ }
58
+ var convertedKey = key
59
+ if (key == "x") {convertedKey = "*"}
60
+ if (key == "÷") {convertedKey = "/"}
61
+ if (value == "" && (key == "0" || key == "000" || key.isNumber == false)) {
62
+ return
63
+ }
64
+ else {
65
+ value.append(key)
66
+ tempValue.append(convertedKey)
67
+ }
68
+ }
69
+
70
+ // MARK: Public
71
+
72
+ public var body: some View {
73
+ return GeometryReader { geo in
74
+ VStack {
75
+ HStack {
76
+ KeyboardButton("AC", action: self.onPressClear, color: Colors.pink07, textColor: Colors.black01)
77
+ KeyboardButton("÷", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
78
+ KeyboardButton("x", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
79
+
80
+ SwiftUI.Button(action: onPressBack) {
81
+ HStack {
82
+ Image(
83
+ systemName: "delete.backward"
84
+ ).foregroundColor(Colors.black01)
85
+ }.frame(maxWidth: .infinity, minHeight: 48)
86
+ .background(Colors.pink07)
87
+ .foregroundColor(Colors.black01)
88
+ .clipShape(RoundedRectangle(cornerRadius: 8))
89
+ }
90
+ }
91
+
92
+ HStack {
93
+ KeyboardButton("7", action: self.onPressKey)
94
+ KeyboardButton("8", action: self.onPressKey)
95
+ KeyboardButton("9", action: self.onPressKey)
96
+ KeyboardButton("-", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
97
+ }
98
+
99
+ HStack {
100
+ KeyboardButton("4", action: self.onPressKey)
101
+ KeyboardButton("5", action: self.onPressKey)
102
+ KeyboardButton("6", action: self.onPressKey)
103
+ KeyboardButton("+", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
104
+ }
105
+
106
+ HStack{
107
+ VStack{
108
+ HStack {
109
+ KeyboardButton("1", action: self.onPressKey)
110
+ KeyboardButton("2", action: self.onPressKey)
111
+ KeyboardButton("3", action: self.onPressKey)
112
+ }
113
+
114
+ HStack{
115
+ KeyboardButton("000", action: self.onPressKey)
116
+ KeyboardButton("0", action: self.onPressKey, width: CGFloat(geo.size.width * 0.25 - 5))
117
+ }
118
+ }
119
+
120
+ KeyboardButton("=", action: self.onPressEqual, width: CGFloat(geo.size.width * 0.25 - 5), height: 102, color: Colors.primary, textColor: Colors.black01)
121
+
122
+ }
123
+ }.background(Colors.background)
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,196 @@
1
+ import Combine
2
+ import SwiftUI
3
+
4
+ /// Native SwiftUI port of the Compose `Carousel` component.
5
+ ///
6
+ /// Mirrors the Compose API: a paging horizontal carousel driven by an
7
+ /// `itemCount` and a `content` builder that receives the real (modulo) index.
8
+ /// Supports infinite `loop`, `autoplay` with a configurable interval,
9
+ /// inactive-slide `opacity`/`scale` effects, optional `contentPadding`,
10
+ /// snap behavior, and scroll enable/disable. Snap / scroll-index callbacks
11
+ /// report the real item index.
12
+ ///
13
+ /// Available iOS 14+ (uses a custom paging ScrollView so behavior is identical
14
+ /// across versions and supports the loop virtualization + transform effects
15
+ /// that `TabView(.page)` cannot express).
16
+ public struct Carousel<Content: View>: View {
17
+ // MARK: Lifecycle
18
+
19
+ public init(
20
+ itemCount: Int,
21
+ loop: Bool = false,
22
+ autoplay: Bool = false,
23
+ autoplayInterval: TimeInterval = 3.0,
24
+ enableSnap: Bool = true,
25
+ scrollEnabled: Bool = true,
26
+ inactiveSlideOpacity: Double = 1.0,
27
+ inactiveSlideScale: Double = 1.0,
28
+ contentPadding: CGFloat = 0,
29
+ onSnapToItem: @escaping (Int) -> Void = { _ in },
30
+ onScrollIndexChanged: @escaping (Int) -> Void = { _ in },
31
+ @ViewBuilder content: @escaping (_ index: Int) -> Content
32
+ ) {
33
+ self.itemCount = itemCount
34
+ self.loop = loop
35
+ self.autoplay = autoplay
36
+ self.autoplayInterval = autoplayInterval
37
+ self.enableSnap = enableSnap
38
+ self.scrollEnabled = scrollEnabled
39
+ self.inactiveSlideOpacity = inactiveSlideOpacity
40
+ self.inactiveSlideScale = inactiveSlideScale
41
+ self.contentPadding = contentPadding
42
+ self.onSnapToItem = onSnapToItem
43
+ self.onScrollIndexChanged = onScrollIndexChanged
44
+ self.content = content
45
+
46
+ let virtual = loop ? itemCount * 1000 : itemCount
47
+ let start = loop ? (virtual / 2) - ((virtual / 2) % max(itemCount, 1)) : 0
48
+ self.virtualPageCount = virtual
49
+ _currentPage = State(initialValue: start)
50
+ }
51
+
52
+ // MARK: Public
53
+
54
+ public var body: some View {
55
+ if itemCount <= 0 {
56
+ EmptyView()
57
+ } else {
58
+ GeometryReader { geo in
59
+ let pageWidth = max(geo.size.width - contentPadding * 2, 1)
60
+ carouselContent(pageWidth: pageWidth, viewportWidth: geo.size.width)
61
+ }
62
+ }
63
+ }
64
+
65
+ // MARK: Private
66
+
67
+ private let itemCount: Int
68
+ private let loop: Bool
69
+ private let autoplay: Bool
70
+ private let autoplayInterval: TimeInterval
71
+ private let enableSnap: Bool
72
+ private let scrollEnabled: Bool
73
+ private let inactiveSlideOpacity: Double
74
+ private let inactiveSlideScale: Double
75
+ private let contentPadding: CGFloat
76
+ private let onSnapToItem: (Int) -> Void
77
+ private let onScrollIndexChanged: (Int) -> Void
78
+ private let content: (Int) -> Content
79
+
80
+ private let virtualPageCount: Int
81
+
82
+ @State private var currentPage: Int
83
+ @State private var dragOffset: CGFloat = 0
84
+ @State private var isDragging: Bool = false
85
+ @State private var autoplayTick: Int = 0
86
+
87
+ private func realIndex(_ page: Int) -> Int {
88
+ ((page % itemCount) + itemCount) % itemCount
89
+ }
90
+
91
+ @ViewBuilder
92
+ private func carouselContent(pageWidth: CGFloat, viewportWidth: CGFloat) -> some View {
93
+ let step = pageWidth
94
+ // Offset so the current page is centered within the viewport padding.
95
+ let baseOffset = -CGFloat(currentPage) * step + contentPadding
96
+
97
+ // Windows the rendered range around `currentPage` instead of instantiating all
98
+ // `virtualPageCount` slides up front — with `loop=true`, that's `itemCount * 1000`
99
+ // eager views, a real memory/perf regression versus Compose's lazy `HorizontalPager`.
100
+ // Clear spacers of the skipped width keep the HStack's total layout size (and thus
101
+ // the existing `baseOffset` math) unchanged.
102
+ let windowRadius = 3
103
+ let windowStart = max(0, currentPage - windowRadius)
104
+ let windowEnd = min(virtualPageCount - 1, currentPage + windowRadius)
105
+
106
+ ZStack {
107
+ HStack(spacing: 0) {
108
+ if windowStart > 0 {
109
+ Color.clear.frame(width: CGFloat(windowStart) * step)
110
+ }
111
+ ForEach(windowStart...windowEnd, id: \.self) { page in
112
+ slide(page: page, step: step, viewportWidth: viewportWidth)
113
+ .frame(width: pageWidth)
114
+ }
115
+ if windowEnd < virtualPageCount - 1 {
116
+ Color.clear.frame(width: CGFloat(virtualPageCount - 1 - windowEnd) * step)
117
+ }
118
+ }
119
+ .offset(x: baseOffset + dragOffset)
120
+ .animation(isDragging ? nil : .easeInOut(duration: 0.25), value: currentPage)
121
+ .frame(width: viewportWidth, alignment: .leading)
122
+ .contentShape(Rectangle())
123
+ .gesture(scrollEnabled ? dragGesture(step: step) : nil)
124
+ }
125
+ .frame(width: viewportWidth)
126
+ .clipped()
127
+ .onReceive(autoplayTimer) { _ in
128
+ guard autoplay, itemCount > 1, !isDragging else { return }
129
+ advance()
130
+ }
131
+ }
132
+
133
+ @ViewBuilder
134
+ private func slide(page: Int, step: CGFloat, viewportWidth: CGFloat) -> some View {
135
+ // Fractional offset of this page from the current page (incl. drag).
136
+ let dragFraction = step > 0 ? -dragOffset / step : 0
137
+ let pageOffset = abs(CGFloat(currentPage - page) + dragFraction)
138
+ let isActive = pageOffset < 0.5
139
+ let scale = isActive ? 1.0 : inactiveSlideScale
140
+ let alpha = isActive ? 1.0 : inactiveSlideOpacity
141
+
142
+ content(realIndex(page))
143
+ .scaleEffect(scale)
144
+ .opacity(alpha)
145
+ .animation(.easeInOut(duration: 0.15), value: isActive)
146
+ }
147
+
148
+ private func dragGesture(step: CGFloat) -> some Gesture {
149
+ DragGesture(minimumDistance: 5)
150
+ .onChanged { value in
151
+ isDragging = true
152
+ dragOffset = value.translation.width
153
+ }
154
+ .onEnded { value in
155
+ isDragging = false
156
+ let predicted = value.predictedEndTranslation.width
157
+ let threshold = step / 4
158
+ var target = currentPage
159
+ if enableSnap {
160
+ if predicted < -threshold {
161
+ target = min(currentPage + 1, virtualPageCount - 1)
162
+ } else if predicted > threshold {
163
+ target = max(currentPage - 1, 0)
164
+ }
165
+ } else {
166
+ // Free scroll still settles to nearest page boundary.
167
+ let moved = step > 0 ? Int((-dragOffset / step).rounded()) : 0
168
+ target = min(max(currentPage + moved, 0), virtualPageCount - 1)
169
+ }
170
+ dragOffset = 0
171
+ setPage(target)
172
+ }
173
+ }
174
+
175
+ private func advance() {
176
+ let next = currentPage + 1
177
+ if next < virtualPageCount {
178
+ setPage(next)
179
+ }
180
+ }
181
+
182
+ private func setPage(_ page: Int) {
183
+ let clamped = min(max(page, 0), virtualPageCount - 1)
184
+ let changed = clamped != currentPage
185
+ currentPage = clamped
186
+ if changed {
187
+ let real = realIndex(clamped)
188
+ onSnapToItem(real)
189
+ onScrollIndexChanged(real)
190
+ }
191
+ }
192
+
193
+ private var autoplayTimer: Publishers.Autoconnect<Timer.TimerPublisher> {
194
+ Timer.publish(every: autoplayInterval, on: .main, in: .common).autoconnect()
195
+ }
196
+ }