UNPKG

react-native-lightbox-v2

Version:

Images etc in Full Screen Lightbox Popovers for React Native

240 lines (239 loc) 8.59 kB
import React, { useRef, useEffect, useState } from "react"; import { Animated, Dimensions, PanResponder, Platform, StyleSheet, StatusBar, TouchableOpacity, Text, Modal, SafeAreaView, } from "react-native"; import { useGesture, useNextTick } from "./hooks"; const { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get("window"); const isIOS = Platform.OS === "ios"; const getDefaultTarget = () => ({ x: 0, y: 0, opacity: 1 }); const styles = StyleSheet.create({ background: { position: "absolute", top: 0, left: 0, width: WINDOW_WIDTH, height: WINDOW_HEIGHT, }, open: { position: "absolute", flex: 1, justifyContent: "center", alignItems: "center", // Android pan handlers crash without this declaration: backgroundColor: "transparent", }, header: { position: "absolute", top: 0, left: 0, width: WINDOW_WIDTH, backgroundColor: "transparent", }, closeButton: { fontSize: 35, color: "white", lineHeight: 60, width: 70, textAlign: "center", shadowOffset: { width: 0, height: 0, }, shadowRadius: 1.5, shadowColor: "black", shadowOpacity: 0.8, }, }); const LightboxOverlay = ({ useNativeDriver = false, dragDismissThreshold, springConfig, isOpen, onClose, willClose, didOpen, swipeToDismiss, origin, backgroundColor, renderHeader, modalProps, children, doubleTapZoomEnabled, doubleTapGapTimer, doubleTapCallback, doubleTapZoomToCenter, doubleTapMaxZoom, doubleTapZoomStep, doubleTapInitialScale, doubleTapAnimationDuration, longPressGapTimer, longPressCallback, }) => { const _panResponder = useRef(); const pan = useRef(new Animated.Value(0)); const openVal = useRef(new Animated.Value(0)); const handlers = useRef(); const [gesture, animations] = useGesture({ useNativeDriver, doubleTapZoomEnabled, doubleTapGapTimer, doubleTapCallback, doubleTapZoomToCenter, doubleTapMaxZoom, doubleTapZoomStep, doubleTapInitialScale, doubleTapAnimationDuration, longPressGapTimer, longPressCallback, }); const [deviceWidth, setDeviceWidth] = useState(Dimensions.get('window').width); const [deviceHeight, setDeviceHeight] = useState(Dimensions.get('window').height); const [{ isAnimating, isPanning, target }, setState] = useState({ isAnimating: false, isPanning: false, target: getDefaultTarget(), }); const handleCloseNextTick = useNextTick(onClose); const close = () => { willClose(); if (isIOS) { StatusBar.setHidden(false, "fade"); } gesture.reset(); setState((s) => ({ ...s, isAnimating: true, })); Animated.spring(openVal.current, { toValue: 0, ...springConfig, useNativeDriver, }).start(({ finished }) => { if (finished) { setState((s) => ({ ...s, isAnimating: false })); handleCloseNextTick(); } }); }; const open = () => { if (isIOS) { StatusBar.setHidden(true, "fade"); } pan.current.setValue(0); setState((s) => ({ ...s, isAnimating: true, target: getDefaultTarget(), })); Animated.spring(openVal.current, { toValue: 1, ...springConfig, useNativeDriver, }).start(({ finished }) => { if (finished) { setState((s) => ({ ...s, isAnimating: false })); didOpen(); } }); }; const initPanResponder = () => { _panResponder.current = PanResponder.create({ // Ask to be the responder: onStartShouldSetPanResponder: () => !isAnimating, onStartShouldSetPanResponderCapture: () => !isAnimating, onMoveShouldSetPanResponder: () => !isAnimating, onMoveShouldSetPanResponderCapture: () => !isAnimating, onPanResponderGrant: (e, gestureState) => { gesture.init(); pan.current.setValue(0); setState((s) => ({ ...s, isPanning: true })); gesture.onLongPress(e, gestureState); gesture.onDoubleTap(e, gestureState); }, onPanResponderMove: Animated.event([null, { dy: pan.current }], { useNativeDriver, }), onPanResponderTerminationRequest: () => true, onPanResponderRelease: (evt, gestureState) => { gesture.release(); if (gesture.isDoubleTaped) return; if (gesture.isLongPressed) return; if (Math.abs(gestureState.dy) > dragDismissThreshold) { setState((s) => ({ ...s, isPanning: false, target: { y: gestureState.dy, x: gestureState.dx, opacity: 1 - Math.abs(gestureState.dy / WINDOW_HEIGHT), }, })); close(); } else { Animated.spring(pan.current, { toValue: 0, ...springConfig, useNativeDriver, }).start(({ finished }) => { finished && setState((s) => ({ ...s, isPanning: false })); }); } }, }); }; useEffect(() => { const onChange = ({ window }) => { setDeviceWidth(window.width); setDeviceHeight(window.height); }; const removeEventListener = () => { if (Dimensions.removeEventListener) { Dimensions.removeEventListener('change', onChange); } }; Dimensions.addEventListener('change', onChange); return removeEventListener; }, []); useEffect(() => { initPanResponder(); }, [useNativeDriver, isAnimating]); useEffect(() => { isOpen && open(); }, [isOpen]); useEffect(() => { if (_panResponder.current && swipeToDismiss) { handlers.current = _panResponder.current.panHandlers; } }, [swipeToDismiss, _panResponder.current]); const lightboxOpacityStyle = { opacity: openVal.current.interpolate({ inputRange: [0, 1], outputRange: [0, target.opacity], }), }; let dragStyle; if (isPanning) { dragStyle = { top: pan.current, }; lightboxOpacityStyle.opacity = pan.current.interpolate({ inputRange: [-WINDOW_HEIGHT, 0, WINDOW_HEIGHT], outputRange: [0, 1, 0], }); } const openStyle = [ styles.open, { left: openVal.current.interpolate({ inputRange: [0, 1], outputRange: [origin.x, target.x], }), top: openVal.current.interpolate({ inputRange: [0, 1], outputRange: [origin.y, target.y], }), width: openVal.current.interpolate({ inputRange: [0, 1], outputRange: [origin.width, deviceWidth], }), height: openVal.current.interpolate({ inputRange: [0, 1], outputRange: [origin.height, deviceHeight], }), }, ]; const background = (<Animated.View style={[styles.background, { backgroundColor }, lightboxOpacityStyle]}></Animated.View>); const header = (<Animated.View style={[styles.header, lightboxOpacityStyle]}> {renderHeader ? (renderHeader(close)) : (<SafeAreaView> <TouchableOpacity onPress={close}> <Text style={styles.closeButton}>×</Text> </TouchableOpacity> </SafeAreaView>)} </Animated.View>); const content = (<Animated.View style={[openStyle, dragStyle, animations]} {...handlers.current}> {children} </Animated.View>); return (<Modal visible={isOpen} transparent={true} onRequestClose={close} {...modalProps}> {background} {content} {header} </Modal>); }; export default LightboxOverlay;