From d701e0d0f1d0761f30c26929c0231e89eaae7afe Mon Sep 17 00:00:00 2001 From: Diego de Blas Mateo <1613216+DiegoBM@users.noreply.github.com> Date: Thu, 7 Oct 2021 00:31:01 +0200 Subject: [PATCH 1/4] Added functionality to disable swiping when more than one finger is used --- CardStack.js | 452 +++++++++++++++++++++++++++++---------------------- README.md | 126 +++++++------- 2 files changed, 326 insertions(+), 252 deletions(-) diff --git a/CardStack.js b/CardStack.js index b83f65c..419628d 100644 --- a/CardStack.js +++ b/CardStack.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types' +import PropTypes from 'prop-types'; import { polyfill } from 'react-lifecycles-compat'; import { View, @@ -7,13 +7,12 @@ import { PanResponder, Dimensions, Text, - Platform + Platform, } from 'react-native'; const { height, width } = Dimensions.get('window'); class CardStack extends Component { - static distance(x, y) { return Math.hypot(x, y); } @@ -35,22 +34,44 @@ class CardStack extends Component { onStartShouldSetPanResponder: (evt, gestureState) => false, onStartShouldSetPanResponderCapture: (evt, gestureState) => false, onMoveShouldSetPanResponder: (evt, gestureState) => { + if ( + this.props.disableMultitouchSwipe && + gestureState.numberActiveTouches > 1 + ) { + return false; + } + const isVerticalSwipe = Math.sqrt( Math.pow(gestureState.dx, 2) < Math.pow(gestureState.dy, 2) - ) + ); if (!this.props.verticalSwipe && isVerticalSwipe) { - return false + return false; } - return Math.sqrt(Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2)) > 10 + return ( + Math.sqrt( + Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2) + ) > 10 + ); }, onMoveShouldSetPanResponderCapture: (evt, gestureState) => { + if ( + this.props.disableMultitouchSwipe && + gestureState.numberActiveTouches > 1 + ) { + return false; + } + const isVerticalSwipe = Math.sqrt( Math.pow(gestureState.dx, 2) < Math.pow(gestureState.dy, 2) - ) + ); if (!this.props.verticalSwipe && isVerticalSwipe) { - return false + return false; } - return Math.sqrt(Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2)) > 10 + return ( + Math.sqrt( + Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2) + ) > 10 + ); }, onPanResponderGrant: (evt, gestureState) => { this.props.onSwipeStart(); @@ -61,9 +82,15 @@ class CardStack extends Component { const movedY = gestureState.moveY - gestureState.y0; this.props.onSwipe(movedX, movedY); const { verticalSwipe, horizontalSwipe } = this.props; - const dragDistance = this.distance((horizontalSwipe) ? gestureState.dx : 0, (verticalSwipe) ? gestureState.dy : 0); + const dragDistance = this.distance( + horizontalSwipe ? gestureState.dx : 0, + verticalSwipe ? gestureState.dy : 0 + ); this.state.dragDistance.setValue(dragDistance); - this.state.drag.setValue({ x: (horizontalSwipe) ? gestureState.dx : 0, y: (verticalSwipe) ? gestureState.dy : 0 }); + this.state.drag.setValue({ + x: horizontalSwipe ? gestureState.dx : 0, + y: verticalSwipe ? gestureState.dy : 0, + }); }, onPanResponderTerminationRequest: (evt, gestureState) => true, onPanResponderRelease: (evt, gestureState) => { @@ -79,44 +106,60 @@ class CardStack extends Component { disableBottomSwipe, } = this.props; - if (((Math.abs(gestureState.dx) > horizontalThreshold) || - (Math.abs(gestureState.dx) > horizontalThreshold * 0.6 && - swipeDuration < 150) - ) && this.props.horizontalSwipe) { - - const swipeDirection = (gestureState.dx < 0) ? width * -1.5 : width * 1.5; + if ( + (Math.abs(gestureState.dx) > horizontalThreshold || + (Math.abs(gestureState.dx) > horizontalThreshold * 0.6 && + swipeDuration < 150)) && + this.props.horizontalSwipe + ) { + const swipeDirection = + gestureState.dx < 0 ? width * -1.5 : width * 1.5; if (swipeDirection < 0 && !disableLeftSwipe) { - this._nextCard('left', swipeDirection, gestureState.dy, this.props.duration); - } - else if (swipeDirection > 0 && !disableRightSwipe) { - this._nextCard('right', swipeDirection, gestureState.dy, this.props.duration); - } - else { + this._nextCard( + 'left', + swipeDirection, + gestureState.dy, + this.props.duration + ); + } else if (swipeDirection > 0 && !disableRightSwipe) { + this._nextCard( + 'right', + swipeDirection, + gestureState.dy, + this.props.duration + ); + } else { this._resetCard(); } - } else if (((Math.abs(gestureState.dy) > verticalThreshold) || - (Math.abs(gestureState.dy) > verticalThreshold * 0.8 && - swipeDuration < 150) - ) && this.props.verticalSwipe) { - - const swipeDirection = (gestureState.dy < 0) ? height * -1 : height; + } else if ( + (Math.abs(gestureState.dy) > verticalThreshold || + (Math.abs(gestureState.dy) > verticalThreshold * 0.8 && + swipeDuration < 150)) && + this.props.verticalSwipe + ) { + const swipeDirection = gestureState.dy < 0 ? height * -1 : height; if (swipeDirection < 0 && !disableTopSwipe) { - - this._nextCard('top', gestureState.dx, swipeDirection, this.props.duration); - } - else if (swipeDirection > 0 && !disableBottomSwipe) { - this._nextCard('bottom', gestureState.dx, swipeDirection, this.props.duration); - } - else { + this._nextCard( + 'top', + gestureState.dx, + swipeDirection, + this.props.duration + ); + } else if (swipeDirection > 0 && !disableBottomSwipe) { + this._nextCard( + 'bottom', + gestureState.dx, + swipeDirection, + this.props.duration + ); + } else { this._resetCard(); } - } - else { + } else { this._resetCard(); } }, - onPanResponderTerminate: (evt, gestureState) => { - }, + onPanResponderTerminate: (evt, gestureState) => {}, onShouldBlockNativeResponder: (evt, gestureState) => { return true; }, @@ -126,25 +169,27 @@ class CardStack extends Component { componentDidUpdate(prevProps) { if (typeof this.props.children === 'undefined') return; if (!this._isSameChildren(this.props.children, prevProps.children)) { - const children = Array.isArray(this.props.children) ? this.props.children : [this.props.children]; - let aIndex = (this.state.topCard == 'cardA') ? - this._getIndex(this.state.sindex - 2, children.length) : - this._getIndex(this.state.sindex - 1, children.length); - let bIndex = (this.state.topCard == 'cardB') ? - this._getIndex(this.state.sindex - 2, children.length) : - this._getIndex(this.state.sindex - 1, children.length); + const children = Array.isArray(this.props.children) + ? this.props.children + : [this.props.children]; + let aIndex = + this.state.topCard == 'cardA' + ? this._getIndex(this.state.sindex - 2, children.length) + : this._getIndex(this.state.sindex - 1, children.length); + let bIndex = + this.state.topCard == 'cardB' + ? this._getIndex(this.state.sindex - 2, children.length) + : this._getIndex(this.state.sindex - 1, children.length); this.setState({ cards: children, cardA: children[aIndex] || null, - cardB: children[bIndex] || null + cardB: children[bIndex] || null, }); } } - _getIndex(index, cards){ - return this.props.loop ? - this.mod(index, cards): - index; + _getIndex(index, cards) { + return this.props.loop ? this.mod(index, cards) : index; } componentDidMount() { @@ -157,21 +202,26 @@ class CardStack extends Component { if (Array.isArray(a) && Array.isArray(b)) { if (a.length != b.length) return false; for (let i in a) { - if (a[i].key != b[i].key) { return false } + if (a[i].key != b[i].key) { + return false; + } } return true; } if (a.key !== b.key) return false; - return true + return true; } initDeck() { if (typeof this.props.children === 'undefined') return; const { children, loop } = this.props; const cards = Array.isArray(children) ? children : [children]; - const initialIndexA = this.props.initialIndex < cards.length ? this.props.initialIndex : 0; - const initialIndexB = loop ? this.mod(initialIndexA + 1, cards.length) : initialIndexA + 1; + const initialIndexA = + this.props.initialIndex < cards.length ? this.props.initialIndex : 0; + const initialIndexB = loop + ? this.mod(initialIndexA + 1, cards.length) + : initialIndexA + 1; const cardA = cards[initialIndexA] || null; const cardB = cards[initialIndexB] || null; this.setState({ @@ -183,22 +233,16 @@ class CardStack extends Component { } _resetCard() { - Animated.timing( - this.state.dragDistance, - { - toValue: 0, - duration: this.props.duration, - useNativeDriver: this.props.useNativeDriver || false, - } - ).start(); - Animated.spring( - this.state.drag, - { - toValue: { x: 0, y: 0 }, - duration: this.props.duration, - useNativeDriver: this.props.useNativeDriver || false, - } - ).start(); + Animated.timing(this.state.dragDistance, { + toValue: 0, + duration: this.props.duration, + useNativeDriver: this.props.useNativeDriver || false, + }).start(); + Animated.spring(this.state.drag, { + toValue: { x: 0, y: 0 }, + duration: this.props.duration, + useNativeDriver: this.props.useNativeDriver || false, + }).start(); } goBackFromTop() { @@ -224,68 +268,62 @@ class CardStack extends Component { _goBack(direction) { const { cards, sindex, topCard } = this.state; - if ((sindex - 3) < 0 && !this.props.loop) return; + if (sindex - 3 < 0 && !this.props.loop) return; - const previusCardIndex = this.mod(sindex - 3, cards.length) + const previusCardIndex = this.mod(sindex - 3, cards.length); let update = {}; if (topCard === 'cardA') { update = { ...update, - cardB: cards[previusCardIndex] - - } + cardB: cards[previusCardIndex], + }; } else { update = { ...update, cardA: cards[previusCardIndex], - } + }; } - this.setState({ - ...update, - topCard: (topCard === 'cardA') ? 'cardB' : 'cardA', - sindex: sindex - 1 - }, () => { - - switch (direction) { - case 'top': - this.state.drag.setValue({ x: 0, y: -height }); - this.state.dragDistance.setValue(height); - break; - case 'left': - this.state.drag.setValue({ x: -width, y: 0 }); - this.state.dragDistance.setValue(width); - break; - case 'right': - this.state.drag.setValue({ x: width, y: 0 }); - this.state.dragDistance.setValue(width); - break; - case 'bottom': - this.state.drag.setValue({ x: 0, y: height }); - this.state.dragDistance.setValue(width); - break; - default: - - } + this.setState( + { + ...update, + topCard: topCard === 'cardA' ? 'cardB' : 'cardA', + sindex: sindex - 1, + }, + () => { + switch (direction) { + case 'top': + this.state.drag.setValue({ x: 0, y: -height }); + this.state.dragDistance.setValue(height); + break; + case 'left': + this.state.drag.setValue({ x: -width, y: 0 }); + this.state.dragDistance.setValue(width); + break; + case 'right': + this.state.drag.setValue({ x: width, y: 0 }); + this.state.dragDistance.setValue(width); + break; + case 'bottom': + this.state.drag.setValue({ x: 0, y: height }); + this.state.dragDistance.setValue(width); + break; + default: + } - Animated.spring( - this.state.dragDistance, - { + Animated.spring(this.state.dragDistance, { toValue: 0, duration: this.props.duration, useNativeDriver: this.props.useNativeDriver || false, - } - ).start(); + }).start(); - Animated.spring( - this.state.drag, - { + Animated.spring(this.state.drag, { toValue: { x: 0, y: 0 }, duration: this.props.duration, useNativeDriver: this.props.useNativeDriver || false, - } - ).start(); - }) + }).start(); + } + ); } swipeTop(d = null) { @@ -309,41 +347,34 @@ class CardStack extends Component { const { sindex, cards, topCard } = this.state; // index for the next card to be renderd - const nextCard = (loop) ? (Math.abs(sindex) % cards.length) : sindex; + const nextCard = loop ? Math.abs(sindex) % cards.length : sindex; // index of the swiped card - const index = (loop) ? this.mod(nextCard - 2, cards.length) : nextCard - 2; + const index = loop ? this.mod(nextCard - 2, cards.length) : nextCard - 2; if (index === cards.length - 1) { this.props.onSwipedAll(); } - if ((sindex - 2 < cards.length) || (loop)) { - Animated.spring( - this.state.dragDistance, - { - toValue: 220, - duration, - useNativeDriver: this.props.useNativeDriver || false, - } - ).start(); - - Animated.timing( - this.state.drag, - { - toValue: { x: (horizontalSwipe) ? x : 0, y: (verticalSwipe) ? y : 0 }, - duration, - useNativeDriver: this.props.useNativeDriver || false, - } - ).start(() => { + if (sindex - 2 < cards.length || loop) { + Animated.spring(this.state.dragDistance, { + toValue: 220, + duration, + useNativeDriver: this.props.useNativeDriver || false, + }).start(); - const newTopCard = (topCard === 'cardA') ? 'cardB' : 'cardA'; + Animated.timing(this.state.drag, { + toValue: { x: horizontalSwipe ? x : 0, y: verticalSwipe ? y : 0 }, + duration, + useNativeDriver: this.props.useNativeDriver || false, + }).start(() => { + const newTopCard = topCard === 'cardA' ? 'cardB' : 'cardA'; let update = {}; if (newTopCard === 'cardA') { update = { ...update, - cardB: cards[nextCard] + cardB: cards[nextCard], }; } if (newTopCard === 'cardB') { @@ -357,49 +388,59 @@ class CardStack extends Component { this.setState({ ...update, topCard: newTopCard, - sindex: nextCard + 1 + sindex: nextCard + 1, }); this.props.onSwiped(index); switch (direction) { case 'left': this.props.onSwipedLeft(index); - if (this.state.cards[index] && this.state.cards[index].props.onSwipedLeft) - this.state.cards[index] && this.state.cards[index].props.onSwipedLeft(); + if ( + this.state.cards[index] && + this.state.cards[index].props.onSwipedLeft + ) + this.state.cards[index] && + this.state.cards[index].props.onSwipedLeft(); break; case 'right': this.props.onSwipedRight(index); - if (this.state.cards[index] && this.state.cards[index].props.onSwipedRight) + if ( + this.state.cards[index] && + this.state.cards[index].props.onSwipedRight + ) this.state.cards[index].props.onSwipedRight(); break; case 'top': this.props.onSwipedTop(index); - if (this.state.cards[index] && this.state.cards[index].props.onSwipedTop) + if ( + this.state.cards[index] && + this.state.cards[index].props.onSwipedTop + ) this.state.cards[index].props.onSwipedTop(); break; case 'bottom': this.props.onSwipedBottom(index); - if (this.state.cards[index] && this.state.cards[index].props.onSwipedBottom) + if ( + this.state.cards[index] && + this.state.cards[index].props.onSwipedBottom + ) this.state.cards[index].props.onSwipedBottom(); break; default: } }); - } } - /** * @description CardB’s click feature is trigger the CardA on the card stack. (Solved on Android) * @see https://facebook.github.io/react-native/docs/view#pointerevents */ _setPointerEvents(topCard, topCardName) { - return { pointerEvents: topCard === topCardName ? "auto" : "none" } + return { pointerEvents: topCard === topCardName ? 'auto' : 'none' }; } render() { - const { secondCardZoom, renderNoMoreCards } = this.props; const { drag, dragDistance, cardA, cardB, topCard, sindex } = this.state; @@ -415,60 +456,76 @@ class CardStack extends Component { }); return ( - - + {renderNoMoreCards()} + style={[ + { + position: 'absolute', + zIndex: topCard === 'cardB' ? 3 : 2, + ...Platform.select({ + android: { + elevation: topCard === 'cardB' ? 3 : 2, + }, + }), + transform: [ + { rotate: topCard === 'cardB' ? rotate : '0deg' }, + { translateX: topCard === 'cardB' ? drag.x : 0 }, + { translateY: topCard === 'cardB' ? drag.y : 0 }, + { scale: topCard === 'cardB' ? 1 : scale }, + ], + }, + this.props.cardContainerStyle, + ]} + > {cardB} + style={[ + { + position: 'absolute', + zIndex: topCard === 'cardA' ? 3 : 2, + ...Platform.select({ + android: { + elevation: topCard === 'cardA' ? 3 : 2, + }, + }), + transform: [ + { rotate: topCard === 'cardA' ? rotate : '0deg' }, + { translateX: topCard === 'cardA' ? drag.x : 0 }, + { translateY: topCard === 'cardA' ? drag.y : 0 }, + { scale: topCard === 'cardA' ? 1 : scale }, + ], + }, + this.props.cardContainerStyle, + ]} + > {cardA} - ); } } CardStack.propTypes = { - children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired, - style: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), - cardContainerStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), + style: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.object, + PropTypes.array, + ]), + cardContainerStyle: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.object, + PropTypes.array, + ]), secondCardZoom: PropTypes.number, loop: PropTypes.bool, initialIndex: PropTypes.number, @@ -488,43 +545,46 @@ CardStack.propTypes = { disableLeftSwipe: PropTypes.bool, disableRightSwipe: PropTypes.bool, disableTopSwipe: PropTypes.bool, + disableMultitouchSwipe: PropTypes.bool, verticalSwipe: PropTypes.bool, verticalThreshold: PropTypes.number, horizontalSwipe: PropTypes.bool, horizontalThreshold: PropTypes.number, outputRotationRange: PropTypes.array, - duration: PropTypes.number -} + duration: PropTypes.number, +}; CardStack.defaultProps = { - style: {}, cardContainerStyle: {}, secondCardZoom: 0.95, loop: false, initialIndex: 0, - renderNoMoreCards: () => { return (No More Cards) }, + renderNoMoreCards: () => { + return No More Cards; + }, onSwipeStart: () => null, onSwipeEnd: () => null, - onSwiped: () => { }, - onSwipedLeft: () => { }, - onSwipedRight: () => { }, - onSwipedTop: () => { }, - onSwipedBottom: () => { }, - onSwipedAll: async () => { }, - onSwipe: () => { }, + onSwiped: () => {}, + onSwipedLeft: () => {}, + onSwipedRight: () => {}, + onSwipedTop: () => {}, + onSwipedBottom: () => {}, + onSwipedAll: async () => {}, + onSwipe: () => {}, disableBottomSwipe: false, disableLeftSwipe: false, disableRightSwipe: false, disableTopSwipe: false, + disableMultitouchSwipe: false, verticalSwipe: true, verticalThreshold: height / 4, horizontalSwipe: true, horizontalThreshold: width / 2, outputRotationRange: ['-15deg', '0deg', '15deg'], - duration: 300 -} + duration: 300, +}; polyfill(CardStack); export default CardStack; diff --git a/README.md b/README.md index 429d513..3de34eb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # react-native-card-stack-swiper + Tinder like react-native card stack swiper [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/lhandel/react-native-card-stack-swiper/issues) @@ -21,62 +22,74 @@ import CardStack, { Card } from 'react-native-card-stack-swiper'; ``` ```javascript - { this.swiper = swiper }}> - A - B - C - + { + this.swiper = swiper; + }} +> + + A + + + B + + + C + + ``` # CardStack ## CardStack props -| Props | type | description | required | default | -| --------------------| ------------- | --------------------------------| ------------- | ------------- | -| style | object | container style | | {} | -| cardContainerStyle | object | cardContainerStyle style | | {} | -| secondCardZoom | number | second card zoom | | 0.95 | -| duration | number | animation duration | | 300 | -| initialIndex | number | initial card index | | 0 | -| loop | bool | keep swiping indefinitely | | false | -| renderNoMoreCards | func | | | false | -| disableTopSwipe | bool | disable top swipe | | false | -| disableBottomSwipe | bool | disable bottom swipe | | false | -| disableLeftSwipe | bool | disable left swipe | | false | -| disableRightSwipe | bool | disable right swipe | | false | -| verticalSwipe | bool | enable/disable vertical swiping | | true | -| horizontalSwipe | bool | enable/disable horizont swiping | | true | -| verticalThreshold | number | vertical swipe threshold | | height/4 | -| horizontalThreshold | number | horizontal swipe threshold | | width/2 | -| outputRotationRange | array | rotation values for the x values| | ['-15deg', '0deg', '15deg'] | +| Props | type | description | required | default | +| ---------------------- | ------ | --------------------------------------- | -------- | --------------------------- | +| style | object | container style | | {} | +| cardContainerStyle | object | cardContainerStyle style | | {} | +| secondCardZoom | number | second card zoom | | 0.95 | +| duration | number | animation duration | | 300 | +| initialIndex | number | initial card index | | 0 | +| loop | bool | keep swiping indefinitely | | false | +| renderNoMoreCards | func | | | false | +| disableTopSwipe | bool | disable top swipe | | false | +| disableBottomSwipe | bool | disable bottom swipe | | false | +| disableLeftSwipe | bool | disable left swipe | | false | +| disableRightSwipe | bool | disable right swipe | | false | +| disableMultitouchSwipe | bool | disable swipe with more than one finger | | false | +| verticalSwipe | bool | enable/disable vertical swiping | | true | +| horizontalSwipe | bool | enable/disable horizont swiping | | true | +| verticalThreshold | number | vertical swipe threshold | | height/4 | +| horizontalThreshold | number | horizontal swipe threshold | | width/2 | +| outputRotationRange | array | rotation values for the x values | | ['-15deg', '0deg', '15deg'] | ## CardStack events -| Props | type | description | -| ----------------- | ------------- | --------------------------- | -| onSwipeStart | func | function to be called when a card swipe starts | -| onSwipeEnd | func | function to be called when a card swipe ends (card is released) | -| onSwiped | func | function to be called when a card is swiped. it receives the swiped card index | -| onSwipedLeft | func | function to be called when a card is swiped left. it receives the swiped card index | -| onSwipedRight | func | function to be called when a card is swiped right. it receives the swiped card index | -| onSwipedTop | func | function to be called when a card is swiped top. it receives the swiped card index | -| onSwipedBottom | func | function to be called when a card is swiped bottom. it receives the swiped card index | -| onSwipedAll | async func | function to be called when the last card is swiped. Could trig action to refresh cards | -| onSwipe | func | function to be called when a card is swiped. It receives the current x, and y coordinates| - +| Props | type | description | +| -------------- | ---------- | ----------------------------------------------------------------------------------------- | +| onSwipeStart | func | function to be called when a card swipe starts | +| onSwipeEnd | func | function to be called when a card swipe ends (card is released) | +| onSwiped | func | function to be called when a card is swiped. it receives the swiped card index | +| onSwipedLeft | func | function to be called when a card is swiped left. it receives the swiped card index | +| onSwipedRight | func | function to be called when a card is swiped right. it receives the swiped card index | +| onSwipedTop | func | function to be called when a card is swiped top. it receives the swiped card index | +| onSwipedBottom | func | function to be called when a card is swiped bottom. it receives the swiped card index | +| onSwipedAll | async func | function to be called when the last card is swiped. Could trig action to refresh cards | +| onSwipe | func | function to be called when a card is swiped. It receives the current x, and y coordinates | ## CardStack actions -| Props | type | -| ----------------- | ------------- | -| swipeLeft | func | -| swipeRight | func | -| swipeBottom | func | -| swipeTop | func | -| goBackFromLeft | func | -| goBackFromRight | func | -| goBackFromBottom | func | -| goBackFromTop | func | + +| Props | type | +| ---------------- | ---- | +| swipeLeft | func | +| swipeRight | func | +| swipeBottom | func | +| swipeTop | func | +| goBackFromLeft | func | +| goBackFromRight | func | +| goBackFromBottom | func | +| goBackFromTop | func | ```javascript { this.swiper = swiper }}> @@ -89,19 +102,20 @@ import CardStack, { Card } from 'react-native-card-stack-swiper'; ``` - # Card ## Card props -| Props | type | description | required | default | -| --------------------| ------------- | --------------------------------| ------------- | ------------- | -| style | object | container style | | {} | + +| Props | type | description | required | default | +| ----- | ------ | --------------- | -------- | ------- | +| style | object | container style | | {} | ## Card events -| Props | type | description | -| ----------------- | ------------- | --------------------------- | -| onSwiped | func | function to be called when a card is swiped. | -| onSwipedLeft | func | function to be called when a card is swiped left. | -| onSwipedRight | func | function to be called when a card is swiped right. | -| onSwipedTop | func | function to be called when a card is swiped top. | -| onSwipedBottom | func | function to be called when a card is swiped bottom. | + +| Props | type | description | +| -------------- | ---- | --------------------------------------------------- | +| onSwiped | func | function to be called when a card is swiped. | +| onSwipedLeft | func | function to be called when a card is swiped left. | +| onSwipedRight | func | function to be called when a card is swiped right. | +| onSwipedTop | func | function to be called when a card is swiped top. | +| onSwipedBottom | func | function to be called when a card is swiped bottom. | From d8b3a8a9dbd6cc233ba60fb06bcdbfb93d7d9539 Mon Sep 17 00:00:00 2001 From: Diego de Blas Mateo <1613216+DiegoBM@users.noreply.github.com> Date: Thu, 7 Oct 2021 00:41:22 +0200 Subject: [PATCH 2/4] disable prettier styles in order to match upstream's repository styles, not by choice --- CardStack.js | 446 +++++++++++++++++++++++---------------------------- 1 file changed, 198 insertions(+), 248 deletions(-) diff --git a/CardStack.js b/CardStack.js index 419628d..84922ef 100644 --- a/CardStack.js +++ b/CardStack.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import PropTypes from 'prop-types' import { polyfill } from 'react-lifecycles-compat'; import { View, @@ -7,12 +7,13 @@ import { PanResponder, Dimensions, Text, - Platform, + Platform } from 'react-native'; const { height, width } = Dimensions.get('window'); class CardStack extends Component { + static distance(x, y) { return Math.hypot(x, y); } @@ -34,44 +35,30 @@ class CardStack extends Component { onStartShouldSetPanResponder: (evt, gestureState) => false, onStartShouldSetPanResponderCapture: (evt, gestureState) => false, onMoveShouldSetPanResponder: (evt, gestureState) => { - if ( - this.props.disableMultitouchSwipe && - gestureState.numberActiveTouches > 1 - ) { + if (this.props.disableMultitouchSwipe && gestureState.numberActiveTouches > 1) { return false; } const isVerticalSwipe = Math.sqrt( Math.pow(gestureState.dx, 2) < Math.pow(gestureState.dy, 2) - ); + ) if (!this.props.verticalSwipe && isVerticalSwipe) { - return false; + return false } - return ( - Math.sqrt( - Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2) - ) > 10 - ); + return Math.sqrt(Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2)) > 10 }, onMoveShouldSetPanResponderCapture: (evt, gestureState) => { - if ( - this.props.disableMultitouchSwipe && - gestureState.numberActiveTouches > 1 - ) { + if (this.props.disableMultitouchSwipe && gestureState.numberActiveTouches > 1) { return false; } const isVerticalSwipe = Math.sqrt( Math.pow(gestureState.dx, 2) < Math.pow(gestureState.dy, 2) - ); + ) if (!this.props.verticalSwipe && isVerticalSwipe) { - return false; + return false } - return ( - Math.sqrt( - Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2) - ) > 10 - ); + return Math.sqrt(Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2)) > 10 }, onPanResponderGrant: (evt, gestureState) => { this.props.onSwipeStart(); @@ -82,15 +69,9 @@ class CardStack extends Component { const movedY = gestureState.moveY - gestureState.y0; this.props.onSwipe(movedX, movedY); const { verticalSwipe, horizontalSwipe } = this.props; - const dragDistance = this.distance( - horizontalSwipe ? gestureState.dx : 0, - verticalSwipe ? gestureState.dy : 0 - ); + const dragDistance = this.distance((horizontalSwipe) ? gestureState.dx : 0, (verticalSwipe) ? gestureState.dy : 0); this.state.dragDistance.setValue(dragDistance); - this.state.drag.setValue({ - x: horizontalSwipe ? gestureState.dx : 0, - y: verticalSwipe ? gestureState.dy : 0, - }); + this.state.drag.setValue({ x: (horizontalSwipe) ? gestureState.dx : 0, y: (verticalSwipe) ? gestureState.dy : 0 }); }, onPanResponderTerminationRequest: (evt, gestureState) => true, onPanResponderRelease: (evt, gestureState) => { @@ -106,60 +87,44 @@ class CardStack extends Component { disableBottomSwipe, } = this.props; - if ( - (Math.abs(gestureState.dx) > horizontalThreshold || - (Math.abs(gestureState.dx) > horizontalThreshold * 0.6 && - swipeDuration < 150)) && - this.props.horizontalSwipe - ) { - const swipeDirection = - gestureState.dx < 0 ? width * -1.5 : width * 1.5; + if (((Math.abs(gestureState.dx) > horizontalThreshold) || + (Math.abs(gestureState.dx) > horizontalThreshold * 0.6 && + swipeDuration < 150) + ) && this.props.horizontalSwipe) { + + const swipeDirection = (gestureState.dx < 0) ? width * -1.5 : width * 1.5; if (swipeDirection < 0 && !disableLeftSwipe) { - this._nextCard( - 'left', - swipeDirection, - gestureState.dy, - this.props.duration - ); - } else if (swipeDirection > 0 && !disableRightSwipe) { - this._nextCard( - 'right', - swipeDirection, - gestureState.dy, - this.props.duration - ); - } else { + this._nextCard('left', swipeDirection, gestureState.dy, this.props.duration); + } + else if (swipeDirection > 0 && !disableRightSwipe) { + this._nextCard('right', swipeDirection, gestureState.dy, this.props.duration); + } + else { this._resetCard(); } - } else if ( - (Math.abs(gestureState.dy) > verticalThreshold || - (Math.abs(gestureState.dy) > verticalThreshold * 0.8 && - swipeDuration < 150)) && - this.props.verticalSwipe - ) { - const swipeDirection = gestureState.dy < 0 ? height * -1 : height; + } else if (((Math.abs(gestureState.dy) > verticalThreshold) || + (Math.abs(gestureState.dy) > verticalThreshold * 0.8 && + swipeDuration < 150) + ) && this.props.verticalSwipe) { + + const swipeDirection = (gestureState.dy < 0) ? height * -1 : height; if (swipeDirection < 0 && !disableTopSwipe) { - this._nextCard( - 'top', - gestureState.dx, - swipeDirection, - this.props.duration - ); - } else if (swipeDirection > 0 && !disableBottomSwipe) { - this._nextCard( - 'bottom', - gestureState.dx, - swipeDirection, - this.props.duration - ); - } else { + + this._nextCard('top', gestureState.dx, swipeDirection, this.props.duration); + } + else if (swipeDirection > 0 && !disableBottomSwipe) { + this._nextCard('bottom', gestureState.dx, swipeDirection, this.props.duration); + } + else { this._resetCard(); } - } else { + } + else { this._resetCard(); } }, - onPanResponderTerminate: (evt, gestureState) => {}, + onPanResponderTerminate: (evt, gestureState) => { + }, onShouldBlockNativeResponder: (evt, gestureState) => { return true; }, @@ -169,27 +134,25 @@ class CardStack extends Component { componentDidUpdate(prevProps) { if (typeof this.props.children === 'undefined') return; if (!this._isSameChildren(this.props.children, prevProps.children)) { - const children = Array.isArray(this.props.children) - ? this.props.children - : [this.props.children]; - let aIndex = - this.state.topCard == 'cardA' - ? this._getIndex(this.state.sindex - 2, children.length) - : this._getIndex(this.state.sindex - 1, children.length); - let bIndex = - this.state.topCard == 'cardB' - ? this._getIndex(this.state.sindex - 2, children.length) - : this._getIndex(this.state.sindex - 1, children.length); + const children = Array.isArray(this.props.children) ? this.props.children : [this.props.children]; + let aIndex = (this.state.topCard == 'cardA') ? + this._getIndex(this.state.sindex - 2, children.length) : + this._getIndex(this.state.sindex - 1, children.length); + let bIndex = (this.state.topCard == 'cardB') ? + this._getIndex(this.state.sindex - 2, children.length) : + this._getIndex(this.state.sindex - 1, children.length); this.setState({ cards: children, cardA: children[aIndex] || null, - cardB: children[bIndex] || null, + cardB: children[bIndex] || null }); } } - _getIndex(index, cards) { - return this.props.loop ? this.mod(index, cards) : index; + _getIndex(index, cards){ + return this.props.loop ? + this.mod(index, cards): + index; } componentDidMount() { @@ -202,26 +165,21 @@ class CardStack extends Component { if (Array.isArray(a) && Array.isArray(b)) { if (a.length != b.length) return false; for (let i in a) { - if (a[i].key != b[i].key) { - return false; - } + if (a[i].key != b[i].key) { return false } } return true; } if (a.key !== b.key) return false; - return true; + return true } initDeck() { if (typeof this.props.children === 'undefined') return; const { children, loop } = this.props; const cards = Array.isArray(children) ? children : [children]; - const initialIndexA = - this.props.initialIndex < cards.length ? this.props.initialIndex : 0; - const initialIndexB = loop - ? this.mod(initialIndexA + 1, cards.length) - : initialIndexA + 1; + const initialIndexA = this.props.initialIndex < cards.length ? this.props.initialIndex : 0; + const initialIndexB = loop ? this.mod(initialIndexA + 1, cards.length) : initialIndexA + 1; const cardA = cards[initialIndexA] || null; const cardB = cards[initialIndexB] || null; this.setState({ @@ -233,16 +191,22 @@ class CardStack extends Component { } _resetCard() { - Animated.timing(this.state.dragDistance, { - toValue: 0, - duration: this.props.duration, - useNativeDriver: this.props.useNativeDriver || false, - }).start(); - Animated.spring(this.state.drag, { - toValue: { x: 0, y: 0 }, - duration: this.props.duration, - useNativeDriver: this.props.useNativeDriver || false, - }).start(); + Animated.timing( + this.state.dragDistance, + { + toValue: 0, + duration: this.props.duration, + useNativeDriver: this.props.useNativeDriver || false, + } + ).start(); + Animated.spring( + this.state.drag, + { + toValue: { x: 0, y: 0 }, + duration: this.props.duration, + useNativeDriver: this.props.useNativeDriver || false, + } + ).start(); } goBackFromTop() { @@ -268,62 +232,68 @@ class CardStack extends Component { _goBack(direction) { const { cards, sindex, topCard } = this.state; - if (sindex - 3 < 0 && !this.props.loop) return; + if ((sindex - 3) < 0 && !this.props.loop) return; - const previusCardIndex = this.mod(sindex - 3, cards.length); + const previusCardIndex = this.mod(sindex - 3, cards.length) let update = {}; if (topCard === 'cardA') { update = { ...update, - cardB: cards[previusCardIndex], - }; + cardB: cards[previusCardIndex] + + } } else { update = { ...update, cardA: cards[previusCardIndex], - }; + } } - this.setState( - { - ...update, - topCard: topCard === 'cardA' ? 'cardB' : 'cardA', - sindex: sindex - 1, - }, - () => { - switch (direction) { - case 'top': - this.state.drag.setValue({ x: 0, y: -height }); - this.state.dragDistance.setValue(height); - break; - case 'left': - this.state.drag.setValue({ x: -width, y: 0 }); - this.state.dragDistance.setValue(width); - break; - case 'right': - this.state.drag.setValue({ x: width, y: 0 }); - this.state.dragDistance.setValue(width); - break; - case 'bottom': - this.state.drag.setValue({ x: 0, y: height }); - this.state.dragDistance.setValue(width); - break; - default: - } + this.setState({ + ...update, + topCard: (topCard === 'cardA') ? 'cardB' : 'cardA', + sindex: sindex - 1 + }, () => { + + switch (direction) { + case 'top': + this.state.drag.setValue({ x: 0, y: -height }); + this.state.dragDistance.setValue(height); + break; + case 'left': + this.state.drag.setValue({ x: -width, y: 0 }); + this.state.dragDistance.setValue(width); + break; + case 'right': + this.state.drag.setValue({ x: width, y: 0 }); + this.state.dragDistance.setValue(width); + break; + case 'bottom': + this.state.drag.setValue({ x: 0, y: height }); + this.state.dragDistance.setValue(width); + break; + default: - Animated.spring(this.state.dragDistance, { + } + + Animated.spring( + this.state.dragDistance, + { toValue: 0, duration: this.props.duration, useNativeDriver: this.props.useNativeDriver || false, - }).start(); + } + ).start(); - Animated.spring(this.state.drag, { + Animated.spring( + this.state.drag, + { toValue: { x: 0, y: 0 }, duration: this.props.duration, useNativeDriver: this.props.useNativeDriver || false, - }).start(); - } - ); + } + ).start(); + }) } swipeTop(d = null) { @@ -347,34 +317,41 @@ class CardStack extends Component { const { sindex, cards, topCard } = this.state; // index for the next card to be renderd - const nextCard = loop ? Math.abs(sindex) % cards.length : sindex; + const nextCard = (loop) ? (Math.abs(sindex) % cards.length) : sindex; // index of the swiped card - const index = loop ? this.mod(nextCard - 2, cards.length) : nextCard - 2; + const index = (loop) ? this.mod(nextCard - 2, cards.length) : nextCard - 2; if (index === cards.length - 1) { this.props.onSwipedAll(); } - if (sindex - 2 < cards.length || loop) { - Animated.spring(this.state.dragDistance, { - toValue: 220, - duration, - useNativeDriver: this.props.useNativeDriver || false, - }).start(); + if ((sindex - 2 < cards.length) || (loop)) { + Animated.spring( + this.state.dragDistance, + { + toValue: 220, + duration, + useNativeDriver: this.props.useNativeDriver || false, + } + ).start(); - Animated.timing(this.state.drag, { - toValue: { x: horizontalSwipe ? x : 0, y: verticalSwipe ? y : 0 }, - duration, - useNativeDriver: this.props.useNativeDriver || false, - }).start(() => { - const newTopCard = topCard === 'cardA' ? 'cardB' : 'cardA'; + Animated.timing( + this.state.drag, + { + toValue: { x: (horizontalSwipe) ? x : 0, y: (verticalSwipe) ? y : 0 }, + duration, + useNativeDriver: this.props.useNativeDriver || false, + } + ).start(() => { + + const newTopCard = (topCard === 'cardA') ? 'cardB' : 'cardA'; let update = {}; if (newTopCard === 'cardA') { update = { ...update, - cardB: cards[nextCard], + cardB: cards[nextCard] }; } if (newTopCard === 'cardB') { @@ -388,59 +365,49 @@ class CardStack extends Component { this.setState({ ...update, topCard: newTopCard, - sindex: nextCard + 1, + sindex: nextCard + 1 }); this.props.onSwiped(index); switch (direction) { case 'left': this.props.onSwipedLeft(index); - if ( - this.state.cards[index] && - this.state.cards[index].props.onSwipedLeft - ) - this.state.cards[index] && - this.state.cards[index].props.onSwipedLeft(); + if (this.state.cards[index] && this.state.cards[index].props.onSwipedLeft) + this.state.cards[index] && this.state.cards[index].props.onSwipedLeft(); break; case 'right': this.props.onSwipedRight(index); - if ( - this.state.cards[index] && - this.state.cards[index].props.onSwipedRight - ) + if (this.state.cards[index] && this.state.cards[index].props.onSwipedRight) this.state.cards[index].props.onSwipedRight(); break; case 'top': this.props.onSwipedTop(index); - if ( - this.state.cards[index] && - this.state.cards[index].props.onSwipedTop - ) + if (this.state.cards[index] && this.state.cards[index].props.onSwipedTop) this.state.cards[index].props.onSwipedTop(); break; case 'bottom': this.props.onSwipedBottom(index); - if ( - this.state.cards[index] && - this.state.cards[index].props.onSwipedBottom - ) + if (this.state.cards[index] && this.state.cards[index].props.onSwipedBottom) this.state.cards[index].props.onSwipedBottom(); break; default: } }); + } } + /** * @description CardB’s click feature is trigger the CardA on the card stack. (Solved on Android) * @see https://facebook.github.io/react-native/docs/view#pointerevents */ _setPointerEvents(topCard, topCardName) { - return { pointerEvents: topCard === topCardName ? 'auto' : 'none' }; + return { pointerEvents: topCard === topCardName ? "auto" : "none" } } render() { + const { secondCardZoom, renderNoMoreCards } = this.props; const { drag, dragDistance, cardA, cardB, topCard, sindex } = this.state; @@ -456,76 +423,60 @@ class CardStack extends Component { }); return ( - + + {renderNoMoreCards()} + style={[{ + position: 'absolute', + zIndex: (topCard === 'cardB') ? 3 : 2, + ...Platform.select({ + android: { + elevation: (topCard === 'cardB') ? 3 : 2, + } + }), + transform: [ + { rotate: (topCard === 'cardB') ? rotate : '0deg' }, + { translateX: (topCard === 'cardB') ? drag.x : 0 }, + { translateY: (topCard === 'cardB') ? drag.y : 0 }, + { scale: (topCard === 'cardB') ? 1 : scale }, + ] + }, this.props.cardContainerStyle]}> {cardB} + style={[{ + position: 'absolute', + zIndex: (topCard === 'cardA') ? 3 : 2, + ...Platform.select({ + android: { + elevation: (topCard === 'cardA') ? 3 : 2, + } + }), + transform: [ + { rotate: (topCard === 'cardA') ? rotate : '0deg' }, + { translateX: (topCard === 'cardA') ? drag.x : 0 }, + { translateY: (topCard === 'cardA') ? drag.y : 0 }, + { scale: (topCard === 'cardA') ? 1 : scale }, + ] + }, this.props.cardContainerStyle]}> {cardA} + ); } } CardStack.propTypes = { + children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired, - style: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.object, - PropTypes.array, - ]), - cardContainerStyle: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.object, - PropTypes.array, - ]), + style: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), + cardContainerStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), secondCardZoom: PropTypes.number, loop: PropTypes.bool, initialIndex: PropTypes.number, @@ -552,27 +503,26 @@ CardStack.propTypes = { horizontalSwipe: PropTypes.bool, horizontalThreshold: PropTypes.number, outputRotationRange: PropTypes.array, - duration: PropTypes.number, -}; + duration: PropTypes.number +} CardStack.defaultProps = { + style: {}, cardContainerStyle: {}, secondCardZoom: 0.95, loop: false, initialIndex: 0, - renderNoMoreCards: () => { - return No More Cards; - }, + renderNoMoreCards: () => { return (No More Cards) }, onSwipeStart: () => null, onSwipeEnd: () => null, - onSwiped: () => {}, - onSwipedLeft: () => {}, - onSwipedRight: () => {}, - onSwipedTop: () => {}, - onSwipedBottom: () => {}, - onSwipedAll: async () => {}, - onSwipe: () => {}, + onSwiped: () => { }, + onSwipedLeft: () => { }, + onSwipedRight: () => { }, + onSwipedTop: () => { }, + onSwipedBottom: () => { }, + onSwipedAll: async () => { }, + onSwipe: () => { }, disableBottomSwipe: false, disableLeftSwipe: false, @@ -584,7 +534,7 @@ CardStack.defaultProps = { horizontalSwipe: true, horizontalThreshold: width / 2, outputRotationRange: ['-15deg', '0deg', '15deg'], - duration: 300, -}; + duration: 300 +} polyfill(CardStack); export default CardStack; From c192272ef4afcc194bcb4851769ee8e7e0deb979 Mon Sep 17 00:00:00 2001 From: Diego de Blas Mateo <1613216+DiegoBM@users.noreply.github.com> Date: Thu, 7 Oct 2021 00:46:08 +0200 Subject: [PATCH 3/4] disable prettier styles for component demo but keep the table alignments since it looks much nicer and easier to read --- README.md | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3de34eb..fa22243 100644 --- a/README.md +++ b/README.md @@ -22,22 +22,11 @@ import CardStack, { Card } from 'react-native-card-stack-swiper'; ``` ```javascript - { - this.swiper = swiper; - }} -> - - A - - - B - - - C - - + { this.swiper = swiper }}> + A + B + C + ``` # CardStack From decaffb8dbf6ea0ab0411f09d0bd66593d170232 Mon Sep 17 00:00:00 2001 From: Diego de Blas Mateo <1613216+DiegoBM@users.noreply.github.com> Date: Thu, 7 Oct 2021 01:00:19 +0200 Subject: [PATCH 4/4] Updated typescript type definitions --- index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.d.ts b/index.d.ts index 112c474..aefcf10 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,6 +10,7 @@ export interface CardStackProps { disableBottomSwipe?: boolean; disableLeftSwipe?: boolean; disableRightSwipe?: boolean; + disableMultitouchSwipe?: boolean; verticalSwipe?: boolean; horizontalSwipe?: boolean; verticalThreshold?: number;