├── .gitignore ├── README.md ├── SwipeableElement.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Swipeable Element for React Native 2 | Experimental element that can be swiped left or right to perform different actions. This is still a major work in progress. 3 | 4 | ![](http://i.imgur.com/PLMnAfc.gif) 5 | 6 | ### Usage 7 | ``` 8 | var React = require('react-native'); 9 | var { 10 | View, 11 | } = React; 12 | var SwipeableElement = require('react-native-swipeable-element'); 13 | 14 | var MyComponent = React.createClass({ 15 | render: function() { 16 | return ( 17 | 18 | {'Some Text'}} 20 | swipeRightTitle={'Delete'} 21 | swipeRightTextColor={'#FFFFFF'} 22 | swipeRightBackgroundColor={'#000000'} 23 | swipeLeftTitle={'Archive'} 24 | swipeLeftTextColor={'#FFFFFF'} 25 | swipeLeftBackgroundColor={'#FF0000'} 26 | onSwipeRight={() => { 27 | // Handle swipe 28 | }} 29 | onSwipeLeft={() => { 30 | // Swipe left 31 | }} /> 32 | 33 | ); 34 | } 35 | }); 36 | 37 | ``` 38 | -------------------------------------------------------------------------------- /SwipeableElement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react-native'); 3 | var { 4 | StyleSheet, 5 | View, 6 | Text, 7 | PanResponder, 8 | LayoutAnimation, 9 | } = React; 10 | var Dimensions = require('Dimensions'); 11 | 12 | var SCREEN_WIDTH = Dimensions.get('window').width; 13 | var SCREEN_HEIGHT = Dimensions.get('window').height; 14 | 15 | var SWIPE_RELEASE_POINT = 70; 16 | 17 | var styles = StyleSheet.create({ 18 | swipeableElementWrapper: { 19 | width: SCREEN_WIDTH, 20 | flexDirection:'row', 21 | justifyContent:'center', 22 | }, 23 | swipeableMain: { 24 | width: SCREEN_WIDTH, 25 | padding:10, 26 | backgroundColor: '#F0F0F0', 27 | }, 28 | swipeableLeft: { 29 | overflow: 'hidden', 30 | width: 0, 31 | backgroundColor: '#FF0000', 32 | alignItems: 'flex-start', 33 | justifyContent:'center', 34 | }, 35 | swipeableRight: { 36 | overflow: 'hidden', 37 | width: 0, 38 | alignItems: 'flex-end', 39 | backgroundColor: '#0000FF', 40 | }, 41 | leftText: { 42 | color:'#FFFFFF', 43 | padding:10, 44 | alignSelf:'center', 45 | }, 46 | rightText: { 47 | color:'#FFFFFF', 48 | padding:10, 49 | } 50 | }); 51 | 52 | var SwipeableElement = React.createClass({ 53 | 54 | _panResponder: {}, 55 | 56 | _handleStartShouldSetPanResponder: function() { 57 | return true; 58 | }, 59 | 60 | _handleMoveShouldSetPanResponder: function() { 61 | return true; 62 | }, 63 | 64 | _handlePanResponderGrant: function() {}, 65 | 66 | _handlePanResponderMove: function(e, gestureState) { 67 | if (!this.refs.mainElement) { 68 | return; 69 | } 70 | 71 | var dx; 72 | var direction = gestureState.dx > 0 ? 'right' : 'left'; 73 | if (gestureState.dx < -150) { 74 | dx = -150; 75 | } else if (gestureState.dx > 150) { 76 | dx = 150; 77 | } else { 78 | dx = gestureState.dx; 79 | } 80 | 81 | var absdx = dx > 0 ? dx : -dx; 82 | var opacity = (absdx / SWIPE_RELEASE_POINT) * 1; 83 | var fontSize = 6 + ((opacity > 1 ? 1 : opacity) * 8); 84 | var paddingTop = 15 - ((opacity > 1 ? 1 : opacity) * 5); 85 | var text; 86 | var element; 87 | 88 | var props = { width: absdx*2, opacity, }; 89 | 90 | this.refs.mainElement.setNativeProps({ left: dx }); 91 | if (dx > 0) { 92 | element = this.refs.leftElement; 93 | props.left = absdx; 94 | text = this.refs.leftText; 95 | } else { 96 | element = this.refs.rightElement; 97 | props.right = absdx; 98 | text = this.refs.rightText; 99 | } 100 | text.setNativeProps({ fontSize, paddingTop }); 101 | element.setNativeProps(props); 102 | 103 | this.setState({ dx }); 104 | }, 105 | 106 | _handlePanResponderEnd: function() { 107 | if (this.state.dx > SWIPE_RELEASE_POINT) { 108 | if (this.props.onSwipeRight) { 109 | this.props.onSwipeRight.call(); 110 | } 111 | } else if (this.state.dx < -SWIPE_RELEASE_POINT) { 112 | if (this.props.onSwipeLeft) { 113 | this.props.onSwipeLeft.call(); 114 | } 115 | } 116 | var animations = require('../../TaskList/animations'); 117 | LayoutAnimation.configureNext(animations.easeInEaseOut); 118 | this.setState({dx:0,}); 119 | this.refs.mainElement && this.refs.mainElement.setNativeProps({ left: 0 }); 120 | this.refs.leftElement && this.refs.leftElement.setNativeProps({ width: 0, left: 0, }); 121 | this.refs.rightElement && this.refs.rightElement.setNativeProps({ width: 0, right: 0,}); 122 | // Reset the left/right values after the animation finishes 123 | // This feels hacky and I hope there's a better way to do this 124 | setTimeout(() => { 125 | this.refs.leftElement && this.refs.leftElement.setNativeProps({ left: null }); 126 | this.refs.rightElement && this.refs.rightElement.setNativeProps({ right: null }); 127 | }, 300); 128 | }, 129 | 130 | componentWillMount: function() { 131 | this._panResponder = PanResponder.create({ 132 | onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder, 133 | onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder, 134 | onPanResponderGrant: this._handlePanResponderGrant, 135 | onPanResponderMove: this._handlePanResponderMove, 136 | onPanResponderRelease: this._handlePanResponderEnd, 137 | onPanResponderTerminate: this._handlePanResponderEnd, 138 | }); 139 | }, 140 | 141 | getInitialState: function() { 142 | return { dx: 0, }; 143 | }, 144 | 145 | render: function() { 146 | var pullOrRelease = (this.state.dx > SWIPE_RELEASE_POINT || this.state.dx < -SWIPE_RELEASE_POINT) ? 147 | 'Release' : 148 | 'Pull'; 149 | 150 | var rightTextStyle = this.props.swipeLeftTextColor ? 151 | [styles.rightText, {color: this.props.swipeLeftTextColor,}] : 152 | styles.rightText; 153 | 154 | var leftTextStyle = this.props.swipeRightTextColor ? 155 | [styles.leftText, {color: this.props.swipeRightTextColor,}] : 156 | styles.leftText; 157 | 158 | var rightElementStyle = this.props.swipeLeftBackgroundColor ? 159 | [styles.swipeableLeft, {backgroundColor: this.props.swipeLeftBackgroundColor}] : 160 | styles.swipeableLeft; 161 | 162 | var leftElementStyle = this.props.swipeRightBackgroundColor ? 163 | [styles.swipeableRight, {backgroundColor: this.props.swipeRightBackgroundColor}] : 164 | styles.swipeableRight; 165 | 166 | return ( 167 | 168 | 169 | 170 | 171 | 172 | {pullOrRelease} to {this.props.swipeRightTitle} 173 | 174 | 175 | 176 | 177 | {this.props.component} 178 | 179 | 180 | 181 | {pullOrRelease} to {this.props.swipeLeftTitle} 182 | 183 | 184 | 185 | ); 186 | } 187 | }); 188 | 189 | module.exports = SwipeableElement; 190 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-swipeable-element", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "SwipeableElement.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Josh Levine", 10 | "license": "MIT", 11 | "dependencies": { 12 | "react-native": "^0.3.4" 13 | } 14 | } 15 | --------------------------------------------------------------------------------