@hero-design/rn 8.130.3 → 8.131.1

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.
package/lib/index.js CHANGED
@@ -7166,7 +7166,11 @@ var getTabsTheme = function getTabsTheme(theme) {
7166
7166
  headerBottom: theme.colors.secondaryOutline,
7167
7167
  indicator: theme.colors.primary,
7168
7168
  text: theme.colors.onDefaultGlobalSurface,
7169
- headerBackground: theme.colors.defaultGlobalSurface
7169
+ headerBackground: theme.colors.defaultGlobalSurface,
7170
+ highlightedActiveText: theme.colors.primary,
7171
+ highlightedActiveBorder: theme.colors.primary,
7172
+ highlightedActiveBackground: theme.colors.neutralGlobalSurface,
7173
+ highlightedDisabledText: theme.colors.disabledOnDefaultGlobalSurface
7170
7174
  };
7171
7175
  var space = {
7172
7176
  flatListHorizontalPadding: theme.space.small,
@@ -7174,14 +7178,17 @@ var getTabsTheme = function getTabsTheme(theme) {
7174
7178
  itemVerticalPadding: theme.space.small,
7175
7179
  itemMargin: theme.space.smallMedium,
7176
7180
  outlineHorizontalPadding: theme.space.small,
7177
- outlineVerticalPadding: theme.space.xsmall,
7178
- tabIndicatorBottom: -theme.space.xxsmall
7181
+ tabIndicatorBottom: -theme.space.xxsmall,
7182
+ highlightedItemMargin: theme.space.xsmall,
7183
+ highlightedBarTopPadding: theme.space.xxsmall
7179
7184
  };
7180
7185
  var radii = {
7181
- outline: theme.radii.xlarge
7186
+ highlightedOutline: theme.radii.medium
7182
7187
  };
7183
7188
  var borderWidths = {
7184
- headerBottom: theme.borderWidths.medium
7189
+ headerBottom: theme.borderWidths.medium,
7190
+ highlightedHeaderBottom: theme.borderWidths.base,
7191
+ highlightedActiveBorder: theme.borderWidths.base
7185
7192
  };
7186
7193
  var sizes = {
7187
7194
  indicator: theme.sizes.xxsmall
@@ -7838,7 +7845,7 @@ var getFilterTriggerTheme = function getFilterTriggerTheme(theme) {
7838
7845
  },
7839
7846
  inactive: {
7840
7847
  filled: theme.colors.neutralGlobalSurface,
7841
- outlined: theme.colors.primary,
7848
+ outlined: theme.colors.secondaryOutline,
7842
7849
  ghost: 'transparent'
7843
7850
  }
7844
7851
  }
@@ -28140,43 +28147,85 @@ var HeaderTabWrapper = index$c(reactNative.View)(function (_ref) {
28140
28147
  backgroundColor: theme.__hd__.tabs.colors.headerBackground
28141
28148
  };
28142
28149
  });
28150
+ var getItemMarginLeft = function getItemMarginLeft(isFirstItem, themeVariant, highlightedMargin, defaultMargin) {
28151
+ if (isFirstItem) return 0;
28152
+ if (themeVariant === 'highlighted') return highlightedMargin;
28153
+ return defaultMargin;
28154
+ };
28143
28155
  var HeaderTabItem = index$c(reactNative.Animated.View)(function (_ref2) {
28144
28156
  var theme = _ref2.theme,
28145
- isFirstItem = _ref2.isFirstItem;
28157
+ isFirstItem = _ref2.isFirstItem,
28158
+ themeVariant = _ref2.themeVariant;
28146
28159
  return {
28147
- marginLeft: isFirstItem ? 0 : theme.__hd__.tabs.space.itemMargin,
28148
- paddingVertical: theme.__hd__.tabs.space.itemVerticalPadding
28160
+ marginLeft: getItemMarginLeft(isFirstItem, themeVariant, theme.__hd__.tabs.space.highlightedItemMargin, theme.__hd__.tabs.space.itemMargin),
28161
+ paddingVertical: theme.__hd__.tabs.space.itemVerticalPadding,
28162
+ alignItems: 'center',
28163
+ justifyContent: 'center'
28149
28164
  };
28150
28165
  });
28151
- var HeaderTabItemOutlineWrapper = index$c(reactNative.View)(function (_ref3) {
28166
+ // Three-piece pill: left cap + body + right cap, all native-driver animated.
28167
+ // Splitting into pieces keeps border-radius on fixed-width views so scaleX
28168
+ // never distorts the rounded corners. Cap width equals the border-radius so
28169
+ // the rounded edge is fully contained within the cap piece.
28170
+ var HeaderTabPillLeft = index$c(reactNative.Animated.View)(function (_ref3) {
28152
28171
  var theme = _ref3.theme;
28153
- return _objectSpread2({
28154
- paddingVertical: theme.__hd__.tabs.space.itemVerticalPadding
28155
- }, reactNative.StyleSheet.absoluteFillObject);
28172
+ return {
28173
+ position: 'absolute',
28174
+ top: 0,
28175
+ bottom: 0,
28176
+ left: 0,
28177
+ width: theme.__hd__.tabs.radii.highlightedOutline,
28178
+ borderTopLeftRadius: theme.__hd__.tabs.radii.highlightedOutline,
28179
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground
28180
+ };
28156
28181
  });
28157
- var HeaderTabItemOutline = index$c(reactNative.Animated.View)(function (_ref4) {
28158
- var theme = _ref4.theme,
28159
- themeActive = _ref4.themeActive;
28182
+ var HeaderTabPillBody = index$c(reactNative.Animated.View)(function (_ref4) {
28183
+ var theme = _ref4.theme;
28160
28184
  return {
28161
- borderRadius: theme.__hd__.tabs.radii.outline,
28162
- backgroundColor: themeActive ? theme.__hd__.tabs.colors.activeBackground : undefined
28185
+ position: 'absolute',
28186
+ top: 0,
28187
+ bottom: 0,
28188
+ left: 0,
28189
+ width: 1,
28190
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground
28163
28191
  };
28164
28192
  });
28165
- var HeaderTabItemWrapper = index$c(reactNative.View)(function (_ref5) {
28193
+ var HeaderTabPillRight = index$c(reactNative.Animated.View)(function (_ref5) {
28166
28194
  var theme = _ref5.theme;
28195
+ return {
28196
+ position: 'absolute',
28197
+ top: 0,
28198
+ bottom: 0,
28199
+ left: 0,
28200
+ width: theme.__hd__.tabs.radii.highlightedOutline,
28201
+ borderTopRightRadius: theme.__hd__.tabs.radii.highlightedOutline,
28202
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBackground
28203
+ };
28204
+ });
28205
+ var HeaderTabItemActiveBorder = index$c(reactNative.Animated.View)(function (_ref6) {
28206
+ var theme = _ref6.theme;
28207
+ return {
28208
+ position: 'absolute',
28209
+ bottom: 0,
28210
+ width: 1,
28211
+ height: theme.__hd__.tabs.borderWidths.highlightedActiveBorder,
28212
+ backgroundColor: theme.__hd__.tabs.colors.highlightedActiveBorder
28213
+ };
28214
+ });
28215
+ var HeaderTabItemWrapper = index$c(reactNative.View)(function (_ref7) {
28216
+ var theme = _ref7.theme;
28167
28217
  return _objectSpread2({
28168
28218
  paddingHorizontal: theme.__hd__.tabs.space.outlineHorizontalPadding,
28169
- paddingVertical: theme.__hd__.tabs.space.outlineVerticalPadding,
28170
28219
  position: 'relative',
28171
28220
  justifyContent: 'center'
28172
28221
  }, !isAndroid8 && {
28173
28222
  alignItems: 'center'
28174
28223
  });
28175
28224
  });
28176
- var HeaderTabItemIndicator = index$c(reactNative.Animated.View)(function (_ref6) {
28177
- var theme = _ref6.theme;
28225
+ var HeaderTabItemIndicator = index$c(reactNative.Animated.View)(function (_ref8) {
28226
+ var theme = _ref8.theme;
28178
28227
  return {
28179
- width: '100%',
28228
+ width: 1,
28180
28229
  height: theme.__hd__.tabs.sizes.indicator,
28181
28230
  position: 'absolute',
28182
28231
  bottom: theme.__hd__.tabs.space.tabIndicatorBottom,
@@ -28257,109 +28306,204 @@ var TabWithBadge = function TabWithBadge(_ref) {
28257
28306
  return /*#__PURE__*/React__namespace.default.createElement(reactNative.View, null, tabItem);
28258
28307
  };
28259
28308
 
28260
- var useAnimatedValueArray = function useAnimatedValueArray(initialValues) {
28261
- var refs = React__namespace.default.useRef([]);
28262
- refs.current.length = initialValues.length;
28263
- initialValues.forEach(function (initialValue, i) {
28264
- var _refs$current$i;
28265
- refs.current[i] = (_refs$current$i = refs.current[i]) !== null && _refs$current$i !== void 0 ? _refs$current$i : new reactNative.Animated.Value(initialValue);
28266
- });
28267
- return refs.current;
28268
- };
28269
-
28270
- var useInitHighlightedAnimation = function useInitHighlightedAnimation(_ref) {
28271
- var _ref$selectedIndex = _ref.selectedIndex,
28272
- selectedIndex = _ref$selectedIndex === void 0 ? 0 : _ref$selectedIndex,
28309
+ /**
28310
+ * Drives two visual layers that slide to the selected tab on every press:
28311
+ *
28312
+ * Layer 1 — bottom border / underline (indicatorStyle)
28313
+ * ─────────────────────────────────────────────────────
28314
+ * Uses the "width:1 + scaleX" trick: the element has a fixed stylesheet
28315
+ * width of 1px and scaleX is set to the target pixel width, giving a visual
28316
+ * width of 1 × scaleX pixels without touching any layout property.
28317
+ * Both translateX and scaleX are transform properties → native driver.
28318
+ * Caveat: scaleX also scales border-radius, so this layer has no border-radius.
28319
+ *
28320
+ * Layer 2 pill background (pillLeftStyle / pillBodyStyle / pillRightStyle)
28321
+ * ─────────────────────────────────────────────────────────────────────────────
28322
+ * The pill is split into three absolutely-positioned children so that
28323
+ * border-radius is never distorted by scale:
28324
+ *
28325
+ * ┌──────────────────────────────────────────────────────┐
28326
+ * │ [cap-left 8px] [body width-1 + scaleX] [cap-right 8px] │
28327
+ * └──────────────────────────────────────────────────────┘
28328
+ *
28329
+ * cap-left — fixed 8px wide, borderTopLeftRadius:8, translateX = pillX
28330
+ * body — width:1 + scaleX trick (scaleX = tabWidth - 16),
28331
+ * transformOrigin 'left center',
28332
+ * translateX = pillX + 8 (via Animated.add)
28333
+ * cap-right — fixed 8px wide, borderTopRightRadius:8,
28334
+ * translateX = pillX + tabWidth - 8 (via Animated.add)
28335
+ *
28336
+ * All four animated values use the native driver (translateX and scaleX are
28337
+ * transform properties). `width` is never animated, so no JS driver needed.
28338
+ *
28339
+ * Driver summary:
28340
+ * indicatorX native translateX — slides the bottom border
28341
+ * indicatorScaleX native scaleX — stretches the bottom border
28342
+ * pillX native translateX — slides all three pill pieces
28343
+ * pillBodyScaleX native scaleX — stretches the pill body
28344
+ * pillRightOffsetX native translateX — positions the right cap
28345
+ * (Animated.add: pillX + tabWidth - 8)
28346
+ */
28347
+ var useIndicatorAnimation = function useIndicatorAnimation(_ref) {
28348
+ var selectedIndex = _ref.selectedIndex,
28273
28349
  tabsLength = _ref.tabsLength,
28274
- variant = _ref.variant;
28275
- var tabsAnims = useAnimatedValueArray(Array.from({
28276
- length: tabsLength
28277
- }).map(function (_, i) {
28278
- return i === selectedIndex ? 1 : 0;
28279
- }));
28350
+ pillCapWidth = _ref.pillCapWidth;
28351
+ // Layer 1 — native driver (bottom border / underline).
28352
+ var indicatorX = React__namespace.default.useRef(new reactNative.Animated.Value(0)).current;
28353
+ var indicatorScaleX = React__namespace.default.useRef(new reactNative.Animated.Value(1)).current;
28354
+ // Layer 2 native driver (pill background, three-piece split).
28355
+ // pillX: left edge of the pill (shared by all three pieces as base).
28356
+ // pillBodyScaleX: scaleX for the body piece (tabWidth - 2 * CAP_WIDTH).
28357
+ // pillRightOffset: additional x offset for the right cap (tabWidth - CAP_WIDTH).
28358
+ var pillX = React__namespace.default.useRef(new reactNative.Animated.Value(0)).current;
28359
+ var pillBodyScaleX = React__namespace.default.useRef(new reactNative.Animated.Value(1)).current;
28360
+ var pillRightOffset = React__namespace.default.useRef(new reactNative.Animated.Value(0)).current;
28361
+ // Stable ref so callbacks don't capture stale closures.
28362
+ var layoutsRef = React__namespace.default.useRef([]);
28363
+ var runningAnimRef = React__namespace.default.useRef(null);
28364
+ var pendingIndexRef = React__namespace.default.useRef(undefined);
28365
+ var initializedRef = React__namespace.default.useRef(false);
28366
+ // Resize layout cache when tabsLength changes.
28280
28367
  React__namespace.default.useEffect(function () {
28281
- if (variant !== 'highlighted') {
28282
- return;
28283
- }
28284
- var animation = reactNative.Animated.parallel(_toConsumableArray(Array.from({
28368
+ layoutsRef.current = Array.from({
28285
28369
  length: tabsLength
28286
- }).map(function (_, i) {
28287
- return reactNative.Animated.timing(tabsAnims[i], {
28288
- toValue: i === selectedIndex ? 1 : 0,
28289
- duration: 150,
28290
- useNativeDriver: reactNative.Platform.OS !== 'web'
28291
- });
28292
- })));
28293
- animation.start();
28294
- return function () {
28295
- animation.stop();
28296
- };
28297
- }, [selectedIndex]);
28298
- return {
28299
- tabsAnims: tabsAnims
28300
- };
28301
- };
28302
-
28303
- var TRANSLATE_DISTANCE = 30;
28304
- var animateOpacity = function animateOpacity(animatedValue, toValue) {
28305
- return reactNative.Animated.timing(animatedValue, {
28306
- toValue: toValue,
28307
- duration: 150,
28308
- easing: reactNative.Easing.ease,
28309
- useNativeDriver: reactNative.Platform.OS !== 'web'
28310
- });
28311
- };
28312
- var animateTranslateX = function animateTranslateX(animatedValue, toValue) {
28313
- return reactNative.Animated.spring(animatedValue, {
28314
- toValue: toValue,
28315
- useNativeDriver: reactNative.Platform.OS !== 'web'
28316
- });
28317
- };
28318
- var useInitUnderlinedAnimation = function useInitUnderlinedAnimation(_ref) {
28319
- var tabsLength = _ref.tabsLength,
28320
- _ref$selectedIndex = _ref.selectedIndex,
28321
- selectedIndex = _ref$selectedIndex === void 0 ? 0 : _ref$selectedIndex,
28322
- variant = _ref.variant;
28323
- var previousIndex = React__namespace.default.useRef(0);
28324
- var translateXAnims = useAnimatedValueArray(Array.from({
28325
- length: tabsLength
28326
- }).map(function () {
28327
- return 0;
28328
- }));
28329
- var opacityAnims = useAnimatedValueArray(Array.from({
28330
- length: tabsLength
28331
- }).map(function (_, i) {
28332
- return selectedIndex !== undefined && selectedIndex === i ? 1 : 0;
28333
- }));
28334
- var underlinedTranslateX = translateXAnims.map(function (anim) {
28335
- return anim.interpolate({
28336
- inputRange: [-1, 0, 1],
28337
- outputRange: [-TRANSLATE_DISTANCE, 0, TRANSLATE_DISTANCE]
28370
+ }, function (_, i) {
28371
+ return layoutsRef.current[i];
28338
28372
  });
28339
- });
28340
- var underlinedOpacity = opacityAnims.map(function (anim) {
28341
- return anim.interpolate({
28342
- inputRange: [0, 1],
28343
- outputRange: [0, 1]
28373
+ }, [tabsLength]);
28374
+ var animateTo = React__namespace.default.useCallback(function (index, animate) {
28375
+ var _runningAnimRef$curre;
28376
+ var layout = layoutsRef.current[index];
28377
+ if (!layout) return;
28378
+ (_runningAnimRef$curre = runningAnimRef.current) === null || _runningAnimRef$curre === void 0 || _runningAnimRef$curre.stop();
28379
+ runningAnimRef.current = null;
28380
+ // Layer 1: bottom-border element has width:1, so scaleX = pixel width.
28381
+ var indicatorScaleXValue = layout.width;
28382
+ // Layer 2 body: width:1 element, scaleX fills space between the two caps.
28383
+ var bodyScaleX = Math.max(layout.width - pillCapWidth * 2, 0);
28384
+ // Layer 2 right cap: offset from pillX to reach the right edge.
28385
+ // Clamped to 0 so the right cap never slides left of the pill's origin
28386
+ // when the tab is narrower than one cap width.
28387
+ var rightOffset = Math.max(layout.width - pillCapWidth, 0);
28388
+ if (!animate || !initializedRef.current) {
28389
+ // First render — snap all values immediately without animation.
28390
+ indicatorX.setValue(layout.x);
28391
+ indicatorScaleX.setValue(indicatorScaleXValue);
28392
+ pillX.setValue(layout.x);
28393
+ pillBodyScaleX.setValue(bodyScaleX);
28394
+ pillRightOffset.setValue(rightOffset);
28395
+ initializedRef.current = true;
28396
+ return;
28397
+ }
28398
+ // All five animations run on the native driver (UI thread):
28399
+ // indicatorX — slides the bottom border
28400
+ // indicatorScaleX — stretches the bottom border
28401
+ // pillX — slides all three pill pieces together
28402
+ // pillBodyScaleX — resizes the body piece to fill between caps
28403
+ // pillRightOffset — keeps the right cap at the pill's right edge
28404
+ var anim = reactNative.Animated.parallel([reactNative.Animated.timing(indicatorX, {
28405
+ toValue: layout.x,
28406
+ useNativeDriver: true
28407
+ }), reactNative.Animated.timing(indicatorScaleX, {
28408
+ toValue: indicatorScaleXValue,
28409
+ useNativeDriver: true
28410
+ }), reactNative.Animated.timing(pillX, {
28411
+ toValue: layout.x,
28412
+ useNativeDriver: true
28413
+ }), reactNative.Animated.timing(pillBodyScaleX, {
28414
+ toValue: bodyScaleX,
28415
+ useNativeDriver: true
28416
+ }), reactNative.Animated.timing(pillRightOffset, {
28417
+ toValue: rightOffset,
28418
+ useNativeDriver: true
28419
+ })]);
28420
+ runningAnimRef.current = anim;
28421
+ anim.start(function (_ref2) {
28422
+ var finished = _ref2.finished;
28423
+ if (finished) runningAnimRef.current = null;
28344
28424
  });
28345
- });
28425
+ }, [indicatorX, indicatorScaleX, pillX, pillBodyScaleX, pillRightOffset, pillCapWidth]);
28426
+ // Animate to selected tab whenever selectedIndex changes.
28346
28427
  React__namespace.default.useEffect(function () {
28347
- if (variant === 'underlined' && selectedIndex !== undefined && previousIndex.current !== selectedIndex) {
28348
- // Prepare for translateX into the right position.
28349
- if (selectedIndex > previousIndex.current) {
28350
- translateXAnims[selectedIndex].setValue(-1);
28351
- } else {
28352
- translateXAnims[selectedIndex].setValue(1);
28353
- }
28354
- // Split animations into 2 sets of parallel animations to prevent race condition.
28355
- reactNative.Animated.parallel([animateOpacity(opacityAnims[selectedIndex], 1), animateTranslateX(translateXAnims[selectedIndex], 0)]).start();
28356
- reactNative.Animated.parallel([animateOpacity(opacityAnims[previousIndex.current], 0), animateTranslateX(translateXAnims[previousIndex.current], selectedIndex > previousIndex.current ? 1 : -1)]).start();
28357
- previousIndex.current = selectedIndex;
28428
+ if (selectedIndex === undefined) return;
28429
+ if (layoutsRef.current[selectedIndex]) {
28430
+ animateTo(selectedIndex, initializedRef.current);
28431
+ } else {
28432
+ // Layout not yet measured — store as pending and resolve in onTabLayout.
28433
+ pendingIndexRef.current = selectedIndex;
28434
+ }
28435
+ }, [selectedIndex, animateTo]);
28436
+ // Stop any in-flight animation on unmount.
28437
+ React__namespace.default.useEffect(function () {
28438
+ return function () {
28439
+ var _runningAnimRef$curre2;
28440
+ (_runningAnimRef$curre2 = runningAnimRef.current) === null || _runningAnimRef$curre2 === void 0 || _runningAnimRef$curre2.stop();
28441
+ };
28442
+ }, []);
28443
+ var onTabLayout = React__namespace.default.useCallback(function (index, event) {
28444
+ var _event$nativeEvent$la = event.nativeEvent.layout,
28445
+ x = _event$nativeEvent$la.x,
28446
+ width = _event$nativeEvent$la.width;
28447
+ var prev = layoutsRef.current[index];
28448
+ // Skip if layout hasn't meaningfully changed (sub-pixel tolerance).
28449
+ if (prev && Math.abs(prev.x - x) < 0.5 && Math.abs(prev.width - width) < 0.5) {
28450
+ return;
28358
28451
  }
28359
- }, [selectedIndex, variant, opacityAnims, translateXAnims]);
28452
+ layoutsRef.current[index] = {
28453
+ x: x,
28454
+ width: width
28455
+ };
28456
+ // Animate if this tab is the selected one (covers the pending case where
28457
+ // selectedIndex was set before the layout was measured).
28458
+ if (index === selectedIndex || index === pendingIndexRef.current) {
28459
+ if (index === pendingIndexRef.current) pendingIndexRef.current = undefined;
28460
+ animateTo(index, initializedRef.current);
28461
+ }
28462
+ // If no tab is selected yet, snap indicators to tab 0 on its first
28463
+ // layout so they appear at a sensible default position.
28464
+ if (!initializedRef.current && index === 0 && selectedIndex === undefined) {
28465
+ indicatorScaleX.setValue(width);
28466
+ pillX.setValue(x);
28467
+ pillBodyScaleX.setValue(Math.max(width - pillCapWidth * 2, 0));
28468
+ pillRightOffset.setValue(Math.max(width - pillCapWidth, 0));
28469
+ initializedRef.current = true;
28470
+ }
28471
+ }, [animateTo, selectedIndex, pillCapWidth]);
28472
+ // Layer 1: transformOrigin 'left center' pins scaleX expansion to left edge.
28473
+ var indicatorStyle = {
28474
+ transformOrigin: 'left center',
28475
+ transform: [{
28476
+ translateX: indicatorX
28477
+ }, {
28478
+ scaleX: indicatorScaleX
28479
+ }]
28480
+ };
28481
+ // Layer 2: three pieces, all absolutely positioned, all native driver.
28482
+ // Animated.add computes derived positions without creating JS-driver nodes.
28483
+ var pillLeftStyle = {
28484
+ transform: [{
28485
+ translateX: pillX
28486
+ }]
28487
+ };
28488
+ var pillBodyStyle = {
28489
+ transformOrigin: 'left center',
28490
+ transform: [{
28491
+ translateX: reactNative.Animated.add(pillX, pillCapWidth)
28492
+ }, {
28493
+ scaleX: pillBodyScaleX
28494
+ }]
28495
+ };
28496
+ var pillRightStyle = {
28497
+ transform: [{
28498
+ translateX: reactNative.Animated.add(pillX, pillRightOffset)
28499
+ }]
28500
+ };
28360
28501
  return {
28361
- underlinedTranslateX: underlinedTranslateX,
28362
- underlinedOpacity: underlinedOpacity
28502
+ indicatorStyle: indicatorStyle,
28503
+ pillLeftStyle: pillLeftStyle,
28504
+ pillBodyStyle: pillBodyStyle,
28505
+ pillRightStyle: pillRightStyle,
28506
+ onTabLayout: onTabLayout
28363
28507
  };
28364
28508
  };
28365
28509
 
@@ -28379,7 +28523,7 @@ var getTabItem$1 = function getTabItem(_ref) {
28379
28523
  }
28380
28524
  if (typeof item === 'string') {
28381
28525
  return /*#__PURE__*/React__namespace.default.createElement(Typography.Body, {
28382
- variant: active ? 'regular-bold' : 'regular',
28526
+ variant: active ? 'small-bold' : 'small',
28383
28527
  numberOfLines: 1,
28384
28528
  style: {
28385
28529
  color: color
@@ -28390,130 +28534,167 @@ var getTabItem$1 = function getTabItem(_ref) {
28390
28534
  color: color
28391
28535
  });
28392
28536
  };
28393
- var ScrollableTabHeader = function ScrollableTabHeader(_ref2) {
28394
- var onTabPress = _ref2.onTabPress,
28395
- selectedIndex = _ref2.selectedIndex,
28396
- tabs = _ref2.tabs,
28397
- barStyle = _ref2.barStyle,
28398
- testID = _ref2.testID,
28399
- _ref2$insets = _ref2.insets,
28400
- insets = _ref2$insets === void 0 ? {
28537
+ var TabItemComponent = /*#__PURE__*/React__namespace.default.memo(function (_ref2) {
28538
+ var _tab$inactiveItem;
28539
+ var tab = _ref2.tab,
28540
+ index = _ref2.index,
28541
+ active = _ref2.active,
28542
+ variant = _ref2.variant,
28543
+ onTabPress = _ref2.onTabPress,
28544
+ onLayout = _ref2.onLayout;
28545
+ var theme = useTheme$1();
28546
+ var isHighlighted = variant === 'highlighted';
28547
+ var getTextColor = function getTextColor() {
28548
+ if (isHighlighted) {
28549
+ if (tab.disabled) return theme.__hd__.tabs.colors.highlightedDisabledText;
28550
+ if (active) return theme.__hd__.tabs.colors.highlightedActiveText;
28551
+ }
28552
+ return active ? theme.__hd__.tabs.colors.active : theme.__hd__.tabs.colors.inactive;
28553
+ };
28554
+ var inactiveItem = (_tab$inactiveItem = tab.inactiveItem) !== null && _tab$inactiveItem !== void 0 ? _tab$inactiveItem : tab.activeItem;
28555
+ var tabItem = getTabItem$1({
28556
+ item: active ? tab.activeItem : inactiveItem,
28557
+ color: getTextColor(),
28558
+ active: active
28559
+ });
28560
+ var handlePress = React__namespace.default.useCallback(function () {
28561
+ return onTabPress(tab.key);
28562
+ }, [onTabPress, tab.key]);
28563
+ return /*#__PURE__*/React__namespace.default.createElement(reactNative.TouchableWithoutFeedback, {
28564
+ key: tab.key,
28565
+ onPress: handlePress,
28566
+ testID: tab.testID,
28567
+ disabled: tab.disabled
28568
+ }, /*#__PURE__*/React__namespace.default.createElement(HeaderTabItem, {
28569
+ testID: "tab-item-".concat(index),
28570
+ isFirstItem: index === 0,
28571
+ themeVariant: variant,
28572
+ onLayout: onLayout
28573
+ }, /*#__PURE__*/React__namespace.default.createElement(HeaderTabItemWrapper, null, /*#__PURE__*/React__namespace.default.createElement(TabWithBadge, {
28574
+ config: tab.badge,
28575
+ tabItem: tabItem
28576
+ }))));
28577
+ });
28578
+ var ScrollableTabHeader = function ScrollableTabHeader(_ref3) {
28579
+ var onTabPress = _ref3.onTabPress,
28580
+ rawSelectedIndex = _ref3.selectedIndex,
28581
+ tabs = _ref3.tabs,
28582
+ barStyle = _ref3.barStyle,
28583
+ testID = _ref3.testID,
28584
+ _ref3$insets = _ref3.insets,
28585
+ insets = _ref3$insets === void 0 ? {
28401
28586
  top: 0,
28402
28587
  bottom: 0,
28403
28588
  right: 0,
28404
28589
  left: 0
28405
- } : _ref2$insets,
28406
- _ref2$variant = _ref2.variant,
28407
- variant = _ref2$variant === void 0 ? 'highlighted' : _ref2$variant;
28590
+ } : _ref3$insets,
28591
+ _ref3$variant = _ref3.variant,
28592
+ variant = _ref3$variant === void 0 ? 'highlighted' : _ref3$variant;
28593
+ var selectedIndex = rawSelectedIndex !== undefined && rawSelectedIndex >= 0 ? rawSelectedIndex : undefined;
28408
28594
  var theme = useTheme$1();
28409
- var flatListRef = React__namespace.default.useRef(null);
28410
- // Init underlined animation data
28411
- var _useInitUnderlinedAni = useInitUnderlinedAnimation({
28412
- tabsLength: tabs.length,
28413
- selectedIndex: selectedIndex,
28414
- variant: variant
28415
- }),
28416
- underlinedTranslateX = _useInitUnderlinedAni.underlinedTranslateX,
28417
- underlinedOpacity = _useInitUnderlinedAni.underlinedOpacity;
28418
- // Init highlighted animation data
28419
- var _useInitHighlightedAn = useInitHighlightedAnimation({
28595
+ var scrollViewRef = React__namespace.default.useRef(null);
28596
+ var isHighlighted = variant === 'highlighted';
28597
+ var _useIndicatorAnimatio = useIndicatorAnimation({
28420
28598
  selectedIndex: selectedIndex,
28421
28599
  tabsLength: tabs.length,
28422
- variant: variant
28600
+ pillCapWidth: theme.__hd__.tabs.radii.highlightedOutline
28423
28601
  }),
28424
- tabsAnims = _useInitHighlightedAn.tabsAnims;
28425
- React__namespace.default.useEffect(function () {
28426
- if (selectedIndex !== undefined && selectedIndex !== -1) {
28427
- var _flatListRef$current;
28428
- (_flatListRef$current = flatListRef.current) === null || _flatListRef$current === void 0 || _flatListRef$current.scrollToIndex({
28429
- index: selectedIndex,
28430
- viewPosition: 0.5
28602
+ indicatorStyle = _useIndicatorAnimatio.indicatorStyle,
28603
+ pillLeftStyle = _useIndicatorAnimatio.pillLeftStyle,
28604
+ pillBodyStyle = _useIndicatorAnimatio.pillBodyStyle,
28605
+ pillRightStyle = _useIndicatorAnimatio.pillRightStyle,
28606
+ onTabLayout = _useIndicatorAnimatio.onTabLayout;
28607
+ // Scroll to the selected tab after its layout is known.
28608
+ var handleTabLayout = React__namespace.default.useCallback(function (index, event) {
28609
+ if (index === selectedIndex) {
28610
+ var _scrollViewRef$curren;
28611
+ (_scrollViewRef$curren = scrollViewRef.current) === null || _scrollViewRef$curren === void 0 || _scrollViewRef$curren.scrollTo({
28612
+ x: event.nativeEvent.layout.x,
28613
+ animated: true
28431
28614
  });
28432
28615
  }
28433
- return function () {
28616
+ onTabLayout(index, event);
28617
+ }, [selectedIndex, onTabLayout]);
28618
+ // Memoize per-tab layout handlers so TabItemComponent memo is not broken.
28619
+ var tabLayoutHandlers = React__namespace.default.useMemo(function () {
28620
+ return tabs.map(function (_, i) {
28621
+ return function (event) {
28622
+ return handleTabLayout(i, event);
28623
+ };
28624
+ });
28625
+ },
28626
+ // Handlers only need to change when tab count or selection changes.
28627
+ [tabs, handleTabLayout]);
28628
+ var scrollViewStyle = React__namespace.default.useMemo(function () {
28629
+ return {
28630
+ borderBottomColor: theme.__hd__.tabs.colors.headerBottom,
28631
+ borderBottomWidth: isHighlighted ? theme.__hd__.tabs.borderWidths.highlightedHeaderBottom : theme.__hd__.tabs.sizes.indicator
28434
28632
  };
28435
- }, [selectedIndex]);
28633
+ }, [theme, isHighlighted]);
28634
+ var contentContainerStyle = React__namespace.default.useMemo(function () {
28635
+ return _objectSpread2({
28636
+ paddingHorizontal: theme.__hd__.tabs.space.flatListHorizontalPadding,
28637
+ position: 'relative'
28638
+ }, reactNative.Platform.OS === 'android' && {
28639
+ borderBottomColor: theme.__hd__.tabs.colors.headerBottom,
28640
+ borderBottomWidth: isHighlighted ? theme.__hd__.tabs.borderWidths.highlightedHeaderBottom : theme.__hd__.tabs.sizes.indicator
28641
+ });
28642
+ }, [theme, isHighlighted]);
28643
+ var wrapperStyle = React__namespace.default.useMemo(function () {
28644
+ return [isHighlighted && {
28645
+ paddingTop: theme.__hd__.tabs.space.highlightedBarTopPadding
28646
+ }, barStyle];
28647
+ }, [isHighlighted, theme, barStyle]);
28436
28648
  return /*#__PURE__*/React__namespace.default.createElement(HeaderTabWrapper, {
28649
+ testID: "tab-header-wrapper",
28437
28650
  themeInsets: insets,
28438
- style: barStyle
28439
- }, /*#__PURE__*/React__namespace.default.createElement(reactNative.FlatList, {
28651
+ style: wrapperStyle
28652
+ }, /*#__PURE__*/React__namespace.default.createElement(reactNative.View, {
28653
+ style: isHighlighted ? {
28654
+ overflow: 'hidden'
28655
+ } : undefined
28656
+ }, /*#__PURE__*/React__namespace.default.createElement(reactNative.ScrollView, {
28657
+ ref: scrollViewRef,
28440
28658
  testID: testID,
28441
- ref: flatListRef,
28442
28659
  horizontal: true,
28443
- data: tabs,
28444
- keyExtractor: function keyExtractor(tab) {
28445
- return String(tab.key);
28446
- },
28447
28660
  showsHorizontalScrollIndicator: false,
28448
- onScrollToIndexFailed: function onScrollToIndexFailed(_ref3) {
28449
- var index = _ref3.index;
28450
- setTimeout(function () {
28451
- var _flatListRef$current2;
28452
- return (_flatListRef$current2 = flatListRef.current) === null || _flatListRef$current2 === void 0 ? void 0 : _flatListRef$current2.scrollToIndex({
28453
- index: index,
28454
- viewPosition: 0.5
28455
- });
28456
- }, 100);
28457
- },
28661
+ contentContainerStyle: contentContainerStyle,
28662
+ style: scrollViewStyle
28663
+ }, /*#__PURE__*/React__namespace.default.createElement(reactNative.View, {
28458
28664
  style: {
28459
- borderBottomColor: theme.__hd__.tabs.colors.headerBottom,
28460
- borderBottomWidth: theme.__hd__.tabs.sizes.indicator
28461
- },
28462
- contentContainerStyle: _objectSpread2({
28463
- paddingHorizontal: theme.__hd__.tabs.space.flatListHorizontalPadding
28464
- }, reactNative.Platform.OS === 'android' && {
28465
- borderBottomColor: theme.__hd__.tabs.colors.headerBottom,
28466
- borderBottomWidth: theme.__hd__.tabs.sizes.indicator
28467
- }),
28468
- renderItem: function renderItem(_ref4) {
28469
- var tab = _ref4.item,
28470
- index = _ref4.index;
28471
- var key = tab.key,
28472
- tabItemTestID = tab.testID,
28473
- activeItem = tab.activeItem,
28474
- originalInactiveItem = tab.inactiveItem,
28475
- badge = tab.badge;
28476
- var active = selectedIndex === index;
28477
- var activeAnimated = tabsAnims[index];
28478
- var outlineScale = activeAnimated.interpolate({
28479
- inputRange: [0, 1],
28480
- outputRange: [0.5, 1]
28481
- });
28482
- var inactiveItem = originalInactiveItem !== null && originalInactiveItem !== void 0 ? originalInactiveItem : activeItem;
28483
- var tabItem = getTabItem$1({
28484
- item: active ? activeItem : inactiveItem,
28485
- color: active ? theme.__hd__.tabs.colors.active : theme.__hd__.tabs.colors.inactive,
28486
- active: active
28487
- });
28488
- return /*#__PURE__*/React__namespace.default.createElement(reactNative.TouchableWithoutFeedback, {
28489
- key: key,
28490
- onPress: function onPress() {
28491
- onTabPress(key);
28492
- },
28493
- testID: tabItemTestID
28494
- }, /*#__PURE__*/React__namespace.default.createElement(HeaderTabItem, {
28495
- isFirstItem: index === 0
28496
- }, variant === 'highlighted' && /*#__PURE__*/React__namespace.default.createElement(HeaderTabItemOutlineWrapper, null, /*#__PURE__*/React__namespace.default.createElement(HeaderTabItemOutline, {
28497
- themeActive: active,
28498
- style: {
28499
- flex: 1,
28500
- transform: [{
28501
- scaleX: outlineScale
28502
- }]
28503
- }
28504
- })), /*#__PURE__*/React__namespace.default.createElement(HeaderTabItemWrapper, null, /*#__PURE__*/React__namespace.default.createElement(TabWithBadge, {
28505
- config: badge,
28506
- tabItem: tabItem
28507
- })), variant === 'underlined' && /*#__PURE__*/React__namespace.default.createElement(HeaderTabItemIndicator, {
28508
- style: {
28509
- opacity: underlinedOpacity[index],
28510
- transform: [{
28511
- translateX: underlinedTranslateX[index]
28512
- }]
28513
- }
28514
- })));
28665
+ flexDirection: 'row',
28666
+ position: 'relative'
28515
28667
  }
28516
- }));
28668
+ }, isHighlighted && /*#__PURE__*/React__namespace.default.createElement(React__namespace.default.Fragment, null, /*#__PURE__*/React__namespace.default.createElement(HeaderTabPillLeft, {
28669
+ testID: "tab-pill-background",
28670
+ style: pillLeftStyle
28671
+ }), /*#__PURE__*/React__namespace.default.createElement(HeaderTabPillBody, {
28672
+ style: pillBodyStyle
28673
+ }), /*#__PURE__*/React__namespace.default.createElement(HeaderTabPillRight, {
28674
+ testID: "tab-pill-background-right",
28675
+ style: pillRightStyle
28676
+ })), tabs.map(function (tab, index) {
28677
+ return /*#__PURE__*/React__namespace.default.createElement(TabItemComponent, {
28678
+ key: tab.key,
28679
+ tab: tab,
28680
+ index: index,
28681
+ active: selectedIndex === index,
28682
+ variant: variant,
28683
+ onTabPress: onTabPress,
28684
+ onLayout: tabLayoutHandlers[index]
28685
+ });
28686
+ }), isHighlighted ? /*#__PURE__*/React__namespace.default.createElement(HeaderTabItemActiveBorder, {
28687
+ testID: "tab-active-border",
28688
+ style: _objectSpread2({
28689
+ position: 'absolute',
28690
+ bottom: 0
28691
+ }, indicatorStyle)
28692
+ }) : /*#__PURE__*/React__namespace.default.createElement(HeaderTabItemIndicator, {
28693
+ testID: "tab-underline-indicator",
28694
+ style: _objectSpread2({
28695
+ position: 'absolute'
28696
+ }, indicatorStyle)
28697
+ })))));
28517
28698
  };
28518
28699
 
28519
28700
  var useHandlePageScroll = function useHandlePageScroll() {