@momo-kits/native-kits 0.162.2-debug → 0.162.2-sp.1-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.claude/settings.local.json +55 -0
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/compose.podspec +1 -1
  4. package/gradle.properties +1 -1
  5. package/ios/Application/Components.swift +34 -43
  6. package/ios/Application/FloatingButton.swift +10 -8
  7. package/ios/Application/Navigation/BottomTab/BottomTab.swift +102 -0
  8. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  9. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  10. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  11. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  12. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  13. package/ios/Application/Navigation/NavigationContainer.swift +151 -0
  14. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  15. package/ios/Application/Navigation/Navigator.swift +360 -0
  16. package/ios/Application/Navigation/Overplay/BottomSheet.swift +176 -0
  17. package/ios/Application/Navigation/Overplay/ModalScreen.swift +68 -0
  18. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  19. package/ios/Application/Navigation/component/Header.swift +178 -0
  20. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  21. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  22. package/ios/Application/StackScreen.swift +449 -0
  23. package/ios/Avatar/Avatar.swift +193 -0
  24. package/ios/Badge/Badge.swift +11 -13
  25. package/ios/Badge/BadgeRibbon.swift +12 -19
  26. package/ios/BaselineView/BaselineView.swift +163 -0
  27. package/ios/Button/Button.swift +62 -23
  28. package/ios/Carousel/Carousel.swift +181 -0
  29. package/ios/Checkbox/Checkbox.swift +14 -12
  30. package/ios/Chip/Chip.swift +23 -9
  31. package/ios/Collapse/Collapse.swift +182 -0
  32. package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
  33. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  34. package/ios/DateTimePicker/DateTimePicker.swift +210 -0
  35. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  36. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  37. package/ios/DateTimePicker/WheelPicker.swift +161 -0
  38. package/ios/Divider/Divider.swift +16 -0
  39. package/ios/Icon/Icon.swift +6 -1
  40. package/ios/IconButton/IconButton.swift +112 -0
  41. package/ios/Information/Information.swift +151 -0
  42. package/ios/Input/Input.swift +42 -25
  43. package/ios/Input/InputPhoneNumber.swift +21 -19
  44. package/ios/Input/InputSearch.swift +101 -45
  45. package/ios/Input/InputTextArea.swift +67 -40
  46. package/ios/InputDropDown/InputDropDown.swift +198 -0
  47. package/ios/InputMoney/InputMoney.swift +335 -0
  48. package/ios/InputOTP/InputOTP.swift +210 -0
  49. package/ios/Loader/Loader.swift +113 -0
  50. package/ios/MoMoUIKits.podspec +5 -0
  51. package/ios/OTPKeyboard/KeyboardButton.swift +1 -1
  52. package/ios/Pagination/PaginationDot.swift +61 -0
  53. package/ios/Pagination/PaginationNumber.swift +35 -0
  54. package/ios/Pagination/PaginationScroll.swift +101 -0
  55. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  56. package/ios/Popup/PopupDisplay.swift +10 -12
  57. package/ios/Popup/PopupInput.swift +2 -6
  58. package/ios/Popup/PopupNotify.swift +219 -0
  59. package/ios/ProgressInfo/ProgressInfo.swift +294 -0
  60. package/ios/Radio/Radio.swift +70 -0
  61. package/ios/Rating/Rating.swift +82 -0
  62. package/ios/Skeleton/Skeleton.swift +99 -0
  63. package/ios/Slider/Slider.swift +237 -0
  64. package/ios/Stepper/Stepper.swift +220 -0
  65. package/ios/Steps/Steps.swift +451 -0
  66. package/ios/SuggestAction/SuggestAction.swift +97 -0
  67. package/ios/Swipeable/SwipeCell.swift +185 -0
  68. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  69. package/ios/TabView/TabView.swift +530 -0
  70. package/ios/Tag/Tag.swift +98 -0
  71. package/ios/Template/TrustBanner/TrustBanner.swift +2 -0
  72. package/ios/Title/Title.swift +269 -0
  73. package/ios/Tooltip/Tooltip.swift +482 -0
  74. package/ios/Typography/Text.swift +77 -5
  75. package/ios/Typography/Typography.swift +9 -0
  76. package/ios/Uploader/Uploader.swift +183 -0
  77. package/ios/native-kits.podspec +58 -8
  78. package/ios-demo/MoMoUIKitsDemo.podspec +18 -0
  79. package/ios-demo/README.md +120 -0
  80. package/ios-demo/Resources/dot_loading.json +558 -0
  81. package/ios-demo/Resources/icon.json +4052 -0
  82. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  83. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  84. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  85. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  86. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  87. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  88. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  89. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  90. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  91. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  92. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  93. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +72 -0
  94. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  95. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
  96. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
  97. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  98. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  99. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
  100. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
  101. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
  102. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
  103. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
  104. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  105. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
  106. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
  107. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  108. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +261 -0
  109. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
  110. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
  111. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
  112. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
  113. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
  114. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
  115. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
  116. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
  117. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
  118. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
  119. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
  120. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +227 -0
  121. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
  122. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  123. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
  124. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
  125. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
  126. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
  127. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
  128. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
  129. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
  130. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
  131. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
  132. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
  133. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
  134. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
  135. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
  136. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
  137. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
  138. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
  139. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
  140. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
  141. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  142. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
  143. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +50 -0
  144. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  145. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  146. package/local.properties +8 -0
  147. package/package.json +1 -1
  148. package/publish.sh +50 -0
  149. package/scripts/gen-ios-color.mjs +136 -0
@@ -0,0 +1,55 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
5
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/maven-metadata.xml\")",
6
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/maven-metadata.xml\")",
7
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/0.144.6/skiko-0.144.6.module\")",
8
+ "Bash(curl -s -o /dev/null -w '%{http_code}' https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.__TRACKED_VAR__)",
9
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/\")",
10
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/\")",
11
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.148.1/skiko-android-0.148.1.pom\")",
12
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.5/skiko-android-0.144.5.pom\")",
13
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://dl.google.com/dl/android/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
14
+ "Bash(curl -s \"https://search.maven.org/solrsearch/select?q=a:skiko-android&rows=5&wt=json\")",
15
+ "Bash(curl -sL -o /dev/null -w \"%{http_code} %{url_effective}\\\\n\" \"https://repo1.maven.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
16
+ "Bash(curl *)",
17
+ "Bash(./gradlew :sample:androidApp:dependencies --configuration debugRuntimeClasspath)",
18
+ "Bash(./gradlew :sample:shared:compileDebugKotlinAndroid)",
19
+ "Bash(./gradlew :sample:androidApp:compileDebugKotlin)",
20
+ "Bash(ls -d *.xcodeproj)",
21
+ "Bash(grep -c ChipDemo.swift __TRACKED_VAR__/project.pbxproj)",
22
+ "Bash(grep -c PBXFileSystemSynchronizedRootGroup __TRACKED_VAR__/project.pbxproj)",
23
+ "Bash(grep -c InformationDemo.swift __TRACKED_VAR__/project.pbxproj)",
24
+ "Bash(plutil -lint project.pbxproj)",
25
+ "Bash(xcode-select -p)",
26
+ "Bash(/usr/bin/xcrun xcodebuild *)",
27
+ "Bash(echo \"exit: $?\")",
28
+ "Bash(/Applications/Xcode26.app/Contents/Developer/usr/bin/xcodebuild -version)",
29
+ "Bash(./gradlew :compose:tasks --all)",
30
+ "Bash(grep -n \"vanniktech\\\\|mavenPublishing\\\\|^publishing\\\\|version = gitlabVersion\\\\|namespace =\\\\|compileSdk =\\\\|minSdk =\\\\|GitLabPackages\\\\|name = \\\\\"\\\\${gitlabRepo}Packages\\\\\"\")",
31
+ "Bash(git checkout *)",
32
+ "Bash(./gradlew :compose:publishAllPublicationsToGitLabPackagesRepository --dry-run)",
33
+ "Bash(git fetch *)",
34
+ "Bash(git pull *)",
35
+ "Bash(git push *)",
36
+ "Bash(git branch *)",
37
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -list)",
38
+ "Bash(xcrun simctl *)",
39
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
40
+ "Bash(pod install *)",
41
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
42
+ "Bash(xcrun swiftc *)",
43
+ "Bash(echo \"=== exit: $? \\(1=none found\\) ===\")",
44
+ "Bash(cat Radius.swift)",
45
+ "Bash(cat Spacing.swift)",
46
+ "Bash(echo \"exit=$?\")",
47
+ "Bash(ruby *)",
48
+ "Bash(xcodebuild -list -workspace iosApp.xcworkspace)",
49
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' build)",
50
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
51
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
52
+ "Bash(xcodebuild build *)"
53
+ ]
54
+ }
55
+ }
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.162.2-debug"
43
+ version = "0.162.2-sp.1-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.161.2-beta.15'
3
+ spec.version = '0.162.2-sp.1-debug'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.162.2
21
+ version=0.162.2-sp.1
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -83,9 +83,8 @@ public struct HeaderBackground: View {
83
83
  self.headerTransparent = headerTransparent
84
84
  self.fullScreenContent = fullScreenContent
85
85
  }
86
-
86
+
87
87
  public var body: some View {
88
-
89
88
  let statusBarHeight = safeAreaTopInset()
90
89
  let opacity = max(0, 1 - scrollState / 200)
91
90
  let height = fullScreenContent ? 0 : statusBarHeight + 52
@@ -101,21 +100,6 @@ public struct HeaderBackground: View {
101
100
  .frame(height: height)
102
101
  .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
103
102
  .background(backgroundColor)
104
- .overlay(
105
- headerTransparent ? AnyView(EmptyView()) :
106
- AnyView(
107
- Rectangle()
108
- .fill(LinearGradient(
109
- gradient: Gradient(colors: [
110
- Color(red: 1, green: 0.8, blue: 0.87),
111
- Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
112
- ]),
113
- startPoint: .top,
114
- endPoint: .bottom))
115
- .opacity(opacity)
116
- .frame(height: height))
117
- )
118
-
119
103
  if !headerTransparent {
120
104
  Rectangle()
121
105
  .fill(Color.black.opacity(0.1))
@@ -148,12 +132,26 @@ public struct HeaderBackground: View {
148
132
  }
149
133
  }
150
134
 
135
+ // MARK: - Header right width preference
136
+
137
+ /// Reports the measured width of the header-right area so the animated search
138
+ /// header can compute its trailing inset (mirrors Compose's headerRightWidthPx).
139
+ public struct HeaderRightWidthKey: PreferenceKey {
140
+ public static var defaultValue: CGFloat = 0
141
+ public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
142
+ value = max(value, nextValue())
143
+ }
144
+ }
145
+
151
146
  // MARK: - Header View
152
147
 
153
148
  public struct Header: View {
154
149
  var headerType: HeaderType = .default
155
150
  var titlePosition: TitlePosition
156
151
  var title: String
152
+ /// Optional rich title (mirrors Compose's `HeaderTitle`); `.user` renders an
153
+ /// avatar group instead of `title`. Defaults to nil → plain `title` text.
154
+ var headerTitle: HeaderTitle?
157
155
  var headerRight: (()->any View)? = {HeaderRight()}
158
156
  var goBack: (() -> Void)?
159
157
  var opacity: CGFloat = 1
@@ -161,22 +159,27 @@ public struct Header: View {
161
159
  var scrollState: CGFloat = 0
162
160
  var inputSearchProps: InputSearchProps?
163
161
  var tintColor: Color?
162
+ /// Title opacity; used by the animated search header to fade the title out on scroll.
163
+ var titleOpacity: CGFloat = 1
164
164
 
165
165
  public init(
166
166
  headerType: HeaderType = .default,
167
167
  titlePosition: TitlePosition,
168
168
  title: String,
169
+ headerTitle: HeaderTitle? = nil,
169
170
  headerRight: (()->any View)? = {HeaderRight()},
170
171
  goBack: (() -> Void)? = nil,
171
172
  opacity: CGFloat = 1,
172
173
  animatedHeader: AnimatedHeader? = nil,
173
174
  scrollState: CGFloat = 0,
174
175
  inputSearchProps: InputSearchProps? = nil,
175
- tintColor: Color? = nil
176
+ tintColor: Color? = nil,
177
+ titleOpacity: CGFloat = 1
176
178
  ) {
177
179
  self.headerType = headerType
178
180
  self.titlePosition = titlePosition
179
181
  self.title = title
182
+ self.headerTitle = headerTitle
180
183
  self.headerRight = headerRight
181
184
  self.goBack = goBack
182
185
  self.opacity = opacity
@@ -184,10 +187,11 @@ public struct Header: View {
184
187
  self.scrollState = scrollState
185
188
  self.inputSearchProps = inputSearchProps
186
189
  self.tintColor = tintColor
190
+ self.titleOpacity = titleOpacity
187
191
  }
188
-
192
+
189
193
  public var body: some View {
190
-
194
+
191
195
  let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
192
196
  let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
193
197
 
@@ -212,6 +216,9 @@ public struct Header: View {
212
216
 
213
217
  if let headerRight = headerRight {
214
218
  AnyView(headerRight())
219
+ .background(GeometryReader { geo in
220
+ Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
221
+ })
215
222
  }
216
223
  }
217
224
  .padding(.horizontal, 12)
@@ -223,32 +230,16 @@ public struct Header: View {
223
230
  }
224
231
 
225
232
  if let inputProps = inputSearchProps {
226
- InputSearch(
227
- text: inputProps.$text,
228
- buttonText: inputProps.buttonText,
229
- showButtonText: inputProps.showButtonText,
230
- showBorder: inputProps.showBorder,
231
- placeholder: inputProps.placeholder,
232
- onChangeText: inputProps.onChangeText,
233
- onPressButtonText: inputProps.onPressButtonText,
234
- error: inputProps.error,
235
- disabled: inputProps.disabled,
236
- icon: inputProps.icon,
237
- iconColor: inputProps.iconColor,
238
- onRightIconPressed: inputProps.onRightIconPressed,
239
- onFocus: inputProps.onFocus,
240
- onBlur: inputProps.onBlur,
241
- loading: inputProps.loading,
242
- fontWeight: inputProps.fontWeight,
243
- keyboardType: inputProps.keyboardType
244
- )
233
+ InputSearch(props: inputProps)
234
+ } else if case let .user(userData)? = headerTitle {
235
+ if #available(iOS 16.0, *) {
236
+ HeaderUserView(userData)
237
+ }
245
238
  } else {
246
- Text(title)
247
- .font(.system(size: 17, weight: .bold))
248
- .foregroundColor(tintColor ?? Colors.black17)
239
+ MomoText(headerTitle?.defaultTitle ?? title, typography: .headerDefaultBold, color: tintColor ?? Colors.black17, maxLines: 1)
240
+ .opacity(titleOpacity)
249
241
  .frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
250
242
  .frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
251
- .lineLimit(1)
252
243
  }
253
244
 
254
245
  Spacer()
@@ -3,11 +3,16 @@ import Combine
3
3
 
4
4
  public enum FABSize {
5
5
  case small
6
+ case `default`
7
+ /// Backward-compatible alias for older iOS call sites. Compose names this
8
+ /// size `DEFAULT`.
6
9
  case large
7
-
8
10
  }
9
11
 
10
12
  public enum FABPosition {
13
+ case end
14
+ /// Backward-compatible alias for older iOS call sites. Compose names this
15
+ /// position `END`.
11
16
  case right
12
17
  case center
13
18
  }
@@ -31,10 +36,10 @@ public struct FabProps {
31
36
  label: String? = nil,
32
37
  onClick: (() -> Void)? = nil,
33
38
  containerColor: Color? = Colors.primary,
34
- size: FABSize? = .small,
39
+ size: FABSize? = .default,
35
40
  iconSize: CGFloat? = nil,
36
41
  scrollOffset: CGFloat? = nil,
37
- position: FABPosition? = .right,
42
+ position: FABPosition? = .end,
38
43
  bottomPadding: CGFloat? = nil,
39
44
  renderComponent: (() -> AnyView)? = nil
40
45
  ) {
@@ -65,7 +70,7 @@ public struct FloatingButton: View {
65
70
  private var keyboardOffset: CGFloat
66
71
  private var scrollOffset: CGFloat
67
72
  private var bottomPadding: CGFloat { props.bottomPadding ?? 12 + keyboardOffset}
68
- private var position: FABPosition? { props.position ?? .right }
73
+ private var position: FABPosition? { props.position ?? .end }
69
74
  private var containerColor: Color? { props.containerColor ?? Colors.primary }
70
75
  private var iconColor: Color? { props.iconColor ?? .white }
71
76
  private var label: String? { props.label }
@@ -152,10 +157,7 @@ public struct FloatingButton: View {
152
157
  if let label = label {
153
158
  if showText {
154
159
  Spacer().frame(width: 8)
155
- Text(label)
156
- .foregroundColor(.white)
157
- .font(.system(size: scaleSize(16), weight: .bold))
158
- .lineLimit(1)
160
+ MomoText(label, typography: .actionDefaultBold, color: .white, maxLines: 1)
159
161
  .background(
160
162
  GeometryReader { geo in
161
163
  Color.clear.onAppear {
@@ -0,0 +1,102 @@
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
+ }
60
+
61
+ @ViewBuilder
62
+ private func tabScreen(for index: Int) -> some View {
63
+ let item = items[index]
64
+ let options = item.options ?? NavigationOptions()
65
+ StackScreen(
66
+ backgroundColor: options.backgroundColor,
67
+ headerTransparent: options.headerTransparent,
68
+ fullScreenContent: options.fullScreenContent,
69
+ tintColor: options.tintColor ?? Colors.black17,
70
+ isBack: !options.hiddenBack,
71
+ headerType: options.headerType,
72
+ useAnimated: options.useAnimated,
73
+ title: options.title,
74
+ headerTitle: options.headerTitle,
75
+ titlePosition: options.titlePosition,
76
+ // Tab back: non-first tab returns to the first tab, first tab
77
+ // delegates to the navigator (mirrors Compose's onBackHandler).
78
+ goBack: options.hiddenBack ? nil : {
79
+ if index != 0 { selectedIndex = 0 } else { navigator.onBackSafe() }
80
+ },
81
+ scrollable: options.scrollable,
82
+ scrollToTopCallback: options.scrollToTopCallback,
83
+ useAvoidKeyboard: options.useAvoidKeyboard,
84
+ keyboardShouldPersistTaps: options.keyboardShouldPersistTaps,
85
+ footer: options.footer,
86
+ headerRight: options.resolvedHeaderRight,
87
+ animatedHeader: options.animatedHeader,
88
+ layoutOffset: options.layoutOffset,
89
+ inputSearchProps: options.inputSearchProps,
90
+ fabProps: options.fabProps,
91
+ content: { item.screen() }
92
+ )
93
+ }
94
+ }
95
+
96
+ /// Bottom safe-area inset (home indicator).
97
+ @available(iOS 16.0, *)
98
+ private func safeAreaBottomInset() -> CGFloat {
99
+ UIApplication.shared.connectedScenes
100
+ .compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
101
+ .first ?? 0
102
+ }
@@ -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
+ }