react-native-tab-view 3.5.2 → 4.0.0-alpha.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.
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
- import type { Layout, NavigationState, PagerProps, Route, SceneRendererProps } from './types';
4
- export type Props<T extends Route> = PagerProps & {
3
+ import type { Layout, LocaleDirection, NavigationState, PagerProps, Route, SceneRendererProps } from './types';
4
+ export type Props<T extends Route> = Omit<PagerProps, 'layoutDirection'> & {
5
5
  onIndexChange: (index: number) => void;
6
6
  navigationState: NavigationState<T>;
7
7
  renderScene: (props: SceneRendererProps & {
@@ -20,8 +20,9 @@ export type Props<T extends Route> = PagerProps & {
20
20
  }) => boolean) | boolean;
21
21
  lazyPreloadDistance?: number;
22
22
  sceneContainerStyle?: StyleProp<ViewStyle>;
23
+ direction?: LocaleDirection;
23
24
  pagerStyle?: StyleProp<ViewStyle>;
24
25
  style?: StyleProp<ViewStyle>;
25
26
  };
26
- export declare function TabView<T extends Route>({ onIndexChange, navigationState, renderScene, initialLayout, keyboardDismissMode, lazy, lazyPreloadDistance, onSwipeStart, onSwipeEnd, renderLazyPlaceholder, renderTabBar, sceneContainerStyle, pagerStyle, style, swipeEnabled, tabBarPosition, animationEnabled, overScrollMode, }: Props<T>): JSX.Element;
27
+ export declare function TabView<T extends Route>({ onIndexChange, navigationState, renderScene, initialLayout, keyboardDismissMode, lazy, lazyPreloadDistance, onSwipeStart, onSwipeEnd, renderLazyPlaceholder, renderTabBar, sceneContainerStyle, pagerStyle, style, direction, swipeEnabled, tabBarPosition, animationEnabled, overScrollMode, }: Props<T>): JSX.Element;
27
28
  //# sourceMappingURL=TabView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TabView.d.ts","sourceRoot":"","sources":["../../../src/TabView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAEL,SAAS,EAGT,SAAS,EACV,MAAM,cAAc,CAAC;AAKtB,OAAO,KAAK,EACV,MAAM,EACN,eAAe,EACf,UAAU,EACV,KAAK,EACL,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,KAAK,IAAI,UAAU,GAAG;IAChD,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IACpC,WAAW,EAAE,CAAC,KAAK,EAAE,kBAAkB,GAAG;QAAE,KAAK,EAAE,CAAC,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;IAC3E,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;IACjE,YAAY,CAAC,EAAE,CACb,KAAK,EAAE,kBAAkB,GAAG;QAAE,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC,CAAA;KAAE,KAChE,KAAK,CAAC,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,OAAO,CAAC;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3C,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,EAAE,EACvC,aAAa,EACb,eAAe,EACf,WAAW,EACX,aAAa,EACb,mBAA4B,EAC5B,IAAY,EACZ,mBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,qBAAkC,EAClC,YAA+C,EAC/C,mBAAmB,EACnB,UAAU,EACV,KAAK,EACL,YAAmB,EACnB,cAAsB,EACtB,gBAAuB,EACvB,cAAc,GACf,EAAE,KAAK,CAAC,CAAC,CAAC,eA2FV"}
1
+ {"version":3,"file":"TabView.d.ts","sourceRoot":"","sources":["../../../src/TabView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAIL,SAAS,EAGT,SAAS,EACV,MAAM,cAAc,CAAC;AAKtB,OAAO,KAAK,EACV,MAAM,EACN,eAAe,EACf,eAAe,EACf,UAAU,EACV,KAAK,EACL,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,GAAG;IACzE,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IACpC,WAAW,EAAE,CAAC,KAAK,EAAE,kBAAkB,GAAG;QAAE,KAAK,EAAE,CAAC,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;IAC3E,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;IACjE,YAAY,CAAC,EAAE,CACb,KAAK,EAAE,kBAAkB,GAAG;QAAE,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC,CAAA;KAAE,KAChE,KAAK,CAAC,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,OAAO,CAAC;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAEF,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,EAAE,EACvC,aAAa,EACb,eAAe,EACf,WAAW,EACX,aAAa,EACb,mBAA4B,EAC5B,IAAY,EACZ,mBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,qBAAkC,EAClC,YAA+C,EAC/C,mBAAmB,EACnB,UAAU,EACV,KAAK,EACL,SAA4D,EAC5D,YAAmB,EACnB,cAAsB,EACtB,gBAAuB,EACvB,cAAc,GACf,EAAE,KAAK,CAAC,CAAC,CAAC,eAuGV"}
@@ -1,5 +1,6 @@
1
1
  import type { Animated } from 'react-native';
2
2
  import type { PagerViewProps } from 'react-native-pager-view';
3
+ export type LocaleDirection = 'ltr' | 'rtl';
3
4
  export type Route = {
4
5
  key: string;
5
6
  icon?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,IAAI,IAAI,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,KAAK,IAAI;IACnC,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,KAAK,IAAI;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,CAAC,EAAE,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gBAAgB,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,IAAI,CAC3B,cAAc,EACZ,aAAa,GACb,eAAe,GACf,cAAc,GACd,gBAAgB,GAChB,0BAA0B,GAC1B,qBAAqB,GACrB,UAAU,CACb,GAAG;IACF,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,CAAC;AAE5C,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,IAAI,IAAI,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,KAAK,IAAI;IACnC,KAAK,EAAE,CAAC,CAAC;CACV,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,KAAK,IAAI;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,CAAC,EAAE,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gBAAgB,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,IAAI,CAC3B,cAAc,EACZ,aAAa,GACb,eAAe,GACf,cAAc,GACd,gBAAgB,GAChB,0BAA0B,GAC1B,qBAAqB,GACrB,UAAU,CACb,GAAG;IACF,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-native-tab-view",
3
3
  "description": "Tab view component for React Native",
4
- "version": "3.5.2",
4
+ "version": "4.0.0-alpha.0",
5
5
  "keywords": [
6
6
  "react-native-component",
7
7
  "react-component",
@@ -45,7 +45,7 @@
45
45
  "del-cli": "^5.0.0",
46
46
  "react": "18.2.0",
47
47
  "react-native": "0.71.8",
48
- "react-native-builder-bob": "^0.20.4",
48
+ "react-native-builder-bob": "^0.21.0",
49
49
  "react-native-pager-view": "6.1.2",
50
50
  "typescript": "^4.9.4"
51
51
  },
@@ -68,5 +68,5 @@
68
68
  ]
69
69
  ]
70
70
  },
71
- "gitHead": "36c8f091556157dab74d3483651bbcbd340762ff"
71
+ "gitHead": "ddc5705b979d62bb3a293f221ec954cbad3dfba4"
72
72
  }
@@ -2,7 +2,6 @@ import * as React from 'react';
2
2
  import {
3
3
  Animated,
4
4
  GestureResponderEvent,
5
- I18nManager,
6
5
  Keyboard,
7
6
  PanResponder,
8
7
  PanResponderGestureState,
@@ -60,6 +59,7 @@ export function PanResponderAdapter<T extends Route>({
60
59
  children,
61
60
  style,
62
61
  animationEnabled = false,
62
+ layoutDirection = 'ltr',
63
63
  }: Props<T>) {
64
64
  const { routes, index } = navigationState;
65
65
 
@@ -147,7 +147,8 @@ export function PanResponderAdapter<T extends Route>({
147
147
  return false;
148
148
  }
149
149
 
150
- const diffX = I18nManager.isRTL ? -gestureState.dx : gestureState.dx;
150
+ const diffX =
151
+ layoutDirection === 'rtl' ? -gestureState.dx : gestureState.dx;
151
152
 
152
153
  return (
153
154
  isMovingHorizontally(event, gestureState) &&
@@ -172,7 +173,8 @@ export function PanResponderAdapter<T extends Route>({
172
173
  _: GestureResponderEvent,
173
174
  gestureState: PanResponderGestureState
174
175
  ) => {
175
- const diffX = I18nManager.isRTL ? -gestureState.dx : gestureState.dx;
176
+ const diffX =
177
+ layoutDirection === 'rtl' ? -gestureState.dx : gestureState.dx;
176
178
 
177
179
  if (
178
180
  // swiping left
@@ -222,7 +224,7 @@ export function PanResponderAdapter<T extends Route>({
222
224
  Math.min(
223
225
  Math.max(
224
226
  0,
225
- I18nManager.isRTL
227
+ layoutDirection === 'rtl'
226
228
  ? currentIndex + gestureState.dx / Math.abs(gestureState.dx)
227
229
  : currentIndex - gestureState.dx / Math.abs(gestureState.dx)
228
230
  ),
@@ -281,7 +283,7 @@ export function PanResponderAdapter<T extends Route>({
281
283
  outputRange: [-maxTranslate, 0],
282
284
  extrapolate: 'clamp',
283
285
  }),
284
- I18nManager.isRTL ? -1 : 1
286
+ layoutDirection === 'rtl' ? -1 : 1
285
287
  );
286
288
 
287
289
  const position = React.useMemo(
package/src/TabBar.tsx CHANGED
@@ -21,6 +21,7 @@ import { Props as TabBarItemProps, TabBarItem } from './TabBarItem';
21
21
  import type {
22
22
  Event,
23
23
  Layout,
24
+ LocaleDirection,
24
25
  NavigationState,
25
26
  Route,
26
27
  Scene,
@@ -65,12 +66,14 @@ export type Props<T extends Route> = SceneRendererProps & {
65
66
  labelStyle?: StyleProp<TextStyle>;
66
67
  contentContainerStyle?: StyleProp<ViewStyle>;
67
68
  style?: StyleProp<ViewStyle>;
69
+ direction?: LocaleDirection;
68
70
  gap?: number;
69
71
  testID?: string;
70
72
  android_ripple?: PressableAndroidRippleConfig;
71
73
  };
72
74
 
73
75
  type FlattenedTabWidth = string | number | undefined;
76
+ type FlattenedTabPadding = string | number | undefined;
74
77
 
75
78
  const Separator = ({ width }: { width: number }) => {
76
79
  return <View style={{ width }} />;
@@ -82,13 +85,50 @@ const getFlattenedTabWidth = (style: StyleProp<ViewStyle>) => {
82
85
  return tabStyle?.width;
83
86
  };
84
87
 
88
+ const getFlattenedPaddingLeft = (style: StyleProp<ViewStyle>) => {
89
+ const flattenStyle = StyleSheet.flatten(style);
90
+
91
+ return flattenStyle
92
+ ? flattenStyle.paddingLeft || flattenStyle.paddingHorizontal || 0
93
+ : 0;
94
+ };
95
+
96
+ const getFlattenedPaddingRight = (style: StyleProp<ViewStyle>) => {
97
+ const flattenStyle = StyleSheet.flatten(style);
98
+
99
+ return flattenStyle
100
+ ? flattenStyle.paddingRight || flattenStyle.paddingHorizontal || 0
101
+ : 0;
102
+ };
103
+
104
+ const convertPaddingPercentToSize = (
105
+ value: FlattenedTabPadding,
106
+ layout: Layout
107
+ ): number => {
108
+ switch (typeof value) {
109
+ case 'number':
110
+ return value;
111
+ case 'string':
112
+ if (value.endsWith('%')) {
113
+ const width = parseFloat(value);
114
+ if (Number.isFinite(width)) {
115
+ return layout.width * (width / 100);
116
+ }
117
+ }
118
+ }
119
+ return 0;
120
+ };
121
+
85
122
  const getComputedTabWidth = (
86
123
  index: number,
87
124
  layout: Layout,
88
125
  routes: Route[],
89
126
  scrollEnabled: boolean | undefined,
90
127
  tabWidths: { [key: string]: number },
91
- flattenedWidth: FlattenedTabWidth
128
+ flattenedWidth: FlattenedTabWidth,
129
+ flattenedPaddingLeft: FlattenedTabPadding,
130
+ flattenedPaddingRight: FlattenedTabPadding,
131
+ gap?: number
92
132
  ) => {
93
133
  if (flattenedWidth === 'auto') {
94
134
  return tabWidths[routes[index].key] || 0;
@@ -109,7 +149,13 @@ const getComputedTabWidth = (
109
149
  if (scrollEnabled) {
110
150
  return (layout.width / 5) * 2;
111
151
  }
112
- return layout.width / routes.length;
152
+
153
+ const gapTotalWidth = (gap ?? 0) * (routes.length - 1);
154
+ const paddingTotalWidth =
155
+ convertPaddingPercentToSize(flattenedPaddingLeft, layout) +
156
+ convertPaddingPercentToSize(flattenedPaddingRight, layout);
157
+
158
+ return (layout.width - gapTotalWidth - paddingTotalWidth) / routes.length;
113
159
  };
114
160
 
115
161
  const getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
@@ -117,13 +163,14 @@ const getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
117
163
 
118
164
  const getTranslateX = (
119
165
  scrollAmount: Animated.Value,
120
- maxScrollDistance: number
166
+ maxScrollDistance: number,
167
+ direction: LocaleDirection
121
168
  ) =>
122
169
  Animated.multiply(
123
- Platform.OS === 'android' && I18nManager.isRTL
170
+ Platform.OS === 'android' && direction === 'rtl'
124
171
  ? Animated.add(maxScrollDistance, Animated.multiply(scrollAmount, -1))
125
172
  : scrollAmount,
126
- I18nManager.isRTL ? 1 : -1
173
+ direction === 'rtl' ? 1 : -1
127
174
  );
128
175
 
129
176
  const getTabBarWidth = <T extends Route>({
@@ -132,13 +179,23 @@ const getTabBarWidth = <T extends Route>({
132
179
  gap,
133
180
  scrollEnabled,
134
181
  flattenedTabWidth,
182
+ flattenedPaddingLeft,
183
+ flattenedPaddingRight,
135
184
  tabWidths,
136
185
  }: Pick<Props<T>, 'navigationState' | 'gap' | 'layout' | 'scrollEnabled'> & {
137
186
  tabWidths: Record<string, number>;
187
+ flattenedPaddingLeft: FlattenedTabPadding;
188
+ flattenedPaddingRight: FlattenedTabPadding;
138
189
  flattenedTabWidth: FlattenedTabWidth;
139
190
  }) => {
140
191
  const { routes } = navigationState;
141
192
 
193
+ const paddingsWidth = Math.max(
194
+ 0,
195
+ convertPaddingPercentToSize(flattenedPaddingLeft, layout) +
196
+ convertPaddingPercentToSize(flattenedPaddingRight, layout)
197
+ );
198
+
142
199
  return routes.reduce<number>(
143
200
  (acc, _, i) =>
144
201
  acc +
@@ -149,9 +206,12 @@ const getTabBarWidth = <T extends Route>({
149
206
  routes,
150
207
  scrollEnabled,
151
208
  tabWidths,
152
- flattenedTabWidth
209
+ flattenedTabWidth,
210
+ flattenedPaddingLeft,
211
+ flattenedPaddingRight,
212
+ gap
153
213
  ),
154
- 0
214
+ paddingsWidth
155
215
  );
156
216
  };
157
217
 
@@ -163,10 +223,16 @@ const normalizeScrollValue = <T extends Route>({
163
223
  tabWidths,
164
224
  value,
165
225
  flattenedTabWidth,
226
+ flattenedPaddingLeft,
227
+ flattenedPaddingRight,
228
+ direction,
166
229
  }: Pick<Props<T>, 'layout' | 'navigationState' | 'gap' | 'scrollEnabled'> & {
167
230
  tabWidths: Record<string, number>;
168
231
  value: number;
169
232
  flattenedTabWidth: FlattenedTabWidth;
233
+ flattenedPaddingLeft: FlattenedTabPadding;
234
+ flattenedPaddingRight: FlattenedTabPadding;
235
+ direction: LocaleDirection;
170
236
  }) => {
171
237
  const tabBarWidth = getTabBarWidth({
172
238
  layout,
@@ -175,11 +241,13 @@ const normalizeScrollValue = <T extends Route>({
175
241
  gap,
176
242
  scrollEnabled,
177
243
  flattenedTabWidth,
244
+ flattenedPaddingLeft,
245
+ flattenedPaddingRight,
178
246
  });
179
247
  const maxDistance = getMaxScrollDistance(tabBarWidth, layout.width);
180
248
  const scrollValue = Math.max(Math.min(value, maxDistance), 0);
181
249
 
182
- if (Platform.OS === 'android' && I18nManager.isRTL) {
250
+ if (Platform.OS === 'android' && direction === 'rtl') {
183
251
  // On Android, scroll value is not applied in reverse in RTL
184
252
  // so we need to manually adjust it to apply correct value
185
253
  return maxDistance - scrollValue;
@@ -195,10 +263,21 @@ const getScrollAmount = <T extends Route>({
195
263
  scrollEnabled,
196
264
  flattenedTabWidth,
197
265
  tabWidths,
266
+ flattenedPaddingLeft,
267
+ flattenedPaddingRight,
268
+ direction,
198
269
  }: Pick<Props<T>, 'layout' | 'navigationState' | 'scrollEnabled' | 'gap'> & {
199
270
  tabWidths: Record<string, number>;
200
271
  flattenedTabWidth: FlattenedTabWidth;
272
+ flattenedPaddingLeft: FlattenedTabPadding;
273
+ flattenedPaddingRight: FlattenedTabPadding;
274
+ direction: LocaleDirection;
201
275
  }) => {
276
+ const paddingInitial =
277
+ direction === 'rtl'
278
+ ? convertPaddingPercentToSize(flattenedPaddingRight, layout)
279
+ : convertPaddingPercentToSize(flattenedPaddingLeft, layout);
280
+
202
281
  const centerDistance = Array.from({
203
282
  length: navigationState.index + 1,
204
283
  }).reduce<number>((total, _, i) => {
@@ -208,18 +287,20 @@ const getScrollAmount = <T extends Route>({
208
287
  navigationState.routes,
209
288
  scrollEnabled,
210
289
  tabWidths,
211
- flattenedTabWidth
290
+ flattenedTabWidth,
291
+ flattenedPaddingLeft,
292
+ flattenedPaddingRight,
293
+ gap
212
294
  );
213
295
 
214
296
  // To get the current index centered we adjust scroll amount by width of indexes
215
297
  // 0 through (i - 1) and add half the width of current index i
216
298
  return (
217
299
  total +
218
- (navigationState.index === i
219
- ? (tabWidth + (gap ?? 0)) / 2
220
- : tabWidth + (gap ?? 0))
300
+ (i > 0 ? gap ?? 0 : 0) +
301
+ (navigationState.index === i ? tabWidth / 2 : tabWidth)
221
302
  );
222
- }, 0);
303
+ }, paddingInitial);
223
304
 
224
305
  const scrollAmount = centerDistance - layout.width / 2;
225
306
 
@@ -231,6 +312,9 @@ const getScrollAmount = <T extends Route>({
231
312
  gap,
232
313
  scrollEnabled,
233
314
  flattenedTabWidth,
315
+ flattenedPaddingLeft,
316
+ flattenedPaddingRight,
317
+ direction,
234
318
  });
235
319
  };
236
320
 
@@ -281,22 +365,27 @@ export function TabBar<T extends Route>({
281
365
  renderBadge,
282
366
  renderIcon,
283
367
  renderLabel,
368
+ direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
284
369
  renderTabBarItem,
285
370
  style,
286
371
  tabStyle,
372
+ layout: propLayout,
287
373
  testID,
288
374
  android_ripple,
289
375
  }: Props<T>) {
290
- const [layout, setLayout] = React.useState<Layout>({ width: 0, height: 0 });
376
+ const [layout, setLayout] = React.useState<Layout>(
377
+ propLayout ?? { width: 0, height: 0 }
378
+ );
291
379
  const [tabWidths, setTabWidths] = React.useState<Record<string, number>>({});
292
380
  const flatListRef = React.useRef<FlatList | null>(null);
293
381
  const isFirst = React.useRef(true);
294
382
  const scrollAmount = useAnimatedValue(0);
295
383
  const measuredTabWidths = React.useRef<Record<string, number>>({});
296
-
297
384
  const { routes } = navigationState;
298
385
  const flattenedTabWidth = getFlattenedTabWidth(tabStyle);
299
386
  const isWidthDynamic = flattenedTabWidth === 'auto';
387
+ const flattenedPaddingRight = getFlattenedPaddingRight(contentContainerStyle);
388
+ const flattenedPaddingLeft = getFlattenedPaddingLeft(contentContainerStyle);
300
389
  const scrollOffset = getScrollAmount({
301
390
  layout,
302
391
  navigationState,
@@ -304,6 +393,9 @@ export function TabBar<T extends Route>({
304
393
  gap,
305
394
  scrollEnabled,
306
395
  flattenedTabWidth,
396
+ flattenedPaddingLeft,
397
+ flattenedPaddingRight,
398
+ direction,
307
399
  });
308
400
 
309
401
  const hasMeasuredTabWidths =
@@ -347,19 +439,25 @@ export function TabBar<T extends Route>({
347
439
  gap,
348
440
  scrollEnabled,
349
441
  flattenedTabWidth,
442
+ flattenedPaddingLeft,
443
+ flattenedPaddingRight,
350
444
  });
351
445
 
352
446
  const separatorsWidth = Math.max(0, routes.length - 1) * gap;
353
- const separatorPercent = (separatorsWidth / tabBarWidth) * 100;
354
- const tabBarWidthPercent = `${routes.length * 40}%`;
447
+ const paddingsWidth = Math.max(
448
+ 0,
449
+ convertPaddingPercentToSize(flattenedPaddingLeft, layout) +
450
+ convertPaddingPercentToSize(flattenedPaddingRight, layout)
451
+ );
355
452
 
356
453
  const translateX = React.useMemo(
357
454
  () =>
358
455
  getTranslateX(
359
456
  scrollAmount,
360
- getMaxScrollDistance(tabBarWidth, layout.width)
457
+ getMaxScrollDistance(tabBarWidth, layout.width),
458
+ direction
361
459
  ),
362
- [layout.width, scrollAmount, tabBarWidth]
460
+ [direction, layout.width, scrollAmount, tabBarWidth]
363
461
  );
364
462
 
365
463
  const renderItem = React.useCallback(
@@ -436,7 +534,10 @@ export function TabBar<T extends Route>({
436
534
  routes,
437
535
  scrollEnabled,
438
536
  tabWidths,
439
- getFlattenedTabWidth(tabStyle)
537
+ getFlattenedTabWidth(tabStyle),
538
+ getFlattenedPaddingRight(contentContainerStyle),
539
+ getFlattenedPaddingLeft(contentContainerStyle),
540
+ gap
440
541
  )
441
542
  : undefined,
442
543
  android_ripple,
@@ -479,6 +580,7 @@ export function TabBar<T extends Route>({
479
580
  routes,
480
581
  scrollEnabled,
481
582
  tabStyle,
583
+ contentContainerStyle,
482
584
  tabWidths,
483
585
  ]
484
586
  );
@@ -488,21 +590,10 @@ export function TabBar<T extends Route>({
488
590
  const contentContainerStyleMemoized = React.useMemo(
489
591
  () => [
490
592
  styles.tabContent,
491
- scrollEnabled
492
- ? {
493
- width:
494
- tabBarWidth > separatorsWidth ? tabBarWidth : tabBarWidthPercent,
495
- }
496
- : styles.container,
593
+ scrollEnabled ? { width: tabBarWidth } : null,
497
594
  contentContainerStyle,
498
595
  ],
499
- [
500
- contentContainerStyle,
501
- scrollEnabled,
502
- separatorsWidth,
503
- tabBarWidth,
504
- tabBarWidthPercent,
505
- ]
596
+ [contentContainerStyle, scrollEnabled, tabBarWidth]
506
597
  );
507
598
 
508
599
  const handleScroll = React.useMemo(
@@ -546,11 +637,7 @@ export function TabBar<T extends Route>({
546
637
  style={[
547
638
  styles.indicatorContainer,
548
639
  scrollEnabled ? { transform: [{ translateX }] as any } : null,
549
- tabBarWidth > separatorsWidth
550
- ? { width: tabBarWidth - separatorsWidth }
551
- : scrollEnabled
552
- ? { width: tabBarWidthPercent }
553
- : null,
640
+ scrollEnabled ? { width: tabBarWidth } : null,
554
641
  indicatorContainerStyle,
555
642
  ]}
556
643
  >
@@ -559,10 +646,17 @@ export function TabBar<T extends Route>({
559
646
  layout,
560
647
  navigationState,
561
648
  jumpTo,
649
+ direction,
562
650
  width: isWidthDynamic
563
651
  ? 'auto'
564
- : `${(100 - separatorPercent) / routes.length}%`,
565
- style: indicatorStyle,
652
+ : Math.max(
653
+ 0,
654
+ (tabBarWidth - separatorsWidth - paddingsWidth) / routes.length
655
+ ),
656
+ style: [
657
+ indicatorStyle,
658
+ { left: flattenedPaddingLeft, right: flattenedPaddingRight },
659
+ ],
566
660
  getTabWidth: (i: number) =>
567
661
  getComputedTabWidth(
568
662
  i,
@@ -570,7 +664,10 @@ export function TabBar<T extends Route>({
570
664
  routes,
571
665
  scrollEnabled,
572
666
  tabWidths,
573
- flattenedTabWidth
667
+ flattenedTabWidth,
668
+ flattenedPaddingRight,
669
+ flattenedPaddingLeft,
670
+ gap
574
671
  ),
575
672
  gap,
576
673
  })}
@@ -605,9 +702,6 @@ export function TabBar<T extends Route>({
605
702
  }
606
703
 
607
704
  const styles = StyleSheet.create({
608
- container: {
609
- flex: 1,
610
- },
611
705
  scroll: {
612
706
  overflow: Platform.select({ default: 'scroll', web: undefined }),
613
707
  },
@@ -624,6 +718,7 @@ const styles = StyleSheet.create({
624
718
  zIndex: 1,
625
719
  },
626
720
  tabContent: {
721
+ flexGrow: 1,
627
722
  flexDirection: 'row',
628
723
  flexWrap: 'nowrap',
629
724
  },
@@ -2,14 +2,18 @@ import * as React from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
5
- I18nManager,
6
5
  Platform,
7
6
  StyleProp,
8
7
  StyleSheet,
9
8
  ViewStyle,
10
9
  } from 'react-native';
11
10
 
12
- import type { NavigationState, Route, SceneRendererProps } from './types';
11
+ import type {
12
+ LocaleDirection,
13
+ NavigationState,
14
+ Route,
15
+ SceneRendererProps,
16
+ } from './types';
13
17
  import { useAnimatedValue } from './useAnimatedValue';
14
18
 
15
19
  export type GetTabWidth = (index: number) => number;
@@ -17,15 +21,18 @@ export type GetTabWidth = (index: number) => number;
17
21
  export type Props<T extends Route> = SceneRendererProps & {
18
22
  navigationState: NavigationState<T>;
19
23
  width: string | number;
20
- style?: StyleProp<ViewStyle>;
21
24
  getTabWidth: GetTabWidth;
25
+ direction: LocaleDirection;
26
+ style?: StyleProp<ViewStyle>;
22
27
  gap?: number;
28
+ children?: React.ReactNode;
23
29
  };
24
30
 
25
31
  const getTranslateX = (
26
32
  position: Animated.AnimatedInterpolation<number>,
27
33
  routes: Route[],
28
34
  getTabWidth: GetTabWidth,
35
+ direction: LocaleDirection,
29
36
  gap?: number
30
37
  ) => {
31
38
  const inputRange = routes.map((_, i) => i);
@@ -42,7 +49,7 @@ const getTranslateX = (
42
49
  extrapolate: 'clamp',
43
50
  });
44
51
 
45
- return Animated.multiply(translateX, I18nManager.isRTL ? -1 : 1);
52
+ return Animated.multiply(translateX, direction === 'rtl' ? -1 : 1);
46
53
  };
47
54
 
48
55
  export function TabBarIndicator<T extends Route>({
@@ -51,8 +58,10 @@ export function TabBarIndicator<T extends Route>({
51
58
  navigationState,
52
59
  position,
53
60
  width,
61
+ direction,
54
62
  gap,
55
63
  style,
64
+ children,
56
65
  }: Props<T>) {
57
66
  const isIndicatorShown = React.useRef(false);
58
67
  const isWidthDynamic = width === 'auto';
@@ -96,7 +105,9 @@ export function TabBarIndicator<T extends Route>({
96
105
 
97
106
  if (layout.width) {
98
107
  const translateX =
99
- routes.length > 1 ? getTranslateX(position, routes, getTabWidth, gap) : 0;
108
+ routes.length > 1
109
+ ? getTranslateX(position, routes, getTabWidth, direction, gap)
110
+ : 0;
100
111
 
101
112
  transform.push({ translateX });
102
113
  }
@@ -116,7 +127,7 @@ export function TabBarIndicator<T extends Route>({
116
127
  })
117
128
  : outputRange[0],
118
129
  },
119
- { translateX: 0.5 }
130
+ { translateX: direction === 'rtl' ? -0.5 : 0.5 }
120
131
  );
121
132
  }
122
133
 
@@ -136,7 +147,9 @@ export function TabBarIndicator<T extends Route>({
136
147
  width === 'auto' ? { opacity: opacity } : null,
137
148
  style,
138
149
  ]}
139
- />
150
+ >
151
+ {children}
152
+ </Animated.View>
140
153
  );
141
154
  }
142
155
 
package/src/TabView.tsx CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import {
3
+ I18nManager,
3
4
  LayoutChangeEvent,
5
+ Platform,
4
6
  StyleProp,
5
7
  StyleSheet,
6
8
  View,
@@ -12,13 +14,14 @@ import { SceneView } from './SceneView';
12
14
  import { TabBar } from './TabBar';
13
15
  import type {
14
16
  Layout,
17
+ LocaleDirection,
15
18
  NavigationState,
16
19
  PagerProps,
17
20
  Route,
18
21
  SceneRendererProps,
19
22
  } from './types';
20
23
 
21
- export type Props<T extends Route> = PagerProps & {
24
+ export type Props<T extends Route> = Omit<PagerProps, 'layoutDirection'> & {
22
25
  onIndexChange: (index: number) => void;
23
26
  navigationState: NavigationState<T>;
24
27
  renderScene: (props: SceneRendererProps & { route: T }) => React.ReactNode;
@@ -31,6 +34,7 @@ export type Props<T extends Route> = PagerProps & {
31
34
  lazy?: ((props: { route: T }) => boolean) | boolean;
32
35
  lazyPreloadDistance?: number;
33
36
  sceneContainerStyle?: StyleProp<ViewStyle>;
37
+ direction?: LocaleDirection;
34
38
  pagerStyle?: StyleProp<ViewStyle>;
35
39
  style?: StyleProp<ViewStyle>;
36
40
  };
@@ -50,11 +54,23 @@ export function TabView<T extends Route>({
50
54
  sceneContainerStyle,
51
55
  pagerStyle,
52
56
  style,
57
+ direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
53
58
  swipeEnabled = true,
54
59
  tabBarPosition = 'top',
55
60
  animationEnabled = true,
56
61
  overScrollMode,
57
62
  }: Props<T>) {
63
+ if (
64
+ Platform.OS !== 'web' &&
65
+ direction !== (I18nManager.getConstants().isRTL ? 'rtl' : 'ltr')
66
+ ) {
67
+ console.warn(
68
+ `The 'direction' prop is set to '${direction}' but the effective value is '${
69
+ I18nManager.getConstants().isRTL ? 'rtl' : 'ltr'
70
+ }'. This is not supported. Please use I18nManager.forceRTL to change the layout direction.`
71
+ );
72
+ }
73
+
58
74
  const [layout, setLayout] = React.useState({
59
75
  width: 0,
60
76
  height: 0,
@@ -92,6 +108,7 @@ export function TabView<T extends Route>({
92
108
  animationEnabled={animationEnabled}
93
109
  overScrollMode={overScrollMode}
94
110
  style={pagerStyle}
111
+ layoutDirection={direction}
95
112
  >
96
113
  {({ position, render, addEnterListener, jumpTo }) => {
97
114
  // All of the props here must not change between re-renders