Skip to content

Commit d79af43

Browse files
committed
Revert merging wrong commit
1 parent 8aaccdd commit d79af43

File tree

1 file changed

+125
-163
lines changed

1 file changed

+125
-163
lines changed

src/components/tabController/TabBar.tsx

Lines changed: 125 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
// TODO: support commented props
22
// TODO: disable scroll when content width is shorter than screen width
3-
import React, {useEffect, useState, useCallback, useMemo, useRef, useContext} from 'react';
4-
import {
5-
StyleSheet,
6-
ScrollView,
7-
Platform,
8-
TextProps,
9-
StyleProp,
10-
ViewStyle,
11-
LayoutRectangle,
12-
NativeSyntheticEvent,
13-
NativeScrollEvent
14-
} from 'react-native';
3+
import React, {useEffect, useMemo, useRef, useContext, ReactNode} from 'react';
4+
import {StyleSheet, ScrollView, Platform, TextProps, StyleProp, ViewStyle} from 'react-native';
155
import Reanimated from 'react-native-reanimated';
166
import _ from 'lodash';
177

@@ -24,6 +14,8 @@ import {Constants} from '../../helpers';
2414
import {LogService} from '../../services';
2515
import FadedScrollView from './FadedScrollView';
2616

17+
import focusItemsHelper, {OffsetType} from '../../helpers/FocusItemHelper';
18+
2719
const {Code, Value, interpolate, block, set} = Reanimated;
2820

2921
const DEFAULT_HEIGHT = 48;
@@ -150,15 +142,15 @@ const TabBar = (props: Props) => {
150142
activeBackgroundColor,
151143
backgroundColor,
152144
containerWidth: propsContainerWidth,
153-
centerSelected: propsCenterSelected,
145+
centerSelected,
154146
containerStyle,
155147
testID,
156148
children: propsChildren
157149
} = props;
158150

159151
const context = useContext(TabBarContext);
160152
// @ts-ignore // TODO: typescript
161-
const {itemStates, items: contextItems, selectedIndex, currentPage, targetPage, registerTabItems} = context;
153+
const {itemStates, items: contextItems, currentPage, targetPage, registerTabItems, selectedIndex} = context;
162154

163155
const children = useRef<Props['children']>(_.filter(propsChildren, (child: ChildProps) => !!child));
164156

@@ -197,12 +189,7 @@ const TabBar = (props: Props) => {
197189
}
198190
}, []);
199191

200-
const [itemsWidths, setItemsWidths] = useState<number[]>([]);
201-
const [itemsOffsets, setItemsOffsets] = useState<number[]>([]);
202-
const [scrollEnabled, setScrollEnabled] = useState<boolean>(false);
203192
const tabBar = useRef<ScrollView>(null);
204-
const tabBarScrollOffset = useRef<number>(0);
205-
const contentWidth = useRef<number>(0);
206193

207194
const containerWidth: number = useMemo(() => {
208195
return propsContainerWidth || Constants.screenWidth;
@@ -212,136 +199,101 @@ const TabBar = (props: Props) => {
212199
}, [contextItems, propsItems]);
213200

214201
const itemsCount = useRef<number>(items ? _.size(items) : React.Children.count(children.current));
215-
const _itemsWidths = useRef<(number | null | undefined)[]>(_.times(itemsCount.current, () => null));
216-
const _itemsOffsets = useRef<(number | null | undefined)[]>(_.times(itemsCount.current, () => null));
217-
218-
const onScroll = useCallback(({nativeEvent: {contentOffset}}: NativeSyntheticEvent<NativeScrollEvent>) => {
219-
tabBarScrollOffset.current = contentOffset.x;
220-
if (Constants.isRTL && Constants.isAndroid) {
221-
const scrollingWidth = Math.max(0, contentWidth.current - containerWidth);
222-
tabBarScrollOffset.current = scrollingWidth - tabBarScrollOffset.current;
223-
}
224-
},
225-
[containerWidth]);
226-
227-
const snapBreakpoints = useMemo(() => {
228-
return itemsWidths && itemsOffsets && itemsWidths.length > 0 && itemsOffsets.length > 0 && propsCenterSelected
229-
? _.times(itemsWidths.length, index => {
230-
const screenCenter = containerWidth / 2;
231-
const itemOffset = itemsOffsets[index];
232-
const itemWidth = itemsWidths[index];
233-
return itemOffset - screenCenter + itemWidth / 2;
234-
})
235-
: undefined;
236-
}, [itemsWidths, itemsOffsets, propsCenterSelected, containerWidth]);
237-
238-
const guesstimateCenterValue = 60;
239-
const centerOffset = propsCenterSelected ? containerWidth / 2 - guesstimateCenterValue : 0;
240-
241-
// TODO: move this logic into a ScrollPresenter or something
242-
const focusSelected = useCallback(([index]: readonly number[], animated = true) => {
243-
const itemOffset = _itemsOffsets.current[index];
244-
const itemWidth = _itemsWidths.current[index];
245-
const screenCenter = containerWidth / 2;
246-
247-
let targetOffset;
248-
249-
if (!_.isNil(itemOffset) && !_.isNil(itemWidth)) {
250-
if (propsCenterSelected) {
251-
targetOffset = itemOffset - screenCenter + itemWidth / 2;
252-
} else if (itemOffset < tabBarScrollOffset.current) {
253-
targetOffset = itemOffset - itemWidth;
254-
} else if (itemOffset + itemWidth > tabBarScrollOffset.current + containerWidth) {
255-
const offsetChange = Math.max(0, itemOffset - (tabBarScrollOffset.current + containerWidth));
256-
targetOffset = tabBarScrollOffset.current + offsetChange + itemWidth;
257-
}
258202

259-
if (!_.isUndefined(targetOffset)) {
260-
if (Constants.isRTL && Constants.isAndroid) {
261-
const scrollingWidth = Math.max(0, contentWidth.current - containerWidth);
262-
targetOffset = scrollingWidth - targetOffset;
263-
}
203+
const {onItemLayout, itemsWidths, focusIndex} = focusItemsHelper({
204+
scrollViewRef: tabBar,
205+
itemsCount: itemsCount.current,
206+
selectedIndex,
207+
offsetType: centerSelected ? OffsetType.CENTER : OffsetType.DYNAMIC
208+
});
264209

265-
if (tabBar?.current) {
266-
tabBar.current.scrollTo({x: targetOffset, animated});
267-
}
268-
}
210+
const indicatorOffsets = useMemo((): number[] => {
211+
let index = 0;
212+
const offsets = [];
213+
offsets.push(0);
214+
while (index < itemsWidths.length - 1) {
215+
++index;
216+
offsets[index] = offsets[index - 1] + itemsWidths[index - 1];
269217
}
270-
},
271-
[containerWidth]);
272-
273-
function getItemsOffsets() {
274-
return _.times(_itemsWidths.current.length,
275-
i => _.chain(_itemsWidths.current).take(i).sum().value() + centerOffset);
276-
}
277218

278-
const setItemsLayouts = useCallback(() => {
279-
// It's important to calculate itemOffsets for RTL support
280-
_itemsOffsets.current = getItemsOffsets();
281-
const itemsOffsets = _.map(_itemsOffsets.current, offset => (offset ? offset : 0) + INDICATOR_INSET);
282-
const itemsWidths = _.map(_itemsWidths.current, width => (width ? width : 0) - INDICATOR_INSET * 2);
283-
contentWidth.current = _.sum(_itemsWidths.current);
284-
const scrollEnabled = contentWidth.current > containerWidth;
285-
286-
setItemsWidths(itemsWidths);
287-
setItemsOffsets(itemsOffsets);
288-
setScrollEnabled(scrollEnabled);
289-
focusSelected([selectedIndex], false);
290-
}, [containerWidth, selectedIndex]);
291-
292-
const onItemLayout = useCallback(({width}: Partial<LayoutRectangle>, itemIndex: number) => {
293-
_itemsWidths.current[itemIndex] = width;
294-
if (!_.includes(_itemsWidths.current, null)) {
295-
setItemsLayouts();
296-
}
297-
},
298-
[setItemsLayouts]);
299-
300-
const _renderTabBarItems = _.map(items, (item, index) => {
301-
return (
302-
<TabBarItem
303-
labelColor={labelColor}
304-
selectedLabelColor={selectedLabelColor}
305-
labelStyle={labelStyle}
306-
selectedLabelStyle={selectedLabelStyle}
307-
uppercase={uppercase}
308-
iconColor={iconColor}
309-
selectedIconColor={selectedIconColor}
310-
activeBackgroundColor={activeBackgroundColor}
311-
key={item.label}
312-
// width={_itemsWidths.current[index]}
313-
{...item}
314-
{...context}
315-
index={index}
316-
state={itemStates[index]}
317-
onLayout={onItemLayout}
318-
/>
319-
);
320-
});
219+
return offsets;
220+
}, [itemsWidths]);
221+
222+
const _renderTabBarItems = useMemo((): ReactNode => {
223+
return _.map(items, (item, index) => {
224+
return (
225+
<TabBarItem
226+
labelColor={labelColor}
227+
selectedLabelColor={selectedLabelColor}
228+
labelStyle={labelStyle}
229+
selectedLabelStyle={selectedLabelStyle}
230+
uppercase={uppercase}
231+
iconColor={iconColor}
232+
selectedIconColor={selectedIconColor}
233+
activeBackgroundColor={activeBackgroundColor}
234+
key={item.label}
235+
// width={_itemsWidths.current[index]}
236+
{...item}
237+
{...context}
238+
index={index}
239+
state={itemStates[index]}
240+
onLayout={onItemLayout}
241+
/>
242+
);
243+
});
244+
}, [
245+
labelColor,
246+
selectedLabelColor,
247+
labelStyle,
248+
selectedLabelStyle,
249+
uppercase,
250+
iconColor,
251+
selectedIconColor,
252+
activeBackgroundColor,
253+
itemStates,
254+
centerSelected,
255+
onItemLayout
256+
]);
321257

322258
// TODO: Remove once props.children is deprecated
323-
const _renderTabBarItemsFromChildren = !children.current
324-
? null
325-
: React.Children.map(children.current, (child: Partial<ChildProps>, index: number) => {
326-
// @ts-ignore TODO: typescript - not sure if this can be easily solved
327-
return React.cloneElement(child, {
328-
labelColor,
329-
selectedLabelColor,
330-
labelStyle,
331-
selectedLabelStyle,
332-
uppercase,
333-
iconColor,
334-
selectedIconColor,
335-
activeBackgroundColor,
336-
...child.props,
337-
...context,
338-
index,
339-
state: itemStates[index],
340-
onLayout: onItemLayout
259+
const _renderTabBarItemsFromChildren = useMemo((): ReactNode | null => {
260+
return !children.current
261+
? null
262+
: React.Children.map(children.current, (child: Partial<ChildProps>, index: number) => {
263+
// @ts-ignore TODO: typescript - not sure if this can be easily solved
264+
return React.cloneElement(child, {
265+
labelColor,
266+
selectedLabelColor,
267+
labelStyle,
268+
selectedLabelStyle,
269+
uppercase,
270+
iconColor,
271+
selectedIconColor,
272+
activeBackgroundColor,
273+
...child.props,
274+
...context,
275+
index,
276+
state: itemStates[index],
277+
onLayout: centerSelected ? onItemLayout : undefined
278+
});
341279
});
342-
});
280+
}, [
281+
labelColor,
282+
selectedLabelColor,
283+
labelStyle,
284+
selectedLabelStyle,
285+
uppercase,
286+
iconColor,
287+
selectedIconColor,
288+
activeBackgroundColor,
289+
itemStates,
290+
centerSelected,
291+
onItemLayout
292+
]);
343293

344-
const renderTabBarItems = _.isEmpty(itemStates) ? null : items ? _renderTabBarItems : _renderTabBarItemsFromChildren;
294+
const renderTabBarItems = useMemo(() => {
295+
return _.isEmpty(itemStates) ? null : items ? _renderTabBarItems : _renderTabBarItemsFromChildren;
296+
}, [itemStates, items, _renderTabBarItems, _renderTabBarItemsFromChildren]);
345297

346298
const _indicatorWidth = new Value(0); // TODO: typescript?
347299
const _indicatorOffset = new Value(0); // TODO: typescript?
@@ -357,44 +309,54 @@ const TabBar = (props: Props) => {
357309
<Reanimated.View style={[styles.selectedIndicator, indicatorStyle, _indicatorTransitionStyle]}/>
358310
) : undefined;
359311

360-
const renderCodeBlock = () => {
312+
const renderCodeBlock = _.memoize(() => {
361313
const nodes: any[] = [];
362314

363315
nodes.push(set(_indicatorOffset,
316+
interpolate(currentPage, {
317+
inputRange: indicatorOffsets.map((_v, i) => i),
318+
outputRange: indicatorOffsets
319+
})));
320+
nodes.push(set(_indicatorWidth,
364321
interpolate(currentPage, {
365322
inputRange: itemsWidths.map((_v, i) => i),
366323
outputRange: itemsWidths.map(v => v - 2 * INDICATOR_INSET)
367324
})));
368-
nodes.push(set(_indicatorWidth,
369-
interpolate(currentPage, {inputRange: itemsWidths.map((_v, i) => i), outputRange: itemsWidths})));
370325

371-
nodes.push(Reanimated.onChange(targetPage, Reanimated.call([targetPage], focusSelected)));
326+
nodes.push(Reanimated.onChange(targetPage, Reanimated.call([targetPage], focusIndex as any)));
372327

373-
return <Code>{() => block(nodes)}</Code>;
374-
};
328+
const temp = <Code>{() => block(nodes)}</Code>;
329+
return temp;
330+
});
331+
332+
const shadowStyle = useMemo(() => {
333+
return enableShadow ? propsShadowStyle || styles.containerShadow : undefined;
334+
}, [enableShadow, propsShadowStyle]);
335+
336+
const _containerStyle = useMemo(() => {
337+
return [styles.container, shadowStyle, {width: containerWidth}, containerStyle];
338+
}, [shadowStyle, containerWidth, containerStyle]);
339+
340+
const indicatorContainerStyle = useMemo(() => {
341+
return [styles.tabBar, !_.isUndefined(height) && {height}, {backgroundColor}];
342+
}, [height, backgroundColor]);
343+
344+
const scrollViewContainerStyle = useMemo(() => {
345+
return {minWidth: containerWidth};
346+
}, [containerWidth]);
375347

376-
const shadowStyle = enableShadow ? propsShadowStyle || styles.containerShadow : undefined;
377348
return (
378-
<View style={[styles.container, shadowStyle, {width: containerWidth}, containerStyle]}>
349+
<View style={_containerStyle}>
379350
<FadedScrollView
380-
// @ts-ignore TODO: typescript
351+
/**
352+
// @ts-ignore TODO: typescript */
381353
ref={tabBar}
382354
horizontal
383-
contentContainerStyle={{minWidth: containerWidth}}
384-
scrollEnabled={scrollEnabled}
385-
onScroll={onScroll}
355+
contentContainerStyle={scrollViewContainerStyle}
356+
scrollEnabled // TODO:
386357
testID={testID}
387-
snapToOffsets={snapBreakpoints}
388358
>
389-
<View
390-
style={[
391-
styles.tabBar,
392-
!_.isUndefined(height) && {height},
393-
{paddingHorizontal: centerOffset, backgroundColor}
394-
]}
395-
>
396-
{renderTabBarItems}
397-
</View>
359+
<View style={indicatorContainerStyle}>{renderTabBarItems}</View>
398360
{renderSelectedIndicator}
399361
</FadedScrollView>
400362
{_.size(itemsWidths) > 1 && renderCodeBlock()}

0 commit comments

Comments
 (0)