@onekeyfe/react-native-pager-view 6.7.0-alpha.354 → 6.7.0-alpha.359

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.
@@ -20,6 +20,117 @@ import kotlin.math.roundToInt
20
20
  class CollapsiblePagerHitTestActivity : Activity()
21
21
 
22
22
  class CollapsiblePagerTabReservationTest : InstrumentationTestCase() {
23
+ fun testSegmentedNativeTabsTrackFractionalProgressAndReset() {
24
+ val context = instrumentation.targetContext
25
+ assertTrue(context.applicationInfo.flags and ApplicationInfo.FLAG_SUPPORTS_RTL != 0)
26
+ val activity = instrumentation.startActivitySync(
27
+ Intent(context, CollapsiblePagerHitTestActivity::class.java)
28
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
29
+ ) as CollapsiblePagerHitTestActivity
30
+ val density = context.resources.displayMetrics.density
31
+ fun dp(value: Int) = (value * density).roundToInt()
32
+ lateinit var tab: CollapsiblePagerNativeTabBarView
33
+ lateinit var row: HorizontalScrollView
34
+ lateinit var content: ViewGroup
35
+ lateinit var track: View
36
+ lateinit var thumb: View
37
+ lateinit var buttons: List<TextView>
38
+ fun layout(width: Int = 320, height: Int = 56) {
39
+ tab.measure(View.MeasureSpec.makeMeasureSpec(dp(width), View.MeasureSpec.EXACTLY),
40
+ View.MeasureSpec.makeMeasureSpec(dp(height), View.MeasureSpec.EXACTLY))
41
+ tab.layout(0, 0, dp(width), dp(height))
42
+ }
43
+ try {
44
+ instrumentation.runOnMainSync {
45
+ tab = CollapsiblePagerNativeTabBarView(context)
46
+ activity.setContentView(tab)
47
+ val presses = mutableListOf<String>()
48
+ tab.onItemPress = { _, key -> presses.add(key) }
49
+ tab.updateItemsJSON("""[{"key":"recent","title":"Recents"},""" +
50
+ """{"key":"account","title":"Accounts"},""" +
51
+ """{"key":"book","title":"Address Book"}]""")
52
+ tab.updateStyle(56.0, "segmented", 20.0, 12.0, 0.0, 14.0, null, 2.0, 0.0)
53
+ tab.updateColors(Color.LTGRAY, Color.BLACK, Color.GRAY, Color.WHITE)
54
+ layout()
55
+ row = tab.getChildAt(0) as HorizontalScrollView
56
+ content = row.getChildAt(0) as ViewGroup
57
+ track = content.getChildAt(0)
58
+ thumb = content.getChildAt(1)
59
+ buttons = (0 until content.childCount).map(content::getChildAt).filterIsInstance<TextView>()
60
+ assertEquals(3, buttons.size)
61
+ assertEquals(dp(20), track.left)
62
+ assertTrue(kotlin.math.abs(dp(32) - track.height) <= 1)
63
+ assertEquals(0, row.scrollX)
64
+ assertTrue(kotlin.math.abs(buttons[0].width - buttons[1].width) <= 1)
65
+ tab.setProgress(0f)
66
+ val firstX = thumb.left
67
+ tab.setProgress(1f)
68
+ val secondX = thumb.left
69
+ tab.setProgress(0.25f)
70
+ assertTrue(kotlin.math.abs(thumb.left - (firstX + (secondX - firstX) * 0.25f)) <= 1)
71
+ tab.setProgress(0.5f)
72
+ assertTrue(kotlin.math.abs(thumb.left - (firstX + secondX) / 2f) <= 1)
73
+ tab.setProgress(0f)
74
+ assertEquals(firstX, thumb.left)
75
+ buttons[1].performClick()
76
+ assertEquals(listOf("account"), presses)
77
+
78
+ tab.layoutDirection = View.LAYOUT_DIRECTION_RTL
79
+ }
80
+ instrumentation.waitForIdleSync()
81
+ instrumentation.runOnMainSync {
82
+ layout()
83
+ assertEquals(View.LAYOUT_DIRECTION_RTL, row.layoutDirection)
84
+ val rtlFirst = buttons[0].layoutParams as android.widget.FrameLayout.LayoutParams
85
+ val rtlSecond = buttons[1].layoutParams as android.widget.FrameLayout.LayoutParams
86
+ assertTrue(rtlFirst.leftMargin > rtlSecond.leftMargin)
87
+ val rtlGeometry = "rowDir=${row.layoutDirection} contentDir=${content.layoutDirection} " +
88
+ buttons.mapIndexed { index, button ->
89
+ val params = button.layoutParams as android.widget.FrameLayout.LayoutParams
90
+ "$index:${button.left}-${button.right} margin=${params.leftMargin}/${params.rightMargin} gravity=${params.gravity}"
91
+ }.joinToString(" ")
92
+ assertTrue(rtlGeometry, buttons[0].left >= buttons[1].right)
93
+ assertEquals(dp(20), track.left)
94
+ tab.setProgress(0f)
95
+ val rtlFirstX = thumb.left
96
+ tab.setProgress(1f)
97
+ val rtlSecondX = thumb.left
98
+ assertTrue(rtlFirstX > rtlSecondX)
99
+ tab.setProgress(0.5f)
100
+ assertTrue(kotlin.math.abs(thumb.left - (rtlFirstX + rtlSecondX) / 2f) <= 1)
101
+ tab.layoutDirection = View.LAYOUT_DIRECTION_LTR
102
+ }
103
+ instrumentation.waitForIdleSync()
104
+ instrumentation.runOnMainSync {
105
+ layout()
106
+ assertTrue(buttons[0].right <= buttons[1].left)
107
+ tab.updateItemsJSON("""[{"key":"recent","title":"Recents (8)"},""" +
108
+ """{"key":"account","title":"Accounts"}]""")
109
+ layout()
110
+ val twoButtons = (0 until content.childCount).map(content::getChildAt).filterIsInstance<TextView>()
111
+ assertEquals(2, twoButtons.size)
112
+ assertTrue(kotlin.math.abs(twoButtons[0].width - track.width / 2) <= 1)
113
+ layout(width = 30)
114
+ assertEquals(dp(15), track.left)
115
+ assertEquals(0, track.width)
116
+ tab.updateStyle(56.0, "segmented", 20.0, 40.0, 0.0, 14.0, null, 2.0, 0.0)
117
+ layout(width = 30)
118
+ assertTrue(kotlin.math.abs(dp(28) - track.top) <= 1)
119
+ assertEquals(0, track.height)
120
+ assertEquals(View.GONE, thumb.visibility)
121
+ tab.updateItemsJSON("[]")
122
+ assertEquals(View.GONE, tab.visibility)
123
+ tab.updateItemsJSON("""[{"key":"recent","title":"Recents"}]""")
124
+ tab.updateStyle(44.0, "underline", 20.0, 0.0, 8.0, 16.0, null, 2.0, 0.0)
125
+ layout(height = 44)
126
+ assertEquals(View.GONE, track.visibility)
127
+ assertEquals(dp(2), thumb.height)
128
+ }
129
+ } finally {
130
+ instrumentation.runOnMainSync { activity.finish() }
131
+ }
132
+ }
133
+
23
134
  fun testTabViewportToolbarHitAndResetInBothDirections() {
24
135
  val context = instrumentation.targetContext
25
136
  assertTrue(context.applicationInfo.flags and ApplicationInfo.FLAG_SUPPORTS_RTL != 0)
@@ -293,6 +293,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
293
293
  var onNativeSubHeaderPress: ((Int, String) -> Unit)? = null
294
294
 
295
295
  var nativeTabBarHeight = 44.0
296
+ var nativeTabBarVariant = "underline"
296
297
  var nativeTabBarTrailingReservedWidth = 0.0
297
298
  set(value) {
298
299
  field = if (value.isFinite() && value >= 0.0) value else 0.0
@@ -308,6 +309,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
308
309
  } else 0
309
310
  }
310
311
  var nativeTabBarContentPaddingHorizontal = 20.0
312
+ var nativeTabBarContentPaddingVertical = 0.0
311
313
  var nativeTabBarItemSpacing = 8.0
312
314
  var nativeTabBarFontSize = 16.0
313
315
  var nativeTabBarFontFamily: String? = null
@@ -430,7 +432,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
430
432
  fun applyNativeHeaderStyle() {
431
433
  nativeTabBarView.updateStyle(
432
434
  nativeTabBarHeight,
435
+ nativeTabBarVariant,
433
436
  nativeTabBarContentPaddingHorizontal,
437
+ nativeTabBarContentPaddingVertical,
434
438
  nativeTabBarItemSpacing,
435
439
  nativeTabBarFontSize,
436
440
  nativeTabBarFontFamily,
@@ -65,7 +65,11 @@ private class CollapsiblePagerHorizontalItemsView(
65
65
  ) : HorizontalScrollView(context) {
66
66
  private val density = resources.displayMetrics.density
67
67
  private val content = FrameLayout(context)
68
+ private val track = View(context)
68
69
  private val indicator = View(context)
70
+ private val trackBackground = GradientDrawable().apply {
71
+ shape = GradientDrawable.RECTANGLE
72
+ }
69
73
  private val indicatorBackground = GradientDrawable().apply {
70
74
  shape = GradientDrawable.RECTANGLE
71
75
  }
@@ -75,6 +79,8 @@ private class CollapsiblePagerHorizontalItemsView(
75
79
  private var selectedKey = ""
76
80
  private var rowHeightPx = dp(if (showsProgressIndicator) 44.0 else 42.0)
77
81
  private var contentPaddingPx = dp(20.0)
82
+ private var contentPaddingVerticalPx = 0
83
+ private var segmented = false
78
84
  private var itemSpacingPx = dp(8.0)
79
85
  private var fontSize = if (showsProgressIndicator) 16.0 else 14.0
80
86
  private var fontFamily: String? = null
@@ -96,7 +102,9 @@ private class CollapsiblePagerHorizontalItemsView(
96
102
  isFillViewport = true
97
103
  overScrollMode = OVER_SCROLL_NEVER
98
104
  clipToPadding = false
105
+ track.background = trackBackground
99
106
  indicator.background = indicatorBackground
107
+ content.addView(track, FrameLayout.LayoutParams(0, 0))
100
108
  content.addView(indicator, FrameLayout.LayoutParams(0, 0))
101
109
  addView(
102
110
  content,
@@ -149,17 +157,24 @@ private class CollapsiblePagerHorizontalItemsView(
149
157
  fontFamily: String?,
150
158
  indicatorHeight: Double = 0.0,
151
159
  indicatorBottom: Double = 0.0,
160
+ variant: String = "underline",
161
+ contentPaddingVertical: Double = 0.0,
152
162
  ) {
153
163
  rowHeightPx = max(1, dp(rowHeight))
154
164
  contentPaddingPx = max(0, dp(contentPadding))
165
+ contentPaddingVerticalPx = max(0, dp(contentPaddingVertical))
166
+ val wasSegmented = segmented
167
+ segmented = showsProgressIndicator && variant == "segmented"
155
168
  itemSpacingPx = max(0, dp(itemSpacing))
156
169
  this.fontSize = max(1.0, fontSize)
157
170
  this.fontFamily = fontFamily
158
171
  if (showsProgressIndicator) {
159
172
  indicatorHeightPx = max(0, dp(indicatorHeight))
160
173
  indicatorBottomPx = max(0, dp(indicatorBottom))
161
- indicatorBackground.cornerRadius = indicatorHeightPx / 2f
174
+ indicatorBackground.cornerRadius = if (segmented) dp(14.0).toFloat() else indicatorHeightPx / 2f
162
175
  }
176
+ track.visibility = if (segmented) VISIBLE else GONE
177
+ if (segmented && !wasSegmented) scrollTo(0, 0)
163
178
  applyTypeface()
164
179
  requestLayout()
165
180
  }
@@ -171,8 +186,9 @@ private class CollapsiblePagerHorizontalItemsView(
171
186
  selectedBackgroundColor: Int,
172
187
  indicatorColor: Int,
173
188
  ) {
174
- setBackgroundColor(backgroundColor)
175
- content.setBackgroundColor(backgroundColor)
189
+ setBackgroundColor(if (segmented) Color.TRANSPARENT else backgroundColor)
190
+ content.setBackgroundColor(if (segmented) Color.TRANSPARENT else backgroundColor)
191
+ trackBackground.setColor(backgroundColor)
176
192
  this.activeTextColor = activeTextColor
177
193
  this.inactiveTextColor = inactiveTextColor
178
194
  this.selectedBackgroundColor = selectedBackgroundColor
@@ -213,6 +229,46 @@ private class CollapsiblePagerHorizontalItemsView(
213
229
  }
214
230
  applyTypeface()
215
231
  itemFrames.clear()
232
+ if (segmented) {
233
+ val actualPaddingHorizontal = min(contentPaddingPx, width / 2)
234
+ val actualPaddingVertical = min(contentPaddingVerticalPx, height / 2)
235
+ val trackLeft = actualPaddingHorizontal
236
+ val trackRight = max(trackLeft, width - contentPaddingPx)
237
+ val trackTop = actualPaddingVertical
238
+ val trackBottom = max(trackTop, height - contentPaddingVerticalPx)
239
+ val trackWidth = trackRight - trackLeft
240
+ val trackHeight = trackBottom - trackTop
241
+ trackBackground.cornerRadius = trackHeight / 2f
242
+ indicatorBackground.cornerRadius = max(0, trackHeight - dp(4.0)) / 2f
243
+ track.layoutParams = FrameLayout.LayoutParams(trackWidth, trackHeight).apply {
244
+ gravity = Gravity.TOP or Gravity.LEFT
245
+ leftMargin = trackLeft
246
+ topMargin = trackTop
247
+ }
248
+ buttons.forEachIndexed { index, button ->
249
+ val count = buttons.size
250
+ val first = trackLeft + trackWidth * index / count
251
+ val last = trackLeft + trackWidth * (index + 1) / count
252
+ val start = if (layoutDirection == View.LAYOUT_DIRECTION_RTL) trackRight - (last - trackLeft) else first
253
+ val end = if (layoutDirection == View.LAYOUT_DIRECTION_RTL) trackRight - (first - trackLeft) else last
254
+ val buttonWidth = max(0, end - start)
255
+ val thumbInset = dp(2.0)
256
+ itemFrames.add(intArrayOf(start, buttonWidth, start + thumbInset, max(0, buttonWidth - 2 * thumbInset)))
257
+ button.layoutParams = FrameLayout.LayoutParams(buttonWidth, trackHeight).apply {
258
+ gravity = Gravity.TOP or Gravity.LEFT
259
+ leftMargin = start
260
+ topMargin = trackTop
261
+ }
262
+ }
263
+ content.layoutParams = LayoutParams(width, height)
264
+ content.measure(
265
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
266
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
267
+ )
268
+ setMeasuredDimension(width, height)
269
+ return
270
+ }
271
+ track.layoutParams = FrameLayout.LayoutParams(0, 0)
216
272
  var x = contentPaddingPx
217
273
  buttons.forEachIndexed { index, button ->
218
274
  val textWidth = ceil(button.paint.measureText(button.text.toString()).toDouble()).toInt()
@@ -260,6 +316,9 @@ private class CollapsiblePagerHorizontalItemsView(
260
316
  return super.onTouchEvent(event)
261
317
  }
262
318
 
319
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean =
320
+ if (segmented) false else super.onInterceptTouchEvent(event)
321
+
263
322
  private fun updatePresentation() {
264
323
  if (buttons.isEmpty() || itemFrames.size != buttons.size) return
265
324
  val selectedIndex: Int
@@ -275,14 +334,16 @@ private class CollapsiblePagerHorizontalItemsView(
275
334
  val interpolatedIndicatorWidth = from[3] + (to[3] - from[3]) * fraction
276
335
  val indicatorX = interpolatedIndicatorX.roundToInt()
277
336
  val indicatorWidth = interpolatedIndicatorWidth.roundToInt()
278
- val indicatorTop = max(0, measuredHeight - indicatorBottomPx - indicatorHeightPx)
337
+ val actualPaddingVertical = min(contentPaddingVerticalPx, measuredHeight / 2)
338
+ val indicatorHeight = if (segmented) max(0, measuredHeight - 2 * actualPaddingVertical - dp(4.0)) else indicatorHeightPx
339
+ val indicatorTop = if (segmented) actualPaddingVertical + dp(2.0) else max(0, measuredHeight - indicatorBottomPx - indicatorHeightPx)
279
340
  indicator.layout(
280
341
  indicatorX,
281
342
  indicatorTop,
282
343
  indicatorX + indicatorWidth,
283
- indicatorTop + indicatorHeightPx,
344
+ indicatorTop + indicatorHeight,
284
345
  )
285
- indicator.visibility = if (indicatorHeightPx > 0) VISIBLE else GONE
346
+ indicator.visibility = if (indicatorHeight > 0) VISIBLE else GONE
286
347
  scrollTargetCenterX = interpolatedIndicatorX + interpolatedIndicatorWidth / 2f
287
348
  selectedIndex = clamped.roundToInt()
288
349
  buttons.forEachIndexed { index, button ->
@@ -304,7 +365,7 @@ private class CollapsiblePagerHorizontalItemsView(
304
365
  }
305
366
  }
306
367
 
307
- if (centerSelectionWhenIdle && !isTouchDragging && width > 0 && selectedIndex in itemFrames.indices) {
368
+ if (centerSelectionWhenIdle && !segmented && !isTouchDragging && width > 0 && selectedIndex in itemFrames.indices) {
308
369
  val desired = (scrollTargetCenterX - width / 2f).roundToInt()
309
370
  .coerceIn(0, max(0, content.measuredWidth - width))
310
371
  scrollTo(desired, 0)
@@ -335,7 +396,9 @@ internal class CollapsiblePagerNativeTabBarView(context: Context) : FrameLayout(
335
396
 
336
397
  fun updateStyle(
337
398
  height: Double,
399
+ variant: String,
338
400
  contentPadding: Double,
401
+ contentPaddingVertical: Double,
339
402
  itemSpacing: Double,
340
403
  fontSize: Double,
341
404
  fontFamily: String?,
@@ -351,6 +414,8 @@ internal class CollapsiblePagerNativeTabBarView(context: Context) : FrameLayout(
351
414
  fontFamily,
352
415
  indicatorHeight,
353
416
  indicatorBottom,
417
+ variant,
418
+ contentPaddingVertical,
354
419
  )
355
420
  requestLayout()
356
421
  }
@@ -239,6 +239,13 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
239
239
  view.applyNativeHeaderStyle()
240
240
  }
241
241
 
242
+ @ReactProp(name = "nativeTabBarVariant")
243
+ override fun setNativeTabBarVariant(view: CollapsiblePagerHost?, value: String?) {
244
+ view ?: return
245
+ view.nativeTabBarVariant = if (value == "segmented") "segmented" else "underline"
246
+ view.applyNativeHeaderStyle()
247
+ }
248
+
242
249
  @ReactProp(name = "nativeTabBarTrailingReservedWidth", defaultDouble = 0.0)
243
250
  override fun setNativeTabBarTrailingReservedWidth(view: CollapsiblePagerHost?, value: Double) {
244
251
  view?.nativeTabBarTrailingReservedWidth = value
@@ -254,6 +261,13 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
254
261
  view.applyNativeHeaderStyle()
255
262
  }
256
263
 
264
+ @ReactProp(name = "nativeTabBarContentPaddingVertical")
265
+ override fun setNativeTabBarContentPaddingVertical(view: CollapsiblePagerHost?, value: Double) {
266
+ view ?: return
267
+ view.nativeTabBarContentPaddingVertical = if (value.isFinite()) value.coerceAtLeast(0.0) else 0.0
268
+ view.applyNativeHeaderStyle()
269
+ }
270
+
257
271
  @ReactProp(name = "nativeTabBarItemSpacing")
258
272
  override fun setNativeTabBarItemSpacing(view: CollapsiblePagerHost?, value: Double) {
259
273
  view ?: return
package/docs/SPEC.md CHANGED
@@ -496,6 +496,40 @@ The attached UIKit regression intentionally constrains the real pager pan to two
496
496
  required touches to prove the no-pan path; it is distinct from natural Home OS
497
497
  input, coalesced-event delivery, and physical-device acceptance.
498
498
 
499
+ ## Native segmented tab bar
500
+
501
+ Status: implemented locally. Focused production-class iOS XCTest and Android
502
+ instrumentation checks pass; consuming App interaction acceptance is separate.
503
+ `nativeTabBar.style.variant` accepts `"underline"` (the default) or
504
+ `"segmented"` on iOS and Android. The optional
505
+ `nativeTabBar.style.contentPaddingVertical` is a nonnegative logical inset,
506
+ defaulting to zero. Omitting both fields preserves the existing content-width,
507
+ horizontally scrollable underline bar. Web ignores `nativeTabBar` and retains
508
+ its caller-rendered sticky header.
509
+
510
+ In segmented mode, the existing `height` is the entire tab slot. The track
511
+ fills the space inside `contentPaddingHorizontal` and
512
+ `contentPaddingVertical`, uses `backgroundColor`, and has a fully rounded
513
+ outline. Its visible items divide the track into equal-width segments in their
514
+ current order, including after item insertion, removal, relabeling, or resize.
515
+ The thumb uses `indicatorColor`, is inset two logical points from its segment,
516
+ and is fully rounded. Segment titles use the existing font and active/inactive
517
+ text colors, remain single-line with truncation, and retain their existing
518
+ accessibility labels, test IDs, and native press event keys. The segmented bar
519
+ does not scroll horizontally; `itemSpacing`, `indicatorHeight`, and
520
+ `indicatorBottom` apply only to underline mode. The pager's native fractional
521
+ page progress moves the thumb continuously between equal-width segments and
522
+ interpolates text emphasis without per-frame JavaScript updates. It settles at
523
+ the actual selected page after a completed or cancelled gesture. RTL reverses
524
+ physical segment order without changing logical page keys or indices.
525
+
526
+ Switching variants or removing the native tab bar clears the previous visual
527
+ state. The existing native press command, page callbacks, retention, vertical
528
+ header geometry, and Web behavior remain unchanged. Acceptance requires
529
+ focused native geometry/progress/press checks on iOS and Android, including
530
+ two or three segments, dynamic labels/items, RTL, narrow widths, and variant
531
+ switching. Type forwarding alone does not prove rendered behavior.
532
+
499
533
  ## Native tab-press animation default
500
534
 
501
535
  Behavior change: previously an omitted `nativeTabPressAnimationEnabled` animated
@@ -369,7 +369,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
369
369
  @property (nonatomic, copy) RNCCollapsiblePagerNativeTabPressHandler onTabPress;
370
370
  - (void)updateItemsJSON:(NSString *)itemsJSON;
371
371
  - (void)updateStyleWithHeight:(CGFloat)height
372
+ variant:(NSString *)variant
372
373
  contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
374
+ contentPaddingVertical:(CGFloat)contentPaddingVertical
373
375
  itemSpacing:(CGFloat)itemSpacing
374
376
  fontSize:(CGFloat)fontSize
375
377
  fontFamily:(NSString *)fontFamily
@@ -386,12 +388,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
386
388
 
387
389
  @implementation RNCCollapsiblePagerNativeTabBarView {
388
390
  RNCCollapsiblePagerHorizontalScrollView *_scrollView;
391
+ UIView *_trackView;
389
392
  UIView *_indicatorView;
390
393
  NSArray<NSDictionary *> *_items;
391
394
  NSMutableArray<UIButton *> *_buttons;
392
395
  NSMutableArray<NSValue *> *_indicatorFrames;
393
396
  CGFloat _barHeight;
394
397
  CGFloat _contentPaddingHorizontal;
398
+ CGFloat _contentPaddingVertical;
399
+ BOOL _segmented;
395
400
  CGFloat _itemSpacing;
396
401
  CGFloat _fontSize;
397
402
  NSString *_fontFamily;
@@ -414,6 +419,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
414
419
  _indicatorFrames = [NSMutableArray new];
415
420
  _barHeight = 44;
416
421
  _contentPaddingHorizontal = 20;
422
+ _contentPaddingVertical = 0;
423
+ _segmented = NO;
417
424
  _itemSpacing = 8;
418
425
  _fontSize = 16;
419
426
  _activeTextColor = UIColor.labelColor;
@@ -435,6 +442,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
435
442
  _scrollView.delegate = self;
436
443
  [self addSubview:_scrollView];
437
444
 
445
+ _trackView = [UIView new];
446
+ _trackView.userInteractionEnabled = NO;
447
+ [_scrollView addSubview:_trackView];
448
+
438
449
  _indicatorView = [UIView new];
439
450
  _indicatorView.userInteractionEnabled = NO;
440
451
  [_scrollView addSubview:_indicatorView];
@@ -489,7 +500,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
489
500
  }
490
501
 
491
502
  - (void)updateStyleWithHeight:(CGFloat)height
503
+ variant:(NSString *)variant
492
504
  contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
505
+ contentPaddingVertical:(CGFloat)contentPaddingVertical
493
506
  itemSpacing:(CGFloat)itemSpacing
494
507
  fontSize:(CGFloat)fontSize
495
508
  fontFamily:(NSString *)fontFamily
@@ -501,19 +514,26 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
501
514
  indicatorBottom:(CGFloat)indicatorBottom
502
515
  {
503
516
  _barHeight = MAX(1, height);
517
+ BOOL wasSegmented = _segmented;
518
+ _segmented = [variant isEqualToString:@"segmented"];
504
519
  _contentPaddingHorizontal = MAX(0, contentPaddingHorizontal);
520
+ _contentPaddingVertical = MAX(0, contentPaddingVertical);
505
521
  _itemSpacing = MAX(0, itemSpacing);
506
522
  _fontSize = MAX(1, fontSize);
507
523
  _fontFamily = [fontFamily copy];
508
- self.backgroundColor = backgroundColor ?: UIColor.clearColor;
524
+ self.backgroundColor = _segmented ? UIColor.clearColor : (backgroundColor ?: UIColor.clearColor);
509
525
  _scrollView.backgroundColor = self.backgroundColor;
526
+ _scrollView.scrollEnabled = !_segmented;
527
+ if (_segmented && !wasSegmented) [_scrollView setContentOffset:CGPointZero animated:NO];
528
+ _trackView.hidden = !_segmented;
529
+ _trackView.backgroundColor = backgroundColor ?: UIColor.clearColor;
510
530
  _activeTextColor = activeTextColor ?: UIColor.labelColor;
511
531
  _inactiveTextColor = inactiveTextColor ?: UIColor.secondaryLabelColor;
512
532
  _indicatorColor = indicatorColor ?: _activeTextColor;
513
533
  _indicatorHeight = MAX(0, indicatorHeight);
514
534
  _indicatorBottom = MAX(0, indicatorBottom);
515
535
  _indicatorView.backgroundColor = _indicatorColor;
516
- _indicatorView.layer.cornerRadius = _indicatorHeight / 2;
536
+ _indicatorView.layer.cornerRadius = _segmented ? 0 : _indicatorHeight / 2;
517
537
  for (UIButton *button in _buttons) button.titleLabel.font = self.tabFont;
518
538
  [self setNeedsLayout];
519
539
  }
@@ -572,6 +592,33 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
572
592
  {
573
593
  [super layoutSubviews];
574
594
  _scrollView.frame = self.bounds;
595
+ if (_segmented) {
596
+ CGFloat width = CGRectGetWidth(self.bounds);
597
+ CGFloat height = CGRectGetHeight(self.bounds);
598
+ CGFloat insetX = MIN(_contentPaddingHorizontal, width / 2);
599
+ CGFloat insetY = MIN(_contentPaddingVertical, height / 2);
600
+ CGRect trackFrame = CGRectMake(insetX, insetY,
601
+ MAX(0, width - 2 * insetX), MAX(0, height - 2 * insetY));
602
+ _trackView.frame = trackFrame;
603
+ _trackView.layer.cornerRadius = CGRectGetHeight(trackFrame) / 2;
604
+ _scrollView.contentSize = self.bounds.size;
605
+ [_indicatorFrames removeAllObjects];
606
+ NSUInteger count = _buttons.count;
607
+ [_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
608
+ CGFloat first = CGRectGetMinX(trackFrame) + CGRectGetWidth(trackFrame) * index / count;
609
+ CGFloat last = CGRectGetMinX(trackFrame) + CGRectGetWidth(trackFrame) * (index + 1) / count;
610
+ CGFloat x = self->_leftToRight ? first : CGRectGetMaxX(trackFrame) - (last - CGRectGetMinX(trackFrame));
611
+ CGFloat end = self->_leftToRight ? last : CGRectGetMaxX(trackFrame) - (first - CGRectGetMinX(trackFrame));
612
+ button.frame = CGRectMake(x, CGRectGetMinY(trackFrame), MAX(0, end - x), CGRectGetHeight(trackFrame));
613
+ CGRect thumbFrame = CGRectMake(x + 2, CGRectGetMinY(trackFrame) + 2,
614
+ MAX(0, CGRectGetWidth(button.frame) - 4), MAX(0, CGRectGetHeight(trackFrame) - 4));
615
+ [self->_indicatorFrames addObject:[NSValue valueWithCGRect:thumbFrame]];
616
+ }];
617
+ _indicatorView.layer.cornerRadius = MAX(0, CGRectGetHeight(trackFrame) - 4) / 2;
618
+ [self updatePresentation];
619
+ return;
620
+ }
621
+ _trackView.frame = CGRectZero;
575
622
  UIFont *font = self.tabFont;
576
623
  NSMutableArray<NSNumber *> *widths = [NSMutableArray arrayWithCapacity:_buttons.count];
577
624
  CGFloat itemsWidth = 0;
@@ -635,10 +682,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
635
682
  CGRectGetMinX(fromFrame) + (CGRectGetMinX(toFrame) - CGRectGetMinX(fromFrame)) * fraction,
636
683
  CGRectGetMinY(fromFrame),
637
684
  CGRectGetWidth(fromFrame) + (CGRectGetWidth(toFrame) - CGRectGetWidth(fromFrame)) * fraction,
638
- _indicatorHeight
685
+ _segmented ? CGRectGetHeight(fromFrame) : _indicatorHeight
639
686
  );
640
687
  _indicatorView.frame = indicatorFrame;
641
- _indicatorView.hidden = _indicatorHeight <= 0;
688
+ _indicatorView.hidden = CGRectGetHeight(indicatorFrame) <= 0;
642
689
 
643
690
  NSInteger selectedIndex = (NSInteger)round(progress);
644
691
  [_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
@@ -655,7 +702,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
655
702
  : UIAccessibilityTraitButton;
656
703
  }];
657
704
 
658
- if (_shouldCenterSelectedItem && !_scrollView.tracking && !_scrollView.dragging &&
705
+ if (_shouldCenterSelectedItem && !_segmented && !_scrollView.tracking && !_scrollView.dragging &&
659
706
  !_scrollView.decelerating && CGRectGetWidth(_scrollView.bounds) > 0) {
660
707
  CGFloat maximumOffset = MAX(0, _scrollView.contentSize.width - CGRectGetWidth(_scrollView.bounds));
661
708
  CGFloat desiredOffset = RNCClamp(
@@ -3457,7 +3504,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3457
3504
  }
3458
3505
  if (_needsPropsReapply ||
3459
3506
  oldViewProps.nativeTabBarHeight != newViewProps.nativeTabBarHeight ||
3507
+ oldViewProps.nativeTabBarVariant != newViewProps.nativeTabBarVariant ||
3460
3508
  oldViewProps.nativeTabBarContentPaddingHorizontal != newViewProps.nativeTabBarContentPaddingHorizontal ||
3509
+ oldViewProps.nativeTabBarContentPaddingVertical != newViewProps.nativeTabBarContentPaddingVertical ||
3461
3510
  oldViewProps.nativeTabBarItemSpacing != newViewProps.nativeTabBarItemSpacing ||
3462
3511
  oldViewProps.nativeTabBarFontSize != newViewProps.nativeTabBarFontSize ||
3463
3512
  oldViewProps.nativeTabBarFontFamily != newViewProps.nativeTabBarFontFamily ||
@@ -3474,7 +3523,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
3474
3523
  NSString *fontFamily = RCTNSStringFromString(newViewProps.nativeTabBarFontFamily);
3475
3524
  [_nativeTabBarView
3476
3525
  updateStyleWithHeight:_nativeTabBarHeight
3526
+ variant:RCTNSStringFromString(newViewProps.nativeTabBarVariant)
3477
3527
  contentPaddingHorizontal:MAX(0, newViewProps.nativeTabBarContentPaddingHorizontal)
3528
+ contentPaddingVertical:MAX(0, newViewProps.nativeTabBarContentPaddingVertical)
3478
3529
  itemSpacing:MAX(0, newViewProps.nativeTabBarItemSpacing)
3479
3530
  fontSize:newViewProps.nativeTabBarFontSize > 0 ? newViewProps.nativeTabBarFontSize : 16
3480
3531
  fontFamily:fontFamily
@@ -16,6 +16,19 @@ using namespace facebook::react;
16
16
 
17
17
  @interface UIView (NativeTabReservationTesting)
18
18
  - (void)updateItemsJSON:(NSString *)itemsJSON;
19
+ - (void)updateStyleWithHeight:(CGFloat)height
20
+ variant:(NSString *)variant
21
+ contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
22
+ contentPaddingVertical:(CGFloat)contentPaddingVertical
23
+ itemSpacing:(CGFloat)itemSpacing
24
+ fontSize:(CGFloat)fontSize
25
+ fontFamily:(NSString *)fontFamily
26
+ backgroundColor:(UIColor *)backgroundColor
27
+ activeTextColor:(UIColor *)activeTextColor
28
+ inactiveTextColor:(UIColor *)inactiveTextColor
29
+ indicatorColor:(UIColor *)indicatorColor
30
+ indicatorHeight:(CGFloat)indicatorHeight
31
+ indicatorBottom:(CGFloat)indicatorBottom;
19
32
  - (void)setProgress:(CGFloat)progress;
20
33
  - (void)setLeftToRight:(BOOL)leftToRight;
21
34
  @end
@@ -143,6 +156,84 @@ using namespace facebook::react;
143
156
  [[self.pager valueForKey:@"nativeTabBarTrailingReservedWidth"] doubleValue], 0, 0.001);
144
157
  }
145
158
 
159
+ - (void)testSegmentedNativeTabsFollowFractionalProgressAndResetToUnderline
160
+ {
161
+ UIView *tabBar = [self.pager valueForKey:@"nativeTabBarView"];
162
+ [tabBar updateItemsJSON:@"[{\"key\":\"recent\",\"title\":\"Recents\"},"
163
+ @"{\"key\":\"account\",\"title\":\"Accounts\"},"
164
+ @"{\"key\":\"book\",\"title\":\"Address Book\"}]"];
165
+ [tabBar updateStyleWithHeight:56 variant:@"segmented"
166
+ contentPaddingHorizontal:20 contentPaddingVertical:12 itemSpacing:0
167
+ fontSize:14 fontFamily:nil backgroundColor:UIColor.lightGrayColor
168
+ activeTextColor:UIColor.blackColor inactiveTextColor:UIColor.grayColor
169
+ indicatorColor:UIColor.whiteColor indicatorHeight:2 indicatorBottom:0];
170
+ tabBar.frame = CGRectMake(0, 0, 320, 56);
171
+ [tabBar layoutIfNeeded];
172
+ UIScrollView *scroll = (UIScrollView *)tabBar.subviews.firstObject;
173
+ UIView *track = scroll.subviews.firstObject;
174
+ UIView *thumb = [tabBar valueForKey:@"indicatorView"];
175
+ NSArray<UIButton *> *buttons = [scroll.subviews filteredArrayUsingPredicate:
176
+ [NSPredicate predicateWithBlock:^BOOL(UIView *view, NSDictionary *bindings) {
177
+ return [view isKindOfClass:UIButton.class];
178
+ }]];
179
+ XCTAssertEqual(buttons.count, 3);
180
+ XCTAssertFalse(scroll.scrollEnabled);
181
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(track.frame), 20, 0.01);
182
+ XCTAssertEqualWithAccuracy(CGRectGetHeight(track.frame), 32, 0.01);
183
+ XCTAssertEqualWithAccuracy(CGRectGetWidth(buttons[0].frame),
184
+ CGRectGetWidth(buttons[1].frame), 0.01);
185
+ [tabBar setProgress:0];
186
+ CGFloat firstX = CGRectGetMinX(thumb.frame);
187
+ [tabBar setProgress:1];
188
+ CGFloat secondX = CGRectGetMinX(thumb.frame);
189
+ [tabBar setProgress:0.25];
190
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstX + (secondX - firstX) * 0.25, 0.01);
191
+ [tabBar setProgress:0.5];
192
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), (firstX + secondX) / 2, 0.01);
193
+ [tabBar setProgress:0];
194
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstX, 0.01);
195
+
196
+ [tabBar setLeftToRight:NO];
197
+ [tabBar layoutIfNeeded];
198
+ XCTAssertGreaterThan(CGRectGetMinX(buttons[0].frame), CGRectGetMinX(buttons[1].frame));
199
+ [tabBar updateItemsJSON:@"[{\"key\":\"recent\",\"title\":\"Recents (8)\"},"
200
+ @"{\"key\":\"account\",\"title\":\"Accounts\"}]"];
201
+ [tabBar layoutIfNeeded];
202
+ NSArray<UIButton *> *twoButtons = [scroll.subviews filteredArrayUsingPredicate:
203
+ [NSPredicate predicateWithBlock:^BOOL(UIView *view, NSDictionary *bindings) {
204
+ return [view isKindOfClass:UIButton.class];
205
+ }]];
206
+ XCTAssertEqual(twoButtons.count, 2);
207
+ XCTAssertEqualWithAccuracy(CGRectGetWidth(twoButtons[0].frame),
208
+ CGRectGetWidth(track.frame) / 2, 0.01);
209
+ [tabBar updateStyleWithHeight:56 variant:@"segmented"
210
+ contentPaddingHorizontal:20 contentPaddingVertical:40 itemSpacing:0
211
+ fontSize:14 fontFamily:nil backgroundColor:UIColor.lightGrayColor
212
+ activeTextColor:UIColor.blackColor inactiveTextColor:UIColor.grayColor
213
+ indicatorColor:UIColor.whiteColor indicatorHeight:2 indicatorBottom:0];
214
+ tabBar.frame = CGRectMake(0, 0, 30, 56);
215
+ [tabBar layoutIfNeeded];
216
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(track.frame), 15, 0.01);
217
+ XCTAssertEqualWithAccuracy(CGRectGetWidth(track.frame), 0, 0.01);
218
+ XCTAssertEqualWithAccuracy(CGRectGetMinY(track.frame), 28, 0.01);
219
+ XCTAssertEqualWithAccuracy(CGRectGetHeight(track.frame), 0, 0.01);
220
+ XCTAssertTrue(thumb.hidden);
221
+ [tabBar updateItemsJSON:@"[]"];
222
+ XCTAssertTrue(tabBar.hidden);
223
+
224
+ [tabBar updateItemsJSON:@"[{\"key\":\"recent\",\"title\":\"Recents\"}]"];
225
+ [tabBar updateStyleWithHeight:44 variant:@"underline"
226
+ contentPaddingHorizontal:20 contentPaddingVertical:0 itemSpacing:8
227
+ fontSize:16 fontFamily:nil backgroundColor:UIColor.clearColor
228
+ activeTextColor:UIColor.blackColor inactiveTextColor:UIColor.grayColor
229
+ indicatorColor:UIColor.blackColor indicatorHeight:2 indicatorBottom:0];
230
+ tabBar.frame = CGRectMake(0, 0, 320, 44);
231
+ [tabBar layoutIfNeeded];
232
+ XCTAssertTrue(scroll.scrollEnabled);
233
+ XCTAssertTrue(track.hidden);
234
+ XCTAssertEqualWithAccuracy(CGRectGetHeight(thumb.frame), 2, 0.01);
235
+ }
236
+
146
237
  - (void)testPinnedChildOutsideHeaderHostStillReceivesViewportTouches
147
238
  {
148
239
  XCTAssertEqual([self.pager hitTest:CGPointMake(20, 90) withEvent:nil], self.sticky);
@@ -763,6 +763,26 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
763
763
  XCTAssertTrue([pager isKindOfClass:NSClassFromString(@"RNCCollapsiblePagerDirectScrollView")]);
764
764
  }
765
765
 
766
+ - (void)configureSegmentedTabsForTwoPages
767
+ {
768
+ auto baseline = std::make_shared<RNCCollapsiblePagerViewProps>();
769
+ baseline->nativeSmoothHeaderScrollEnabled = true;
770
+ baseline->headerHeight = 120;
771
+ baseline->stickyHeaderHeight = 44;
772
+ auto segmented = std::make_shared<RNCCollapsiblePagerViewProps>();
773
+ segmented->nativeSmoothHeaderScrollEnabled = true;
774
+ segmented->headerHeight = 120;
775
+ segmented->stickyHeaderHeight = 56;
776
+ segmented->nativeTabBarItems =
777
+ R"([{"key":"recent","title":"Recents"},{"key":"account","title":"Accounts"}])";
778
+ segmented->nativeTabBarVariant = "segmented";
779
+ segmented->nativeTabBarHeight = 56;
780
+ segmented->nativeTabBarContentPaddingHorizontal = 20;
781
+ segmented->nativeTabBarContentPaddingVertical = 12;
782
+ [self.pager updateProps:segmented oldProps:baseline];
783
+ [self.window layoutIfNeeded];
784
+ }
785
+
766
786
  - (void)recordPagerPan:(UIPanGestureRecognizer *)pan
767
787
  {
768
788
  if (pan.state == UIGestureRecognizerStateBegan) self.pagerPanBegins += 1;
@@ -904,6 +924,51 @@ typedef struct __IOHIDEvent *RNCTestHIDEventRef;
904
924
  XCTAssertEqual(self.handler.events.count, eventsBeforeLift);
905
925
  }
906
926
 
927
+ - (void)testSegmentedThumbTracksDeliveredPagerDragAndReverseBeforeLift
928
+ {
929
+ [self assertFixtureIsReadyForDelivery];
930
+ [self assertDirectPagerForRegression];
931
+ [self configureSegmentedTabsForTwoPages];
932
+
933
+ UIScrollView *pagerScrollView = [self.pager valueForKey:@"pagerScrollView"];
934
+ UIView *tabBar = [self.pager valueForKey:@"nativeTabBarView"];
935
+ UIView *thumb = [tabBar valueForKey:@"indicatorView"];
936
+ NSArray<UIButton *> *buttons = [tabBar valueForKey:@"buttons"];
937
+ XCTAssertEqual(buttons.count, 2);
938
+ XCTAssertFalse(tabBar.hidden);
939
+ XCTAssertEqualWithAccuracy(CGRectGetHeight(tabBar.bounds), 56, 0.01);
940
+ CGFloat firstThumbX = CGRectGetMinX(buttons[0].frame) + 2;
941
+ CGFloat secondThumbX = CGRectGetMinX(buttons[1].frame) + 2;
942
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstThumbX, 1);
943
+
944
+ CGPoint point = CGPointMake(350, 600);
945
+ UITouch *touch = [self.driver beginAt:point];
946
+ CGFloat largestOffset = 0;
947
+ NSUInteger intermediateFrames = 0;
948
+ for (NSInteger step = 1; step <= 18; ++step) {
949
+ [self.driver move:touch to:CGPointMake(point.x - 14 * step, point.y)];
950
+ CGFloat offset = pagerScrollView.contentOffset.x;
951
+ largestOffset = MAX(largestOffset, offset);
952
+ if (offset > 1 && offset < CGRectGetWidth(pagerScrollView.bounds) - 1) {
953
+ CGFloat progress = offset / CGRectGetWidth(pagerScrollView.bounds);
954
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame),
955
+ firstThumbX + (secondThumbX - firstThumbX) * progress, 1);
956
+ intermediateFrames += 1;
957
+ }
958
+ }
959
+ XCTAssertTrue(pagerScrollView.isDragging);
960
+ XCTAssertGreaterThan(largestOffset, 80);
961
+ XCTAssertGreaterThan(intermediateFrames, 2);
962
+
963
+ for (NSInteger step = 17; step >= 0; --step) {
964
+ [self.driver move:touch to:CGPointMake(point.x - 14 * step, point.y)];
965
+ }
966
+ // UIKit can briefly bounce past the leading edge before the finger lifts.
967
+ XCTAssertLessThan(fabs(pagerScrollView.contentOffset.x), 10);
968
+ XCTAssertEqualWithAccuracy(CGRectGetMinX(thumb.frame), firstThumbX, 1);
969
+ [self.driver end:touch];
970
+ }
971
+
907
972
  - (void)testStationaryTapKeepsReactPress
908
973
  {
909
974
  [self assertFixtureIsReadyForDelivery];
@@ -242,6 +242,8 @@ export class CollapsiblePagerView extends React.PureComponent {
242
242
  const deducedLayoutDirection = !layoutDirection || layoutDirection === "locale" ? I18nManager.isRTL ? "rtl" : "ltr" : layoutDirection;
243
243
  const nativeTabBarEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeTabBar?.items.length;
244
244
  const nativeTabBarStyle = nativeTabBar?.style;
245
+ const requestedContentPaddingVertical = nativeTabBarStyle?.contentPaddingVertical;
246
+ const nativeTabBarContentPaddingVertical = typeof requestedContentPaddingVertical === "number" && Number.isFinite(requestedContentPaddingVertical) && requestedContentPaddingVertical >= 0 ? requestedContentPaddingVertical : 0;
245
247
  const requestedTrailingReservedWidth = nativeTabBarStyle?.trailingReservedWidth;
246
248
  const nativeTabBarTrailingReservedWidth = typeof requestedTrailingReservedWidth === "number" && Number.isFinite(requestedTrailingReservedWidth) && requestedTrailingReservedWidth >= 0 ? requestedTrailingReservedWidth : 0;
247
249
  const nativeSubHeaderEnabled = (Platform.OS === "ios" || Platform.OS === "android") && !!nativeSubHeader?.items.length;
@@ -274,8 +276,10 @@ export class CollapsiblePagerView extends React.PureComponent {
274
276
  onPageScrollStateChanged: this.onPageScrollStateChanged,
275
277
  nativeTabBarItems: nativeTabBarEnabled ? JSON.stringify(nativeTabBar.items) : undefined,
276
278
  nativeTabBarHeight: nativeTabBarEnabled ? nativeTabBarStyle?.height ?? 44 : undefined,
279
+ nativeTabBarVariant: nativeTabBarEnabled ? nativeTabBarStyle?.variant ?? "underline" : undefined,
277
280
  nativeTabBarTrailingReservedWidth: nativeTabBarEnabled ? nativeTabBarTrailingReservedWidth : 0,
278
281
  nativeTabBarContentPaddingHorizontal: nativeTabBarEnabled ? nativeTabBarStyle?.contentPaddingHorizontal ?? 20 : undefined,
282
+ nativeTabBarContentPaddingVertical: nativeTabBarEnabled ? nativeTabBarContentPaddingVertical : undefined,
279
283
  nativeTabBarItemSpacing: nativeTabBarEnabled ? nativeTabBarStyle?.itemSpacing ?? 8 : undefined,
280
284
  nativeTabBarFontSize: nativeTabBarEnabled ? nativeTabBarStyle?.fontSize ?? 16 : undefined,
281
285
  nativeTabBarFontFamily: nativeTabBarEnabled ? nativeTabBarStyle?.fontFamily : undefined,
@@ -67,8 +67,10 @@ export interface NativeProps extends ViewProps {
67
67
  retainedPages?: string;
68
68
  nativeTabBarItems?: string;
69
69
  nativeTabBarHeight?: Double;
70
+ nativeTabBarVariant?: string;
70
71
  nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
71
72
  nativeTabBarContentPaddingHorizontal?: Double;
73
+ nativeTabBarContentPaddingVertical?: Double;
72
74
  nativeTabBarItemSpacing?: Double;
73
75
  nativeTabBarFontSize?: Double;
74
76
  nativeTabBarFontFamily?: string;
@@ -16,9 +16,11 @@ export type CollapsiblePagerNativeTabBarItem = Readonly<{
16
16
  testID?: string;
17
17
  }>;
18
18
  export type CollapsiblePagerNativeTabBarStyle = Readonly<{
19
+ variant?: "underline" | "segmented";
19
20
  height?: number;
20
21
  trailingReservedWidth?: number;
21
22
  contentPaddingHorizontal?: number;
23
+ contentPaddingVertical?: number;
22
24
  itemSpacing?: number;
23
25
  fontSize?: number;
24
26
  fontFamily?: string;
@@ -57,7 +59,7 @@ export type CollapsiblePagerNativeSubHeaderConfig = Readonly<{
57
59
  }>;
58
60
  export type CollapsiblePagerViewOnNativeTabPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
59
61
  export type CollapsiblePagerViewOnNativeSubHeaderPressEvent = ReactNative.NativeSyntheticEvent<OnNativeTabPressEventData>;
60
- export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children" | "headerHeight" | "stickyHeaderHeight" | "topScrollEdgeEffect" | "topOverlayHeight" | "externalTopOverlayTag" | "pageKeys" | "retainedPages" | "nativeTabBarItems" | "nativeTabBarHeight" | "nativeTabBarTrailingReservedWidth" | "nativeTabBarContentPaddingHorizontal" | "nativeTabBarItemSpacing" | "nativeTabBarFontSize" | "nativeTabBarFontFamily" | "nativeTabBarBackgroundColor" | "nativeTabBarActiveTextColor" | "nativeTabBarInactiveTextColor" | "nativeTabBarIndicatorColor" | "nativeTabBarIndicatorHeight" | "nativeTabBarIndicatorBottom" | "nativeSubHeaderConfig" | "nativeSubHeaderSelectedBackgroundColor" | "onNativeTabPress" | "onNativeSubHeaderPress" | "layoutDirection"> {
62
+ export interface CollapsiblePagerViewProps extends Omit<NativeProps, "children" | "headerHeight" | "stickyHeaderHeight" | "topScrollEdgeEffect" | "topOverlayHeight" | "externalTopOverlayTag" | "pageKeys" | "retainedPages" | "nativeTabBarItems" | "nativeTabBarHeight" | "nativeTabBarVariant" | "nativeTabBarTrailingReservedWidth" | "nativeTabBarContentPaddingHorizontal" | "nativeTabBarContentPaddingVertical" | "nativeTabBarItemSpacing" | "nativeTabBarFontSize" | "nativeTabBarFontFamily" | "nativeTabBarBackgroundColor" | "nativeTabBarActiveTextColor" | "nativeTabBarInactiveTextColor" | "nativeTabBarIndicatorColor" | "nativeTabBarIndicatorHeight" | "nativeTabBarIndicatorBottom" | "nativeSubHeaderConfig" | "nativeSubHeaderSelectedBackgroundColor" | "onNativeTabPress" | "onNativeSubHeaderPress" | "layoutDirection"> {
61
63
  /** Collapsible content rendered above the sticky bar. */
62
64
  header: React.ReactNode;
63
65
  /** Bar which remains pinned after the header has collapsed. */
@@ -50,8 +50,10 @@ export interface NativeProps extends ViewProps {
50
50
  retainedPages?: string;
51
51
  nativeTabBarItems?: string;
52
52
  nativeTabBarHeight?: Double;
53
+ nativeTabBarVariant?: string;
53
54
  nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
54
55
  nativeTabBarContentPaddingHorizontal?: Double;
56
+ nativeTabBarContentPaddingVertical?: Double;
55
57
  nativeTabBarItemSpacing?: Double;
56
58
  nativeTabBarFontSize?: Double;
57
59
  nativeTabBarFontFamily?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-pager-view",
3
- "version": "6.7.0-alpha.354",
3
+ "version": "6.7.0-alpha.359",
4
4
  "description": "React Native wrapper for Android and iOS ViewPager",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -73,7 +73,7 @@
73
73
  "typescript": "^5.9.2"
74
74
  },
75
75
  "peerDependencies": {
76
- "@onekeyfe/react-native-native-logger": "6.7.0-alpha.354",
76
+ "@onekeyfe/react-native-native-logger": "6.7.0-alpha.359",
77
77
  "react": "*",
78
78
  "react-native": "*"
79
79
  },
@@ -32,9 +32,11 @@ export type CollapsiblePagerNativeTabBarItem = Readonly<{
32
32
  }>;
33
33
 
34
34
  export type CollapsiblePagerNativeTabBarStyle = Readonly<{
35
+ variant?: "underline" | "segmented";
35
36
  height?: number;
36
37
  trailingReservedWidth?: number;
37
38
  contentPaddingHorizontal?: number;
39
+ contentPaddingVertical?: number;
38
40
  itemSpacing?: number;
39
41
  fontSize?: number;
40
42
  fontFamily?: string;
@@ -95,8 +97,10 @@ export interface CollapsiblePagerViewProps
95
97
  | "retainedPages"
96
98
  | "nativeTabBarItems"
97
99
  | "nativeTabBarHeight"
100
+ | "nativeTabBarVariant"
98
101
  | "nativeTabBarTrailingReservedWidth"
99
102
  | "nativeTabBarContentPaddingHorizontal"
103
+ | "nativeTabBarContentPaddingVertical"
100
104
  | "nativeTabBarItemSpacing"
101
105
  | "nativeTabBarFontSize"
102
106
  | "nativeTabBarFontFamily"
@@ -568,6 +572,14 @@ export class CollapsiblePagerView extends React.PureComponent<
568
572
  (Platform.OS === "ios" || Platform.OS === "android") &&
569
573
  !!nativeTabBar?.items.length;
570
574
  const nativeTabBarStyle = nativeTabBar?.style;
575
+ const requestedContentPaddingVertical =
576
+ nativeTabBarStyle?.contentPaddingVertical;
577
+ const nativeTabBarContentPaddingVertical =
578
+ typeof requestedContentPaddingVertical === "number" &&
579
+ Number.isFinite(requestedContentPaddingVertical) &&
580
+ requestedContentPaddingVertical >= 0
581
+ ? requestedContentPaddingVertical
582
+ : 0;
571
583
  const requestedTrailingReservedWidth =
572
584
  nativeTabBarStyle?.trailingReservedWidth;
573
585
  const nativeTabBarTrailingReservedWidth =
@@ -629,6 +641,9 @@ export class CollapsiblePagerView extends React.PureComponent<
629
641
  nativeTabBarHeight={
630
642
  nativeTabBarEnabled ? nativeTabBarStyle?.height ?? 44 : undefined
631
643
  }
644
+ nativeTabBarVariant={
645
+ nativeTabBarEnabled ? nativeTabBarStyle?.variant ?? "underline" : undefined
646
+ }
632
647
  nativeTabBarTrailingReservedWidth={
633
648
  nativeTabBarEnabled ? nativeTabBarTrailingReservedWidth : 0
634
649
  }
@@ -637,6 +652,9 @@ export class CollapsiblePagerView extends React.PureComponent<
637
652
  ? nativeTabBarStyle?.contentPaddingHorizontal ?? 20
638
653
  : undefined
639
654
  }
655
+ nativeTabBarContentPaddingVertical={
656
+ nativeTabBarEnabled ? nativeTabBarContentPaddingVertical : undefined
657
+ }
640
658
  nativeTabBarItemSpacing={
641
659
  nativeTabBarEnabled ? nativeTabBarStyle?.itemSpacing ?? 8 : undefined
642
660
  }
@@ -67,8 +67,10 @@ export interface NativeProps extends ViewProps {
67
67
  retainedPages?: string;
68
68
  nativeTabBarItems?: string;
69
69
  nativeTabBarHeight?: Double;
70
+ nativeTabBarVariant?: string;
70
71
  nativeTabBarTrailingReservedWidth?: WithDefault<Double, 0>;
71
72
  nativeTabBarContentPaddingHorizontal?: Double;
73
+ nativeTabBarContentPaddingVertical?: Double;
72
74
  nativeTabBarItemSpacing?: Double;
73
75
  nativeTabBarFontSize?: Double;
74
76
  nativeTabBarFontFamily?: string;