react-native-schedule-week-view
Version:
Week View Calendar for React Native
152 lines (137 loc) • 4.3 kB
JavaScript
import React, { useRef, useEffect, useMemo, useCallback } from 'react';
import PropTypes from 'prop-types';
import { Animated, PanResponder, Text, TouchableOpacity } from 'react-native';
import styles from './Event.styles';
const UPDATE_EVENT_ANIMATION_DURATION = 150;
const hasMovedEnough = (gestureState) => {
const { dx, dy } = gestureState;
return Math.abs(dx) > 2 || Math.abs(dy) > 2;
};
const Event = ({
event,
onPress,
onLongPress,
position,
EventComponent,
containerStyle,
onDrag,
}) => {
const isDragEnabled = !!onDrag;
const isPressDisabled = !onPress && !onLongPress;
const onDragRelease = useCallback(
(dx, dy) => {
if (!onDrag) {
return;
}
const newX = position.left + position.width / 2 + dx;
const newY = position.top + dy;
onDrag(event, newX, newY);
},
[event, position, onDrag],
);
const translatedByDrag = useRef(new Animated.ValueXY()).current;
const currentWidth = useRef(new Animated.Value(position.width)).current;
const currentLeft = useRef(new Animated.Value(position.left)).current;
useEffect(() => {
translatedByDrag.setValue({ x: 0, y: 0 });
const { left, width } = position;
const animations = [
Animated.timing(currentWidth, {
toValue: width,
duration: UPDATE_EVENT_ANIMATION_DURATION,
useNativeDriver: false,
}),
Animated.timing(currentLeft, {
toValue: left,
duration: UPDATE_EVENT_ANIMATION_DURATION,
useNativeDriver: false,
}),
];
Animated.parallel(animations).start();
}, [position]);
const panResponder = useMemo(() => {
return PanResponder.create({
onStartShouldSetPanResponder: () => isDragEnabled,
onStartShouldSetPanResponderCapture: () =>
isPressDisabled && isDragEnabled,
onMoveShouldSetPanResponder: (_, gestureState) =>
isDragEnabled && hasMovedEnough(gestureState),
onMoveShouldSetPanResponderCapture: (_, gestureState) =>
isPressDisabled && isDragEnabled && hasMovedEnough(gestureState),
onPanResponderMove: Animated.event(
[
null,
{
dx: translatedByDrag.x,
dy: translatedByDrag.y,
},
],
{
useNativeDriver: false,
},
),
onPanResponderTerminationRequest: () => false,
onPanResponderRelease: (_, gestureState) => {
const { dx, dy } = gestureState;
onDragRelease(dx, dy);
},
onPanResponderTerminate: () => {
translatedByDrag.setValue({ x: 0, y: 0 });
},
});
}, [onDragRelease, isDragEnabled, isPressDisabled]);
return (
<Animated.View
style={[
styles.container,
{
top: position.top,
left: currentLeft,
height: position.height,
width: currentWidth,
backgroundColor: event.color,
transform: translatedByDrag.getTranslateTransform(),
},
containerStyle,
]}
/* eslint-disable react/jsx-props-no-spreading */
{...panResponder.panHandlers}
>
<TouchableOpacity
onPress={() => onPress && onPress(event)}
onLongPress={() => onLongPress && onLongPress(event)}
style={styles.touchableContainer}
disabled={!onPress && !onLongPress}
>
{EventComponent ? (
<EventComponent event={event} position={position} />
) : (
<Text style={styles.description}>{event.description}</Text>
)}
</TouchableOpacity>
</Animated.View>
);
};
const eventPropType = PropTypes.shape({
color: PropTypes.string,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
description: PropTypes.string,
startDate: PropTypes.instanceOf(Date).isRequired,
endDate: PropTypes.instanceOf(Date).isRequired,
});
const positionPropType = PropTypes.shape({
height: PropTypes.number,
width: PropTypes.number,
top: PropTypes.number,
left: PropTypes.number,
});
Event.propTypes = {
event: eventPropType.isRequired,
position: positionPropType.isRequired,
onPress: PropTypes.func,
onLongPress: PropTypes.func,
containerStyle: PropTypes.object,
EventComponent: PropTypes.elementType,
onDrag: PropTypes.func,
};
export default Event;