@hero-design/rn 8.130.3 → 8.131.0
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/CHANGELOG.md +6 -0
- package/es/index.js +405 -224
- package/lib/index.js +405 -224
- package/package.json +1 -1
- package/src/components/Tabs/ScrollableTabsHeader/ScrollableTabsHeader.tsx +217 -131
- package/src/components/Tabs/ScrollableTabsHeader/hooks/useIndicatorAnimation.ts +242 -0
- package/src/components/Tabs/StyledScrollableTabs.tsx +68 -21
- package/src/components/Tabs/index.tsx +2 -0
- package/src/theme/components/tabs.ts +9 -2
- package/types/components/Tabs/ScrollableTabsHeader/ScrollableTabsHeader.d.ts +1 -1
- package/types/components/Tabs/ScrollableTabsHeader/hooks/useIndicatorAnimation.d.ts +75 -0
- package/types/components/Tabs/StyledScrollableTabs.d.ts +13 -8
- package/types/components/Tabs/index.d.ts +3 -1
- package/types/theme/components/tabs.d.ts +9 -2
- package/src/components/Tabs/ScrollableTabsHeader/hooks/useInitHighlightedAnimation.ts +0 -45
- package/types/components/Tabs/ScrollableTabsHeader/hooks/useInitHighlightedAnimation.d.ts +0 -9
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
|
-
|
|
7178
|
-
|
|
7181
|
+
tabIndicatorBottom: -theme.space.xxsmall,
|
|
7182
|
+
highlightedItemMargin: theme.space.xsmall,
|
|
7183
|
+
highlightedBarTopPadding: theme.space.xxsmall
|
|
7179
7184
|
};
|
|
7180
7185
|
var radii = {
|
|
7181
|
-
|
|
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
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
28154
|
-
|
|
28155
|
-
|
|
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
|
|
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
|
-
|
|
28162
|
-
|
|
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
|
|
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 (
|
|
28177
|
-
var theme =
|
|
28225
|
+
var HeaderTabItemIndicator = index$c(reactNative.Animated.View)(function (_ref8) {
|
|
28226
|
+
var theme = _ref8.theme;
|
|
28178
28227
|
return {
|
|
28179
|
-
width:
|
|
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
|
-
|
|
28261
|
-
|
|
28262
|
-
|
|
28263
|
-
|
|
28264
|
-
|
|
28265
|
-
|
|
28266
|
-
|
|
28267
|
-
|
|
28268
|
-
|
|
28269
|
-
|
|
28270
|
-
|
|
28271
|
-
|
|
28272
|
-
|
|
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
|
-
|
|
28275
|
-
|
|
28276
|
-
|
|
28277
|
-
|
|
28278
|
-
|
|
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
|
-
|
|
28282
|
-
return;
|
|
28283
|
-
}
|
|
28284
|
-
var animation = reactNative.Animated.parallel(_toConsumableArray(Array.from({
|
|
28368
|
+
layoutsRef.current = Array.from({
|
|
28285
28369
|
length: tabsLength
|
|
28286
|
-
}
|
|
28287
|
-
return
|
|
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
|
|
28341
|
-
|
|
28342
|
-
|
|
28343
|
-
|
|
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 (
|
|
28348
|
-
|
|
28349
|
-
|
|
28350
|
-
|
|
28351
|
-
|
|
28352
|
-
|
|
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;
|
|
28358
28434
|
}
|
|
28359
|
-
}, [selectedIndex,
|
|
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;
|
|
28451
|
+
}
|
|
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
|
-
|
|
28362
|
-
|
|
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 ? '
|
|
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
|
|
28394
|
-
var
|
|
28395
|
-
|
|
28396
|
-
|
|
28397
|
-
|
|
28398
|
-
|
|
28399
|
-
|
|
28400
|
-
|
|
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
|
-
} :
|
|
28406
|
-
|
|
28407
|
-
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
|
|
28410
|
-
|
|
28411
|
-
var
|
|
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
|
-
|
|
28600
|
+
pillCapWidth: theme.__hd__.tabs.radii.highlightedOutline
|
|
28423
28601
|
}),
|
|
28424
|
-
|
|
28425
|
-
|
|
28426
|
-
|
|
28427
|
-
|
|
28428
|
-
|
|
28429
|
-
|
|
28430
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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:
|
|
28439
|
-
}, /*#__PURE__*/React__namespace.default.createElement(reactNative.
|
|
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
|
-
|
|
28449
|
-
|
|
28450
|
-
|
|
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
|
-
|
|
28460
|
-
|
|
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() {
|