@fto-consult/expo-ui
Version:
Bibliothèque de composants UI Expo,react-native
255 lines (234 loc) • 7.41 kB
JavaScript
import React from '$react';
import View from "$ecomponents/View";
import {
Animated,
StyleSheet,
Platform,
} from 'react-native';
import PropTypes from "prop-types";
import theme,{StylePropTypes,Colors} from "$theme";
import {isMobileNative,isMobileBrowser} from "$cplatform"
import {defaultStr} from "$cutils";
import {Elevations} from "$ecomponents/Surface";
import stableHash from "stable-hash";
import ScrollView from '$ecomponents/ScrollView';
const showScrollBarIndicator = !isMobileBrowser() && !isMobileNative();
import TabItem from "./TabItem";
const TabItemsComponent = ({
children,
activeIndex,
scrollable = true,
onChange = () => {},
indicatorProps,
disableIndicator,
removeShadow,
elevation = 7,
fixed = false,
testID,
onTabItemClick,
scrollViewProps,
...rest
}) => {
const backgroundColor = theme.isDark()? theme.colors.surface : theme.colors.primary;
indicatorProps = defaultObj(indicatorProps);
const indicatorStyle = Object.assign({},StyleSheet.flatten(indicatorProps.style));
indicatorStyle.backgroundColor = Colors.isValid(indicatorStyle.backgroundColor)? indicatorStyle.backgroundColor : theme.colors.secondary;
const animationRef = React.useRef(new Animated.Value(0));
const scrollViewRef = React.useRef(null);
const scrollViewPosition = React.useRef(0);
const tabItemsPosition = React.useRef([]);
const [tabContainerWidth,setTabContainerWidth] = React.useState(0);
const scrollHandler = React.useCallback(() => {
if (tabItemsPosition.current.length > activeIndex) {
let itemStartPosition =
activeIndex === 0 ? 0 : tabItemsPosition.current[activeIndex - 1].position;
let itemEndPosition = tabItemsPosition.current[activeIndex].position;
const scrollCurrentPosition = scrollViewPosition.current;
const tabContainerCurrentWidth = tabContainerWidth;
let scrollX = scrollCurrentPosition;
if (itemStartPosition < scrollCurrentPosition) {
scrollX += itemStartPosition - scrollCurrentPosition;
} else if (
scrollCurrentPosition + tabContainerCurrentWidth <
itemEndPosition
) {
scrollX +=
itemEndPosition - (scrollCurrentPosition + tabContainerCurrentWidth);
}
scrollViewRef.current?.scrollTo({
x: scrollX,
y: 0,
animated: true,
});
}
}, [tabContainerWidth, activeIndex]);
React.useEffect(() => {
Animated.timing(animationRef.current, {
toValue: activeIndex ,
useNativeDriver: true,
duration: 170,
}).start();
scrollable && requestAnimationFrame(scrollHandler);
}, [animationRef, scrollHandler, activeIndex, scrollable]);
const onScrollHandler = React.useCallback((event) => {
scrollViewPosition.current = event.nativeEvent.contentOffset.x;
}, []);
const activeIndicatorLayout = tabItemsPosition.current[activeIndex];
const WIDTH = activeIndicatorLayout?.width;
const getLeftPosition = React.useCallback(()=>{
let left = 0;
for(let i =0; i< activeIndex;i++){
if(isObj(tabItemsPosition.current[i]) && typeof tabItemsPosition.current[i].width =='number'){
left+= tabItemsPosition.current[i].width;
}
}
return left;
},[activeIndex])
indicatorStyle.left = getLeftPosition();
testID = defaultStr(testID,"RNE_TabComponent");
scrollViewProps = defaultObj(scrollViewProps)
const childrenContent = React.useMemo(()=>{
return React.Children.map(children, (child, index) => {
const active = index === activeIndex?true : false;
return React.cloneElement(
child,
{
onPress: (e) => {
if(typeof onTabItemClick =="function"){
onTabItemClick({...React.getOnPressArgs(e),index,tabIndex:index,event:e});
}
onChange(index);
},
onLayout: (event) => {
const { width } = event.nativeEvent.layout;
const previousItemPosition =
tabItemsPosition.current[index - 1]?.position || 0;
tabItemsPosition.current[index] = {
position: previousItemPosition + width,
width,
};
},
activeIndex,
index,
active,
testID : testID+'_Children_'+index
}
);
})
},[stableHash({children,activeIndex})])
return (<View
{...rest}
testID = {testID}
style={[
elevation && Elevations[elevation],
{
backgroundColor,
},
styles.viewStyle,
rest.style,
]}
onLayout={({ nativeEvent: { layout } }) => {
setTabContainerWidth(layout.width);
}}
>
<ScrollView
{...scrollViewProps}
showsHorizontalScrollIndicator = {showScrollBarIndicator}
scrollEventThrottle = {0}
horizontal
ref={scrollViewRef}
testID={testID+"_ScrollView"}
onScroll={onScrollHandler}
disableIndicator
>
{childrenContent}
{!disableIndicator && (
<Animated.View
{...indicatorProps}
testID={testID+'_Indicator'}
style={[
styles.indicator,
{
width: WIDTH,
},
indicatorStyle,
]}
/>
)}
</ScrollView>
<Animated.View
testID={testID+"_Shadow"}
style={[
styles.removeTopShadow,
{
height: elevation,
backgroundColor,
top: -elevation,
},
]}
/>
</View>);
};
const styles = StyleSheet.create({
relative : {
position : 'relative'
},
buttonStyle: {
borderRadius: 0,
backgroundColor: 'transparent',
},
titleStyle: {
paddingHorizontal: 16,
paddingVertical: 0,
textTransform: 'uppercase',
},
viewStyle: {
flexDirection: 'row',
position: 'relative',
maxWidth : '100%',
minHeight : 50,
},
indicator: {
display: 'flex',
position: 'absolute',
height: 4,
bottom: 0,
width : '100%',
...Platform.select({
web: {
backgroundColor: 'transparent',
transitionDuration: '150ms',
transitionProperty: 'all',
transformOrigin: 'left',
willChange: 'transform',
},
default: {},
}),
},
removeTopShadow: {
position: 'absolute',
left: 0,
right: 0,
zIndex: 2,
},
fixedContentContainerStyle: {
flex: 1,
},
});
TabItemsComponent.displayName = 'TabComponent.Items';
TabItemsComponent.propTypes = {
/** Child position index activeIndex. */
activeIndex : PropTypes.number,
onTabItemClick : PropTypes.func,
/** Makes Tab Scrolling */
scrollable : PropTypes.bool,
/** On Index Change Callback. */
onChange : PropTypes.func,
/** Disable the indicator below. */
disableIndicator : PropTypes.bool,
/** Additional styling for tab indicator. */
indicatorProps : PropTypes.object,
fixed : PropTypes.bool, //si les tabs items seronts fixes
}
export default TabItemsComponent
TabItemsComponent.Item = TabItem;