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
JavaScript
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);