@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
@@ -0,0 +1,486 @@
1
+ import SwiftUI
2
+
3
+ // MARK: - Types
4
+
5
+ /// Tooltip placement relative to the anchor element.
6
+ public enum TooltipPlacement {
7
+ case top
8
+ case bottom
9
+ case left
10
+ case right
11
+ }
12
+
13
+ /// Cross-axis alignment of the tooltip relative to the anchor.
14
+ public enum TooltipAlign {
15
+ case start
16
+ case center
17
+ case end
18
+ }
19
+
20
+ /// Describes a single action button displayed in the tooltip.
21
+ public struct TooltipButton {
22
+ public let title: String?
23
+ public let icon: String?
24
+ public let onPress: (() -> Void)?
25
+
26
+ public init(
27
+ title: String? = nil,
28
+ icon: String? = nil,
29
+ onPress: (() -> Void)? = nil
30
+ ) {
31
+ self.title = title
32
+ self.icon = icon
33
+ self.onPress = onPress
34
+ }
35
+ }
36
+
37
+ /// State holder for controlling tooltip visibility imperatively.
38
+ /// Mirrors the Compose `TooltipState` / `rememberTooltipState()` pattern.
39
+ public final class TooltipState: ObservableObject {
40
+ @Published public private(set) var isVisible: Bool = false
41
+
42
+ public init() {}
43
+
44
+ /// Shows the tooltip.
45
+ public func show() {
46
+ isVisible = true
47
+ }
48
+
49
+ /// Hides the tooltip.
50
+ public func hide() {
51
+ isVisible = false
52
+ }
53
+
54
+ /// Toggles the tooltip visibility.
55
+ public func toggle() {
56
+ isVisible.toggle()
57
+ }
58
+ }
59
+
60
+ // MARK: - Constants
61
+
62
+ private enum TooltipConst {
63
+ static let offset = Spacing.S // TOOLTIP_OFFSET
64
+ static let arrowSize: CGFloat = 6 // ARROW_SIZE
65
+ static let maxWidth: CGFloat = 300
66
+ static let cornerRadius = Radius.S
67
+ static let arrowCornerRadius: CGFloat = 2
68
+ static let arrowEdgeMargin = Spacing.M
69
+ static let arrowOverlap: CGFloat = 1
70
+ }
71
+
72
+ // MARK: - Arrow Shape
73
+
74
+ /// Triangular arrow pointing away from the anchor in the given placement direction.
75
+ struct TooltipArrowShape: Shape {
76
+ let placement: TooltipPlacement
77
+
78
+ func path(in rect: CGRect) -> Path {
79
+ let w = rect.width
80
+ let h = rect.height
81
+ var path = Path()
82
+ switch placement {
83
+ case .top:
84
+ path.move(to: CGPoint(x: 0, y: 0))
85
+ path.addLine(to: CGPoint(x: w, y: 0))
86
+ path.addLine(to: CGPoint(x: w / 2, y: h))
87
+ case .bottom:
88
+ path.move(to: CGPoint(x: 0, y: h))
89
+ path.addLine(to: CGPoint(x: w, y: h))
90
+ path.addLine(to: CGPoint(x: w / 2, y: 0))
91
+ case .left:
92
+ path.move(to: CGPoint(x: 0, y: 0))
93
+ path.addLine(to: CGPoint(x: 0, y: h))
94
+ path.addLine(to: CGPoint(x: w, y: h / 2))
95
+ case .right:
96
+ path.move(to: CGPoint(x: w, y: 0))
97
+ path.addLine(to: CGPoint(x: w, y: h))
98
+ path.addLine(to: CGPoint(x: 0, y: h / 2))
99
+ }
100
+ path.closeSubpath()
101
+ return path
102
+ }
103
+ }
104
+
105
+ // MARK: - Anchor bounds preference
106
+
107
+ private struct TooltipAnchorBoundsKey: PreferenceKey {
108
+ static var defaultValue: Anchor<CGRect>? = nil
109
+ static func reduce(value: inout Anchor<CGRect>?, nextValue: () -> Anchor<CGRect>?) {
110
+ value = nextValue() ?? value
111
+ }
112
+ }
113
+
114
+ // MARK: - Tooltip
115
+
116
+ /// A tooltip component that wraps `content` (the anchor) and shows a positioned
117
+ /// tooltip popup with title, description, close button, and action buttons.
118
+ ///
119
+ /// Port of the Compose / React Native `Tooltip` component.
120
+ public struct Tooltip<Content: View>: View {
121
+ @ObservedObject private var state: TooltipState
122
+ private let title: String?
123
+ private let description: String
124
+ private let buttons: [TooltipButton]
125
+ private let placement: TooltipPlacement
126
+ private let align: TooltipAlign
127
+ private let onVisibleChange: ((Bool) -> Void)?
128
+ private let onPressClose: (() -> Void)?
129
+ private let content: Content
130
+
131
+ public init(
132
+ state: TooltipState,
133
+ title: String? = nil,
134
+ description: String,
135
+ buttons: [TooltipButton] = [],
136
+ placement: TooltipPlacement = .top,
137
+ align: TooltipAlign = .center,
138
+ onVisibleChange: ((Bool) -> Void)? = nil,
139
+ onPressClose: (() -> Void)? = nil,
140
+ @ViewBuilder content: () -> Content
141
+ ) {
142
+ self.state = state
143
+ self.title = title
144
+ self.description = description
145
+ self.buttons = buttons
146
+ self.placement = placement
147
+ self.align = align
148
+ self.onVisibleChange = onVisibleChange
149
+ self.onPressClose = onPressClose
150
+ self.content = content()
151
+ }
152
+
153
+ public var body: some View {
154
+ content
155
+ .anchorPreference(key: TooltipAnchorBoundsKey.self, value: .bounds) { $0 }
156
+ .overlayPreferenceValue(TooltipAnchorBoundsKey.self) { anchor in
157
+ GeometryReader { proxy in
158
+ if state.isVisible, let anchor = anchor {
159
+ let anchorRect = proxy[anchor]
160
+ TooltipPopupContent(
161
+ title: title,
162
+ description: description,
163
+ buttons: buttons,
164
+ placement: placement,
165
+ align: align,
166
+ onPressClose: onPressClose
167
+ )
168
+ .fixedSize()
169
+ .modifier(
170
+ TooltipPositionModifier(
171
+ anchorRect: anchorRect,
172
+ containerSize: proxy.size,
173
+ placement: placement,
174
+ align: align
175
+ )
176
+ )
177
+ }
178
+ }
179
+ }
180
+ .onChange(of: state.isVisible) { newValue in
181
+ onVisibleChange?(newValue)
182
+ }
183
+ }
184
+ }
185
+
186
+ // MARK: - Positioning
187
+
188
+ /// Positions the tooltip (already sized via `.fixedSize()`) relative to the anchor,
189
+ /// mirroring `TooltipPositionProvider.calculatePosition`.
190
+ private struct TooltipPositionModifier: ViewModifier {
191
+ let anchorRect: CGRect
192
+ let containerSize: CGSize
193
+ let placement: TooltipPlacement
194
+ let align: TooltipAlign
195
+
196
+ @State private var popupSize: CGSize = .zero
197
+
198
+ private var offsetPx: CGFloat {
199
+ TooltipConst.offset + TooltipConst.arrowSize
200
+ }
201
+
202
+ func body(content: Content) -> some View {
203
+ content
204
+ .background(
205
+ GeometryReader { p in
206
+ Color.clear
207
+ .onAppear { popupSize = p.size }
208
+ .onChange(of: p.size) { popupSize = $0 }
209
+ }
210
+ )
211
+ .offset(x: originX, y: originY)
212
+ }
213
+
214
+ private var originX: CGFloat {
215
+ let popW = popupSize.width
216
+ switch placement {
217
+ case .top, .bottom:
218
+ return alignHorizontal(anchorRect.minX, anchorRect.width, popW)
219
+ case .left:
220
+ return anchorRect.minX - popW - offsetPx
221
+ case .right:
222
+ return anchorRect.maxX + offsetPx
223
+ }
224
+ }
225
+
226
+ private var originY: CGFloat {
227
+ let popH = popupSize.height
228
+ switch placement {
229
+ case .top:
230
+ return anchorRect.minY - popH - offsetPx
231
+ case .bottom:
232
+ return anchorRect.maxY + offsetPx
233
+ case .left, .right:
234
+ return alignVertical(anchorRect.minY, anchorRect.height, popH)
235
+ }
236
+ }
237
+
238
+ private func alignHorizontal(_ anchorX: CGFloat, _ anchorW: CGFloat, _ popW: CGFloat) -> CGFloat {
239
+ switch align {
240
+ case .start: return anchorX
241
+ case .end: return anchorX + anchorW - popW
242
+ case .center: return anchorX + (anchorW - popW) / 2
243
+ }
244
+ }
245
+
246
+ private func alignVertical(_ anchorY: CGFloat, _ anchorH: CGFloat, _ popH: CGFloat) -> CGFloat {
247
+ switch align {
248
+ case .start: return anchorY
249
+ case .end: return anchorY + anchorH - popH
250
+ case .center: return anchorY + (anchorH - popH) / 2
251
+ }
252
+ }
253
+ }
254
+
255
+ // MARK: - Popup content
256
+
257
+ private struct TooltipPopupContent: View {
258
+ let title: String?
259
+ let description: String?
260
+ let buttons: [TooltipButton]
261
+ let placement: TooltipPlacement
262
+ let align: TooltipAlign
263
+ let onPressClose: (() -> Void)?
264
+
265
+ var body: some View {
266
+ ZStack {
267
+ VStack(alignment: .leading, spacing: 0) {
268
+ HStack(alignment: .top, spacing: 0) {
269
+ VStack(alignment: .leading, spacing: 0) {
270
+ if let title = title, !title.isEmpty {
271
+ MomoText(title, typography: .headerSSemibold, color: Colors.black01)
272
+ .lineLimit(1)
273
+ .truncationMode(.tail)
274
+ .padding(.bottom, Spacing.XS)
275
+ }
276
+ if let description = description, !description.isEmpty {
277
+ MomoText(description, typography: .descriptionDefaultRegular, color: Colors.black01)
278
+ .lineLimit(2)
279
+ .truncationMode(.tail)
280
+ }
281
+ }
282
+ .frame(maxWidth: .infinity, alignment: .leading)
283
+
284
+ if let onPressClose = onPressClose {
285
+ Spacer().frame(width: Spacing.M)
286
+ SwiftUI.Button(action: onPressClose) {
287
+ Icon(source: "navigation_close", size: 20, color: Colors.black01)
288
+ .frame(width: 20, height: 20)
289
+ }
290
+ .buttonStyle(.plain)
291
+ }
292
+ }
293
+
294
+ if !buttons.isEmpty {
295
+ Spacer().frame(height: Spacing.M)
296
+ TooltipButtons(buttons: buttons)
297
+ .frame(maxWidth: .infinity, alignment: .trailing)
298
+ }
299
+ }
300
+ .padding(Spacing.M)
301
+ .frame(maxWidth: TooltipConst.maxWidth, alignment: .leading)
302
+ .background(Colors.black17)
303
+ .clipShape(RoundedRectangle(cornerRadius: TooltipConst.cornerRadius))
304
+ }
305
+ .overlay(arrow, alignment: arrowAlignment)
306
+ }
307
+
308
+ // MARK: Arrow
309
+
310
+ @ViewBuilder
311
+ private var arrow: some View {
312
+ let arrowWidth = TooltipConst.arrowSize * 2
313
+ let arrowHeight = TooltipConst.arrowSize
314
+ let isVertical = placement == .top || placement == .bottom
315
+ TooltipArrowShape(placement: placement)
316
+ .fill(Colors.black17)
317
+ .frame(
318
+ width: isVertical ? arrowWidth : arrowHeight,
319
+ height: isVertical ? arrowHeight : arrowWidth
320
+ )
321
+ .offset(x: arrowOffsetX, y: arrowOffsetY)
322
+ }
323
+
324
+ private var arrowAlignment: Alignment {
325
+ switch placement {
326
+ case .top:
327
+ switch align {
328
+ case .start: return .bottomLeading
329
+ case .center: return .bottom
330
+ case .end: return .bottomTrailing
331
+ }
332
+ case .bottom:
333
+ switch align {
334
+ case .start: return .topLeading
335
+ case .center: return .top
336
+ case .end: return .topTrailing
337
+ }
338
+ case .left:
339
+ switch align {
340
+ case .start: return .topTrailing
341
+ case .center: return .trailing
342
+ case .end: return .bottomTrailing
343
+ }
344
+ case .right:
345
+ switch align {
346
+ case .start: return .topLeading
347
+ case .center: return .leading
348
+ case .end: return .bottomLeading
349
+ }
350
+ }
351
+ }
352
+
353
+ private var arrowOffsetX: CGFloat {
354
+ let arrowHeight = TooltipConst.arrowSize
355
+ switch placement {
356
+ case .left: return arrowHeight - TooltipConst.arrowOverlap
357
+ case .right: return -arrowHeight + TooltipConst.arrowOverlap
358
+ default:
359
+ switch align {
360
+ case .start: return TooltipConst.arrowEdgeMargin
361
+ case .end: return -TooltipConst.arrowEdgeMargin
362
+ case .center: return 0
363
+ }
364
+ }
365
+ }
366
+
367
+ private var arrowOffsetY: CGFloat {
368
+ let arrowHeight = TooltipConst.arrowSize
369
+ switch placement {
370
+ case .top: return arrowHeight - TooltipConst.arrowOverlap
371
+ case .bottom: return -arrowHeight + TooltipConst.arrowOverlap
372
+ default:
373
+ switch align {
374
+ case .start: return TooltipConst.arrowEdgeMargin
375
+ case .end: return -TooltipConst.arrowEdgeMargin
376
+ case .center: return 0
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ // MARK: - Buttons row
383
+
384
+ private struct TooltipButtons: View {
385
+ let buttons: [TooltipButton]
386
+
387
+ var body: some View {
388
+ HStack(spacing: 0) {
389
+ content
390
+ }
391
+ }
392
+
393
+ @ViewBuilder
394
+ private var content: some View {
395
+ if buttons.count == 1 {
396
+ TooltipSingleButton(btn: buttons[0])
397
+ } else if buttons.count == 2 {
398
+ let primary = buttons[0]
399
+ let secondary = buttons[1]
400
+ let bothIcons = primary.icon != nil && secondary.icon != nil
401
+ if bothIcons {
402
+ TooltipIconButton(icon: secondary.icon ?? "", onPress: secondary.onPress ?? {})
403
+ Spacer().frame(width: Spacing.S)
404
+ TooltipIconButton(icon: primary.icon ?? "", onPress: primary.onPress ?? {})
405
+ } else {
406
+ TooltipSecondaryButton(title: secondary.title ?? "", onPress: secondary.onPress ?? {})
407
+ Spacer().frame(width: Spacing.S)
408
+ TooltipPrimaryButton(title: primary.title ?? "", onPress: primary.onPress ?? {})
409
+ }
410
+ } else {
411
+ ForEach(Array(buttons.enumerated()), id: \.offset) { index, btn in
412
+ if index > 0 {
413
+ Spacer().frame(width: Spacing.XXS)
414
+ }
415
+ TooltipSingleButton(btn: btn)
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ private func tooltipLimitWithEllipsis(_ value: String, max: Int = 16) -> String {
422
+ value.count > max ? String(value.prefix(max)) + "…" : value
423
+ }
424
+
425
+ private struct TooltipSingleButton: View {
426
+ let btn: TooltipButton
427
+
428
+ var body: some View {
429
+ if let icon = btn.icon {
430
+ TooltipIconButton(icon: icon, onPress: btn.onPress ?? {})
431
+ } else {
432
+ TooltipPrimaryButton(title: btn.title ?? "", onPress: btn.onPress ?? {})
433
+ }
434
+ }
435
+ }
436
+
437
+ private struct TooltipPrimaryButton: View {
438
+ let title: String
439
+ let onPress: () -> Void
440
+
441
+ var body: some View {
442
+ Button(
443
+ title: tooltipLimitWithEllipsis(title),
444
+ action: onPress,
445
+ type: .secondary,
446
+ size: .medium,
447
+ isFull: false
448
+ )
449
+ }
450
+ }
451
+
452
+ private struct TooltipSecondaryButton: View {
453
+ let title: String
454
+ let onPress: () -> Void
455
+
456
+ var body: some View {
457
+ SwiftUI.Button(action: onPress) {
458
+ MomoText(tooltipLimitWithEllipsis(title), typography: .actionSBold, color: Colors.black01)
459
+ .lineLimit(1)
460
+ .truncationMode(.tail)
461
+ .padding(.horizontal, Spacing.M)
462
+ .padding(.vertical, Spacing.S)
463
+ }
464
+ .buttonStyle(.plain)
465
+ // Mirrors Compose's `Modifier.weight(1f, fill = false)` on the secondary button:
466
+ // it yields space to the primary button first under width pressure.
467
+ .layoutPriority(-1)
468
+ }
469
+ }
470
+
471
+ private struct TooltipIconButton: View {
472
+ @Environment(\.momoFontScale) private var fontScale
473
+ let icon: String
474
+ let onPress: () -> Void
475
+
476
+ var body: some View {
477
+ let iconButtonSize = fontScale(36)
478
+ SwiftUI.Button(action: onPress) {
479
+ Icon(source: icon, size: 24)
480
+ .frame(width: iconButtonSize, height: iconButtonSize)
481
+ .background(Colors.black01)
482
+ .clipShape(RoundedRectangle(cornerRadius: Radius.XL))
483
+ }
484
+ .buttonStyle(.plain)
485
+ }
486
+ }
@@ -0,0 +1,103 @@
1
+ // File: ios/Typography/FontScale.swift
2
+ // Created At: 2026-08-06 10:00:00 +07:00
3
+ // Created By: AI
4
+ // AI Agent: Claude Code
5
+ // Model: claude-opus-5
6
+
7
+ // AI-GENERATED START: reactive font-scale snapshot injected through the environment
8
+ import SwiftUI
9
+
10
+ /// Immutable snapshot of the kit's font-scale inputs. The formula is a 1:1 port of Compose's
11
+ /// `scaleSize()` (`const/Typography.kt`): the device-width scale and the OS Dynamic Type scale are
12
+ /// computed independently and the larger one wins.
13
+ public struct FontScale: Equatable {
14
+ private static let defaultScreenSize: CGFloat = 375
15
+ private static let maxFontScale: CGFloat = 1.5
16
+ private static let maxDeviceScale: CGFloat = 5
17
+
18
+ public let userScaleRate: CGFloat
19
+ public let useOSScaleRate: Bool
20
+ /// OS Dynamic Type factor at the time the snapshot was taken.
21
+ public let osFontScale: CGFloat
22
+
23
+ public init(userScaleRate: CGFloat = 1, useOSScaleRate: Bool = true, osFontScale: CGFloat = 1) {
24
+ self.userScaleRate = userScaleRate
25
+ self.useOSScaleRate = useOSScaleRate
26
+ self.osFontScale = osFontScale
27
+ }
28
+
29
+ /// `callAsFunction` so call sites keep reading like Compose's `scaleSize(16)`.
30
+ public func callAsFunction(_ size: CGFloat) -> CGFloat {
31
+ guard useOSScaleRate else { return size * userScaleRate }
32
+
33
+ // Device width is read live rather than snapshotted, matching Compose's
34
+ // getScreenDimensions() read inside scaleSize(), so a rotation or split-view resize counts.
35
+ let deviceScale = UIScreen.main.bounds.width / Self.defaultScreenSize
36
+
37
+ var deviceScaled = size
38
+ var osScaled = size
39
+ if deviceScale > 1 {
40
+ deviceScaled = min(size * deviceScale, size + Self.maxDeviceScale)
41
+ }
42
+ if osFontScale > 1 {
43
+ osScaled = min(size * osFontScale, size * Self.maxFontScale)
44
+ }
45
+ return max(deviceScaled, osScaled)
46
+ }
47
+
48
+ /// `.system(size:)` is a fixed-size font — SwiftUI does not Dynamic-Type it — so the scale has to
49
+ /// be baked into the point size here, and there is no double-scaling to undo.
50
+ public func font(size: CGFloat, weight: Font.Weight = .regular) -> Font {
51
+ .system(size: self(size), weight: weight)
52
+ }
53
+
54
+ /// A host-pushed mini-app context wins per field, matching Compose's
55
+ /// `context?.useOSScaleRate ?: KitFontScale.useOSScaleRate`.
56
+ public func overriding(userScaleRate: CGFloat?, useOSScaleRate: Bool?) -> FontScale {
57
+ guard userScaleRate != nil || useOSScaleRate != nil else { return self }
58
+ return FontScale(
59
+ userScaleRate: userScaleRate ?? self.userScaleRate,
60
+ useOSScaleRate: useOSScaleRate ?? self.useOSScaleRate,
61
+ osFontScale: osFontScale
62
+ )
63
+ }
64
+ }
65
+
66
+ private struct FontScaleKey: EnvironmentKey {
67
+ /// Computed, never stored: a bare `UIHostingController` root that never got `.momoFontScale()`
68
+ /// still reads the live store instead of a value frozen at first access.
69
+ static var defaultValue: FontScale { FontScaleStore.shared.snapshot }
70
+ }
71
+
72
+ public extension EnvironmentValues {
73
+ var momoFontScale: FontScale {
74
+ get { self[FontScaleKey.self] }
75
+ set { self[FontScaleKey.self] = newValue }
76
+ }
77
+ }
78
+
79
+ public extension View {
80
+ /// Injects a Dynamic-Type-observing font scale and re-renders this subtree on change.
81
+ /// Use at every `UIHostingController` root that is not inside a `NavigationContainer`.
82
+ func momoFontScale(_ store: FontScaleStore = .shared, context: MiniAppContext? = nil) -> some View {
83
+ modifier(MomoFontScaleModifier(store: store, context: context))
84
+ }
85
+ }
86
+
87
+ /// `@ObservedObject` is load-bearing, same as `MomoLocalizeModifier`: `@Environment(\.momoFontScale)`
88
+ /// only invalidates when the boxed snapshot changes, which requires *this* body to re-run on publish.
89
+ private struct MomoFontScaleModifier: ViewModifier {
90
+ @ObservedObject var store: FontScaleStore
91
+ let context: MiniAppContext?
92
+
93
+ func body(content: Content) -> some View {
94
+ content.environment(
95
+ \.momoFontScale,
96
+ store.snapshot.overriding(
97
+ userScaleRate: context?.userScaleRate,
98
+ useOSScaleRate: context?.useOSScaleRate
99
+ )
100
+ )
101
+ }
102
+ }
103
+ // AI-GENERATED END: reactive font-scale snapshot injected through the environment
@@ -0,0 +1,58 @@
1
+ import SwiftUI
2
+
3
+ /// App-wide font-scale inputs for the SwiftUI kit, and the single place they publish from.
4
+ /// The host writes `update(...)` at startup (from the cache-backed `FontScaleProvider`) and again
5
+ /// whenever the in-app font-size preference changes; the OS Dynamic Type factor is tracked here.
6
+ /// Mirrors Compose's `KitFontScale` + `ProvideOsFontScale` pair, including its live behaviour —
7
+ /// views re-render on change as long as the subtree got `.momoFontScale()` (`NavigationContainer`
8
+ /// applies it). When `useOSScaleRate` is false the kit applies `userScaleRate` instead of the OS factor.
9
+ public final class FontScaleStore: ObservableObject {
10
+ public static let shared = FontScaleStore()
11
+
12
+ @Published public private(set) var userScaleRate: CGFloat = 1
13
+ @Published public private(set) var useOSScaleRate: Bool = true
14
+ @Published public private(set) var osFontScale: CGFloat = FontScaleStore.currentOsFontScale()
15
+
16
+ private init() {
17
+ NotificationCenter.default.addObserver(
18
+ self,
19
+ selector: #selector(contentSizeCategoryDidChange),
20
+ name: UIContentSizeCategory.didChangeNotification,
21
+ object: nil
22
+ )
23
+ }
24
+
25
+ public var snapshot: FontScale {
26
+ FontScale(
27
+ userScaleRate: userScaleRate,
28
+ useOSScaleRate: useOSScaleRate,
29
+ osFontScale: osFontScale
30
+ )
31
+ }
32
+
33
+ public func update(userScaleRate: CGFloat, useOSScaleRate: Bool) {
34
+ onMain {
35
+ // Re-assigning an unchanged value would still publish and invalidate every kit subtree.
36
+ if self.userScaleRate != userScaleRate { self.userScaleRate = userScaleRate }
37
+ if self.useOSScaleRate != useOSScaleRate { self.useOSScaleRate = useOSScaleRate }
38
+ }
39
+ }
40
+
41
+ @objc private func contentSizeCategoryDidChange() {
42
+ let next = Self.currentOsFontScale()
43
+ guard next != osFontScale else { return }
44
+ osFontScale = next
45
+ }
46
+
47
+ /// Same factor Compose's iOS `ProvideOsFontScale` derives from
48
+ /// `UIFont.preferredFont(forTextStyle: .body).pointSize / 17`, so both iOS surfaces scale alike.
49
+ static func currentOsFontScale() -> CGFloat {
50
+ let base = UIFont.systemFont(ofSize: UIFont.labelFontSize)
51
+ return UIFontMetrics.default.scaledFont(for: base).pointSize / base.pointSize
52
+ }
53
+
54
+ /// Synchronous when already on main — an async hop would defer the change by a frame.
55
+ private func onMain(_ work: @escaping () -> Void) {
56
+ if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
57
+ }
58
+ }
@@ -0,0 +1,17 @@
1
+ import Foundation
2
+
3
+ /// Design-system rollout flag for the SwiftUI kit, mirroring Compose's `KitDesignSystem`.
4
+ /// The kit is sandboxed and can't read the host config, so the host writes `update(_:)` once at
5
+ /// startup from `DesignSystemConfig`. Startup-only and non-reactive, like `FontScaleStore` was
6
+ /// before it gained live updates: there is no SwiftUI navigation container to re-render from.
7
+ public final class KitDesignSystem {
8
+ public static let shared = KitDesignSystem()
9
+
10
+ public private(set) var isWhiteList: Bool = false
11
+
12
+ private init() {}
13
+
14
+ public func update(isWhiteList: Bool) {
15
+ self.isWhiteList = isWhiteList
16
+ }
17
+ }