react-native-swipe
Version:
365 lines (315 loc) • 10.4 kB
JavaScript
import tweenState from 'react-tween-state';
import NativeButton from './NativeButton';
import styles from './styles';
import React, {
Component,
PropTypes,
} from 'react';
import {
PanResponder,
TouchableHighlight,
StyleSheet,
Text,
View,
} from 'react-native';
const SwipeoutBtn = React.createClass({
propTypes: {
backgroundColor: PropTypes.string,
color: PropTypes.string,
component: PropTypes.node,
onPress: PropTypes.func,
text: PropTypes.string,
type: PropTypes.string,
underlayColor: PropTypes.string,
},
getDefaultProps: function() {
return {
backgroundColor: null,
color: null,
component: null,
underlayColor: null,
height: 0,
key: null,
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 (
<NativeButton
onPress={this.props.onPress}
style={styles.swipeoutBtnTouchable}
underlayColor={this.props.underlayColor}
disabled={this.props.disabled}
style={styleSwipeoutBtn}
textStyle={styleSwipeoutBtnText}>
{
(btn.component ?
<View style={styleSwipeoutBtnComponent}>{btn.component}</View>
:
btn.text
)
}
</NativeButton>
);
}
});
const Swipeout = React.createClass({
mixins: [tweenState.Mixin],
propTypes: {
autoClose: PropTypes.bool,
backgroundColor: PropTypes.string,
close: PropTypes.bool,
left: PropTypes.array,
onOpen: PropTypes.func,
right: PropTypes.array,
scroll: PropTypes.func,
style: View.propTypes.style,
sensitivity: PropTypes.number,
},
getDefaultProps: function() {
return {
rowID: -1,
sectionID: -1,
sensitivity: 0,
};
},
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,
onMoveShouldSetPanResponder: (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) => true,
});
},
componentWillReceiveProps: function(nextProps) {
if (nextProps.close) this._close();
},
_handlePanResponderGrant: function(e: Object, gestureState: Object) {
if(this.props.onOpen){
this.props.onOpen(this.props.sectionID, this.props.rowID);
}
this.refs.swipeoutContent.measure((ox, oy, width, height) => {
this.setState({
btnWidth: (width/5),
btnsLeftWidth: this.props.left ? (width/5)*this.props.left.length : 0,
btnsRightWidth: this.props.right ? (width/5)*this.props.right.length : 0,
swiping: true,
timeStart: (new Date()).getTime(),
});
});
},
_handlePanResponderMove: function(e: Object, gestureState: Object) {
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) {
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) {
// open swipeout right
this._tweenContent('contentPos', -btnsRightWidth);
this.setState({ contentPos: -btnsRightWidth, openedLeft: false, openedRight: true });
} else if (openLeft && contentPos > 0 && posX > 0) {
// open swipeout left
this._tweenContent('contentPos', btnsLeftWidth);
this.setState({ contentPos: btnsLeftWidth, openedLeft: true, openedRight: false });
}
else {
// close swipeout
this._tweenContent('contentPos', 0);
this.setState({ contentPos: 0, openedLeft: false, openedRight: false });
}
}
// 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) {
var onPress = btn.onPress;
if (onPress) onPress();
if (this.state.autoClose) this._close();
},
_close: function() {
this._tweenContent('contentPos', 0);
this.setState({
openedRight: false,
openedLeft: 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: {
left: 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 (
<View style={styleSwipeout}>
<View
ref="swipeoutContent"
style={styleContent}
onLayout={this._onLayout}
{...this._panResponder.panHandlers}>
{this.props.children}
</View>
{ this._renderButtons(this.props.right, isRightVisible, styleRight) }
{ this._renderButtons(this.props.left, isLeftVisible, styleLeft) }
</View>
);
},
_onLayout: function(event) {
var { width, height } = event.nativeEvent.layout;
this.setState({
contentWidth: width,
contentHeight: height,
});
},
_renderButtons: function(buttons, isVisible, style) {
if (buttons && isVisible) {
return( <View style={style}>
{ buttons.map(this._renderButton) }
</View>);
} else {
return (
<View/>
);
}
},
_renderButton: function(btn, i) {
return (
<SwipeoutBtn
backgroundColor={btn.backgroundColor}
color={btn.color}
component={btn.component}
disabled={btn.disabled}
height={this.state.contentHeight}
key={i}
onPress={() => 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;