@fto-consult/expo-ui
Version:
Bibliothèque de composants UI Expo,react-native
225 lines (204 loc) • 6.07 kB
JavaScript
import React from '$react';
import {
PanResponder,
Animated,
Dimensions,
Button,
View,
StyleSheet,
} from 'react-native';
import PropTypes from "prop-types";
import {StyleProp} from "$theme";
import {defaultStr,defaultObj} from "$cutils";
/** We offer a special kind of ListItem which is swipeable from both ends and allows users select an event. */
export const SwipeableComponent = React.forwardRef(({
children,
leftProps,
rightProps,
left,
right,
onSwipeBegin,
onSwipeEnd,
animation = { type: 'spring', duration: 200 },
testID,
contentProps,
containerProps,
leftWidth,
rightWidth,
...rest
},ref) => {
testID = defaultStr(testID,"RN_SwipeableComponent");
containerProps = defaultObj(contentProps);
contentProps = defaultObj(contentProps);
leftProps = defaultObj(leftProps);
rightProps = defaultObj(rightProps);
const screenWidth = Dimensions.get("window").width;
const rightStyle = Object.assign({},StyleSheet.flatten(rightProps.style));
rightStyle.width = rightWidth || rightStyle.width || screenWidth/3;
const leftStyle = Object.assign({},leftProps.style);
leftStyle.width = leftWidth || leftStyle.width || screenWidth/3;
const translateX = React.useRef(new Animated.Value(0));
const panX = React.useRef(0);
const slideAnimation = React.useCallback(
(toValue) => {
panX.current = toValue;
Animated[animation.type || 'spring'](translateX.current, {
toValue,
useNativeDriver: true,
duration: animation.duration || 200,
}).start();
},
[animation.duration, animation.type]
);
const resetCallBack = React.useCallback(() => {
slideAnimation(0);
}, [slideAnimation]);
const onMove = React.useCallback(
(_, { dx }) => {
translateX.current.setValue(panX.current + dx);
},
[]
);
const onRelease = React.useCallback(
(_, { dx }) => {
if (Math.abs(panX.current + dx) >= ScreenWidth / 3) {
slideAnimation(panX.current + dx > 0 ? leftStyle.width : -rightStyle.width);
} else {
slideAnimation(0);
}
},
[leftStyle.width, rightStyle.width, slideAnimation]
);
const shouldSlide = React.useCallback(
(_, { dx, dy, vx, vy }) => {
if (dx > 0 && !left && !panX.current) {
return false;
}
if (dx < 0 && !right && !panX.current) {
return false;
}
return (
Math.abs(dx) > Math.abs(dy) * 2 && Math.abs(vx) > Math.abs(vy) * 2.5
);
},
[left, right]
);
const _panResponder = React.useMemo(
() =>
PanResponder.create({
onMoveShouldSetPanResponder: shouldSlide,
onPanResponderGrant: (_event, { vx }) => {
onSwipeBegin?.(vx > 0 ? 'left' : 'right');
},
onPanResponderMove: onMove,
onPanResponderRelease: onRelease,
onPanResponderReject: onRelease,
onPanResponderTerminate: onRelease,
onPanResponderEnd: () => {
onSwipeEnd?.();
},
}),
[onMove, onRelease, onSwipeBegin, onSwipeEnd, shouldSlide]
);
return (
<View
testID = {testID+"_Container"}
{...containerProps}
style={[{justifyContent: 'center' },containerProps.style]}
>
<View testID={testID+"_Actions"} style={styles.actions}>
<View
testID = {testID+"_Left"}
{...leftProps}
style={[
{
zIndex: 1,
},
leftStyle,
]}
>
{typeof left === 'function'
? left(resetCallBack)
: left}
</View>
<View style={{ flex: 0 }} />
<View
testID = {testID+"_Right"}
{...rightProps}
style={[
{
zIndex: 1,
},
rightStyle,
]}
>
{typeof right === 'function'
? right(resetCallBack)
: right}
</View>
</View>
<Animated.View
ref = {ref}
testID={testID}
{...rest}
style={[{
transform: [
{
translateX: translateX.current,
},
],
},rest.style]}
{..._panResponder.panHandlers}
>
{children}
</Animated.View>
</View>
);
});
const styles = StyleSheet.create({
actions: {
bottom: 0,
left: 0,
overflow: 'hidden',
position: 'absolute',
right: 0,
top: 0,
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
},
});
SwipeableComponent.displayName = 'SwipeableComponent';
SwipeableComponent.propTypes = {
/**
* Left Content.
* @type ReactNode or resetCallback => ReactNode
*/
left: PropTypes.oneOfType([PropTypes.func,PropTypes.node]),// | ((reset: () => void) => React.ReactNode);
/**
* Right Content.
* @type ReactNode or resetCallback => ReactNode
*/
right: PropTypes.oneOfType([PropTypes.func,PropTypes.node]),//?: React.ReactNode | ((reset: () => void) => React.ReactNode);
/** Style of left container.*/
leftProps : PropTypes.object,
/** Style of right container.*/
rightProps : PropTypes.object,
/** Width to swipe left. */
leftWidth : PropTypes.number,
/** Width to swipe right. */
rightWidth : PropTypes.number,
/** Handler for swipe in either direction */
onSwipeBegin : PropTypes.func,///?: (direction: 'left' | 'right') => unknown;
/** Handler for swipe end. */
onSwipeEnd : PropTypes.func,//?: () => unknown;
/** Decide whether to show animation.
* @default Object with duration 350ms and type timing
* @type Animated.TimingAnimationConfig
*/
animation : PropTypes.shape({
type : PropTypes.oneOf(['timing', 'spring']),
duration: PropTypes.number,
}),
}
export default SwipeableComponent;