react-native-lightbox
Version:
Images etc in Full Screen Lightbox Popovers for React Native
253 lines (232 loc) • 6.7 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Animated, Dimensions, Modal, PanResponder, Platform, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
const WINDOW_HEIGHT = Dimensions.get('window').height;
const WINDOW_WIDTH = Dimensions.get('window').width;
const DRAG_DISMISS_THRESHOLD = 150;
const STATUS_BAR_OFFSET = (Platform.OS === 'android' ? -25 : 0);
const isIOS = Platform.OS === 'ios';
const styles = StyleSheet.create({
background: {
position: 'absolute',
top: 0,
left: 0,
width: WINDOW_WIDTH,
height: WINDOW_HEIGHT,
},
open: {
position: 'absolute',
flex: 1,
justifyContent: '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: 40,
width: 40,
textAlign: 'center',
shadowOffset: {
width: 0,
height: 0,
},
shadowRadius: 1.5,
shadowColor: 'black',
shadowOpacity: 0.8,
},
});
export default class LightboxOverlay extends Component {
static propTypes = {
origin: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
}),
springConfig: PropTypes.shape({
tension: PropTypes.number,
friction: PropTypes.number,
}),
backgroundColor: PropTypes.string,
isOpen: PropTypes.bool,
renderHeader: PropTypes.func,
onOpen: PropTypes.func,
onClose: PropTypes.func,
willClose: PropTypes.func,
swipeToDismiss: PropTypes.bool,
};
static defaultProps = {
springConfig: { tension: 30, friction: 7 },
backgroundColor: 'black',
};
constructor(props) {
super(props);
this.state = {
isAnimating: false,
isPanning: false,
target: {
x: 0,
y: 0,
opacity: 1,
},
pan: new Animated.Value(0),
openVal: new Animated.Value(0),
};
this._panResponder = PanResponder.create({
// Ask to be the responder:
onStartShouldSetPanResponder: (evt, gestureState) => !this.state.isAnimating,
onStartShouldSetPanResponderCapture: (evt, gestureState) => !this.state.isAnimating,
onMoveShouldSetPanResponder: (evt, gestureState) => !this.state.isAnimating,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => !this.state.isAnimating,
onPanResponderGrant: (evt, gestureState) => {
this.state.pan.setValue(0);
this.setState({ isPanning: true });
},
onPanResponderMove: Animated.event([
null,
{ dy: this.state.pan }
]),
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderRelease: (evt, gestureState) => {
if(Math.abs(gestureState.dy) > DRAG_DISMISS_THRESHOLD) {
this.setState({
isPanning: false,
target: {
y: gestureState.dy,
x: gestureState.dx,
opacity: 1 - Math.abs(gestureState.dy / WINDOW_HEIGHT)
}
});
this.close();
} else {
Animated.spring(
this.state.pan,
{ toValue: 0, ...this.props.springConfig }
).start(() => { this.setState({ isPanning: false }); });
}
},
});
}
componentDidMount() {
if(this.props.isOpen) {
this.open();
}
}
open = () => {
if(isIOS) {
StatusBar.setHidden(true, 'fade');
}
this.state.pan.setValue(0);
this.setState({
isAnimating: true,
target: {
x: 0,
y: 0,
opacity: 1,
}
});
Animated.spring(
this.state.openVal,
{ toValue: 1, ...this.props.springConfig }
).start(() => {
this.setState({ isAnimating: false });
this.props.didOpen();
});
}
close = () => {
this.props.willClose();
if(isIOS) {
StatusBar.setHidden(false, 'fade');
}
this.setState({
isAnimating: true,
});
Animated.spring(
this.state.openVal,
{ toValue: 0, ...this.props.springConfig }
).start(() => {
this.setState({
isAnimating: false,
});
this.props.onClose();
});
}
componentDidUpdate(prevProps) {
if(this.props.isOpen !== prevProps.isOpen && this.props.isOpen) {
this.open();
}
}
render() {
const {
isOpen,
renderHeader,
swipeToDismiss,
origin,
backgroundColor,
} = this.props;
const {
isPanning,
isAnimating,
openVal,
target,
} = this.state;
const lightboxOpacityStyle = {
opacity: openVal.interpolate({inputRange: [0, 1], outputRange: [0, target.opacity]})
};
let handlers;
if(swipeToDismiss) {
handlers = this._panResponder.panHandlers;
}
let dragStyle;
if(isPanning) {
dragStyle = {
top: this.state.pan,
};
lightboxOpacityStyle.opacity = this.state.pan.interpolate({inputRange: [-WINDOW_HEIGHT, 0, WINDOW_HEIGHT], outputRange: [0, 1, 0]});
}
const openStyle = [styles.open, {
left: openVal.interpolate({inputRange: [0, 1], outputRange: [origin.x, target.x]}),
top: openVal.interpolate({inputRange: [0, 1], outputRange: [origin.y + STATUS_BAR_OFFSET, target.y + STATUS_BAR_OFFSET]}),
width: openVal.interpolate({inputRange: [0, 1], outputRange: [origin.width, WINDOW_WIDTH]}),
height: openVal.interpolate({inputRange: [0, 1], outputRange: [origin.height, WINDOW_HEIGHT]}),
}];
const background = (<Animated.View style={[styles.background, { backgroundColor: backgroundColor }, lightboxOpacityStyle]}></Animated.View>);
const header = (<Animated.View style={[styles.header, lightboxOpacityStyle]}>{(renderHeader ?
renderHeader(this.close) :
(
<TouchableOpacity onPress={this.close}>
<Text style={styles.closeButton}>×</Text>
</TouchableOpacity>
)
)}</Animated.View>);
const content = (
<Animated.View style={[openStyle, dragStyle]} {...handlers}>
{this.props.children}
</Animated.View>
);
if (this.props.navigator) {
return (
<View>
{background}
{content}
{header}
</View>
);
}
return (
<Modal visible={isOpen} transparent={true} onRequestClose={() => this.close()}>
{background}
{content}
{header}
</Modal>
);
}
}