@momo-kits/native-kits 0.167.1-duo.3-debug → 0.167.1-duo.4

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 (189) hide show
  1. package/CLAUDE.md +17 -8
  2. package/ios/Application/ApplicationEnvironment.swift +6 -0
  3. package/ios/Application/Components.swift +1 -3
  4. package/ios/Application/HeaderRight.swift +19 -27
  5. package/ios/Application/Navigation/BottomTab/BottomTab.swift +9 -30
  6. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +2 -5
  7. package/ios/Application/Navigation/HeaderUser.swift +1 -7
  8. package/ios/Application/Navigation/NavigationContainer.swift +45 -45
  9. package/ios/Application/Navigation/Navigator.swift +122 -29
  10. package/ios/Application/Navigation/NavigatorManager.swift +13 -6
  11. package/ios/Application/Navigation/Overplay/BottomSheet.swift +4 -41
  12. package/ios/Application/Navigation/Overplay/ModalScreen.swift +1 -5
  13. package/ios/Application/Navigation/component/Header.swift +4 -12
  14. package/ios/Application/Navigation/component/HeaderBackground.swift +2 -3
  15. package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
  16. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +9 -61
  17. package/ios/Application/StackScreen.swift +18 -135
  18. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +59 -5
  19. package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
  20. package/ios/Extensions/Color++.swift +20 -9
  21. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  22. package/ios/Icon/Icon.swift +14 -22
  23. package/ios/Icon/Icons.swift +1329 -0
  24. package/ios/Image/Image.swift +16 -5
  25. package/ios/Lottie/LottieView.swift +4 -2
  26. package/ios/Popup/PopupDisplay.swift +68 -84
  27. package/ios/Popup/PopupNotify.swift +32 -43
  28. package/ios/Popup/PopupPromotion.swift +6 -18
  29. package/ios/Resources/dot_loading.json +558 -0
  30. package/ios/Resources/lottie_circle_loader.json +1 -0
  31. package/ios/Template/TrustBanner/TrustBanner.swift +69 -31
  32. package/ios/native-kits.podspec +1 -1
  33. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  34. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +0 -1
  35. package/package.json +1 -1
  36. package/scripts/gen-ios-icons.mjs +36 -0
  37. package/.claude/scheduled_tasks.lock +0 -1
  38. package/.claude/settings.local.json +0 -185
  39. package/build.gradle.kts +0 -11
  40. package/compose/build.gradle.kts +0 -204
  41. package/compose/build.gradle.kts.backup +0 -204
  42. package/compose/compose.podspec +0 -47
  43. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  44. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  45. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  46. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  47. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -187
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
  140. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  141. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  142. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -648
  143. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  144. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  145. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -194
  146. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -232
  147. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  148. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -281
  149. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  150. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -408
  151. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -108
  152. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -105
  153. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  154. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -354
  155. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
  156. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  157. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -169
  158. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
  159. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  160. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  161. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  162. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  163. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
  164. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  168. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
  169. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  170. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  171. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  172. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  173. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  174. package/gradle/libs.versions.toml +0 -69
  175. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  176. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  177. package/gradle.properties +0 -26
  178. package/gradlew +0 -252
  179. package/gradlew.bat +0 -94
  180. package/ios/Application/Layout/ScreenLayout.swift +0 -196
  181. package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +0 -275
  182. package/ios/Application/Navigation/component/DuoScrollGradient.swift +0 -55
  183. package/ios/Application/Navigation/component/SideBar.swift +0 -155
  184. package/ios/Popup/PopupLayout.swift +0 -45
  185. package/ios-demo/README.md +0 -120
  186. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +0 -76
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +0 -170
  188. package/local.properties +0 -8
  189. package/settings.gradle.kts +0 -64
@@ -1,275 +0,0 @@
1
- //
2
- // DuoTabBar.swift
3
- // MoMoUIKits
4
- //
5
- // iPhone Duo tab bar, port of RN `BottomTab/DuoTabBar.tsx` (Figma Fold-Design-System MT1/MT2):
6
- // a glass rail in the side bar, or a floating glass bar when unfolded portrait.
7
- //
8
-
9
- import SwiftUI
10
-
11
- private let itemSize = DuoMetrics.controlSize
12
- private let iconSize: CGFloat = 28
13
- private let qrIconSize: CGFloat = 24
14
- private let railPadding: CGFloat = 6
15
- private let railGap = Spacing.XXS
16
- // selection bubble overflows the 48 rail cell by 5 above and below (Figma MT2)
17
- private let railBubbleWidth: CGFloat = 44
18
- private let railBubbleHeight: CGFloat = 58
19
- let duoFloatingTabBarHeight: CGFloat = 62
20
- private let floatingPadding = Spacing.XS
21
- private let floatingQrSize: CGFloat = 44
22
- // floating bar spans 6 of the 8 portrait grid columns, like the content
23
- private let gridColumns: CGFloat = 8
24
- private let gridSpan: CGFloat = 6
25
- private let tabSpring = Animation.interpolatingSpring(mass: 0.8, stiffness: 220, damping: 18)
26
-
27
- /// Room tab screens leave for the Duo tab bar, provided by BottomTab to its tab StackScreens.
28
- struct DuoTabBarInsets: Equatable {
29
- /// Floating bar overlays the content: scroll content, footer and FAB keep clear of it.
30
- var overlay: CGFloat = 0
31
- /// Rail sits at the bottom of the side strip: the header side bar stays above it.
32
- var sideBarReserve: CGFloat = 0
33
- }
34
-
35
- private struct DuoTabBarInsetsKey: EnvironmentKey {
36
- static let defaultValue = DuoTabBarInsets()
37
- }
38
-
39
- extension EnvironmentValues {
40
- var duoTabBarInsets: DuoTabBarInsets {
41
- get { self[DuoTabBarInsetsKey.self] }
42
- set { self[DuoTabBarInsetsKey.self] = newValue }
43
- }
44
- }
45
-
46
- extension ScreenLayout {
47
- func duoTabBarInsets(itemCount: Int, hasFloatingButton: Bool) -> DuoTabBarInsets {
48
- guard isIPhoneDuo else { return DuoTabBarInsets() }
49
- guard sideBar != nil else {
50
- return DuoTabBarInsets(overlay: duoFloatingTabBarHeight + Spacing.M)
51
- }
52
- let count = CGFloat(itemCount)
53
- let rail = railPadding * 2 + count * itemSize + max(count - 1, 0) * railGap
54
- let qr = hasFloatingButton ? Spacing.M + itemSize : 0
55
- return DuoTabBarInsets(sideBarReserve: rail + qr + Spacing.XL)
56
- }
57
- }
58
-
59
- /// Glass on iOS 26, a system blur before it (RN GlassView's fallback).
60
- struct DuoBarMaterial<S: Shape>: ViewModifier {
61
- let shape: S
62
- var visible: Bool = true
63
-
64
- func body(content: Content) -> some View {
65
- content.background(material.allowsHitTesting(false))
66
- }
67
-
68
- @ViewBuilder
69
- private var material: some View {
70
- #if compiler(>=6.2)
71
- if #available(iOS 26.0, *) {
72
- // glass materializes by toggling its effect, it does not fade with opacity
73
- Color.clear
74
- .glassEffect(visible ? .regular : .identity, in: shape)
75
- .shadow(color: Color.black.opacity(visible ? 0.16 : 0), radius: 16, x: 0, y: 6)
76
- .animation(.easeInOut(duration: 0.25), value: visible)
77
- } else {
78
- blur
79
- }
80
- #else
81
- blur
82
- #endif
83
- }
84
-
85
- private var blur: some View {
86
- shape.fill(.regularMaterial)
87
- .shadow(color: Color.black.opacity(0.16), radius: 16, x: 0, y: 6)
88
- .opacity(visible ? 1 : 0)
89
- .animation(.easeInOut(duration: 0.25), value: visible)
90
- }
91
- }
92
-
93
- struct DuoTabBar: View {
94
- let items: [BottomTabItem]
95
- let floatingButton: BottomTabFloatingButton?
96
- let selectedIndex: Int
97
- let onTabSelected: (Int) -> Void
98
-
99
- @Environment(\.screenLayout) private var layout
100
-
101
- var body: some View {
102
- if let sideBar = layout.sideBar {
103
- DuoRailTabBar(sideBar: sideBar, items: items, floatingButton: floatingButton,
104
- selectedIndex: selectedIndex, onTabSelected: onTabSelected)
105
- } else {
106
- DuoFloatingTabBar(items: items, floatingButton: floatingButton,
107
- selectedIndex: selectedIndex, onTabSelected: onTabSelected)
108
- }
109
- }
110
- }
111
-
112
- /// Vertical bar anchored to the bottom of the side bar, QR button below it (Figma MT1-C/E).
113
- private struct DuoRailTabBar: View {
114
- let sideBar: SideBarLayout
115
- let items: [BottomTabItem]
116
- let floatingButton: BottomTabFloatingButton?
117
- let selectedIndex: Int
118
- let onTabSelected: (Int) -> Void
119
-
120
- @Environment(\.appTheme) private var theme
121
-
122
- private var isRight: Bool { sideBar.edge == .right }
123
-
124
- var body: some View {
125
- VStack(spacing: Spacing.M) {
126
- ZStack(alignment: .top) {
127
- Capsule()
128
- .fill(theme.colors.background.selected)
129
- .frame(width: railBubbleWidth, height: railBubbleHeight)
130
- .offset(y: railPadding - (railBubbleHeight - itemSize) / 2
131
- + CGFloat(selectedIndex) * (itemSize + railGap))
132
- .animation(tabSpring, value: selectedIndex)
133
-
134
- VStack(spacing: railGap) {
135
- ForEach(items.indices, id: \.self) { index in
136
- SwiftUI.Button { onTabSelected(index) } label: {
137
- DuoTabIcon(item: items[index], selected: index == selectedIndex)
138
- .frame(width: itemSize, height: itemSize)
139
- .contentShape(Rectangle())
140
- }
141
- .buttonStyle(.plain)
142
- .accessibilityLabel(items[index].label)
143
- }
144
- }
145
- .padding(.vertical, railPadding)
146
- }
147
- .frame(width: itemSize)
148
- .modifier(DuoBarMaterial(shape: RoundedRectangle(cornerRadius: Radius.XL)))
149
- .accessibilityIdentifier("bottom_tab_bar")
150
-
151
- if let floatingButton {
152
- DuoQrButton(data: floatingButton, size: itemSize)
153
- }
154
- }
155
- .padding(isRight ? .leading : .trailing, Spacing.M)
156
- .frame(width: sideBar.width, alignment: isRight ? .leading : .trailing)
157
- .padding(.bottom, Spacing.XL)
158
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: isRight ? .bottomTrailing : .bottomLeading)
159
- .ignoresSafeArea()
160
- }
161
- }
162
-
163
- /// Floating labelled bar for the unfolded portrait display, QR in the middle cell (Figma MT1-M).
164
- private struct DuoFloatingTabBar: View {
165
- let items: [BottomTabItem]
166
- let floatingButton: BottomTabFloatingButton?
167
- let selectedIndex: Int
168
- let onTabSelected: (Int) -> Void
169
-
170
- @Environment(\.screenLayout) private var layout
171
- @Environment(\.appTheme) private var theme
172
-
173
- private var barWidth: CGFloat {
174
- let columnWidth = (layout.width - 2 * Spacing.M - (gridColumns - 1) * Spacing.M) / gridColumns
175
- return columnWidth * gridSpan + (gridSpan - 1) * Spacing.M
176
- }
177
-
178
- private var useQr: Bool { floatingButton != nil && items.count % 2 == 0 }
179
- private var middle: Int { items.count / 2 }
180
- private var cellCount: Int { items.count + (useQr ? 1 : 0) }
181
- private var cellWidth: CGFloat { (barWidth - floatingPadding * 2) / CGFloat(max(cellCount, 1)) }
182
- private var visualIndex: Int { useQr && selectedIndex >= middle ? selectedIndex + 1 : selectedIndex }
183
-
184
- var body: some View {
185
- ZStack(alignment: .leading) {
186
- Capsule()
187
- .fill(theme.colors.background.selected)
188
- .frame(width: cellWidth)
189
- .offset(x: CGFloat(visualIndex) * cellWidth)
190
- .animation(tabSpring, value: visualIndex)
191
-
192
- HStack(spacing: 0) {
193
- ForEach(0..<cellCount, id: \.self) { cell in
194
- if useQr && cell == middle, let floatingButton {
195
- DuoQrButton(data: floatingButton, size: floatingQrSize)
196
- .frame(width: cellWidth)
197
- } else {
198
- let index = useQr && cell > middle ? cell - 1 : cell
199
- tabCell(index)
200
- }
201
- }
202
- }
203
- }
204
- .padding(floatingPadding)
205
- .frame(width: barWidth, height: duoFloatingTabBarHeight)
206
- .modifier(DuoBarMaterial(shape: Capsule()))
207
- .accessibilityIdentifier("bottom_tab_bar")
208
- .padding(.bottom, layout.insets.bottom + Spacing.M)
209
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
210
- .ignoresSafeArea()
211
- }
212
-
213
- private func tabCell(_ index: Int) -> some View {
214
- let selected = index == selectedIndex
215
- return SwiftUI.Button { onTabSelected(index) } label: {
216
- VStack(spacing: Spacing.XXS) {
217
- DuoTabIcon(item: items[index], selected: selected)
218
- MomoText(
219
- items[index].label,
220
- typography: .labelXsMedium,
221
- color: selected ? theme.colors.primary : theme.colors.text.default,
222
- textAlign: .center,
223
- maxLines: 1
224
- )
225
- }
226
- .frame(width: cellWidth)
227
- .frame(maxHeight: .infinity)
228
- .contentShape(Rectangle())
229
- }
230
- .buttonStyle(.plain)
231
- .accessibilityLabel(items[index].label)
232
- }
233
- }
234
-
235
- private struct DuoTabIcon: View {
236
- let item: BottomTabItem
237
- let selected: Bool
238
-
239
- @Environment(\.appTheme) private var theme
240
-
241
- private var badgeLabel: String? {
242
- guard let label = item.badgeLabel else { return nil }
243
- if let n = Int(label), n == 0 { return nil }
244
- return label
245
- }
246
-
247
- var body: some View {
248
- Icon(source: item.icon, size: iconSize, color: selected ? theme.colors.primary : theme.colors.text.default)
249
- .overlay(alignment: .topTrailing) {
250
- if let badgeLabel, !badgeLabel.isEmpty {
251
- Badge(label: badgeLabel).offset(x: Spacing.S, y: -Spacing.XS)
252
- } else if badgeLabel != nil || item.showDot {
253
- BadgeDot(size: .small)
254
- }
255
- }
256
- }
257
- }
258
-
259
- private struct DuoQrButton: View {
260
- let data: BottomTabFloatingButton
261
- let size: CGFloat
262
-
263
- @Environment(\.appTheme) private var theme
264
-
265
- var body: some View {
266
- SwiftUI.Button(action: data.onPress) {
267
- Circle()
268
- .fill(theme.colors.primary)
269
- .frame(width: size, height: size)
270
- .overlay(Icon(source: data.icon, size: qrIconSize, color: Colors.black01))
271
- }
272
- .buttonStyle(.plain)
273
- .accessibilityLabel(data.label)
274
- }
275
- }
@@ -1,55 +0,0 @@
1
- //
2
- // DuoScrollGradient.swift
3
- // MoMoUIKits
4
- //
5
- // Port of RN `DuoScrollGradient` (Components/DuoGlassBackground.tsx): with the iPhone Duo
6
- // glass header the content scrolls under the bar, so the header gradient is drawn under it.
7
- //
8
-
9
- import SwiftUI
10
-
11
- // extended headers run their gradient this far below the header, as off Duo
12
- private let extendedGradientExtra: CGFloat = 52
13
- // scroll distance over which the extended gradient collapses to the header
14
- private let extendedCollapseDistance: CGFloat = 100
15
-
16
- struct DuoScrollGradient: View {
17
- let scrollOffset: CGFloat
18
- let headerHeight: CGFloat
19
- let extended: Bool
20
-
21
- @Environment(\.appTheme) private var theme
22
- @Environment(\.screenLayout) private var layout
23
-
24
- private var scroll: CGFloat { max(scrollOffset, 0) }
25
-
26
- private var gradient: LinearGradient {
27
- LinearGradient(
28
- colors: [theme.colors.gradient, theme.colors.gradient.opacity(0)],
29
- startPoint: .top,
30
- endPoint: .bottom
31
- )
32
- }
33
-
34
- var body: some View {
35
- Group {
36
- if extended {
37
- // extended squeezes the whole fade into its box and fades out
38
- let progress = min(scroll / extendedCollapseDistance, 1)
39
- gradient
40
- .frame(height: headerHeight + extendedGradientExtra * (1 - progress))
41
- .opacity(1 - min(scroll / extendedGradientExtra, 1))
42
- } else {
43
- // default keeps the off-Duo 375:154 shape, the header clips it, and scrolls away
44
- gradient
45
- .frame(height: layout.width * 154 / 375)
46
- .frame(height: headerHeight, alignment: .top)
47
- .clipped()
48
- .offset(y: -scroll)
49
- }
50
- }
51
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
52
- .ignoresSafeArea()
53
- .allowsHitTesting(false)
54
- }
55
- }
@@ -1,155 +0,0 @@
1
- //
2
- // SideBar.swift
3
- // MoMoUIKits
4
- //
5
- // iPhone Duo: back and toolkit leave the header bar for a column in the side safe area,
6
- // as the system bars do. Port of RN `SideBarButton.tsx` + `useSideBarItemStyle`.
7
- //
8
-
9
- import SwiftUI
10
-
11
- private let sideBarIconSize: CGFloat = 24
12
-
13
- /// Toolkit data when `headerRight` renders the kit toolkit; nil for a custom header right.
14
- func toolkitHeaderRight(_ headerRight: (() -> any View)?) -> (data: HeaderRightData?, tintColor: Color?)? {
15
- guard let view = headerRight?() else { return nil }
16
- if let toolkit = view as? HeaderRight { return (toolkit.headerRight, toolkit.tintColor) }
17
- if let toolkit = view as? NavigationHeaderRight { return (toolkit.headerRight, toolkit.tintColor) }
18
- return nil
19
- }
20
-
21
- /// The back button and toolkit column, placed in window coordinates over the side strip.
22
- struct HeaderSideBar: View {
23
- let sideBar: SideBarLayout
24
- let goBack: (() -> Void)?
25
- let toolkit: (data: HeaderRightData?, tintColor: Color?)?
26
- let headerColor: HeaderColor
27
- /// What footer / tab bar / home indicator take from the bottom; the column stays above it.
28
- let bottomInset: CGFloat
29
-
30
- @Environment(\.screenLayout) private var layout
31
- @State private var columnHeight: CGFloat = 0
32
-
33
- private var isRight: Bool { sideBar.edge == .right }
34
-
35
- private var top: CGFloat {
36
- let maxY = layout.height - bottomInset - Spacing.XL - columnHeight
37
- return max(min(sideBar.top, maxY), 0)
38
- }
39
-
40
- var body: some View {
41
- VStack(alignment: isRight ? .leading : .trailing, spacing: Spacing.M) {
42
- if let goBack {
43
- SideBarButton(
44
- icon: "arrow-back",
45
- tintColor: headerColor.tintIconColor,
46
- accessibilityId: "btn_navigation_back",
47
- action: goBack
48
- )
49
- } else {
50
- // keep the slot so the toolkit does not jump between root and pushed screens
51
- Color.clear.frame(width: DuoMetrics.controlSize, height: DuoMetrics.controlSize)
52
- }
53
- if let toolkit {
54
- ToolkitNavigationHeaderRight(
55
- headerRight: toolkit.data,
56
- tintColor: toolkit.tintColor ?? headerColor.tintIconColor,
57
- vertical: true
58
- )
59
- }
60
- }
61
- .padding(isRight ? .leading : .trailing, Spacing.M)
62
- .frame(width: sideBar.width, alignment: isRight ? .leading : .trailing)
63
- .background(
64
- GeometryReader { proxy in
65
- Color.clear
66
- .onAppear { columnHeight = proxy.size.height }
67
- .onChange(of: proxy.size.height) { columnHeight = $0 }
68
- }
69
- )
70
- .padding(.top, top)
71
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: isRight ? .topTrailing : .topLeading)
72
- .ignoresSafeArea()
73
- }
74
- }
75
-
76
- /// Dark glass under light icons (banner headers), light glass otherwise.
77
- private struct SideBarGlass<S: Shape>: ViewModifier {
78
- let tintColor: Color?
79
- let shape: S
80
-
81
- private var isDark: Bool { tintColor == Colors.black01 || tintColor == .white }
82
-
83
- func body(content: Content) -> some View {
84
- if isDark {
85
- content
86
- .background(shape.fill(Colors.black20.opacity(0.5)))
87
- .overlay(shape.stroke(Colors.black01.opacity(0.2), lineWidth: 0.5))
88
- } else {
89
- content
90
- .background(
91
- shape.fill(Colors.black01.opacity(0.85))
92
- .shadow(color: Color.black.opacity(0.12), radius: 4, x: 0, y: 2)
93
- )
94
- .overlay(shape.stroke(Colors.black20.opacity(0.08), lineWidth: 0.5))
95
- }
96
- }
97
- }
98
-
99
- /// 48pt glass circle for a single side bar control.
100
- struct SideBarButton: View {
101
- let icon: String
102
- var tintColor: Color?
103
- var showBadge: Bool = false
104
- var disabled: Bool = false
105
- var accessibilityId: String?
106
- let action: () -> Void
107
-
108
- var body: some View {
109
- SwiftUI.Button(action: { if !disabled { action() } }) {
110
- Icon(source: icon, size: sideBarIconSize, color: tintColor ?? Colors.black17)
111
- .frame(width: DuoMetrics.controlSize, height: DuoMetrics.controlSize)
112
- .modifier(SideBarGlass(tintColor: tintColor, shape: Circle()))
113
- .overlay(alignment: .topTrailing) {
114
- if showBadge {
115
- BadgeDot(size: .small).offset(x: -Spacing.XXS, y: Spacing.XXS)
116
- }
117
- }
118
- }
119
- .buttonStyle(.plain)
120
- .accessibilityIdentifier(accessibilityId ?? "")
121
- }
122
- }
123
-
124
- /// 48pt wide glass capsule grouping related controls, one item per 50pt.
125
- struct SideBarGroup<Content: View>: View {
126
- var tintColor: Color?
127
- let itemCount: Int
128
- @ViewBuilder let content: () -> Content
129
-
130
- var body: some View {
131
- VStack(spacing: 0) { content() }
132
- .frame(
133
- width: DuoMetrics.controlSize,
134
- height: itemCount > 1 ? DuoMetrics.itemPitch * CGFloat(itemCount) : DuoMetrics.controlSize
135
- )
136
- .modifier(SideBarGlass(tintColor: tintColor, shape: RoundedRectangle(cornerRadius: Radius.XL)))
137
- }
138
- }
139
-
140
- struct SideBarGroupItem: View {
141
- let icon: String
142
- var tintColor: Color?
143
- let accessibilityId: String
144
- let action: () -> Void
145
-
146
- var body: some View {
147
- SwiftUI.Button(action: action) {
148
- Icon(source: icon, size: sideBarIconSize, color: tintColor ?? Colors.black17)
149
- .frame(maxWidth: .infinity, maxHeight: .infinity)
150
- .contentShape(Rectangle())
151
- }
152
- .buttonStyle(.plain)
153
- .accessibilityIdentifier(accessibilityId)
154
- }
155
- }
@@ -1,45 +0,0 @@
1
- import SwiftUI
2
-
3
- // Width of a system alert on the iPhone Duo inner display, in both orientations (RN Popup/utils.ts).
4
- private let duoPopupMaxWidth: CGFloat = 440
5
-
6
- extension ScreenLayout {
7
- var isDuoDevice: Bool { isIPhoneDuoDevice(windowSize: CGSize(width: width, height: height)) }
8
-
9
- /// Card width cap; other devices keep the full window width minus the popup's own padding.
10
- var popupMaxWidth: CGFloat {
11
- isDuoDevice ? min(width - Spacing.XL * 2, duoPopupMaxWidth) : .infinity
12
- }
13
-
14
- /// Room left for a popup on iPhone Duo, where landscape is too short for it; nil elsewhere.
15
- var popupMaxHeight: CGFloat? {
16
- guard isDuoDevice else { return nil }
17
- return height - duoTopInset - insets.bottom - Spacing.XL * 2
18
- }
19
- }
20
-
21
- /// Scrolls `content` once it is taller than `maxHeight`; otherwise sizes to it.
22
- struct PopupScrollBody<Content: View>: View {
23
- let maxHeight: CGFloat?
24
- @ViewBuilder let content: () -> Content
25
-
26
- @State private var contentHeight: CGFloat = 0
27
-
28
- var body: some View {
29
- if let maxHeight {
30
- ScrollView(.vertical, showsIndicators: false) {
31
- content()
32
- .background(
33
- GeometryReader { proxy in
34
- Color.clear
35
- .onAppear { contentHeight = proxy.size.height }
36
- .onChange(of: proxy.size.height) { contentHeight = $0 }
37
- }
38
- )
39
- }
40
- .frame(height: min(contentHeight, max(maxHeight, 0)))
41
- } else {
42
- content()
43
- }
44
- }
45
- }
@@ -1,120 +0,0 @@
1
- # MoMoUIKitsDemo
2
-
3
- SwiftUI demo browser for `MoMoUIKits`.
4
-
5
- The package can optionally host a Compose UIKits screen by receiving a
6
- `UIViewController` factory from the host app. It does not import the KMP
7
- `shared` module directly.
8
-
9
- SwiftUI mode reuses the full demo screen set from
10
- `sample/iosApp/iosApp/Demo`, including the sample `HomeView` registry and demo
11
- screens for all currently available components.
12
-
13
- ## CocoaPods
14
-
15
- In the host app `Podfile`, include both local pods:
16
-
17
- ```ruby
18
- pod 'MoMoUIKits', :path => '../momo-native-kits/ios/native-kits.podspec'
19
- pod 'MoMoUIKitsDemo', :path => '../momo-native-kits/ios-demo'
20
- ```
21
-
22
- Adjust the relative paths to match the host app checkout.
23
-
24
- ## Host App Usage
25
-
26
- ```swift
27
- import MoMoUIKitsDemo
28
- import shared
29
-
30
- class UIKitsViewController: BaseComposeAppViewController {
31
- override func getViewController() -> UIViewController? {
32
- let params = self.application.data as? [String: Any]
33
-
34
- return MoMoUIKitsDemoViewControllerFactory.make(
35
- context: MoMoUIKitsDemoContext(
36
- appId: params?["appId"] as? String ?? "",
37
- appCode: params?["code"] as? String ?? "",
38
- appName: params?["name"] as? String,
39
- appIcon: params?["icon"] as? String ?? ""
40
- ),
41
- composeViewController: {
42
- UIKitsScreenComposeViewControllerKt.UIKitsScreenComposeViewController(
43
- params: params
44
- )
45
- }
46
- )
47
- }
48
-
49
- override func viewDidDisappear(_ animated: Bool) {
50
- super.viewDidDisappear(animated)
51
- composeViewController = nil
52
- }
53
- }
54
- ```
55
-
56
- If the host does not pass `composeViewController`, the demo opens directly in
57
- SwiftUI mode.
58
-
59
- ## Included SwiftUI Demos
60
-
61
- The pod includes the sample demo screens for:
62
-
63
- - Animated Header
64
- - Avatar
65
- - Badge
66
- - Baseline View
67
- - Bottom Tab
68
- - Button
69
- - Carousel
70
- - Checkbox
71
- - Chip
72
- - Collapse
73
- - DateTimePicker
74
- - Divider
75
- - Header User
76
- - Icon Button
77
- - Image
78
- - Information
79
- - Input
80
- - Input DropDown
81
- - Input Money
82
- - Input OTP
83
- - Input Phone Number
84
- - Input Search
85
- - Input TextArea
86
- - Loader
87
- - Navigation
88
- - Pagination
89
- - Popup Display
90
- - Popup Notify
91
- - Popup Promotion
92
- - Progress Info
93
- - Radio
94
- - Rating
95
- - Skeleton
96
- - Slider
97
- - SnackBar
98
- - Stepper
99
- - Steps
100
- - Suggest Action
101
- - Swipe
102
- - Switch
103
- - TabView
104
- - Tag
105
- - Theme
106
- - Title
107
- - Tooltip
108
- - Typography
109
- - Uploader
110
-
111
- ## Validation Notes
112
-
113
- Validated locally with:
114
-
115
- ```sh
116
- pod ipc spec ios/MoMoUIKits.podspec
117
- pod ipc spec ios-demo/MoMoUIKitsDemo.podspec
118
- pod lib lint ios/MoMoUIKits.podspec --allow-warnings --skip-import-validation
119
- pod lib lint ios-demo/MoMoUIKitsDemo.podspec --allow-warnings --skip-import-validation --include-podspecs=ios/MoMoUIKits.podspec
120
- ```