UNPKG

react-native-swipeout-component

Version:

`React Native Swipeout` is a customizable swipeable row component for React Native, allowing you to create swipe actions like deleting, archiving, or other actions you might want to trigger with a swipe.

389 lines (386 loc) 15.1 kB
import React, { Component } from 'react'; import { PanResponder, View, StyleSheet, } from 'react-native'; import PropTypes from 'prop-types'; import SwipeoutBtn from './SwipeoutBtn'; import { debounce } from 'lodash'; class Swipeout extends Component { constructor(props) { super(props); this.swipeoutContent = null; this._handleSwipe = () => { if (this.props.autoOpenRight && !this.props.disabled && this.props.buttonWidth) { this._autoOpenRight(); } if (this.props.autoOpenLeft && !this.props.disabled && this.props.buttonWidth) { this._autoOpenLeft(); } /** * if (this.props.autoOpenBoth && !this.props.disabled && this.props.buttonWidth) { this._autoOpenBoth() } * */ }; /** * _autoOpenBoth = () => { this._autoOpenRight() setTimeout(() => { this.setState({ contentPos: 0, btnsLeftWidth: 0 }); }, this.props.timeout || 2000) this._autoOpenLeft() } */ this._autoOpenRight = () => { setTimeout(() => { this.setState({ contentPos: -this.props.buttonWidth, btnsRightWidth: this.props.buttonWidth }); }, 500); setTimeout(() => { this.setState({ contentPos: 0, btnsRightWidth: 0 }); }, this.props.timeout || 2000); }; this._autoOpenLeft = () => { setTimeout(() => { this.setState({ contentPos: this.props.buttonWidth / 2, btnsLeftWidth: this.props.left ? this.props.buttonWidth / 2 * this.props.left.length : 0, }); }, 500); setTimeout(() => { this.setState({ contentPos: 0, btnsLeftWidth: 0 }); }, this.props.timeout || 2000); }; this._debouncedOnCTA = debounce(() => this.props.onCTA(), 300); this._handlePanResponderGrant = () => { var _a; if (this.props.disabled) return; if (!this.state.openedLeft && !this.state.openedRight) { this._callOnOpen(); } else { this._callOnClose(); } (_a = this.swipeoutContent) === null || _a === void 0 ? void 0 : _a.measure((ox, oy, width) => { let 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(), }); }); }; this._handlePanResponderMove = (event, gestureState) => { if (this.props.disabled) return; let posX = gestureState.dx; let posY = gestureState.dy; const leftWidth = this.state.btnsLeftWidth; const rightWidth = this.state.btnsRightWidth; if (this.state.openedRight) posX = gestureState.dx - rightWidth; else if (this.state.openedLeft) posX = gestureState.dx + leftWidth; const 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) { if (posX < 0 && this.props.right) { this.setState({ backgroundColor: this.props.rightBackgroundColor, }); if (this.props.dragToCTA && this.props.right.length == 1 && (-(rightWidth + 150)) >= (Math.round(Math.min(posX, 0)))) { return this._debouncedOnCTA(); } this.setState({ contentPos: Math.min(posX, 0) }); } else if (posX > 0 && this.props.left) { this.setState({ backgroundColor: this.props.leftBackgroundColor, }); if (this.props.dragToCTA && this.props.left.length == 1 && (leftWidth + 70) <= (Math.round(Math.max(posX, 0)))) { return this._debouncedOnCTA(); } this.setState({ contentPos: Math.max(posX, 0) }); } } }; this._handlePanResponderEnd = (event, gestureState) => { if (this.props.disabled) return; const posX = gestureState.dx; const contentPos = this.state.contentPos; const contentWidth = this.state.contentWidth; const btnsLeftWidth = this.state.btnsLeftWidth; const btnsRightWidth = this.state.btnsRightWidth; const openX = contentWidth * 0.33; let openLeft = posX > openX || posX > btnsLeftWidth / 2; let openRight = posX < -openX || posX < -btnsRightWidth / 2; if (this.state.openedRight) openRight = posX - openX < -openX; if (this.state.openedLeft) openLeft = posX + openX > openX; const timeDiff = (new Date()).getTime() - this.state.timeStart < 200; if (timeDiff) { openRight = posX < -openX / 10 && !this.state.openedLeft; 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(); } } if (this.props.scroll) this.props.scroll(true); }; this._tweenContent = (state, endValue) => { this.setState({ [state]: endValue }); }; this._rubberBandEasing = (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; }; this._autoClose = (btn) => { if (this.state.autoClose) this._close(); const { onPress } = btn; if (onPress) onPress(); }; this._open = (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, }); }; this._close = () => { 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, }); }; this._callOnClose = () => { if (this.props.onClose) this.props.onClose(this.props.sectionID, this.props.rowID); }; this._callOnOpen = () => { if (this.props.onOpen) this.props.onOpen(this.props.sectionID, this.props.rowID); }; this._openRight = () => { var _a; (_a = this.swipeoutContent) === null || _a === void 0 ? void 0 : _a.measure((ox, oy, width) => { let btnWidth = (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 }); }); }); }; this._openLeft = () => { var _a; (_a = this.swipeoutContent) === null || _a === void 0 ? void 0 : _a.measure((ox, oy, width) => { let btnWidth = (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 }); }); }); }; this._onLayout = (event) => { const { width, height } = event.nativeEvent.layout; this.setState({ contentWidth: width, contentHeight: height, }); }; this._renderButtons = (buttons, isVisible, style) => { if (buttons && isVisible) { return (<View style={style}> {buttons.map(this._renderButton)} </View>); } else { return (<View />); } }; this._renderButton = (btn, i) => { return (<SwipeoutBtn backgroundColor={btn.buttonBackgroundColor} 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}/>); }; this.state = { 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, backgroundColor: '' }; this._initializePanResponder(); } componentDidMount() { this._initializePanResponder(); if (this.props.autoOpenRight || this.props.autoOpenLeft || this.props.autoOpenBoth) { this._handleSwipe(); } } _initializePanResponder() { 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: () => false, onPanResponderTerminationRequest: () => false, }); } render() { const { style, children, right, left } = this.props; const contentWidth = this.state.contentWidth; const height = this.state.contentHeight; const posX = this.state.contentPos; const styleSwipeout = [styles.swipeout, style]; if (this.state.backgroundColor) { styleSwipeout.push({ backgroundColor: this.state.backgroundColor, }); } let limit = -this.state.btnsRightWidth; if (posX > 0) limit = this.state.btnsLeftWidth; const styleLeftPos = { left: { left: 0, overflow: 'hidden', width: Math.min(limit * (posX / limit), limit), }, }; const styleRightPos = { right: { left: Math.abs(contentWidth + Math.max(limit, posX)), right: 0, }, }; const styleContentPos = { content: { transform: [{ translateX: this._rubberBandEasing(posX, limit) }], }, }; const styleContent = [styles.swipeoutContent]; styleContent.push(styleContentPos.content); const styleRight = [styles.swipeoutBtns]; styleRight.push(styleRightPos.right); const styleLeft = [styles.swipeoutBtns]; styleLeft.push(styleLeftPos.left); const isRightVisible = posX < 0; const isLeftVisible = posX > 0; return (<View style={styleSwipeout}> <View ref={node => (this.swipeoutContent = node)} style={[styleContent]} onLayout={this._onLayout} {...this._panResponder.panHandlers}> {children} </View> {this._renderButtons(right, isRightVisible, styleRight)} {this._renderButtons(left, isLeftVisible, styleLeft)} </View>); } } Swipeout.propTypes = { autoClose: PropTypes.bool, buttonBackgroundColor: PropTypes.string, close: PropTypes.bool, left: PropTypes.array, onOpen: PropTypes.func, onClose: PropTypes.func, right: PropTypes.array, scroll: PropTypes.func, style: PropTypes.object, sensitivity: PropTypes.number, buttonWidth: PropTypes.number, disabled: PropTypes.bool, autoOpenRight: PropTypes.bool, onCTA: PropTypes.func }; Swipeout.defaultProps = { disabled: false, rowID: -1, sectionID: -1, sensitivity: 50, }; const styles = StyleSheet.create({ swipeout: { overflow: 'hidden', }, swipeoutContent: { flex: 1, }, swipeoutBtns: { position: 'absolute', top: 0, bottom: 0, flexDirection: 'row', }, }); export default Swipeout;