import * as React from 'react'; import { Animated, Dimensions, PanResponder, PanResponderGestureState, TouchableWithoutFeedback, View, } from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {TailwindFn} from 'twrnc'; import {Style} from 'twrnc/dist/esm/types'; const WINDOW_HEIGHT = Dimensions.get('window').height; const duration = 1000; type Props = { tw: TailwindFn; children: JSX.Element | JSX.Element[]; maxHeight?: number; show: boolean; setShow: | React.Dispatch> | ((v: boolean) => void); style?: Style; }; let ref: Animated.LegacyRef; const DraggableView = ({ tw, children, maxHeight, show, setShow, style, }: Props): JSX.Element => { const insets = useSafeAreaInsets(); const [touched, setTouched] = React.useState(false); const [top, setTop] = React.useState(WINDOW_HEIGHT); const [position, setPosition] = React.useState(show ? 100 : 0); const [opacity, setOpacity] = React.useState(0); const [maxHeight_, setMaxHeight_] = React.useState(maxHeight); const positionToTop = (position_: number) => { return WINDOW_HEIGHT - (position_ / 100) * (maxHeight_ || 0); }; const topToPosition = (top_: number) => { return (100 * (WINDOW_HEIGHT - top_)) / (maxHeight_ || 0); }; React.useEffect(() => { setTop(positionToTop(position)); setOpacity(position * 0.8); }, [position, maxHeight_]); React.useEffect(() => { startAnimation(); }, [show]); const isAValidMovement = (dx: number, dy: number) => { return Math.abs(dy) > Math.abs(dx) && dy > 2; }; const moveDrawerView = (gesture: PanResponderGestureState) => { if (!ref) { return; } const displacement = positionToTop(position) + gesture.dy; const newPosition = topToPosition(displacement); setPosition(newPosition); }; const startAnimation = () => { const position_ = new Animated.Value(position); position_.removeAllListeners(); const toValue = show ? 100 : 0; const animation = Animated.timing(position_, { toValue, duration, useNativeDriver: true, }); animation.start(); position_.addListener(position__ => { setPosition(parseFloat(position__.value.toFixed(2))); }); }; const moveFinished = () => { if (!ref) { return; } setShow(false); }; const _panGesture = PanResponder.create({ onMoveShouldSetPanResponder: (_, gesture) => { return isAValidMovement(gesture.dx, gesture.dy) && touched; }, onPanResponderMove: (_, gesture) => { moveDrawerView(gesture); }, onPanResponderRelease: () => { moveFinished(); }, }); return ( <> {position !== 0 && ( ) => { ref = ref_; }} style={{ ...tw`absolute left-0 w-full rounded-t-2xl bg-white h-full shadow-2xl`, top: top, }} {..._panGesture.panHandlers}> { if (!maxHeight) { setMaxHeight_( Math.ceil(e.nativeEvent.layout.height + insets.bottom + 46), ); } }} style={style}> setTouched(true)} onPressOut={() => setTouched(false)}> {children} )} ); }; export default DraggableView;