tuya-panel-kit
Version:
a functional component library for developing tuya device panels!
287 lines (282 loc) • 8.14 kB
JavaScript
/* eslint-disable prettier/prettier */
/* eslint-disable react/no-unused-state */
import React from 'react';
import PropTypes from 'prop-types';
import {
View,
ScrollView,
Animated,
ViewPropTypes,
StyleSheet,
TouchableOpacity,
Text,
} from 'react-native';
import { RatioUtils } from '../../utils';
import TYText from '../TYText';
import wrapper from './tabHoc';
const { convertX, winWidth } = RatioUtils;
const styles = StyleSheet.create({
underlineStyle: {
height: 2,
backgroundColor: '#108ee9',
width: convertX(80),
position: 'absolute',
bottom: 0,
},
tabContainerStyle: {
flexDirection: 'row',
justifyContent: 'space-around',
},
tabStyle: {
alignItems: 'center',
justifyContent: 'center',
height: 43,
backgroundColor: '#fff',
width: convertX(80),
},
tabTextStyle: {
color: '#333',
fontSize: 16,
},
tabTextActiveStyle: {
color: '#108ee9',
},
tabWrapperStyle: {
backgroundColor: '#fff',
},
});
class TabBar extends React.PureComponent {
static propTypes = {
/**
* 下划线的样式
*/
underlineStyle: ViewPropTypes.style,
/**
* 每个tab的样式
*/
tabStyle: ViewPropTypes.style,
/**
* 高亮tab的样式
*/
tabActiveStyle: ViewPropTypes.style,
/**
* 每个tab内文字的样式
*/
tabTextStyle: Text.propTypes.style,
/**
* 高亮tab的文字样式
*/
tabActiveTextStyle: Text.propTypes.style,
/**
* tab内层容器样式
*/
wrapperStyle: ViewPropTypes.style,
/**
* tab外层容器样式
*/
style: ViewPropTypes.style,
/**
* 高亮tab的key
*/
// eslint-disable-next-line react/require-default-props
activeKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* 默认高亮tab的key
*/
defaultActiveKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* tab数据
*/
tabs: PropTypes.array,
/**
* 下划线是否居中
*/
isUnderlineCenter: PropTypes.bool,
/**
* tab切换的回调
*/
onChange: PropTypes.func,
};
static defaultProps = {
underlineStyle: {},
defaultActiveKey: 0,
tabs: [],
tabStyle: {},
tabActiveStyle: {},
tabTextStyle: {},
tabActiveTextStyle: {},
wrapperStyle: {},
style: {},
onChange: () => {},
isUnderlineCenter: true,
};
constructor(props) {
super(props);
this.state = {
activeKey: props.activeKey || props.defaultActiveKey,
underlineLeft: new Animated.Value(0),
underlineWidth: new Animated.Value(0),
};
this.tab = [];
this.tabBar = null;
this.getActiveIndexByKey(this.state.activeKey);
}
componentWillReceiveProps(nextProps) {
if ('activeKey' in nextProps) {
if (nextProps.activeKey === this.state.activeKey) return;
this.getActiveIndexByKey(nextProps.activeKey);
this.setState({ activeKey: nextProps.activeKey }, () => {
this.updateView(false);
});
}
}
onTabClick = (key, callback) => {
const { onChange } = this.props;
if (!('activeKey' in this.props)) {
this.setState({ activeKey: key }, () => {
this.updateView(false);
});
}
onChange && onChange(key);
callback && callback(key);
};
getTabs = () => {
const { tabs, tabStyle, tabActiveStyle, tabTextStyle, tabActiveTextStyle } = this.props;
const { activeKey } = this.state;
return tabs.map((tab, index) => {
const tabKey = typeof tab.key === 'undefined' ? `tab_${index}` : tab.key;
const isActive = tabKey === activeKey;
const style = [
styles.tabStyle,
tabStyle,
tab.style && tab.style,
isActive && tabActiveStyle,
isActive && tab.activeStyle && tab.activeStyle,
];
const textStyle = [
styles.tabTextStyle,
tabTextStyle,
tab.textStyle && tab.textStyle,
isActive && styles.tabTextActiveStyle,
isActive && tabActiveTextStyle,
isActive && tab.activeTextStyle && tab.activeTextStyle,
];
const { title } = tab;
return (
<TouchableOpacity
key={tabKey}
style={style}
onPress={() => this.onTabClick(tabKey, tab.onPress)}
onLayout={e => this.tabLayout(index, e)}
accessibilityLabel={tab.accessibilityLabel}
>
{typeof title !== 'string' ? (
title
) : (
<TYText style={textStyle} accessibilityLabel={tab.textAccessibilityLabel}>
{title}
</TYText>
)}
</TouchableOpacity>
);
});
};
getUnderline = () => {
const { underlineStyle } = this.props;
const { underlineLeft, underlineWidth } = this.state;
const style = [
styles.underlineStyle,
{ width: underlineWidth },
underlineStyle,
{ left: underlineLeft },
];
return <Animated.View style={style} />;
};
setRef = ref => {
this.scrollView = ref;
};
getActiveIndexByKey = activeKey => {
const { tabs } = this.props;
let activeIndex = 0;
for (let i = 0; i < tabs.length; i++) {
const tabKey = typeof tabs[i].key !== 'undefined' ? tabs[i].key : `tab_${i}`;
if (activeKey === tabKey) {
activeIndex = i;
break;
}
}
this.activeIndex = activeIndex;
};
updateScrollView = isSysUpdate => {
const { left, width } = this.tab[this.activeIndex];
const tempWidth = this.tabBarContainerWidth - width;
const newScrollX = Math.max(Math.min(left - tempWidth / 2, this.tabBar.width - winWidth), 0);
this.scrollView.scrollTo({ x: newScrollX, y: 0, animated: !isSysUpdate });
};
updateUnderline = isSysUpdate => {
const { underlineStyle, isUnderlineCenter } = this.props;
const underLineWidth = StyleSheet.flatten([styles.underlineStyle, underlineStyle]).width;
this.underlineLeftAnimation && this.underlineLeftAnimation.stop();
this.underlineWidthAnimation && this.underlineWidthAnimation.stop();
let { left } = this.tab[this.activeIndex];
const { width } = this.tab[this.activeIndex];
if (isUnderlineCenter) {
left += (width - underLineWidth) / 2;
}
if (isSysUpdate) {
this.state.underlineLeft.setValue(left);
this.state.underlineWidth.setValue(width);
} else {
this.underlineLeftAnimation = Animated.timing(this.state.underlineLeft, {
toValue: left,
duration: 200,
});
this.underlineWidthAnimation = Animated.timing(this.state.underlineWidth, {
toValue: width,
duration: 200,
});
this.underlineLeftAnimation.start();
this.underlineWidthAnimation.start();
}
};
updateView = isSysUpdate => {
const { tabs } = this.props;
if (!this.tabBar) return;
if (!this.tabBarContainerWidth) return;
if (this.tab.length <= 0) return;
const tabIsReady = this.tab.filter(value => value).length === tabs.length;
if (tabIsReady) {
this.updateScrollView(isSysUpdate);
this.updateUnderline(isSysUpdate);
}
};
tabBarContainerLayout = e => {
this.tabBarContainerWidth = e.nativeEvent.layout.width;
this.updateView(true);
};
tabBarLayout = e => {
this.tabBar = e.nativeEvent.layout;
this.updateView(true);
};
tabLayout = (index, e) => {
const { x, width, height } = e.nativeEvent.layout;
this.tab[index] = { left: x, right: x + width, width, height };
this.updateView(true);
};
render() {
const { wrapperStyle, style } = this.props;
const cWrapperStyle = [styles.tabContainerStyle, wrapperStyle];
const cStyle = [styles.tabWrapperStyle, style];
return (
<View style={cStyle} onLayout={this.tabBarContainerLayout}>
<ScrollView ref={this.setRef} showsHorizontalScrollIndicator={false} horizontal={true}>
<View onLayout={this.tabBarLayout} style={cWrapperStyle}>
{this.getTabs()}
</View>
{this.getUnderline()}
</ScrollView>
</View>
);
}
}
export default wrapper(TabBar);