import tweenState from 'react-tween-state'; import NativeButton from './NativeButton'; import styles from './styles'; import React, { Component, } from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import { PanResponder, TouchableHighlight, StyleSheet, Text, View, ViewPropTypes, } from 'react-native'; const SwipeoutBtn = createReactClass({ propTypes: { backgroundColor: PropTypes.string, color: PropTypes.string, component: PropTypes.node, onPress: PropTypes.func, text: PropTypes.node, type: PropTypes.string, underlayColor: PropTypes.string, }, getDefaultProps: function () { return { backgroundColor: null, color: null, component: null, underlayColor: null, height: 0, onPress: null, disabled: false, text: 'Click me', type: '', width: 0, }; }, render: function () { var btn = this.props; var styleSwipeoutBtn = [styles.swipeoutBtn]; // apply "type" styles (delete || primary || secondary) if (btn.type === 'delete') styleSwipeoutBtn.push(styles.colorDelete); else if (btn.type === 'primary') styleSwipeoutBtn.push(styles.colorPrimary); else if (btn.type === 'secondary') styleSwipeoutBtn.push(styles.colorSecondary); // apply background color if (btn.backgroundColor) styleSwipeoutBtn.push([{ backgroundColor: btn.backgroundColor }]); styleSwipeoutBtn.push([{ height: btn.height, width: btn.width, }]); var styleSwipeoutBtnComponent = []; // set button dimensions styleSwipeoutBtnComponent.push([{ height: btn.height, width: btn.width, }]); var styleSwipeoutBtnText = [styles.swipeoutBtnText]; // apply text color if (btn.color) styleSwipeoutBtnText.push({color: btn.color }); return ( { (btn.component ? {btn.component} : btn.text ) } ); } }); const Swipeout = createReactClass({ mixins: [tweenState.Mixin], propTypes: { autoClose: PropTypes.bool, backgroundColor: PropTypes.string, close: PropTypes.bool, left: PropTypes.array, onOpen: PropTypes.func, onClose: PropTypes.func, right: PropTypes.array, scroll: PropTypes.func, style: (ViewPropTypes || View.propTypes).style, sensitivity: PropTypes.number, buttonWidth: PropTypes.number, disabled: PropTypes.bool, }, getDefaultProps: function () { return { disabled: false, rowID: -1, sectionID: -1, sensitivity: 50, }; }, getInitialState: function () { return { autoClose: this.props.autoClose || false, btnWidth: 0, btnsLeftWidth: 0, btnsRightWidth: 0, contentHeight: 0, contentPos: 0, contentWidth: 0, openedRight: false, swiping: false, tweenDuration: 160, timeStart: null, }; }, componentWillMount: function () { this._panResponder = PanResponder.create({ onStartShouldSetPanResponder: (event, gestureState) => true, onStartShouldSetPanResponderCapture: (event, gestureState) => 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: (event, gestureState) => false, onPanResponderTerminationRequest: () => false, }); }, componentWillReceiveProps: function (nextProps) { if (nextProps.close) this._close(); if (nextProps.openRight) this._openRight(); if (nextProps.openLeft) this._openLeft(); }, _handlePanResponderGrant: function (e: Object, gestureState: Object) { if (this.props.disabled) return; if (!this.state.openedLeft && !this.state.openedRight) { this._callOnOpen(); } else { this._callOnClose(); } this.refs.swipeoutContent.measure((ox, oy, width, height) => { let buttonWidth = this.props.buttonWidth || (width / 5); this.setState({ btnWidth: buttonWidth, btnsLeftWidth: this.props.left ? buttonWidth * this.props.left.length : 0, btnsRightWidth: this.props.right ? buttonWidth * this.props.right.length : 0, swiping: true, timeStart: (new Date()).getTime(), }); }); }, _handlePanResponderMove: function (e: Object, gestureState: Object) { if (this.props.disabled) return; var posX = gestureState.dx; var posY = gestureState.dy; var leftWidth = this.state.btnsLeftWidth; var rightWidth = this.state.btnsRightWidth; if (this.state.openedRight) var posX = gestureState.dx - rightWidth; else if (this.state.openedLeft) var posX = gestureState.dx + leftWidth; // prevent scroll if moveX is true var moveX = Math.abs(posX) > Math.abs(posY); if (this.props.scroll) { if (moveX) this.props.scroll(false); else this.props.scroll(true); } if (this.state.swiping) { // move content to reveal swipeout if (posX < 0 && this.props.right) { this.setState({ contentPos: Math.min(posX, 0) }) } else if (posX > 0 && this.props.left) { this.setState({ contentPos: Math.max(posX, 0) }) }; } }, _handlePanResponderEnd: function (e: Object, gestureState: Object) { if (this.props.disabled) return; var posX = gestureState.dx; var contentPos = this.state.contentPos; var contentWidth = this.state.contentWidth; var btnsLeftWidth = this.state.btnsLeftWidth; var btnsRightWidth = this.state.btnsRightWidth; // minimum threshold to open swipeout var openX = contentWidth * 0.33; // should open swipeout var openLeft = posX > openX || posX > btnsLeftWidth / 2; var openRight = posX < -openX || posX < -btnsRightWidth / 2; // account for open swipeouts if (this.state.openedRight) var openRight = posX - openX < -openX; if (this.state.openedLeft) var openLeft = posX + openX > openX; // reveal swipeout on quick swipe var timeDiff = (new Date()).getTime() - this.state.timeStart < 200; if (timeDiff) { var openRight = posX < -openX / 10 && !this.state.openedLeft; var openLeft = posX > openX / 10 && !this.state.openedRight; } if (this.state.swiping) { if (openRight && contentPos < 0 && posX < 0) { this._open(-btnsRightWidth, 'right'); } else if (openLeft && contentPos > 0 && posX > 0) { this._open(btnsLeftWidth, 'left'); } else { this._close(); } } // Allow scroll if (this.props.scroll) this.props.scroll(true); }, _tweenContent: function (state, endValue) { this.tweenState(state, { easing: tweenState.easingTypes.easeInOutQuad, duration: endValue === 0 ? this.state.tweenDuration * 1.5 : this.state.tweenDuration, endValue: endValue, }); }, _rubberBandEasing: function (value, limit) { if (value < 0 && value < limit) return limit - Math.pow(limit - value, 0.85); else if (value > 0 && value > limit) return limit + Math.pow(value - limit, 0.85); return value; }, // close swipeout on button press _autoClose: function (btn) { if (this.state.autoClose) this._close(); var onPress = btn.onPress; if (onPress) onPress(); }, _open: function (contentPos, direction) { const left = direction === 'left'; const { sectionID, rowID, onOpen } = this.props; onOpen && onOpen(sectionID, rowID, direction); this._tweenContent('contentPos', contentPos); this.setState({ contentPos, openedLeft: left, openedRight: !left, swiping: false, }); }, _close: function () { const { sectionID, rowID, onClose } = this.props; if (onClose && (this.state.openedLeft || this.state.openedRight)) { const direction = this.state.openedRight ? 'right' : 'left'; onClose(sectionID, rowID, direction); } this._tweenContent('contentPos', 0); this._callOnClose(); this.setState({ openedRight: false, openedLeft: false, swiping: false, }); }, _callOnClose: function () { if (this.props.onClose) this.props.onClose(this.props.sectionID, this.props.rowID); }, _callOnOpen: function () { if (this.props.onOpen) this.props.onOpen(this.props.sectionID, this.props.rowID); }, _openRight: function () { this.refs.swipeoutContent.measure((ox, oy, width, height) => { let btnWidth = this.props.buttonWidth || (width / 5); this.setState({ btnWidth, btnsRightWidth: this.props.right ? btnWidth * this.props.right.length : 0, }, () => { this._tweenContent('contentPos', -this.state.btnsRightWidth); this._callOnOpen(); this.setState({ contentPos: -this.state.btnsRightWidth, openedLeft: false, openedRight: true, swiping: false }); }); }); }, _openLeft: function () { this.refs.swipeoutContent.measure((ox, oy, width, height) => { let btnWidth = this.props.buttonWidth || (width / 5); this.setState({ btnWidth, btnsLeftWidth: this.props.left ? btnWidth * this.props.left.length : 0, }, () => { this._tweenContent('contentPos', this.state.btnsLeftWidth); this._callOnOpen(); this.setState({ contentPos: this.state.btnsLeftWidth, openedLeft: true, openedRight: false, swiping: false }); }); }); }, render: function () { var contentWidth = this.state.contentWidth; var posX = this.getTweeningValue('contentPos'); var styleSwipeout = [styles.swipeout, this.props.style]; if (this.props.backgroundColor) { styleSwipeout.push([{ backgroundColor: this.props.backgroundColor }]); } var limit = -this.state.btnsRightWidth; if (posX > 0) var limit = this.state.btnsLeftWidth; var styleLeftPos = { left: { left: 0, overflow: 'hidden', width: Math.min(limit * (posX / limit), limit), }, }; var styleRightPos = { right: { left: Math.abs(contentWidth + Math.max(limit, posX)), right: 0, }, }; var styleContentPos = { content: { transform: [{ translateX: this._rubberBandEasing(posX, limit) }], }, }; var styleContent = [styles.swipeoutContent]; styleContent.push(styleContentPos.content); var styleRight = [styles.swipeoutBtns]; styleRight.push(styleRightPos.right); var styleLeft = [styles.swipeoutBtns]; styleLeft.push(styleLeftPos.left); var isRightVisible = posX < 0; var isLeftVisible = posX > 0; return ( {this.props.children} {this._renderButtons(this.props.right, isRightVisible, styleRight)} {this._renderButtons(this.props.left, isLeftVisible, styleLeft)} ); }, _onLayout: function (event) { var { width, height } = event.nativeEvent.layout; this.setState({ contentWidth: width, contentHeight: height, }); }, _renderButtons: function (buttons, isVisible, style) { if (buttons && isVisible) { return ( {buttons.map(this._renderButton)} ); } else { return ( ); } }, _renderButton: function (btn, i) { return ( this._autoClose(btn)} text={btn.text} type={btn.type} underlayColor={btn.underlayColor} width={this.state.btnWidth} /> ); } }) Swipeout.NativeButton = NativeButton; Swipeout.SwipeoutButton = SwipeoutBtn; export default Swipeout;