@momo-kits/native-kits 0.167.1-duo.7-debug → 0.167.1-duo.8-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 (25) hide show
  1. package/CLAUDE.md +7 -4
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +56 -11
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +55 -3
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +14 -5
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +71 -40
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +11 -27
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +19 -7
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +20 -4
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  13. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +86 -0
  14. package/ios/Application/Layout/ScreenLayout.swift +122 -6
  15. package/ios/Application/Navigation/BottomTab/BottomTab.swift +41 -5
  16. package/ios/Application/Navigation/BottomTab/DuoBarCompression.swift +105 -0
  17. package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +60 -31
  18. package/ios/Application/Navigation/component/DuoScrollGradient.swift +18 -19
  19. package/ios/Application/Navigation/component/Header.swift +3 -2
  20. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +32 -0
  21. package/ios/Application/Navigation/component/SideBar.swift +2 -2
  22. package/ios/Application/StackScreen.swift +46 -6
  23. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +7 -3
  24. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +3 -0
  25. package/package.json +1 -1
@@ -28,7 +28,7 @@ private let tabSpring = Animation.interpolatingSpring(mass: 0.8, stiffness: 220,
28
28
  struct DuoTabBarInsets: Equatable {
29
29
  /// Floating bar overlays the content: scroll content, footer and FAB keep clear of it.
30
30
  var overlay: CGFloat = 0
31
- /// Rail sits at the bottom of the side strip: the header side bar stays above it.
31
+ /// Rail sits at the bottom of the side strip: the header side bar stays this far above the window bottom.
32
32
  var sideBarReserve: CGFloat = 0
33
33
  }
34
34
 
@@ -43,16 +43,21 @@ extension EnvironmentValues {
43
43
  }
44
44
  }
45
45
 
46
+ /// Rail plus the QR button under it; compressed, the rail is a single button.
47
+ func duoRailHeight(itemCount: Int, hasQr: Bool, compressed: Bool = false) -> CGFloat {
48
+ let count = CGFloat(itemCount)
49
+ let rail = compressed ? itemSize : railPadding * 2 + count * itemSize + max(count - 1, 0) * railGap
50
+ return rail + (hasQr ? Spacing.M + itemSize : 0)
51
+ }
52
+
46
53
  extension ScreenLayout {
47
- func duoTabBarInsets(itemCount: Int, hasFloatingButton: Bool) -> DuoTabBarInsets {
54
+ func duoTabBarInsets(itemCount: Int, hasFloatingButton: Bool, tabBarCompressed: Bool = false) -> DuoTabBarInsets {
48
55
  guard isIPhoneDuo else { return DuoTabBarInsets() }
49
- guard sideBar != nil else {
56
+ guard let sideBar else {
50
57
  return DuoTabBarInsets(overlay: duoFloatingTabBarHeight + Spacing.M)
51
58
  }
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)
59
+ let rail = duoRailHeight(itemCount: itemCount, hasQr: hasFloatingButton, compressed: tabBarCompressed)
60
+ return DuoTabBarInsets(sideBarReserve: sideBar.bottom + rail + DuoBarCompression.groupGap)
56
61
  }
57
62
  }
58
63
 
@@ -95,13 +100,14 @@ struct DuoTabBar: View {
95
100
  let floatingButton: BottomTabFloatingButton?
96
101
  let selectedIndex: Int
97
102
  let onTabSelected: (Int) -> Void
103
+ var compression = DuoBarCompression()
98
104
 
99
105
  @Environment(\.screenLayout) private var layout
100
106
 
101
107
  var body: some View {
102
108
  if let sideBar = layout.sideBar {
103
109
  DuoRailTabBar(sideBar: sideBar, items: items, floatingButton: floatingButton,
104
- selectedIndex: selectedIndex, onTabSelected: onTabSelected)
110
+ selectedIndex: selectedIndex, onTabSelected: onTabSelected, compression: compression)
105
111
  } else {
106
112
  DuoFloatingTabBar(items: items, floatingButton: floatingButton,
107
113
  selectedIndex: selectedIndex, onTabSelected: onTabSelected)
@@ -110,12 +116,15 @@ struct DuoTabBar: View {
110
116
  }
111
117
 
112
118
  /// Vertical bar anchored to the bottom of the side bar, QR button below it (Figma MT1-C/E).
119
+ /// When the side bar is short it can shrink to one button showing the current tab
120
+ /// (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar.
113
121
  private struct DuoRailTabBar: View {
114
122
  let sideBar: SideBarLayout
115
123
  let items: [BottomTabItem]
116
124
  let floatingButton: BottomTabFloatingButton?
117
125
  let selectedIndex: Int
118
126
  let onTabSelected: (Int) -> Void
127
+ let compression: DuoBarCompression
119
128
 
120
129
  @Environment(\.appTheme) private var theme
121
130
 
@@ -123,30 +132,19 @@ private struct DuoRailTabBar: View {
123
132
 
124
133
  var body: some View {
125
134
  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
- }
135
+ if compression.tabBarCompressed, items.indices.contains(selectedIndex) {
136
+ SwiftUI.Button(action: compression.expandTabBar) {
137
+ DuoTabIcon(item: items[selectedIndex], selected: true)
138
+ .frame(width: itemSize, height: itemSize)
139
+ .contentShape(Circle())
144
140
  }
145
- .padding(.vertical, railPadding)
141
+ .buttonStyle(.plain)
142
+ .modifier(DuoBarMaterial(shape: Circle()))
143
+ .accessibilityLabel(items[selectedIndex].label)
144
+ .accessibilityIdentifier("bottom_tab_bar")
145
+ } else {
146
+ rail
146
147
  }
147
- .frame(width: itemSize)
148
- .modifier(DuoBarMaterial(shape: RoundedRectangle(cornerRadius: Radius.XL)))
149
- .accessibilityIdentifier("bottom_tab_bar")
150
148
 
151
149
  if let floatingButton {
152
150
  DuoQrButton(data: floatingButton, size: itemSize)
@@ -154,10 +152,41 @@ private struct DuoRailTabBar: View {
154
152
  }
155
153
  .padding(isRight ? .leading : .trailing, Spacing.M)
156
154
  .frame(width: sideBar.width, alignment: isRight ? .leading : .trailing)
157
- .padding(.bottom, Spacing.XL)
155
+ .padding(.bottom, sideBar.bottom)
158
156
  .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: isRight ? .bottomTrailing : .bottomLeading)
159
157
  .ignoresSafeArea()
160
158
  }
159
+
160
+ /// Glass column of tab items with the sliding selection bubble.
161
+ private var rail: some View {
162
+ ZStack(alignment: .top) {
163
+ Capsule()
164
+ .fill(theme.colors.background.selected)
165
+ .frame(width: railBubbleWidth, height: railBubbleHeight)
166
+ .offset(y: railPadding - (railBubbleHeight - itemSize) / 2
167
+ + CGFloat(selectedIndex) * (itemSize + railGap))
168
+ .animation(tabSpring, value: selectedIndex)
169
+
170
+ VStack(spacing: railGap) {
171
+ ForEach(items.indices, id: \.self) { index in
172
+ SwiftUI.Button {
173
+ onTabSelected(index)
174
+ compression.onTabSelected()
175
+ } label: {
176
+ DuoTabIcon(item: items[index], selected: index == selectedIndex)
177
+ .frame(width: itemSize, height: itemSize)
178
+ .contentShape(Rectangle())
179
+ }
180
+ .buttonStyle(.plain)
181
+ .accessibilityLabel(items[index].label)
182
+ }
183
+ }
184
+ .padding(.vertical, railPadding)
185
+ }
186
+ .frame(width: itemSize)
187
+ .modifier(DuoBarMaterial(shape: RoundedRectangle(cornerRadius: Radius.XL)))
188
+ .accessibilityIdentifier("bottom_tab_bar")
189
+ }
161
190
  }
162
191
 
163
192
  /// Floating labelled bar for the unfolded portrait display, QR in the middle cell (Figma MT1-M).
@@ -17,39 +17,38 @@ struct DuoScrollGradient: View {
17
17
  let scrollOffset: CGFloat
18
18
  let headerHeight: CGFloat
19
19
  let extended: Bool
20
+ /// Quick return header: the gradient leaves with the content instead of fading.
21
+ var followScroll = false
20
22
 
21
23
  @Environment(\.appTheme) private var theme
22
- @Environment(\.screenLayout) private var layout
23
24
 
24
25
  private var scroll: CGFloat { max(scrollOffset, 0) }
25
26
 
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
27
  var body: some View {
35
28
  Group {
36
- if extended {
37
- // extended squeezes the whole fade into its box and fades out
38
- let progress = min(scroll / extendedCollapseDistance, 1)
29
+ if followScroll {
39
30
  gradient
40
- .frame(height: headerHeight + extendedGradientExtra * (1 - progress))
41
- .opacity(1 - min(scroll / extendedGradientExtra, 1))
31
+ .frame(height: headerHeight + (extended ? extendedGradientExtra : 0))
32
+ .offset(y: -scroll)
42
33
  } else {
43
- // default keeps the off-Duo 375:154 shape, the header clips it, and scrolls away
34
+ // fades out on scroll as the extended header does off Duo; extended also collapses
35
+ let progress = min(scroll / extendedCollapseDistance, 1)
44
36
  gradient
45
- .frame(height: layout.width * 154 / 375)
46
- .frame(height: headerHeight, alignment: .top)
47
- .clipped()
48
- .offset(y: -scroll)
37
+ .frame(height: headerHeight + (extended ? extendedGradientExtra * (1 - progress) : 0))
38
+ .opacity(1 - min(scroll / extendedGradientExtra, 1))
49
39
  }
50
40
  }
51
41
  .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
52
42
  .ignoresSafeArea()
53
43
  .allowsHitTesting(false)
54
44
  }
45
+
46
+ /// The fade spans the gradient box.
47
+ private var gradient: LinearGradient {
48
+ LinearGradient(
49
+ colors: [theme.colors.gradient, theme.colors.gradient.opacity(0)],
50
+ startPoint: .top,
51
+ endPoint: .bottom
52
+ )
53
+ }
55
54
  }
@@ -113,8 +113,9 @@ public struct NavigationHeader: View {
113
113
  }
114
114
  .frame(height: layout.headerBarHeight)
115
115
  .overlay(alignment: .bottom) { VerticalShadow(opacity: opacity) }
116
- // status bar off the top edge: the row spans the whole header from the window top
117
- .padding(.top, min(layout.headerStatusBarHeight - layout.insets.top, 0))
116
+ // the row starts headerStatusBarHeight below the window top: 0 with the status bar off
117
+ // the top edge, lower on the iPhone Duo outer display to center on the camera
118
+ .padding(.top, layout.headerStatusBarHeight - layout.insets.top)
118
119
  }
119
120
  }
120
121
 
@@ -31,6 +31,7 @@ struct ToolkitNavigationHeaderRight: View {
31
31
  @State private var isFavorite: Bool = false
32
32
  @State private var isLoading: Bool = false
33
33
  @Environment(\.applicationEnvironment) private var environment
34
+ @Environment(\.duoBarCompression) private var compression
34
35
 
35
36
  private func apiValue(_ value: Any?) -> Any {
36
37
  value ?? NSNull()
@@ -169,7 +170,38 @@ struct ToolkitNavigationHeaderRight: View {
169
170
  } ?? false
170
171
  }
171
172
 
173
+ /// Back slot, the optional shortcut, then the help/home capsule.
174
+ private var fullToolbarHeight: CGFloat {
175
+ DuoMetrics.controlSize + Spacing.M
176
+ + (shouldShowShortcut ? DuoMetrics.controlSize + Spacing.M : 0)
177
+ + (showHelpCenter ? 2 * DuoMetrics.itemPitch : DuoMetrics.controlSize)
178
+ }
179
+
180
+ @ViewBuilder
172
181
  private var verticalBody: some View {
182
+ Group {
183
+ if compression.toolbarHidden {
184
+ // an open tab rail takes this slot for a moment
185
+ EmptyView()
186
+ } else if compression.toolbarCompressed {
187
+ // HIG "Toolbar compressed": tools fold into one button, distinct from the useMore icon
188
+ SideBarButton(
189
+ icon: "navigation_menu",
190
+ tintColor: tintColor,
191
+ showBadge: showBadge,
192
+ accessibilityId: "btn_navigation_toolbar",
193
+ action: compression.expandToolbar
194
+ )
195
+ } else {
196
+ toolbarColumn
197
+ }
198
+ }
199
+ // full height, so the tab bar compression decision matches this screen's toolkit
200
+ .onAppear { compression.setToolbarHeight(fullToolbarHeight) }
201
+ .onChange(of: fullToolbarHeight) { compression.setToolbarHeight($0) }
202
+ }
203
+
204
+ private var toolbarColumn: some View {
173
205
  let navButtonConfig = getNavigationButtonConfig()
174
206
  return VStack(spacing: Spacing.M) {
175
207
  if shouldShowShortcut {
@@ -24,7 +24,7 @@ struct HeaderSideBar: View {
24
24
  let goBack: (() -> Void)?
25
25
  let toolkit: (data: HeaderRightData?, tintColor: Color?)?
26
26
  let headerColor: HeaderColor
27
- /// What footer / tab bar / home indicator take from the bottom; the column stays above it.
27
+ /// What footer / tab bar / home indicator take from the bottom, gap included; the column stays above it.
28
28
  let bottomInset: CGFloat
29
29
 
30
30
  @Environment(\.screenLayout) private var layout
@@ -33,7 +33,7 @@ struct HeaderSideBar: View {
33
33
  private var isRight: Bool { sideBar.edge == .right }
34
34
 
35
35
  private var top: CGFloat {
36
- let maxY = layout.height - bottomInset - Spacing.XL - columnHeight
36
+ let maxY = layout.height - bottomInset - columnHeight
37
37
  return max(min(sideBar.top, maxY), 0)
38
38
  }
39
39
 
@@ -102,6 +102,9 @@ public struct StackScreen<Content: View>: View {
102
102
  @State private var keyboardHeight: CGFloat = 0
103
103
  @State private var headerRightWidth: CGFloat = 0
104
104
  @State private var isBottomTabRoot: Bool = false
105
+ @State private var headerHide: CGFloat = 0
106
+ @State private var lastScrollOffset: CGFloat = 0
107
+ @State private var headerSettle: Task<Void, Never>?
105
108
 
106
109
  @Environment(\.applicationEnvironment) private var appEnv
107
110
  @Environment(\.screenLayout) private var layout
@@ -140,11 +143,44 @@ public struct StackScreen<Content: View>: View {
140
143
  && inputSearchProps == nil && !headerTransparent && !fullScreenContent
141
144
  }
142
145
 
143
- /// Height the footer, tab bar or home indicator takes from the bottom of the window.
146
+ /// iPhone Duo: the header leaves with the content and slides back in on a reverse scroll
147
+ /// (RN DuoQuickReturnHeader). The side bar is drawn apart from the header, so it stays put.
148
+ private var useQuickReturn: Bool {
149
+ layout.sideBar != nil && scrollable && (useDuoGlass || animatedHeader != nil)
150
+ }
151
+
152
+ private func updateHeaderHide(_ offset: CGFloat) {
153
+ let delta = offset - lastScrollOffset
154
+ lastScrollOffset = offset
155
+ guard useQuickReturn, offset > 0 else {
156
+ if headerHide != 0 { headerHide = 0 }
157
+ return
158
+ }
159
+ headerHide = min(max(headerHide + delta, 0), layout.headerHeight)
160
+ settleHeaderWhenScrollStops()
161
+ }
162
+
163
+ /// A half-shown header settles fully in or out. SwiftUI before iOS 18 reports no scroll
164
+ /// phase, so the scroll counts as ended once the offset stops changing.
165
+ private func settleHeaderWhenScrollStops() {
166
+ headerSettle?.cancel()
167
+ headerSettle = Task { @MainActor in
168
+ try? await Task.sleep(nanoseconds: 150_000_000)
169
+ guard !Task.isCancelled else { return }
170
+ let height = layout.headerHeight
171
+ // within the first header height the header still sits on the content
172
+ guard scrollOffset >= height, headerHide > 0, headerHide < height else { return }
173
+ withAnimation(.easeOut(duration: 0.2)) {
174
+ headerHide = headerHide > height / 2 ? height : 0
175
+ }
176
+ }
177
+ }
178
+
179
+ /// What the footer, tab bar or home indicator takes from the bottom of the window, gap included.
144
180
  private var bottomBarsHeight: CGFloat {
145
- if footer != nil { return footerHeight + NavigationFooter.safeAreaBottom }
146
- if duoTabBarInsets.sideBarReserve > 0 { return duoTabBarInsets.sideBarReserve - Spacing.XL }
147
- return NavigationFooter.navigationBarInset
181
+ if footer != nil { return footerHeight + NavigationFooter.safeAreaBottom + Spacing.XL }
182
+ if duoTabBarInsets.sideBarReserve > 0 { return duoTabBarInsets.sideBarReserve }
183
+ return NavigationFooter.navigationBarInset + Spacing.XL
148
184
  }
149
185
 
150
186
  /// Keyboard frames are in screen coordinates; UIScreen stays on the outer display on iPhone Duo.
@@ -234,6 +270,7 @@ public struct StackScreen<Content: View>: View {
234
270
  }
235
271
  }
236
272
  .ignoresSafeArea(.keyboard, edges: .bottom)
273
+ .onChange(of: scrollOffset) { updateHeaderHide($0) }
237
274
  .onPreferenceChange(HeaderRightWidthKey.self) { width in
238
275
  if headerRightWidth != width { headerRightWidth = width }
239
276
  }
@@ -319,6 +356,7 @@ public struct StackScreen<Content: View>: View {
319
356
  }
320
357
  .edgesIgnoringSafeArea([.top, .horizontal])
321
358
  )
359
+ .offset(y: -headerHide)
322
360
  .zIndex(100)
323
361
  }
324
362
  } else if headerTransparent && fullScreenContent {
@@ -342,7 +380,8 @@ public struct StackScreen<Content: View>: View {
342
380
  DuoScrollGradient(
343
381
  scrollOffset: scrollOffset,
344
382
  headerHeight: layout.headerHeight,
345
- extended: headerType == .extended
383
+ extended: headerType == .extended,
384
+ followScroll: useQuickReturn
346
385
  )
347
386
 
348
387
  StackScreenContent(
@@ -360,6 +399,7 @@ public struct StackScreen<Content: View>: View {
360
399
  .modifier(DuoBarMaterial(shape: Rectangle(), visible: scrollOffset > 0))
361
400
  .edgesIgnoringSafeArea([.top, .horizontal])
362
401
  )
402
+ .offset(y: -headerHide)
363
403
  .zIndex(100)
364
404
  }
365
405
  } else {
@@ -526,7 +566,7 @@ private struct SearchAnimatedHeader: View {
526
566
  let scroll = max(scrollOffset, 0)
527
567
 
528
568
  // Insets are measured from the safe-area top; collapsed, the search is centered in the bar row.
529
- let rowTop = min(layout.headerStatusBarHeight - layout.insets.top, 0)
569
+ let rowTop = layout.headerStatusBarHeight - layout.insets.top
530
570
  let minTop = rowTop + (layout.headerBarHeight - headerHeight) / 2
531
571
  let maxTop = layout.headerHeight - layout.insets.top
532
572
  let minSide = Spacing.M
@@ -4,7 +4,8 @@
4
4
  //
5
5
  // Mirrors RN `app/screens/IPhoneDuo/DuoTabUsage`: glass rail in the Duo side bar, floating
6
6
  // glass bar when unfolded portrait. `useShortcut` gives the tallest side bar header, for the
7
- // overlap review.
7
+ // overlap review; `compressTabBar` folds the tab bar instead of the toolbar when the side bar
8
+ // is short (HIG "Tab bar compressed").
8
9
  //
9
10
 
10
11
  import MoMoUIKits
@@ -15,10 +16,11 @@ private let history = (1...18).map { "Giao dịch #\($0)" }
15
16
 
16
17
  struct DuoTabDemo: View {
17
18
  let useShortcut: Bool
19
+ var compressTabBar = false
18
20
 
19
21
  var body: some View {
20
22
  if #available(iOS 16.0, *) {
21
- DuoTabDemoContent(useShortcut: useShortcut)
23
+ DuoTabDemoContent(useShortcut: useShortcut, compressTabBar: compressTabBar)
22
24
  } else {
23
25
  Text("Requires iOS 16+").padding()
24
26
  }
@@ -28,6 +30,7 @@ struct DuoTabDemo: View {
28
30
  @available(iOS 16.0, *)
29
31
  private struct DuoTabDemoContent: View {
30
32
  let useShortcut: Bool
33
+ let compressTabBar: Bool
31
34
 
32
35
  var body: some View {
33
36
  BottomTab(
@@ -37,7 +40,8 @@ private struct DuoTabDemoContent: View {
37
40
  tab("DuoHistory", label: "Lịch sử GD", icon: "time_clock_reset", title: "Lịch sử giao dịch", badge: "3"),
38
41
  tab("DuoMe", label: "Tôi", icon: "basic_person", title: "Tôi"),
39
42
  ],
40
- floatingButton: BottomTabFloatingButton(icon: "navigation_full_screen", label: "Quét mọi QR", onPress: {})
43
+ floatingButton: BottomTabFloatingButton(icon: "navigation_full_screen", label: "Quét mọi QR", onPress: {}),
44
+ compressionBehavior: compressTabBar ? .tabBar : .toolbar
41
45
  )
42
46
  }
43
47
 
@@ -53,6 +53,9 @@ private struct IPhoneDuoDemoContent: View {
53
53
  demoButton("Glass tab bar + useShortcut") {
54
54
  navigator.push({ DuoTabDemo(useShortcut: true) }, options: tabOptions)
55
55
  }
56
+ demoButton("Glass tab bar + compress tab bar") {
57
+ navigator.push({ DuoTabDemo(useShortcut: true, compressTabBar: true) }, options: tabOptions)
58
+ }
56
59
  demoButton("Bottom sheet: iPhone Duo safe area") {
57
60
  navigator.showBottomSheet({ BottomSheetSafeArea() }, isSurface: true, header: .title("Safe area"))
58
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.167.1-duo.7-debug",
3
+ "version": "0.167.1-duo.8-debug",
4
4
  "private": false,
5
5
  "dependeNavigationncies": {},
6
6
  "devDependencies": {},