Skip to content

Commit 7b58df8

Browse files
authored
Feat/tab controller 2 orientation try 3 (#1515)
* Fix Android width (window instead of screen) * Add a single source of truth to containerWidth (screen\window width) * Change key to trigger onLayout * Fix fixed width item to correct width and move to onLayout * Revert moving to onLayout - causing extra calls to onLayout * Change key for correct order of useEffect Otherwise the useEffect that resets the data in useScrollToItem is called after the useEffect that triggers the onLayout from fixed width items (and data is lost). * Fix page scroll * Fix not scrolling to iniitla tabItem * Remove unused prop * Reset only when scroll is not enabled (i.e. tabs fill the container and spread) * Update types * Move style to useMemo (instead of useAnimatedStyle) * Change key to orientation * Move reset triggering to TabBar
1 parent f615859 commit 7b58df8

File tree

12 files changed

+126
-42
lines changed

12 files changed

+126
-42
lines changed

generatedTypes/src/components/tabController2/TabBarContext.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ interface TabControllerContext {
55
selectedIndex?: number;
66
items?: any[];
77
asCarousel?: boolean;
8-
containerWidth: Reanimated.SharedValue<number>;
9-
pageWidth?: number;
8+
containerWidth: number;
9+
pageWidth: number;
1010
/** static page index */
1111
currentPage: Reanimated.SharedValue<number>;
1212
/** transition page index (can be a fraction when transitioning between pages) */

generatedTypes/src/components/tabController2/useScrollToItem.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ export declare type ScrollToItemProps<T extends ScrollToSupportedViews> = {
1717
* The selected item's index
1818
*/
1919
selectedIndex?: number;
20+
/**
21+
* The container width, should update on orientation change
22+
*/
23+
containerWidth: number;
2024
/**
2125
* Where would the item be located (default to CENTER)
2226
*/
@@ -53,6 +57,10 @@ export declare type ScrollToItemResultProps<T extends ScrollToSupportedViews> =
5357
* Use in order to focus the item with the specified index (use when the selectedIndex is not changed)
5458
*/
5559
focusIndex: (index: number, animated?: boolean) => void;
60+
/**
61+
* Use in order to reset the data.
62+
*/
63+
reset: () => void;
5664
/**
5765
* onContentSizeChange callback (should be set to your onContentSizeChange).
5866
* Needed for RTL support on Android.

src/components/tabController2/FadedScrollView.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useCallback} from 'react';
1+
import React, {useCallback, useImperativeHandle, useRef} from 'react';
22
import {
33
ViewProps,
44
ScrollView,
@@ -29,6 +29,7 @@ const FadedScrollView = (props: Props) => {
2929
onLayout: propsOnLayout,
3030
...other
3131
} = props;
32+
const ref = useRef<ScrollView>();
3233
const {onContentSizeChange, onLayout, scrollEnabled} = useScrollEnabler({horizontal: true});
3334
const {
3435
onScroll: onScrollReached,
@@ -60,6 +61,15 @@ const FadedScrollView = (props: Props) => {
6061
},
6162
[propsOnLayout, onLayout]);
6263

64+
const isScrollEnabled = () => {
65+
return scrollEnabled;
66+
};
67+
68+
useImperativeHandle(props.forwardedRef, () => ({
69+
scrollTo: (...data: any) => ref.current?.scrollTo?.(...data),
70+
isScrollEnabled
71+
}));
72+
6373
if (children) {
6474
return (
6575
<>
@@ -73,7 +83,8 @@ const FadedScrollView = (props: Props) => {
7383
onContentSizeChange={_onContentSizeChange}
7484
onLayout={_onLayout}
7585
onScroll={onScroll}
76-
ref={props.forwardedRef}
86+
// @ts-ignore
87+
ref={ref}
7788
>
7889
{children}
7990
</ScrollView>

src/components/tabController2/PageCarousel.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useCallback, useContext, useMemo} from 'react';
1+
import React, {useCallback, useContext, useMemo, useEffect} from 'react';
22
import TabBarContext from './TabBarContext';
33
import Reanimated, {
44
runOnJS,
@@ -8,7 +8,6 @@ import Reanimated, {
88
useSharedValue,
99
withTiming
1010
} from 'react-native-reanimated';
11-
import {Constants} from 'helpers';
1211

1312
/**
1413
* @description: TabController's Page Carousel
@@ -21,7 +20,7 @@ function PageCarousel({...props}) {
2120
currentPage,
2221
targetPage,
2322
selectedIndex = 0,
24-
pageWidth = Constants.screenWidth,
23+
pageWidth,
2524
carouselOffset
2625
} = useContext(TabBarContext);
2726
const contentOffset = useMemo(() => ({x: selectedIndex * pageWidth, y: 0}), [selectedIndex, pageWidth]);
@@ -62,6 +61,11 @@ function PageCarousel({...props}) {
6261
}
6362
});
6463

64+
useEffect(() => {
65+
// @ts-expect-error
66+
carousel.current?.scrollTo({x: currentPage.value * pageWidth, animated: false});
67+
}, [pageWidth]);
68+
6569
return (
6670
<Reanimated.ScrollView
6771
{...props}

src/components/tabController2/TabBar.tsx

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useMemo, useContext, ReactNode} from 'react';
1+
import React, {useMemo, useContext, useState, useRef, ReactNode} from 'react';
22
import {StyleSheet, Platform, StyleProp, ViewStyle} from 'react-native';
33
import Reanimated, {runOnJS, useAnimatedReaction, useAnimatedStyle, interpolate} from 'react-native-reanimated';
44
import _ from 'lodash';
@@ -8,10 +8,12 @@ import TabBarItem, {TabControllerItemProps} from './TabBarItem';
88
import {asBaseComponent, forwardRef, BaseComponentInjectedProps, ForwardRefInjectedProps} from '../../commons/new';
99
import View from '../view';
1010
import {Colors, Spacings, Typography} from '../../style';
11-
import {Constants} from '../../helpers';
1211
import FadedScrollView from './FadedScrollView';
1312

1413
import useScrollToItem from './useScrollToItem';
14+
import {orientations} from '../../helpers/Constants';
15+
import {Constants} from 'helpers';
16+
import {useDidUpdate} from 'hooks';
1517

1618
const DEFAULT_HEIGHT = 48;
1719
const DEFAULT_BACKGROUND_COLOR = Colors.white;
@@ -144,30 +146,42 @@ const TabBar = (props: Props) => {
144146
testID
145147
} = props;
146148

149+
const tabBar = useRef<typeof FadedScrollView>();
150+
const [key, setKey] = useState<orientations>(Constants.orientation);
147151
const context = useContext(TabBarContext);
148-
const {items: contextItems, currentPage, targetPage, selectedIndex} = context;
152+
const {
153+
items: contextItems,
154+
currentPage,
155+
targetPage,
156+
initialIndex,
157+
selectedIndex,
158+
containerWidth: contextContainerWidth
159+
} = context;
149160

150161
const containerWidth: number = useMemo(() => {
151-
return propsContainerWidth || Constants.screenWidth;
152-
}, [propsContainerWidth]);
162+
return propsContainerWidth || contextContainerWidth;
163+
}, [propsContainerWidth, contextContainerWidth]);
153164

154165
const items = useMemo(() => {
155166
return contextItems || propsItems;
156167
}, [contextItems, propsItems]);
157168

158169
const {
159-
scrollViewRef: tabBar,
160170
onItemLayout,
161171
itemsWidthsAnimated,
162172
itemsOffsetsAnimated,
163173
// itemsWidths,
164174
// itemsOffsets,
165175
focusIndex,
176+
reset,
166177
onContentSizeChange,
167178
onLayout
168179
} = useScrollToItem({
180+
// @ts-expect-error TODO: typing bug
181+
scrollViewRef: tabBar,
169182
itemsCount: items?.length || 0,
170-
selectedIndex,
183+
selectedIndex: selectedIndex || initialIndex,
184+
containerWidth,
171185
offsetType: centerSelected ? useScrollToItem.offsetType.CENTER : useScrollToItem.offsetType.DYNAMIC
172186
});
173187

@@ -247,14 +261,23 @@ const TabBar = (props: Props) => {
247261
return {minWidth: containerWidth};
248262
}, [containerWidth]);
249263

264+
useDidUpdate(() => {
265+
// @ts-expect-error TODO: fix forwardRef Statics
266+
if (tabBar.current?.isScrollEnabled()) {
267+
focusIndex(currentPage.value);
268+
} else {
269+
reset();
270+
setKey(Constants.orientation);
271+
}
272+
}, [containerWidth]);
273+
250274
return (
251-
<View style={_containerStyle}>
275+
<View style={_containerStyle} key={key}>
252276
<FadedScrollView
253277
// @ts-expect-error
254278
ref={tabBar}
255279
horizontal
256280
contentContainerStyle={scrollViewContainerStyle}
257-
scrollEnabled // TODO:
258281
testID={testID}
259282
onContentSizeChange={onContentSizeChange}
260283
onLayout={onLayout}
@@ -283,9 +306,6 @@ const styles = StyleSheet.create({
283306
flexDirection: 'row',
284307
justifyContent: 'space-between'
285308
},
286-
tabBarScrollContent: {
287-
minWidth: Constants.screenWidth
288-
},
289309
tab: {
290310
flex: 1,
291311
alignItems: 'center',

src/components/tabController2/TabBarContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ interface TabControllerContext {
77
selectedIndex?: number;
88
items?: any[];
99
asCarousel?: boolean;
10-
containerWidth: Reanimated.SharedValue<number>;
11-
pageWidth?: number;
10+
containerWidth: number;
11+
pageWidth: number;
1212
/** static page index */
1313
currentPage: Reanimated.SharedValue<number>;
1414
/** transition page index (can be a fraction when transitioning between pages) */

src/components/tabController2/TabBarItem.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// TODO: support commented props
2-
import React, {useCallback, useContext, useEffect, useRef, ReactElement} from 'react';
2+
import React, {useCallback, useContext, useEffect, useRef, useMemo, ReactElement} from 'react';
33
import {StyleSheet, TextStyle, LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native';
44
import _ from 'lodash';
55
import Reanimated, {useAnimatedStyle, useSharedValue} from 'react-native-reanimated';
@@ -183,11 +183,16 @@ export default function TabBarItem({
183183
};
184184
});
185185

186+
const _style = useMemo(() => {
187+
const constantWidthStyle = itemWidth.current ? {flex: 0, width: itemWidth.current} : undefined;
188+
return [styles.tabItem, style, constantWidthStyle];
189+
}, [style]);
190+
186191
return (
187192
<TouchableOpacity
188193
// @ts-expect-error
189194
ref={itemRef}
190-
style={[styles.tabItem, style]}
195+
style={_style}
191196
onLayout={onLayout}
192197
activeBackgroundColor={activeBackgroundColor}
193198
activeOpacity={activeOpacity}

src/components/tabController2/TabPage.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import React, {PropsWithChildren, useCallback, useContext, useState} from 'react';
1+
import React, {PropsWithChildren, useCallback, useContext, useState, useMemo} from 'react';
22
import {StyleSheet} from 'react-native';
33
import Reanimated, {useAnimatedStyle, useAnimatedReaction, runOnJS} from 'react-native-reanimated';
44
import TabBarContext from './TabBarContext';
5-
import {Constants} from 'helpers';
65

76
export interface TabControllerPageProps {
87
/**
@@ -60,13 +59,16 @@ export default function TabPage({
6059
const isActive = Math.round(currentPage.value) === index;
6160
return {
6261
opacity: isActive || asCarousel ? 1 : 0,
63-
zIndex: isActive || asCarousel ? 1 : 0,
64-
width: asCarousel ? containerWidth.value || Constants.screenWidth : undefined
62+
zIndex: isActive || asCarousel ? 1 : 0
6563
};
6664
});
6765

66+
const style = useMemo(() => {
67+
return [!asCarousel && styles.page, animatedPageStyle, {width: asCarousel ? containerWidth : undefined}];
68+
}, [asCarousel, animatedPageStyle, containerWidth]);
69+
6870
return (
69-
<Reanimated.View style={[!asCarousel && styles.page, animatedPageStyle]} testID={testID}>
71+
<Reanimated.View style={style} testID={testID}>
7072
{!shouldLoad && renderLoading?.()}
7173
{shouldLoad && props.children}
7274
</Reanimated.View>

src/components/tabController2/index.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// TODO: support commented props
2-
import React, {PropsWithChildren, useMemo, useEffect} from 'react';
2+
import React, {PropsWithChildren, useMemo, useEffect, useRef, useState} from 'react';
33
import _ from 'lodash';
44
import {useAnimatedReaction, useSharedValue, withTiming, runOnJS} from 'react-native-reanimated';
55
import {Constants} from '../../helpers';
6+
import {orientations} from '../../helpers/Constants';
67
import {asBaseComponent} from '../../commons/new';
78
import {LogService} from '../../services';
89
import TabBarContext from './TabBarContext';
@@ -56,9 +57,24 @@ function TabController({
5657
carouselPageWidth,
5758
children
5859
}: PropsWithChildren<TabControllerProps>) {
60+
const [screenWidth, setScreenWidth] = useState<number>(Constants.windowWidth);
61+
const orientation = useRef<orientations>(Constants.orientation);
62+
useEffect(() => {
63+
const onOrientationChange = () => {
64+
if (orientation.current !== Constants.orientation) {
65+
orientation.current = Constants.orientation;
66+
setScreenWidth(Constants.windowWidth);
67+
}
68+
};
69+
Constants.addDimensionsEventListener(onOrientationChange);
70+
return () => {
71+
Constants.removeDimensionsEventListener(onOrientationChange);
72+
};
73+
}, []);
74+
5975
const pageWidth = useMemo(() => {
60-
return carouselPageWidth || Constants.screenWidth;
61-
}, [carouselPageWidth]);
76+
return carouselPageWidth || screenWidth;
77+
}, [carouselPageWidth, screenWidth]);
6278

6379
const ignoredItems = useMemo(() => {
6480
return _.filter<TabControllerItemProps[]>(items, (item: TabControllerItemProps) => item.ignore);
@@ -72,7 +88,6 @@ function TabController({
7288
/* targetPage - transitioned page index (can be a fraction when transitioning between pages) */
7389
const targetPage = useSharedValue(initialIndex);
7490
const carouselOffset = useSharedValue(initialIndex * Math.round(pageWidth));
75-
const containerWidth = useSharedValue(pageWidth);
7691

7792
useEffect(() => {
7893
if (!_.isUndefined(selectedIndex)) {
@@ -107,11 +122,11 @@ function TabController({
107122
targetPage,
108123
currentPage,
109124
carouselOffset,
110-
containerWidth,
125+
containerWidth: screenWidth,
111126
/* Callbacks */
112127
onChangeIndex
113128
};
114-
}, [/* initialIndex,*/initialIndex, asCarousel, items, onChangeIndex]);
129+
}, [/* initialIndex,*/initialIndex, asCarousel, items, onChangeIndex, screenWidth]);
115130

116131
return <TabBarContext.Provider value={context}>{children}</TabBarContext.Provider>;
117132
}

0 commit comments

Comments
 (0)