UNPKG

tuya-panel-kit

Version:

a functional component library for developing tuya device panels!

427 lines (404 loc) 11.2 kB
/* eslint-disable react/prop-types */ /* eslint-disable react/require-default-props */ import React from 'react'; import { PanResponder, StyleSheet, View, Animated, Easing, ViewPropTypes } from 'react-native'; import PropTypes from 'prop-types'; import NativeButton from './nativeButton'; const SwipeoutButton = props => { const swipeoutBtnStyle = [styles.swipeoutBtn]; // type if (props.type === 'delete') { swipeoutBtnStyle.push(styles.colorDelete); } if (props.type === 'primary') { swipeoutBtnStyle.push(styles.colorPrimary); } if (props.type === 'secondary') { swipeoutBtnStyle.push(styles.colorSecondary); } // background if (props.backgroundColor) { swipeoutBtnStyle.push([{ backgroundColor: props.backgroundColor }]); } // height|width swipeoutBtnStyle.push([ { height: props.height, width: props.width, }, ]); const swipeoutBtnContentStyle = { height: props.height, width: props.width, }; // textColor const swipeoutBtnTextStyle = [props.textStyle, styles.swipeoutBtnText]; if (props.color) { swipeoutBtnTextStyle.push([{ color: props.color }]); } if (props.fontSize) { swipeoutBtnTextStyle.push([{ fontSize: props.fontSize }]); } return ( <NativeButton onPress={props.onPress || null} disabled={props.disabled || false} style={[styles.swipeoutBtnWrapperStyle, swipeoutBtnStyle]} textStyle={swipeoutBtnTextStyle} > {props.content ? <View style={swipeoutBtnContentStyle}>{props.content}</View> : props.text} </NativeButton> ); }; class Swipeout extends React.Component { static defaultProps = { accessibilityLabel: 'Swipeout', disabled: false, rowID: -1, sectionID: -1, sensitivity: 50, }; static propTypes = { /** * 测试标志 */ accessibilityLabel: PropTypes.string, /** * 是否禁用swipeout所提供的侧滑操作 */ disabled: PropTypes.bool, /** * 往左滑出现的按钮 */ left: PropTypes.array, /** * 往右滑出现的按钮 */ right: PropTypes.array, /** * 侧滑之后出现按钮的宽度 */ buttonWidth: PropTypes.number, /** * 任意一侧按钮全显示的回调 */ onOpen: PropTypes.func, /** * 任意一侧按钮全隐藏的回调 */ onClose: PropTypes.func, /** * 侧滑的距离 */ sensitivity: PropTypes.number, /** * 滑动回调函数 */ scroll: PropTypes.func, /** * 滑动结束函数 */ onScrollEnd: PropTypes.func, /** * 容器样式 */ style: ViewPropTypes.style, /** * 当close从false变为true时,会隐藏所有侧滑操作按钮。反过来true变为false无任何变化。 */ close: PropTypes.bool, }; constructor(props) { super(props); this.state = { contentDot: new Animated.Value(0), contentDotNum: 0, btnWidth: 0, leftWidth: 0, rightWidth: 0, contentHeight: 0, contentWidth: 0, openedRight: false, openedLeft: false, swiping: false, }; } componentWillMount() { this.panResponder = PanResponder.create({ onStartShouldSetPanResponder: () => true, onStartShouldSetPanResponderCapture: () => this.state.openedLeft || this.state.openedRight, onMoveShouldSetPanResponderCapture: (event, gestureState) => Math.abs(gestureState.dx) > this.props.sensitivity && Math.abs(gestureState.dy) <= this.props.sensitivity, onPanResponderGrant: this.handlePanResponderGrant, onPanResponderMove: this.handlePanResponderMove, onPanResponderRelease: this.handlePanResponderEnd, onPanResponderTerminate: this.handlePanResponderEnd, onShouldBlockNativeResponder: /* istanbul ignore next */ () => false, onPanResponderTerminationRequest: () => false, }); this.state.contentDot.addListener(obj => { this.setState({ contentDotNum: obj.value, }); }); } componentWillReceiveProps(nextProps) { if (nextProps.close) this.onHide(); } onLayout = event => { const { width, height } = event.nativeEvent.layout; this.setState({ contentWidth: width, contentHeight: height, }); }; onOpen = () => { const { onOpen, sectionID, rowID } = this.props; if (onOpen && typeof onOpen === 'function') onOpen(sectionID, rowID); }; onClose = () => { const { onClose, sectionID, rowID } = this.props; if (onClose && typeof onClose === 'function') onClose(sectionID, rowID); }; onShow = (contentDot, direction, duration) => { const left = direction === 'left'; this.onOpen(); Animated.timing(this.state.contentDot, { duration, easing: Easing.linear, delay: 0, toValue: contentDot, }).start(() => { this.setState({ openedLeft: left, openedRight: !left, swiping: false, }); }); }; onHide = () => { const { sectionID, rowID, onClose } = this.props; const { openedLeft, openedRight } = this.state; if (onClose && (openedLeft || openedRight)) { const direction = openedRight ? 'right' : 'left'; onClose(sectionID, rowID, direction); } Animated.timing(this.state.contentDot, { duration: 160, easing: Easing.linear, delay: 0, toValue: 0, }).start(() => { this.setState({ openedRight: false, openedLeft: false, swiping: false, }); }); }; grantMeasureCallback = (x, y, width) => { const { left, right, buttonWidth } = this.props; const btnWidth = buttonWidth || width / 5; this.setState({ btnWidth, leftWidth: left ? left.length * btnWidth : 0, rightWidth: right ? right.length * btnWidth : 0, swiping: true, }); }; handlePanResponderGrant = () => { const { disabled } = this.props; if (disabled) return; const { openedLeft, openedRight } = this.state; if (!openedLeft && !openedRight) { this.onOpen(); } else { this.onClose(); } this.swipeoutContent.measure(this.grantMeasureCallback); }; handlePanResponderMove = (e, gestureState) => { const { disabled } = this.props; const { openedLeft, openedRight, leftWidth, rightWidth } = this.state; if (disabled) return; let { dx } = gestureState; const { dy } = gestureState; if (openedRight) { dx -= rightWidth; } else if (openedLeft) { dx += leftWidth; } const moveHorizontal = Math.abs(dx) > Math.abs(dy); if (this.props.scroll) { if (moveHorizontal) { this.props.scroll(false); } else { this.props.scroll(true); } } if (!this.state.swiping) return; if (dx < 0 && this.props.right) { this.state.contentDot.setValue(dx); } else if (dx > 0 && this.props.left) { this.state.contentDot.setValue(dx); } }; handlePanResponderEnd = (e, gestureState) => { const { disabled, onScrollEnd } = this.props; const { openedLeft, openedRight, leftWidth, rightWidth, contentDotNum, contentWidth, swiping, } = this.state; if (disabled) return; const { dx } = gestureState; const openX = contentWidth * 0.33; let openLeft = dx > openX || dx > leftWidth / 2; let openRight = dx < -openX || dx < -rightWidth / 2; if (openedRight) { openRight = dx - openX < -openX; } if (openedLeft) { openLeft = dx + openX > openX; } if (swiping) { if (openRight && contentDotNum < 0 && dx < 0) { this.onShow(-rightWidth, 'right', dx > openX ? 350 : 160); } else if (openLeft && contentDotNum > 0 && dx > 0) { this.onShow(leftWidth, 'left'); } else { this.onHide(); } } if (typeof onScrollEnd === 'function' && onScrollEnd) { onScrollEnd(); } }; autoClose = btn => { const { onPress } = btn; if (this.props.autoClose) this.onHide(); if (onPress && typeof onPress === 'function') { onPress(); } }; renderButtons = (btnsArray, visible, style) => { const { accessibilityLabel } = this.props; if (btnsArray && visible) { return ( <Animated.View style={style}> {btnsArray.map((btn, index) => ( <SwipeoutButton backgroundColor={btn.backgroundColor} color={btn.color} disabled={btn.disabled} key={btn.key || index} accessibilityLabel={`${accessibilityLabel}_${btn.key || index}`} onPress={() => this.autoClose(btn)} text={btn.text} content={btn.content} type={btn.type} width={this.state.btnWidth} height={this.state.contentHeight} fontSize={btn.fontSize} textStyle={btn.textStyle} /> ))} </Animated.View> ); } }; render() { const { accessibilityLabel } = this.props; const { contentDotNum, contentWidth } = this.state; const styleSwipeout = [styles.swipeout, this.props.style]; if (this.props.backgroundColor) { styleSwipeout.push([{ backgroundColor: this.props.backgroundColor }]); } const styleLeft = [ styles.swipeoutBtns, { left: 0, overflow: 'hidden', width: contentDotNum, }, ]; const styleRight = [ styles.swipeoutBtns, { right: 0, left: Math.abs(contentWidth + contentDotNum), }, ]; const styleContent = [ styles.swipeoutContent, { left: this.state.contentDot, }, ]; const isRightVisible = contentDotNum < 0; const isLeftVisible = contentDotNum > 0; return ( <View style={styleSwipeout}> <Animated.View style={styleContent}> <View ref={ref => { this.swipeoutContent = ref; }} accessibilityLabel={accessibilityLabel} // style={styleContent} onLayout={this.onLayout} {...this.panResponder.panHandlers} > {this.props.children} </View> </Animated.View> {this.renderButtons(this.props.left, isLeftVisible, styleLeft)} {this.renderButtons(this.props.right, isRightVisible, styleRight)} </View> ); } } const styles = StyleSheet.create({ colorDelete: { backgroundColor: '#fb3d38', }, colorPrimary: { backgroundColor: '#006fff', }, colorSecondary: { backgroundColor: '#fd9427', }, swipeout: { backgroundColor: '#dbddde', overflow: 'hidden', }, swipeoutBtn: { alignItems: 'center', backgroundColor: '#fff', flex: 1, justifyContent: 'center', overflow: 'hidden', }, swipeoutBtnText: { color: '#fff', textAlign: 'center', }, swipeoutBtnWrapperStyle: { flex: 1, }, swipeoutBtns: { bottom: 0, flex: 1, flexDirection: 'row', position: 'absolute', right: 0, top: 0, }, swipeoutContent: {}, }); export default Swipeout;