UNPKG

react-native-ui-lib

Version:

<p align="center"> <img src="https://user-images.githubusercontent.com/1780255/105469025-56759000-5ca0-11eb-993d-3568c1fd54f4.png" height="250px" style="display:block"/> </p> <p align="center">UI Toolset & Components Library for React Native</p> <p a

176 lines (159 loc) • 5.49 kB
import _pt from "prop-types"; import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { PanGestureHandler } from 'react-native-gesture-handler'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, useAnimatedGestureHandler, runOnJS } from 'react-native-reanimated'; import { asBaseComponent } from "../../commons/new"; import View from "../../components/view"; import { PanningDirections, PanningDirectionsEnum, getTranslation, getDismissVelocity, DEFAULT_THRESHOLD } from "./panningUtil"; import useHiddenLocation from "../hooks/useHiddenLocation"; const PanViewDirectionsEnum = PanningDirectionsEnum; export { PanningDirections, PanningDirectionsEnum, PanViewDirectionsEnum }; const SPRING_BACK_ANIMATION_CONFIG = { velocity: 300, damping: 20, stiffness: 300, mass: 0.8 }; const PanView = props => { const { directions = [PanViewDirectionsEnum.UP, PanViewDirectionsEnum.DOWN, PanViewDirectionsEnum.LEFT, PanViewDirectionsEnum.RIGHT], dismissible, animateToOrigin, onDismiss, directionLock, threshold, containerStyle, children, ...others } = props; const waitingForDismiss = useSharedValue(false); const translationX = useSharedValue(0); const translationY = useSharedValue(0); const animatedStyle = useAnimatedStyle(() => { return { transform: [{ translateX: translationX.value }, { translateY: translationY.value }] }; }, []); const containerRef = React.createRef(); const { onLayout, hiddenLocation } = useHiddenLocation({ containerRef }); const getTranslationOptions = () => { 'worklet'; return { directionLock, currentTranslation: { x: translationX.value, y: translationY.value } }; }; const setTranslation = (event, initialTranslation) => { 'worklet'; const result = getTranslation(event, initialTranslation, directions, getTranslationOptions()); translationX.value = result.x; translationY.value = result.y; }; const dismiss = useCallback(isFinished => { 'worklet'; if (isFinished && waitingForDismiss.value && onDismiss) { waitingForDismiss.value = false; runOnJS(onDismiss)(); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [onDismiss]); const returnToOrigin = useCallback(() => { 'worklet'; if (animateToOrigin) { translationX.value = withSpring(0, SPRING_BACK_ANIMATION_CONFIG); translationY.value = withSpring(0, SPRING_BACK_ANIMATION_CONFIG); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [animateToOrigin]); const onGestureEvent = useAnimatedGestureHandler({ onStart: (_event, context) => { context.initialTranslation = { x: translationX.value, y: translationY.value }; }, onActive: (event, context) => { setTranslation(event, context.initialTranslation); }, onEnd: event => { if (dismissible) { const velocity = getDismissVelocity(event, directions, getTranslationOptions(), threshold); if (velocity) { waitingForDismiss.value = true; if (translationX.value !== 0 && velocity.x !== undefined && velocity.x !== 0) { const toX = velocity.x > 0 ? hiddenLocation.right : hiddenLocation.left; const duration = Math.abs((toX - translationX.value) / velocity.x) * 1000; translationX.value = withTiming(toX, { duration }, dismiss); } if (translationY.value !== 0 && velocity.y !== undefined && velocity.y !== 0) { const toY = velocity.y > 0 ? hiddenLocation.down : hiddenLocation.up; const duration = Math.abs((toY - translationY.value) / velocity.y) * 1000; translationY.value = withTiming(toY, { duration }, dismiss); } } else { returnToOrigin(); } } else { returnToOrigin(); } } }, [directions, dismissible, setTranslation, returnToOrigin]); return (// TODO: delete comments once completed <View ref={containerRef} style={containerStyle} onLayout={onLayout}> <PanGestureHandler onGestureEvent={isEmpty(directions) ? undefined : onGestureEvent}> <Animated.View // !visible.current && styles.hidden is done to fix a bug is iOS // style={[style, animatedStyle, !visible.current && styles.hidden]} style={animatedStyle} // style={[style]} > <View {...others}>{children}</View> </Animated.View> </PanGestureHandler> </View> ); }; PanView.propTypes = { /** * The directions of the allowed pan (default is all) * Types: UP, DOWN, LEFT and RIGHT (using PanView.directions.###) */ directions: _pt.array, /** * Dismiss the view if over the threshold (translation or velocity). */ dismissible: _pt.bool, /** * Animate to start if not dismissed. */ animateToOrigin: _pt.bool, /** * Callback to the dismiss animation end */ onDismiss: _pt.func, /** * Should the direction of dragging be locked once a drag has started. */ directionLock: _pt.bool, children: _pt.oneOfType([_pt.node, _pt.arrayOf(_pt.node)]) }; PanView.displayName = 'PanView'; PanView.directions = PanViewDirectionsEnum; PanView.defaultProps = { threshold: DEFAULT_THRESHOLD }; export default asBaseComponent(PanView);