@momo-kits/native-kits 0.163.1-beta.8-debug → 0.163.1-beta.9-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 (220) hide show
  1. package/CLAUDE.md +1 -1
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/compose.podspec +1 -1
  4. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +30 -3
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +5 -0
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +1 -1
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +3 -0
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +4 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +8 -2
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +63 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +32 -19
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +49 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +1 -1
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +3 -1
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +93 -75
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +26 -7
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +3 -1
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +23 -2
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +11 -0
  25. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  26. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +26 -3
  27. package/gradle.properties +1 -1
  28. package/ios/Application/ApplicationEnvironment.swift +15 -2
  29. package/ios/Application/Components.swift +50 -44
  30. package/ios/Application/FloatingButton.swift +10 -9
  31. package/ios/Application/HeaderRight.swift +79 -45
  32. package/ios/Application/Localize.swift +277 -0
  33. package/ios/Application/MaxApi.swift +18 -0
  34. package/ios/Application/Navigation/BottomTab/BottomTab.swift +104 -0
  35. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  36. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  37. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  38. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  39. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  40. package/ios/Application/Navigation/NavigationContainer.swift +200 -0
  41. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  42. package/ios/Application/Navigation/Navigator.swift +385 -0
  43. package/ios/Application/Navigation/Overplay/BottomSheet.swift +256 -0
  44. package/ios/Application/Navigation/Overplay/ModalScreen.swift +74 -0
  45. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  46. package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
  47. package/ios/Application/Navigation/component/Header.swift +178 -0
  48. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  49. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  50. package/ios/Application/Screen.swift +2 -1
  51. package/ios/Application/StackScreen.swift +501 -0
  52. package/ios/Avatar/Avatar.swift +193 -0
  53. package/ios/Badge/Badge.swift +11 -14
  54. package/ios/Badge/BadgeRibbon.swift +92 -21
  55. package/ios/BaselineView/BaselineView.swift +163 -0
  56. package/ios/Button/Button.swift +124 -29
  57. package/ios/Carousel/Carousel.swift +196 -0
  58. package/ios/Checkbox/Checkbox.swift +23 -28
  59. package/ios/Chip/Chip.swift +56 -33
  60. package/ios/Collapse/Collapse.swift +192 -0
  61. package/ios/Colors+Radius+Spacing/Colors.swift +20 -17
  62. package/ios/Colors+Radius+Spacing/Spacing.swift +3 -1
  63. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  64. package/ios/DateTimePicker/DateTimePicker.swift +212 -0
  65. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  66. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  67. package/ios/DateTimePicker/WheelPicker.swift +165 -0
  68. package/ios/Divider/Divider.swift +16 -0
  69. package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
  70. package/ios/Extensions/Color++.swift +32 -0
  71. package/ios/Icon/Icon.swift +10 -1
  72. package/ios/IconButton/IconButton.swift +140 -0
  73. package/ios/Image/Image.swift +16 -3
  74. package/ios/Information/Information.swift +3 -1
  75. package/ios/Input/ErrorView.swift +3 -1
  76. package/ios/Input/Input.swift +72 -33
  77. package/ios/Input/InputPhoneNumber.swift +33 -21
  78. package/ios/Input/InputSearch.swift +107 -48
  79. package/ios/Input/InputTextArea.swift +134 -64
  80. package/ios/InputDropDown/InputDropDown.swift +201 -0
  81. package/ios/InputMoney/InputMoney.swift +347 -0
  82. package/ios/InputOTP/InputOTP.swift +210 -0
  83. package/ios/Loader/Loader.swift +113 -0
  84. package/ios/MoMoUIKits.podspec +5 -0
  85. package/ios/Pagination/PaginationDot.swift +61 -0
  86. package/ios/Pagination/PaginationNumber.swift +35 -0
  87. package/ios/Pagination/PaginationScroll.swift +101 -0
  88. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  89. package/ios/Popup/PopupDisplay.swift +11 -20
  90. package/ios/Popup/PopupInput.swift +2 -8
  91. package/ios/Popup/PopupNotify.swift +218 -0
  92. package/ios/Popup/PopupPromotion.swift +16 -8
  93. package/ios/ProgressInfo/ProgressInfo.swift +296 -0
  94. package/ios/Radio/Radio.swift +69 -0
  95. package/ios/Rating/Rating.swift +82 -0
  96. package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +11 -0
  97. package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +38 -0
  98. package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +38 -0
  99. package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +38 -0
  100. package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +6 -0
  101. package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +38 -0
  102. package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +38 -0
  103. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +38 -0
  104. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +38 -0
  105. package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +38 -0
  106. package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +38 -0
  107. package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +38 -0
  108. package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +38 -0
  109. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
  110. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
  111. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
  112. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
  113. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
  114. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
  115. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
  116. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
  117. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
  118. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
  119. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
  120. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
  121. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
  122. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
  123. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
  124. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
  125. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
  126. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
  127. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
  128. package/ios/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +38 -0
  129. package/ios/Skeleton/Skeleton.swift +99 -0
  130. package/ios/Slider/Slider.swift +237 -0
  131. package/ios/Stepper/Stepper.swift +220 -0
  132. package/ios/Steps/Steps.swift +467 -0
  133. package/ios/SuggestAction/SuggestAction.swift +99 -0
  134. package/ios/Swipeable/SwipeCell.swift +187 -0
  135. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  136. package/ios/Switch/Switch.swift +53 -28
  137. package/ios/TabView/TabView.swift +534 -0
  138. package/ios/Tag/Tag.swift +98 -0
  139. package/ios/Template/TrustBanner/TrustBanner.swift +6 -7
  140. package/ios/Title/Title.swift +269 -0
  141. package/ios/Tooltip/Tooltip.swift +485 -0
  142. package/ios/Typography/FontScaleStore.swift +19 -0
  143. package/ios/Typography/Text.swift +79 -5
  144. package/ios/Typography/Typography.swift +3 -0
  145. package/ios/Uploader/Uploader.swift +174 -0
  146. package/ios/native-kits.podspec +61 -8
  147. package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
  148. package/ios-demo/README.md +120 -0
  149. package/ios-demo/Resources/dot_loading.json +558 -0
  150. package/ios-demo/Resources/icon.json +4052 -0
  151. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  152. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  153. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  154. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  155. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  156. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  157. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  158. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  159. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  160. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  161. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
  163. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  164. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
  165. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
  166. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  167. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  168. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
  169. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
  170. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
  171. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
  172. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
  173. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +279 -0
  178. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
  179. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
  180. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
  181. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
  182. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
  183. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
  184. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
  185. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
  186. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
  188. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
  189. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +188 -0
  190. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +229 -0
  191. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
  192. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  193. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
  194. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
  195. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
  196. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
  197. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
  198. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
  199. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
  200. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
  201. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
  202. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
  203. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
  204. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
  205. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
  206. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
  207. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
  208. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
  209. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
  210. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
  211. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  212. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
  213. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +51 -0
  214. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  215. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  216. package/local.properties +2 -2
  217. package/package.json +1 -1
  218. package/publish.sh +53 -0
  219. package/scripts/gen-ios-color.mjs +136 -0
  220. package/.claude/settings.local.json +0 -62
@@ -0,0 +1,18 @@
1
+ //
2
+ // MaxApi.swift
3
+ // MoMoUIKits
4
+ //
5
+ // Swift mirror of the host-app bridge exposed on Kotlin as `vn.momo.maxapi.IMaxApi`
6
+ // (`LocalMaxApi`). `IMaxApi` itself is an external compiled dependency with no
7
+ // source in this repo, so this protocol intentionally exposes only the methods
8
+ // this kit's Kotlin source actually calls (`track`, `dismiss`, `getLanguage`) —
9
+ // not a full guess at IMaxApi's real surface.
10
+ //
11
+
12
+ import Foundation
13
+
14
+ public protocol MaxApi {
15
+ func track(eventName: String, params: [String: Any?])
16
+ func dismiss(completion: (() -> Void)?)
17
+ func getLanguage(callback: @escaping ([String: Any?]?) -> Void)
18
+ }
@@ -0,0 +1,104 @@
1
+ //
2
+ // BottomTab.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `BottomTab` (navigation/bottomtab/BottomTab.kt).
6
+ // Hosts a set of tabbed screens, each wrapped in `StackScreen` chrome built
7
+ // from that tab's `NavigationOptions`, with a `BottomTabBar` pinned to the
8
+ // bottom. Switching tabs cross-fades + scales the content (parity with Compose).
9
+ //
10
+
11
+ import SwiftUI
12
+
13
+ @available(iOS 16.0, *)
14
+ public struct BottomTab: View {
15
+ private let items: [BottomTabItem]
16
+ private let floatingButton: BottomTabFloatingButton?
17
+
18
+ @EnvironmentObject private var navigator: Navigator
19
+ @State private var selectedIndex: Int = 0
20
+
21
+ public init(items: [BottomTabItem], floatingButton: BottomTabFloatingButton? = nil) {
22
+ self.items = items
23
+ self.floatingButton = floatingButton
24
+ }
25
+
26
+ private var bottomInset: CGFloat { safeAreaBottomInset() }
27
+ private var reservedBottom: CGFloat { BOTTOM_TAB_BAR_HEIGHT + bottomInset + Spacing.S }
28
+
29
+ public var body: some View {
30
+ ZStack(alignment: .bottom) {
31
+ // Tab content area, inset above the bar.
32
+ ZStack {
33
+ ForEach(items.indices, id: \.self) { index in
34
+ if index == selectedIndex {
35
+ tabScreen(for: index)
36
+ .transition(.scale(scale: 0.97).combined(with: .opacity))
37
+ }
38
+ }
39
+ }
40
+ .padding(.bottom, reservedBottom)
41
+ .animation(.easeInOut(duration: 0.2), value: selectedIndex)
42
+
43
+ // Tab bar + home-indicator filler.
44
+ VStack(spacing: 0) {
45
+ BottomTabBar(
46
+ items: items,
47
+ floatingButton: floatingButton,
48
+ selectedIndex: selectedIndex,
49
+ onTabSelected: { index in
50
+ if index != selectedIndex { selectedIndex = index }
51
+ }
52
+ )
53
+ Colors.black01
54
+ .frame(height: bottomInset + Spacing.S)
55
+ }
56
+ }
57
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
58
+ .ignoresSafeArea(edges: .bottom)
59
+ .preference(key: IsBottomTabRootKey.self, value: true)
60
+ }
61
+
62
+ @ViewBuilder
63
+ private func tabScreen(for index: Int) -> some View {
64
+ let item = items[index]
65
+ let options = item.options ?? NavigationOptions()
66
+ StackScreen(
67
+ backgroundColor: options.backgroundColor,
68
+ headerTransparent: options.headerTransparent,
69
+ fullScreenContent: options.fullScreenContent,
70
+ tintColor: options.tintColor ?? Colors.black17,
71
+ isBack: !options.hiddenBack,
72
+ headerType: options.headerType,
73
+ useAnimated: options.useAnimated,
74
+ title: options.title,
75
+ headerTitle: options.headerTitle,
76
+ titlePosition: options.titlePosition,
77
+ // Tab back: non-first tab returns to the first tab, first tab
78
+ // delegates to the navigator (mirrors Compose's onBackHandler).
79
+ goBack: options.hiddenBack ? nil : {
80
+ if index != 0 { selectedIndex = 0 } else { navigator.onBackSafe() }
81
+ },
82
+ scrollable: options.scrollable,
83
+ scrollToTopCallback: options.scrollToTopCallback,
84
+ useAvoidKeyboard: options.useAvoidKeyboard,
85
+ keyboardShouldPersistTaps: options.keyboardShouldPersistTaps,
86
+ footer: options.footer,
87
+ headerRight: options.resolvedHeaderRight,
88
+ animatedHeader: options.animatedHeader,
89
+ layoutOffset: options.layoutOffset,
90
+ inputSearchProps: options.inputSearchProps,
91
+ fabProps: options.fabProps,
92
+ content: { item.screen() }
93
+ )
94
+ }
95
+ }
96
+
97
+ /// Bottom safe-area inset (home indicator).
98
+ @available(iOS 16.0, *)
99
+ private func safeAreaBottomInset() -> CGFloat {
100
+ let bottomInset = UIApplication.shared.connectedScenes
101
+ .compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
102
+ .first ?? 0
103
+ return min(bottomInset, 21)
104
+ }
@@ -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
+ }