react-native-tab-view 5.0.0-alpha.0 → 5.0.0-alpha.10
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/module/DefaultAdapter.android.js +4 -0
- package/lib/module/DefaultAdapter.android.js.map +1 -0
- package/lib/module/DefaultAdapter.ios.js +4 -0
- package/lib/module/DefaultAdapter.ios.js.map +1 -0
- package/lib/module/DefaultAdapter.js +5 -0
- package/lib/module/DefaultAdapter.js.map +1 -0
- package/lib/module/PagerViewAdapter.native.js +29 -13
- package/lib/module/PagerViewAdapter.native.js.map +1 -1
- package/lib/module/PanResponderAdapter.js +3 -0
- package/lib/module/PanResponderAdapter.js.map +1 -1
- package/lib/module/PlatformPressable.js +1 -1
- package/lib/module/PlatformPressable.js.map +1 -1
- package/lib/module/SceneMap.js +12 -6
- package/lib/module/SceneMap.js.map +1 -1
- package/lib/module/SceneView.js +25 -1
- package/lib/module/SceneView.js.map +1 -1
- package/lib/module/ScrollViewAdapter.js +49 -18
- package/lib/module/ScrollViewAdapter.js.map +1 -1
- package/lib/module/TabBar.js +279 -169
- package/lib/module/TabBar.js.map +1 -1
- package/lib/module/TabBarIndicator.js +314 -90
- package/lib/module/TabBarIndicator.js.map +1 -1
- package/lib/module/TabBarItem.js +94 -44
- package/lib/module/TabBarItem.js.map +1 -1
- package/lib/module/TabBarItemLabel.js +3 -2
- package/lib/module/TabBarItemLabel.js.map +1 -1
- package/lib/module/TabView.js +2 -2
- package/lib/module/TabView.js.map +1 -1
- package/lib/module/constants.js +10 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/useLayoutWidths.js +46 -0
- package/lib/module/useLayoutWidths.js.map +1 -0
- package/lib/typescript/src/DefaultAdapter.android.d.ts +2 -0
- package/lib/typescript/src/DefaultAdapter.android.d.ts.map +1 -0
- package/lib/typescript/src/DefaultAdapter.d.ts +6 -0
- package/lib/typescript/src/DefaultAdapter.d.ts.map +1 -0
- package/lib/typescript/src/DefaultAdapter.ios.d.ts +2 -0
- package/lib/typescript/src/DefaultAdapter.ios.d.ts.map +1 -0
- package/lib/typescript/src/PagerViewAdapter.d.ts +1 -1
- package/lib/typescript/src/PagerViewAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PagerViewAdapter.native.d.ts +2 -2
- package/lib/typescript/src/PagerViewAdapter.native.d.ts.map +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts +1 -1
- package/lib/typescript/src/PanResponderAdapter.d.ts.map +1 -1
- package/lib/typescript/src/PlatformPressable.d.ts +5 -5
- package/lib/typescript/src/PlatformPressable.d.ts.map +1 -1
- package/lib/typescript/src/SceneMap.d.ts +2 -2
- package/lib/typescript/src/SceneMap.d.ts.map +1 -1
- package/lib/typescript/src/SceneView.d.ts +2 -2
- package/lib/typescript/src/SceneView.d.ts.map +1 -1
- package/lib/typescript/src/ScrollViewAdapter.d.ts +2 -3
- package/lib/typescript/src/ScrollViewAdapter.d.ts.map +1 -1
- package/lib/typescript/src/TabBar.d.ts +26 -25
- package/lib/typescript/src/TabBar.d.ts.map +1 -1
- package/lib/typescript/src/TabBarIndicator.d.ts +5 -7
- package/lib/typescript/src/TabBarIndicator.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItem.d.ts +17 -10
- package/lib/typescript/src/TabBarItem.d.ts.map +1 -1
- package/lib/typescript/src/TabBarItemLabel.d.ts +4 -3
- package/lib/typescript/src/TabBarItemLabel.d.ts.map +1 -1
- package/lib/typescript/src/TabView.d.ts +111 -17
- package/lib/typescript/src/TabView.d.ts.map +1 -1
- package/lib/typescript/src/constants.d.ts +8 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +13 -12
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +31 -31
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useLayoutWidths.d.ts +2 -0
- package/lib/typescript/src/useLayoutWidths.d.ts.map +1 -0
- package/lib/typescript/src/useMeasureLayout.d.ts +1 -1
- package/lib/typescript/src/useMeasureLayout.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/DefaultAdapter.android.tsx +1 -0
- package/src/DefaultAdapter.ios.tsx +1 -0
- package/src/DefaultAdapter.tsx +13 -0
- package/src/PagerViewAdapter.native.tsx +36 -18
- package/src/PanResponderAdapter.tsx +4 -0
- package/src/PlatformPressable.tsx +11 -7
- package/src/SceneMap.tsx +17 -9
- package/src/SceneView.tsx +41 -2
- package/src/ScrollViewAdapter.tsx +88 -22
- package/src/TabBar.tsx +440 -235
- package/src/TabBarIndicator.tsx +389 -124
- package/src/TabBarItem.tsx +107 -46
- package/src/TabBarItemLabel.tsx +5 -4
- package/src/TabView.tsx +118 -20
- package/src/constants.tsx +8 -0
- package/src/index.tsx +5 -1
- package/src/types.tsx +40 -36
- package/src/useLayoutWidths.tsx +51 -0
- package/lib/module/Pager.android.js +0 -4
- package/lib/module/Pager.android.js.map +0 -1
- package/lib/module/Pager.ios.js +0 -4
- package/lib/module/Pager.ios.js.map +0 -1
- package/lib/module/Pager.js +0 -4
- package/lib/module/Pager.js.map +0 -1
- package/lib/typescript/src/Pager.android.d.ts +0 -2
- package/lib/typescript/src/Pager.android.d.ts.map +0 -1
- package/lib/typescript/src/Pager.d.ts +0 -2
- package/lib/typescript/src/Pager.d.ts.map +0 -1
- package/lib/typescript/src/Pager.ios.d.ts +0 -2
- package/lib/typescript/src/Pager.ios.d.ts.map +0 -1
- package/src/Pager.android.tsx +0 -1
- package/src/Pager.ios.tsx +0 -1
- package/src/Pager.tsx +0 -1
package/src/TabBarItem.tsx
CHANGED
|
@@ -12,28 +12,37 @@ import {
|
|
|
12
12
|
} from 'react-native';
|
|
13
13
|
import useLatestCallback from 'use-latest-callback';
|
|
14
14
|
|
|
15
|
+
import {
|
|
16
|
+
TAB_BAR_INACTIVE_COLOR,
|
|
17
|
+
TAB_BAR_PRIMARY_ACTIVE_COLOR,
|
|
18
|
+
TAB_BAR_SECONDARY_ACTIVE_COLOR,
|
|
19
|
+
} from './constants';
|
|
15
20
|
import { PlatformPressable } from './PlatformPressable';
|
|
16
21
|
import { TabBarItemLabel } from './TabBarItemLabel';
|
|
17
22
|
import type { NavigationState, Route, TabDescriptor } from './types';
|
|
18
23
|
|
|
24
|
+
type Layout = {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
19
29
|
export type Props<T extends Route> = TabDescriptor<T> & {
|
|
30
|
+
variant?: 'primary' | 'secondary' | undefined;
|
|
20
31
|
position: Animated.AnimatedInterpolation<number>;
|
|
21
32
|
route: T;
|
|
22
33
|
navigationState: NavigationState<T>;
|
|
23
|
-
activeColor?: ColorValue;
|
|
24
|
-
inactiveColor?: ColorValue;
|
|
25
|
-
pressColor?: ColorValue;
|
|
26
|
-
pressOpacity?: number;
|
|
27
|
-
|
|
34
|
+
activeColor?: ColorValue | undefined;
|
|
35
|
+
inactiveColor?: ColorValue | undefined;
|
|
36
|
+
pressColor?: ColorValue | undefined;
|
|
37
|
+
pressOpacity?: number | undefined;
|
|
38
|
+
onMeasureLayout: (layout: Layout) => void;
|
|
39
|
+
onMeasureLabelLayout: (layout: Layout) => void;
|
|
28
40
|
onPress: () => void;
|
|
29
41
|
onLongPress: () => void;
|
|
30
|
-
defaultTabWidth?: number;
|
|
31
42
|
style: StyleProp<ViewStyle>;
|
|
32
|
-
android_ripple?: PressableAndroidRippleConfig;
|
|
43
|
+
android_ripple?: PressableAndroidRippleConfig | undefined;
|
|
33
44
|
};
|
|
34
45
|
|
|
35
|
-
const DEFAULT_ACTIVE_COLOR = 'rgba(0, 0, 0, 1)';
|
|
36
|
-
const DEFAULT_INACTIVE_COLOR = 'rgba(0, 0, 0, 0.5)';
|
|
37
46
|
const ICON_SIZE = 24;
|
|
38
47
|
|
|
39
48
|
const getActiveOpacity = (
|
|
@@ -78,15 +87,18 @@ type TabBarItemInternalProps<T extends Route> = Omit<
|
|
|
78
87
|
| 'getTestID'
|
|
79
88
|
| 'getAccessible'
|
|
80
89
|
| 'options'
|
|
90
|
+
| 'variant'
|
|
81
91
|
> & {
|
|
82
92
|
isFocused: boolean;
|
|
83
93
|
index: number;
|
|
84
94
|
routesLength: number;
|
|
95
|
+
variant: NonNullable<Props<T>['variant']>;
|
|
85
96
|
} & TabDescriptor<T>;
|
|
86
97
|
|
|
87
|
-
const ANDROID_RIPPLE_DEFAULT = { borderless: true };
|
|
98
|
+
const ANDROID_RIPPLE_DEFAULT = { borderless: false, foreground: true };
|
|
88
99
|
|
|
89
100
|
const TabBarItemInternal = <T extends Route>({
|
|
101
|
+
variant,
|
|
90
102
|
accessibilityLabel,
|
|
91
103
|
accessible,
|
|
92
104
|
label: customlabel,
|
|
@@ -99,11 +111,11 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
99
111
|
inactiveColor: inactiveColorCustom,
|
|
100
112
|
activeColor: activeColorCustom,
|
|
101
113
|
labelStyle,
|
|
102
|
-
|
|
114
|
+
onMeasureLayout,
|
|
115
|
+
onMeasureLabelLayout,
|
|
103
116
|
index: tabIndex,
|
|
104
117
|
pressColor,
|
|
105
118
|
pressOpacity,
|
|
106
|
-
defaultTabWidth,
|
|
107
119
|
icon: customIcon,
|
|
108
120
|
badge: customBadge,
|
|
109
121
|
href,
|
|
@@ -114,19 +126,23 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
114
126
|
route,
|
|
115
127
|
}: TabBarItemInternalProps<T>) => {
|
|
116
128
|
const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color;
|
|
129
|
+
const defaultActiveColor =
|
|
130
|
+
variant === 'primary'
|
|
131
|
+
? TAB_BAR_PRIMARY_ACTIVE_COLOR
|
|
132
|
+
: TAB_BAR_SECONDARY_ACTIVE_COLOR;
|
|
117
133
|
|
|
118
134
|
const activeColor =
|
|
119
135
|
activeColorCustom !== undefined
|
|
120
136
|
? activeColorCustom
|
|
121
137
|
: typeof labelColorFromStyle === 'string'
|
|
122
138
|
? labelColorFromStyle
|
|
123
|
-
:
|
|
139
|
+
: defaultActiveColor;
|
|
124
140
|
const inactiveColor =
|
|
125
141
|
inactiveColorCustom !== undefined
|
|
126
142
|
? inactiveColorCustom
|
|
127
143
|
: typeof labelColorFromStyle === 'string'
|
|
128
144
|
? labelColorFromStyle
|
|
129
|
-
:
|
|
145
|
+
: TAB_BAR_INACTIVE_COLOR;
|
|
130
146
|
|
|
131
147
|
const activeOpacity = getActiveOpacity(position, routesLength, tabIndex);
|
|
132
148
|
const inactiveOpacity = getInactiveOpacity(position, routesLength, tabIndex);
|
|
@@ -151,7 +167,7 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
151
167
|
});
|
|
152
168
|
|
|
153
169
|
return (
|
|
154
|
-
<View
|
|
170
|
+
<View>
|
|
155
171
|
<Animated.View style={{ opacity: inactiveOpacity }}>
|
|
156
172
|
{inactiveIcon}
|
|
157
173
|
</Animated.View>
|
|
@@ -188,6 +204,7 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
188
204
|
icon={icon}
|
|
189
205
|
label={labelText}
|
|
190
206
|
style={labelStyle}
|
|
207
|
+
allowFontScaling={labelAllowFontScaling}
|
|
191
208
|
/>
|
|
192
209
|
),
|
|
193
210
|
[
|
|
@@ -202,16 +219,39 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
202
219
|
]
|
|
203
220
|
);
|
|
204
221
|
|
|
205
|
-
const tabStyle = StyleSheet.flatten(style);
|
|
206
|
-
const isWidthSet = tabStyle?.width !== undefined;
|
|
207
|
-
|
|
208
|
-
const tabContainerStyle: ViewStyle | null = isWidthSet
|
|
209
|
-
? null
|
|
210
|
-
: { width: defaultTabWidth };
|
|
211
|
-
|
|
212
222
|
const ariaLabel =
|
|
213
223
|
typeof accessibilityLabel !== 'undefined' ? accessibilityLabel : labelText;
|
|
214
224
|
|
|
225
|
+
const viewRef = React.useRef<View>(null);
|
|
226
|
+
const labelRef = React.useRef<View>(null);
|
|
227
|
+
|
|
228
|
+
React.useLayoutEffect(() => {
|
|
229
|
+
viewRef.current?.measure((_x, _y, width, height) => {
|
|
230
|
+
onMeasureLayout({ width, height });
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
labelRef.current?.measure((_x, _y, width, height) => {
|
|
234
|
+
onMeasureLabelLayout({ width, height });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Only measure on mount. onLayout handles updates.
|
|
238
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
239
|
+
}, []);
|
|
240
|
+
|
|
241
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
242
|
+
const { width, height } = event.nativeEvent.layout;
|
|
243
|
+
|
|
244
|
+
onMeasureLayout({ width, height });
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const onLabelLayout = (event: LayoutChangeEvent) => {
|
|
248
|
+
const { width, height } = event.nativeEvent.layout;
|
|
249
|
+
|
|
250
|
+
onMeasureLabelLayout({ width, height });
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const hasIconAndLabel = Boolean(customIcon && labelText);
|
|
254
|
+
|
|
215
255
|
return (
|
|
216
256
|
<PlatformPressable
|
|
217
257
|
android_ripple={android_ripple}
|
|
@@ -223,23 +263,32 @@ const TabBarItemInternal = <T extends Route>({
|
|
|
223
263
|
pressColor={pressColor}
|
|
224
264
|
pressOpacity={pressOpacity}
|
|
225
265
|
unstable_pressDelay={0}
|
|
226
|
-
onLayout={onLayout}
|
|
227
266
|
onPress={onPress}
|
|
228
267
|
onLongPress={onLongPress}
|
|
229
268
|
href={href}
|
|
230
|
-
style={
|
|
269
|
+
style={styles.pressable}
|
|
231
270
|
>
|
|
232
|
-
<View
|
|
233
|
-
{
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
271
|
+
<View
|
|
272
|
+
ref={viewRef}
|
|
273
|
+
onLayout={onLayout}
|
|
274
|
+
style={[
|
|
275
|
+
styles.item,
|
|
276
|
+
hasIconAndLabel ? styles.itemWithIcon : null,
|
|
277
|
+
style,
|
|
278
|
+
]}
|
|
279
|
+
>
|
|
280
|
+
<View ref={labelRef} onLayout={onLabelLayout} style={styles.content}>
|
|
281
|
+
{icon}
|
|
282
|
+
<View>
|
|
283
|
+
<Animated.View style={{ opacity: inactiveOpacity }}>
|
|
284
|
+
{renderLabel(false)}
|
|
285
|
+
</Animated.View>
|
|
286
|
+
<Animated.View
|
|
287
|
+
style={[StyleSheet.absoluteFill, { opacity: activeOpacity }]}
|
|
288
|
+
>
|
|
289
|
+
{renderLabel(true)}
|
|
290
|
+
</Animated.View>
|
|
291
|
+
</View>
|
|
243
292
|
</View>
|
|
244
293
|
{customBadge != null ? (
|
|
245
294
|
<View style={styles.badge}>{customBadge({ route })}</View>
|
|
@@ -253,21 +302,30 @@ const MemoizedTabBarItemInternal = React.memo(
|
|
|
253
302
|
TabBarItemInternal
|
|
254
303
|
) as typeof TabBarItemInternal;
|
|
255
304
|
|
|
256
|
-
export function TabBarItem<T extends Route>(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
305
|
+
export function TabBarItem<T extends Route>({
|
|
306
|
+
variant = 'primary',
|
|
307
|
+
onPress,
|
|
308
|
+
onLongPress,
|
|
309
|
+
onMeasureLayout,
|
|
310
|
+
onMeasureLabelLayout,
|
|
311
|
+
navigationState,
|
|
312
|
+
route,
|
|
313
|
+
...rest
|
|
314
|
+
}: Props<T>) {
|
|
260
315
|
const onPressLatest = useLatestCallback(onPress);
|
|
261
316
|
const onLongPressLatest = useLatestCallback(onLongPress);
|
|
262
|
-
const
|
|
317
|
+
const onMeasureLayoutLatest = useLatestCallback(onMeasureLayout);
|
|
318
|
+
const onMeasureLabelLayoutLatest = useLatestCallback(onMeasureLabelLayout);
|
|
263
319
|
|
|
264
320
|
const tabIndex = navigationState.routes.indexOf(route);
|
|
265
321
|
|
|
266
322
|
return (
|
|
267
323
|
<MemoizedTabBarItemInternal
|
|
268
324
|
{...rest}
|
|
325
|
+
variant={variant}
|
|
269
326
|
onPress={onPressLatest}
|
|
270
|
-
|
|
327
|
+
onMeasureLayout={onMeasureLayoutLatest}
|
|
328
|
+
onMeasureLabelLayout={onMeasureLabelLayoutLatest}
|
|
271
329
|
onLongPress={onLongPressLatest}
|
|
272
330
|
isFocused={navigationState.index === tabIndex}
|
|
273
331
|
route={route}
|
|
@@ -278,17 +336,20 @@ export function TabBarItem<T extends Route>(props: Props<T>) {
|
|
|
278
336
|
}
|
|
279
337
|
|
|
280
338
|
const styles = StyleSheet.create({
|
|
281
|
-
icon: {
|
|
282
|
-
margin: 2,
|
|
283
|
-
},
|
|
284
339
|
item: {
|
|
285
|
-
flex: 1,
|
|
286
340
|
alignItems: 'center',
|
|
287
341
|
justifyContent: 'center',
|
|
288
|
-
|
|
342
|
+
paddingHorizontal: 16,
|
|
289
343
|
minHeight: 48,
|
|
290
344
|
pointerEvents: 'none',
|
|
291
345
|
},
|
|
346
|
+
itemWithIcon: {
|
|
347
|
+
minHeight: 62,
|
|
348
|
+
},
|
|
349
|
+
content: {
|
|
350
|
+
alignItems: 'center',
|
|
351
|
+
justifyContent: 'center',
|
|
352
|
+
},
|
|
292
353
|
badge: {
|
|
293
354
|
position: 'absolute',
|
|
294
355
|
top: 0,
|
package/src/TabBarItemLabel.tsx
CHANGED
|
@@ -4,19 +4,21 @@ import { Animated, StyleSheet } from 'react-native';
|
|
|
4
4
|
|
|
5
5
|
interface TabBarItemLabelProps {
|
|
6
6
|
color: ColorValue;
|
|
7
|
-
label
|
|
8
|
-
style: StyleProp<ViewStyle>;
|
|
7
|
+
label: string | undefined;
|
|
9
8
|
icon: React.ReactNode;
|
|
9
|
+
style: StyleProp<ViewStyle>;
|
|
10
|
+
allowFontScaling?: boolean | undefined;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export const TabBarItemLabel = React.memo(
|
|
13
|
-
({ color, label, style, icon }: TabBarItemLabelProps) => {
|
|
14
|
+
({ color, label, style, icon, allowFontScaling }: TabBarItemLabelProps) => {
|
|
14
15
|
if (!label) {
|
|
15
16
|
return null;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
20
|
<Animated.Text
|
|
21
|
+
allowFontScaling={allowFontScaling}
|
|
20
22
|
style={[
|
|
21
23
|
styles.label,
|
|
22
24
|
icon ? { marginTop: 0 } : null,
|
|
@@ -34,7 +36,6 @@ TabBarItemLabel.displayName = 'TabBarItemLabel';
|
|
|
34
36
|
|
|
35
37
|
const styles = StyleSheet.create({
|
|
36
38
|
label: {
|
|
37
|
-
margin: 4,
|
|
38
39
|
fontSize: 14,
|
|
39
40
|
fontWeight: '500',
|
|
40
41
|
backgroundColor: 'transparent',
|
package/src/TabView.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type ViewStyle,
|
|
9
9
|
} from 'react-native';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { DefaultAdapter } from './DefaultAdapter';
|
|
12
12
|
import { SceneView } from './SceneView';
|
|
13
13
|
import { TabBar } from './TabBar';
|
|
14
14
|
import type {
|
|
@@ -23,27 +23,125 @@ import type {
|
|
|
23
23
|
} from './types';
|
|
24
24
|
|
|
25
25
|
export type Props<T extends Route> = AdapterCommonProps & {
|
|
26
|
+
/**
|
|
27
|
+
* Callback which is called when the index of the active tab changes.
|
|
28
|
+
* Must update the `navigationState.index` prop to the new index.
|
|
29
|
+
*
|
|
30
|
+
* Example:
|
|
31
|
+
* ```js
|
|
32
|
+
* onIndexChange={(index) => setState({ ...state, index })}
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
26
35
|
onIndexChange: (index: number) => void;
|
|
27
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Callback which is called when the tab navigation animation ends.
|
|
38
|
+
* Useful for side effects that should run after the tab change animation.
|
|
39
|
+
*
|
|
40
|
+
* Unlike `onIndexChange`, this is called regardless of whether the index changed or not.
|
|
41
|
+
*/
|
|
42
|
+
onTabSelect?: ((props: { index: number }) => void) | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* State for the tab view containing the current index and routes.
|
|
45
|
+
*
|
|
46
|
+
* Example:
|
|
47
|
+
* ```js
|
|
48
|
+
* {
|
|
49
|
+
* index: 0,
|
|
50
|
+
* routes: [
|
|
51
|
+
* { key: 'first' },
|
|
52
|
+
* { key: 'second' },
|
|
53
|
+
* ],
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
28
57
|
navigationState: NavigationState<T>;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Callback which returns a custom placeholder element.
|
|
60
|
+
* The placeholder is shown when a scene is not yet loaded when `lazy` is enabled.
|
|
61
|
+
*/
|
|
62
|
+
renderLazyPlaceholder?:
|
|
63
|
+
| ((props: { route: T }) => React.ReactNode)
|
|
64
|
+
| undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Callback which returns a custom tab bar element to display.
|
|
67
|
+
*/
|
|
68
|
+
renderTabBar?:
|
|
69
|
+
| ((
|
|
70
|
+
props: SceneRendererProps &
|
|
71
|
+
EventEmitterProps & {
|
|
72
|
+
navigationState: NavigationState<T>;
|
|
73
|
+
options: Record<string, TabDescriptor<T>> | undefined;
|
|
74
|
+
}
|
|
75
|
+
) => React.ReactNode)
|
|
76
|
+
| undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Callback which returns a custom adapter to use for the tab view.
|
|
79
|
+
* Adapters are responsible for handling gestures and animations between tabs.
|
|
80
|
+
*
|
|
81
|
+
* The following adapters are provided out of the box:
|
|
82
|
+
* - `PagerViewAdapter`: Uses `react-native-pager-view` for native experience.
|
|
83
|
+
* - `PanResponderAdapter`: Uses `PanResponder` for a JS-based implementation.
|
|
84
|
+
* - `ScrollViewAdapter`: Uses `ScrollView` for an implementation based on `ScrollView`.
|
|
85
|
+
*
|
|
86
|
+
* Defaults to `PagerViewAdapter` on Android and iOS, and `PanResponderAdapter` on other platforms.
|
|
87
|
+
*/
|
|
88
|
+
renderAdapter?: ((props: AdapterProps) => React.ReactElement) | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Position of the tab bar in the tab view.
|
|
91
|
+
* Defaults to `'top'`.
|
|
92
|
+
*/
|
|
93
|
+
tabBarPosition?: 'top' | 'bottom' | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Whether to lazily render the scenes.
|
|
96
|
+
* When enabled, scenes are rendered only when they come into view.
|
|
97
|
+
*
|
|
98
|
+
* Can be a boolean or a function that receives the route and returns a boolean.
|
|
99
|
+
* Defaults to `false`.
|
|
100
|
+
*/
|
|
101
|
+
lazy?: ((props: { route: T }) => boolean) | boolean | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* How many screens to preload when `lazy` is enabled.
|
|
104
|
+
*
|
|
105
|
+
* Defaults to `0`.
|
|
106
|
+
*/
|
|
107
|
+
lazyPreloadDistance?: number | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* The layout direction of the tab view.
|
|
110
|
+
*
|
|
111
|
+
* Defaults to the app's locale direction (RTL or LTR).
|
|
112
|
+
*/
|
|
113
|
+
direction?: LocaleDirection | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Style to apply to the pager container.
|
|
116
|
+
*/
|
|
117
|
+
pagerStyle?: StyleProp<ViewStyle> | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Style to apply to the tab view container.
|
|
120
|
+
*/
|
|
121
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* Callback which returns a React element to render for each route.
|
|
124
|
+
*/
|
|
44
125
|
renderScene: (props: SceneRendererProps & { route: T }) => React.ReactNode;
|
|
45
|
-
|
|
46
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Options for individual tabs, keyed by route key.
|
|
128
|
+
*
|
|
129
|
+
* Example:
|
|
130
|
+
* ```js
|
|
131
|
+
* {
|
|
132
|
+
* first: { labelText: 'First Tab' },
|
|
133
|
+
* second: { labelText: 'Second Tab' },
|
|
134
|
+
* }
|
|
135
|
+
*
|
|
136
|
+
* These options are merged with `commonOptions`.
|
|
137
|
+
*/
|
|
138
|
+
options?: Record<string, TabDescriptor<T>> | undefined;
|
|
139
|
+
/**
|
|
140
|
+
* Options that apply to all tabs.
|
|
141
|
+
*
|
|
142
|
+
* Individual tab options from `options` will override these.
|
|
143
|
+
*/
|
|
144
|
+
commonOptions?: TabDescriptor<T> | undefined;
|
|
47
145
|
};
|
|
48
146
|
|
|
49
147
|
const renderLazyPlaceholderDefault = () => null;
|
|
@@ -62,7 +160,7 @@ export function TabView<T extends Route>({
|
|
|
62
160
|
// eslint-disable-next-line @eslint-react/no-unstable-default-props
|
|
63
161
|
renderTabBar = (props) => <TabBar {...props} />,
|
|
64
162
|
// eslint-disable-next-line @eslint-react/no-unstable-default-props
|
|
65
|
-
renderAdapter = (props) => <
|
|
163
|
+
renderAdapter = (props) => <DefaultAdapter {...props} />,
|
|
66
164
|
pagerStyle,
|
|
67
165
|
style,
|
|
68
166
|
direction = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr',
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const TAB_BAR_PRIMARY_ACTIVE_COLOR = '#6750a4';
|
|
2
|
+
export const TAB_BAR_SECONDARY_ACTIVE_COLOR = '#1d1b20';
|
|
3
|
+
export const TAB_BAR_INACTIVE_COLOR = '#49454f';
|
|
4
|
+
export const TAB_BAR_BACKGROUND_COLOR = '#fef7ff';
|
|
5
|
+
export const TAB_BAR_BORDER_COLOR = '#cac4d0';
|
|
6
|
+
|
|
7
|
+
export const PRIMARY_INDICATOR_MIN_WIDTH = 24;
|
|
8
|
+
export const TAB_MIN_WIDTH = 90;
|
package/src/index.tsx
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
export { DefaultAdapter, type DefaultAdapterProps } from './DefaultAdapter';
|
|
1
2
|
export {
|
|
2
3
|
PagerViewAdapter,
|
|
3
4
|
type PagerViewAdapterProps,
|
|
4
5
|
} from './PagerViewAdapter';
|
|
5
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
PanResponderAdapter,
|
|
8
|
+
type PanResponderAdapterProps,
|
|
9
|
+
} from './PanResponderAdapter';
|
|
6
10
|
export { SceneMap } from './SceneMap';
|
|
7
11
|
export {
|
|
8
12
|
ScrollViewAdapter,
|
package/src/types.tsx
CHANGED
|
@@ -8,40 +8,44 @@ import type {
|
|
|
8
8
|
} from 'react-native';
|
|
9
9
|
|
|
10
10
|
export type TabDescriptor<T extends Route> = {
|
|
11
|
-
accessibilityLabel?: string;
|
|
12
|
-
accessible?: boolean;
|
|
13
|
-
testID?: string;
|
|
14
|
-
labelText?: string;
|
|
15
|
-
labelAllowFontScaling?: boolean;
|
|
16
|
-
href?: string;
|
|
17
|
-
label?:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
11
|
+
accessibilityLabel?: string | undefined;
|
|
12
|
+
accessible?: boolean | undefined;
|
|
13
|
+
testID?: string | undefined;
|
|
14
|
+
labelText?: string | undefined;
|
|
15
|
+
labelAllowFontScaling?: boolean | undefined;
|
|
16
|
+
href?: string | undefined;
|
|
17
|
+
label?:
|
|
18
|
+
| ((props: {
|
|
19
|
+
route: T;
|
|
20
|
+
labelText?: string | undefined;
|
|
21
|
+
focused: boolean;
|
|
22
|
+
color: ColorValue;
|
|
23
|
+
allowFontScaling?: boolean | undefined;
|
|
24
|
+
style?: StyleProp<TextStyle> | undefined;
|
|
25
|
+
}) => React.ReactNode)
|
|
26
|
+
| undefined;
|
|
27
|
+
labelStyle?: StyleProp<TextStyle> | undefined;
|
|
28
|
+
icon?:
|
|
29
|
+
| ((props: {
|
|
30
|
+
route: T;
|
|
31
|
+
focused: boolean;
|
|
32
|
+
color: ColorValue;
|
|
33
|
+
size: number;
|
|
34
|
+
}) => React.ReactNode)
|
|
35
|
+
| undefined;
|
|
36
|
+
badge?: ((props: { route: T }) => React.ReactElement) | undefined;
|
|
37
|
+
sceneStyle?: StyleProp<ViewStyle> | undefined;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
export type LocaleDirection = 'ltr' | 'rtl';
|
|
37
41
|
|
|
38
42
|
export type Route = {
|
|
39
43
|
key: string;
|
|
40
|
-
icon?: string;
|
|
41
|
-
title?: string;
|
|
42
|
-
accessible?: boolean;
|
|
43
|
-
accessibilityLabel?: string;
|
|
44
|
-
testID?: string;
|
|
44
|
+
icon?: string | undefined;
|
|
45
|
+
title?: string | undefined;
|
|
46
|
+
accessible?: boolean | undefined;
|
|
47
|
+
accessibilityLabel?: string | undefined;
|
|
48
|
+
testID?: string | undefined;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
export type Event = {
|
|
@@ -81,33 +85,33 @@ export type AdapterCommonProps = {
|
|
|
81
85
|
* - 'on-drag' - the keyboard is dismissed when a drag begins
|
|
82
86
|
* - 'none' - drags and tab changes do not dismiss the keyboard
|
|
83
87
|
*/
|
|
84
|
-
keyboardDismissMode?: 'none' | 'on-drag' | 'auto';
|
|
88
|
+
keyboardDismissMode?: 'none' | 'on-drag' | 'auto' | undefined;
|
|
85
89
|
/**
|
|
86
90
|
* Whether swiping between tabs is enabled.
|
|
87
91
|
*/
|
|
88
|
-
swipeEnabled?: boolean;
|
|
92
|
+
swipeEnabled?: boolean | undefined;
|
|
89
93
|
/**
|
|
90
94
|
* Whether the tab switch animation is enabled.
|
|
91
95
|
* If set to false, the tab switch will happen immediately without animation.
|
|
92
96
|
*/
|
|
93
|
-
animationEnabled?: boolean;
|
|
97
|
+
animationEnabled?: boolean | undefined;
|
|
94
98
|
/**
|
|
95
99
|
* Callback that is called when the swipe gesture starts.
|
|
96
100
|
*/
|
|
97
|
-
onSwipeStart?: () => void;
|
|
101
|
+
onSwipeStart?: (() => void) | undefined;
|
|
98
102
|
/**
|
|
99
103
|
* Callback that is called when the swipe gesture ends.
|
|
100
104
|
*/
|
|
101
|
-
onSwipeEnd?: () => void;
|
|
105
|
+
onSwipeEnd?: (() => void) | undefined;
|
|
102
106
|
/**
|
|
103
107
|
* Callback that is called when a tab is selected.
|
|
104
108
|
* This is called regardless of whether the index has changed or not.
|
|
105
109
|
*/
|
|
106
|
-
onTabSelect?: (props: { index: number }) => void;
|
|
110
|
+
onTabSelect?: ((props: { index: number }) => void) | undefined;
|
|
107
111
|
/**
|
|
108
112
|
* Style for the pager adapter.
|
|
109
113
|
*/
|
|
110
|
-
style?: StyleProp<ViewStyle
|
|
114
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
111
115
|
};
|
|
112
116
|
|
|
113
117
|
export type AdapterRendererProps = {
|
|
@@ -123,7 +127,7 @@ export type AdapterRendererProps = {
|
|
|
123
127
|
* The writing direction of the layout.
|
|
124
128
|
* This can be 'ltr' or 'rtl' based on tab view's `direction` prop.
|
|
125
129
|
*/
|
|
126
|
-
layoutDirection?: LocaleDirection;
|
|
130
|
+
layoutDirection?: LocaleDirection | undefined;
|
|
127
131
|
/**
|
|
128
132
|
* Render callback that should render the pages of the tab view.
|
|
129
133
|
*/
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
// Batches onLayout updates from multiple keyed views into a single state update
|
|
4
|
+
// per frame, so layout passes don't trigger N renders.
|
|
5
|
+
export function useLayoutWidths(priorityKeys: string[]) {
|
|
6
|
+
const [widths, setWidths] = React.useState<Record<string, number>>({});
|
|
7
|
+
|
|
8
|
+
const measured = React.useRef<Record<string, number>>({});
|
|
9
|
+
const handle = React.useRef<ReturnType<typeof requestAnimationFrame>>(null);
|
|
10
|
+
|
|
11
|
+
const priorityKeysRef = React.useRef(priorityKeys);
|
|
12
|
+
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
priorityKeysRef.current = priorityKeys;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const hasFinishedLayoutRef = React.useRef(false);
|
|
18
|
+
|
|
19
|
+
React.useLayoutEffect(() => {
|
|
20
|
+
hasFinishedLayoutRef.current = true;
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
const onMeasureWidth = React.useCallback((key: string, width: number) => {
|
|
24
|
+
measured.current[key] = width;
|
|
25
|
+
|
|
26
|
+
if (handle.current != null) {
|
|
27
|
+
cancelAnimationFrame(handle.current);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
priorityKeysRef.current.includes(key) &&
|
|
32
|
+
!hasFinishedLayoutRef.current
|
|
33
|
+
) {
|
|
34
|
+
// Synchronously set widths during the initial layout phase
|
|
35
|
+
// So we don't have to wait for the next frame to render the indicator
|
|
36
|
+
setWidths((prev) => ({ ...prev, [key]: width }));
|
|
37
|
+
} else if (hasFinishedLayoutRef.current) {
|
|
38
|
+
handle.current = requestAnimationFrame(() => {
|
|
39
|
+
setWidths({ ...measured.current });
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
if (handle.current != null) {
|
|
45
|
+
cancelAnimationFrame(handle.current);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return [widths, onMeasureWidth] as const;
|
|
51
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["PagerViewAdapter","Pager"],"sourceRoot":"../../src","sources":["Pager.android.tsx"],"mappings":";;AAAA,SAASA,gBAAgB,IAAIC,KAAK,QAAQ,oBAAoB","ignoreList":[]}
|
package/lib/module/Pager.ios.js
DELETED