diff --git a/example/src/App.tsx b/example/src/App.tsx index 7cc14c7..c405a02 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -21,6 +21,8 @@ const IMAGES: ImageSourcePropType[] = [ require('../assets/images/6.jpg'), ]; +const ICON_SIZE = 24; + const App = () => { const ref = useRef(); @@ -71,6 +73,18 @@ const App = () => { /> ); }, []); + const OverlayLabelBottom = useCallback(() => { + return ( + + ); + }, []); return ( @@ -92,9 +106,13 @@ const App = () => { onSwipeTop={(cardIndex) => { console.log('onSwipeTop', cardIndex); }} + onSwipeBottom={(cardIndex) => { + console.log('onSwipeBottom', cardIndex); + }} OverlayLabelRight={OverlayLabelRight} OverlayLabelLeft={OverlayLabelLeft} OverlayLabelTop={OverlayLabelTop} + OverlayLabelBottom={OverlayLabelBottom} onSwipeActive={() => { console.log('onSwipeActive'); }} @@ -108,21 +126,29 @@ const App = () => { + { + ref.current?.swipeBack(); + }} + > + + { ref.current?.swipeLeft(); }} > - + { - ref.current?.swipeBack(); + ref.current?.swipeBottom(); }} > - + { ref.current?.swipeTop(); }} > - + { ref.current?.swipeRight(); }} > - + @@ -158,11 +184,11 @@ const styles = StyleSheet.create({ bottom: 34, alignItems: 'center', justifyContent: 'center', + gap: 24, }, button: { - height: 80, + height: 50, borderRadius: 40, - marginHorizontal: 20, aspectRatio: 1, backgroundColor: '#3A3D45', elevation: 4, diff --git a/src/Swiper.tsx b/src/Swiper.tsx index 13af7ea..5cae531 100644 --- a/src/Swiper.tsx +++ b/src/Swiper.tsx @@ -16,10 +16,12 @@ const Swiper = ( onSwipeLeft, onSwipedAll, onSwipeTop, + onSwipeBottom, cardStyle, disableRightSwipe, disableLeftSwipe, disableTopSwipe, + disableBottomSwipe, translateXRange = [-windowWidth / 3, 0, windowWidth / 3], translateYRange = [-windowHeight / 3, 0, windowHeight / 3], rotateInputRange = [-windowWidth / 3, 0, windowWidth / 3], @@ -30,17 +32,27 @@ const Swiper = ( outputOverlayLabelLeftOpacityRange = [0, 1], inputOverlayLabelTopOpacityRange = [0, -(windowHeight / 3)], outputOverlayLabelTopOpacityRange = [0, 1], + inputOverlayLabelBottomOpacityRange = [0, windowHeight / 3], + outputOverlayLabelBottomOpacityRange = [0, 1], OverlayLabelRight, OverlayLabelLeft, OverlayLabelTop, + OverlayLabelBottom, onSwipeStart, onSwipeActive, onSwipeEnd, }: SwiperOptions, ref: ForwardedRef ) => { - const { activeIndex, refs, swipeRight, swipeLeft, swipeBack, swipeTop } = - useSwipeControls(data); + const { + activeIndex, + refs, + swipeRight, + swipeLeft, + swipeBack, + swipeTop, + swipeBottom, + } = useSwipeControls(data); useImperativeHandle( ref, @@ -50,9 +62,10 @@ const Swiper = ( swipeRight, swipeBack, swipeTop, + swipeBottom, }; }, - [swipeLeft, swipeRight, swipeBack, swipeTop] + [swipeLeft, swipeRight, swipeBack, swipeTop, swipeBottom] ); useAnimatedReaction( @@ -76,6 +89,7 @@ const Swiper = ( disableRightSwipe={disableRightSwipe} disableLeftSwipe={disableLeftSwipe} disableTopSwipe={disableTopSwipe} + disableBottomSwipe={disableBottomSwipe} translateXRange={translateXRange} translateYRange={translateYRange} rotateOutputRange={rotateOutputRange} @@ -88,10 +102,17 @@ const Swiper = ( outputOverlayLabelLeftOpacityRange={outputOverlayLabelLeftOpacityRange} inputOverlayLabelTopOpacityRange={inputOverlayLabelTopOpacityRange} outputOverlayLabelTopOpacityRange={outputOverlayLabelTopOpacityRange} + inputOverlayLabelBottomOpacityRange={ + inputOverlayLabelBottomOpacityRange + } + outputOverlayLabelBottomOpacityRange={ + outputOverlayLabelBottomOpacityRange + } activeIndex={activeIndex} OverlayLabelRight={OverlayLabelRight} OverlayLabelLeft={OverlayLabelLeft} OverlayLabelTop={OverlayLabelTop} + OverlayLabelBottom={OverlayLabelBottom} ref={refs[index]} onSwipeRight={(cardIndex) => { onSwipeRight?.(cardIndex); @@ -102,6 +123,9 @@ const Swiper = ( onSwipeTop={(cardIndex) => { onSwipeTop?.(cardIndex); }} + onSwipeBottom={(cardIndex) => { + onSwipeBottom?.(cardIndex); + }} onSwipeStart={onSwipeStart} onSwipeActive={onSwipeActive} onSwipeEnd={onSwipeEnd} diff --git a/src/SwiperCard/index.tsx b/src/SwiperCard/index.tsx index 251942e..239e862 100644 --- a/src/SwiperCard/index.tsx +++ b/src/SwiperCard/index.tsx @@ -21,6 +21,15 @@ import type { SwiperCardOptions, SwiperCardRefType } from 'rn-swiper-list'; import OverlayLabel from './OverlayLabel'; +const SwipeBackUserConfig = { + damping: 15, + stiffness: 120, + mass: 0.5, + overshootClamping: false, + restDisplacementThreshold: 0.001, + restSpeedThreshold: 0.001, +}; + const SwipeableCard = forwardRef< SwiperCardRefType, PropsWithChildren @@ -32,11 +41,13 @@ const SwipeableCard = forwardRef< onSwipeLeft, onSwipeRight, onSwipeTop, + onSwipeBottom, cardStyle, children, disableRightSwipe, disableLeftSwipe, disableTopSwipe, + disableBottomSwipe, translateXRange, translateYRange, rotateInputRange, @@ -47,9 +58,12 @@ const SwipeableCard = forwardRef< outputOverlayLabelLeftOpacityRange, inputOverlayLabelTopOpacityRange, outputOverlayLabelTopOpacityRange, + inputOverlayLabelBottomOpacityRange, + outputOverlayLabelBottomOpacityRange, OverlayLabelRight, OverlayLabelLeft, OverlayLabelTop, + OverlayLabelBottom, onSwipeStart, onSwipeActive, onSwipeEnd, @@ -83,11 +97,17 @@ const SwipeableCard = forwardRef< activeIndex.value++; }, [index, activeIndex, maxCardTranslationY, onSwipeTop, translateY]); + const swipeBottom = useCallback(() => { + onSwipeBottom?.(index); + translateY.value = withSpring(maxCardTranslationY); + activeIndex.value++; + }, [index, activeIndex, maxCardTranslationY, onSwipeBottom, translateY]); + const swipeBack = useCallback(() => { cancelAnimation(translateX); cancelAnimation(translateY); - translateX.value = withSpring(0); - translateY.value = withSpring(0); + translateX.value = withSpring(0, SwipeBackUserConfig); + translateY.value = withSpring(0, SwipeBackUserConfig); }, [translateX, translateY]); useImperativeHandle( @@ -98,9 +118,10 @@ const SwipeableCard = forwardRef< swipeRight, swipeBack, swipeTop, + swipeBottom, }; }, - [swipeLeft, swipeRight, swipeBack, swipeTop] + [swipeLeft, swipeRight, swipeBack, swipeTop, swipeBottom] ); const inputRangeX = React.useMemo(() => { @@ -129,6 +150,7 @@ const SwipeableCard = forwardRef< translateX.value = event.translationX; translateY.value = event.translationY; + if (height / 3 < Math.abs(event.translationY)) { nextActiveIndex.value = interpolate( translateY.value, @@ -159,6 +181,7 @@ const SwipeableCard = forwardRef< if (onSwipeEnd) runOnJS(onSwipeEnd)(); if (nextActiveIndex.value === activeIndex.value + 1) { const sign = Math.sign(event.translationX); + const signY = Math.sign(event.translationY); const signPositionY = Number.isInteger( interpolate( translateY.value, @@ -172,9 +195,15 @@ const SwipeableCard = forwardRef< ) ); - if (signPositionY && !disableTopSwipe) { - runOnJS(swipeTop)(); - return; + if (signPositionY) { + if (signY === -1 && !disableTopSwipe) { + runOnJS(swipeTop)(); + return; + } + if (signY === 1 && !disableBottomSwipe) { + runOnJS(swipeBottom)(); + return; + } } if (!signPositionY) { @@ -188,8 +217,8 @@ const SwipeableCard = forwardRef< } } } - translateX.value = withSpring(0); - translateY.value = withSpring(0); + translateX.value = withSpring(0, SwipeBackUserConfig); + translateY.value = withSpring(0, SwipeBackUserConfig); }); const rCardStyle = useAnimatedStyle(() => { @@ -240,6 +269,14 @@ const SwipeableCard = forwardRef< opacityValue={translateY} /> )} + {OverlayLabelBottom && ( + + )} {children} diff --git a/src/hooks/useSwipeControls.ts b/src/hooks/useSwipeControls.ts index c98e9e1..8e00e86 100644 --- a/src/hooks/useSwipeControls.ts +++ b/src/hooks/useSwipeControls.ts @@ -33,6 +33,12 @@ const useSwipeControls = (data: T[]) => { } refs[activeIndex.value]?.current?.swipeLeft(); }, [activeIndex.value, refs]); + const swipeBottom = useCallback(() => { + if (!refs[activeIndex.value]) { + return; + } + refs[activeIndex.value]?.current?.swipeBottom(); + }, [activeIndex.value, refs]); const swipeBack = useCallback(() => { if (!refs[activeIndex.value - 1]) { @@ -49,6 +55,7 @@ const useSwipeControls = (data: T[]) => { swipeLeft, swipeBack, swipeTop, + swipeBottom, }; }; diff --git a/src/index.ts b/src/index.ts index dca3dc0..f56633a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ export type SwiperCardRefType = swipeLeft: () => void; swipeBack: () => void; swipeTop: () => void; + swipeBottom: () => void; } | undefined; @@ -21,6 +22,7 @@ export type SwiperOptions = { onSwipeLeft?: (cardIndex: number) => void; onSwipeRight?: (cardIndex: number) => void; onSwipeTop?: (cardIndex: number) => void; + onSwipeBottom?: (cardIndex: number) => void; onSwipedAll?: () => void; onSwipeStart?: () => void; onSwipeEnd?: () => void; @@ -29,6 +31,7 @@ export type SwiperOptions = { disableRightSwipe?: boolean; disableLeftSwipe?: boolean; disableTopSwipe?: boolean; + disableBottomSwipe?: boolean; //* Rotation Animation Prop translateXRange?: number[]; translateYRange?: number[]; @@ -41,9 +44,12 @@ export type SwiperOptions = { outputOverlayLabelLeftOpacityRange?: number[]; inputOverlayLabelTopOpacityRange?: number[]; outputOverlayLabelTopOpacityRange?: number[]; + inputOverlayLabelBottomOpacityRange?: number[]; + outputOverlayLabelBottomOpacityRange?: number[]; OverlayLabelRight?: () => JSX.Element; OverlayLabelLeft?: () => JSX.Element; OverlayLabelTop?: () => JSX.Element; + OverlayLabelBottom?: () => JSX.Element; }; export type SwiperCardOptions = { index: number; @@ -51,6 +57,7 @@ export type SwiperCardOptions = { onSwipeRight?: (index: number) => void; onSwipeLeft?: (index: number) => void; onSwipeTop?: (index: number) => void; + onSwipeBottom?: (index: number) => void; onSwipeStart?: () => void; onSwipeActive?: () => void; onSwipeEnd?: () => void; @@ -58,6 +65,7 @@ export type SwiperCardOptions = { disableRightSwipe?: boolean; disableLeftSwipe?: boolean; disableTopSwipe?: boolean; + disableBottomSwipe?: boolean; translateXRange?: number[]; rotateOutputRange?: number[]; rotateInputRange?: number[]; @@ -68,7 +76,10 @@ export type SwiperCardOptions = { outputOverlayLabelLeftOpacityRange?: number[]; inputOverlayLabelTopOpacityRange?: number[]; outputOverlayLabelTopOpacityRange?: number[]; + inputOverlayLabelBottomOpacityRange?: number[]; + outputOverlayLabelBottomOpacityRange?: number[]; OverlayLabelRight?: () => JSX.Element; OverlayLabelLeft?: () => JSX.Element; OverlayLabelTop?: () => JSX.Element; + OverlayLabelBottom?: () => JSX.Element; };