UNPKG

react-native-enhanced-toast

Version:

react native `enhanced` toast like component, pure javascript solution

300 lines (276 loc) 7.72 kB
import React, { Component } from 'react' import PropTypes from 'prop-types' import { ViewPropTypes, StyleSheet, View, Text, Animated, Dimensions, TouchableWithoutFeedback, Easing, Keyboard } from 'react-native' import EvilIcons from 'react-native-vector-icons/EvilIcons' const TOAST_MAX_WIDTH = 0.8 const TOAST_ANIMATION_DURATION = 200 const positions = { TOP: 20, BOTTOM: -20, CENTER: 0 } const durations = { LONG: 3500, SHORT: 2000 } const styles = StyleSheet.create({ defaultStyle: { position: 'absolute', left: 0, right: 0, justifyContent: 'center', alignItems: 'center' }, containerStyle: { flexDirection: 'row', alignItems: 'center', padding: 10, backgroundColor: '#000', opacity: 0.8, borderRadius: 5 }, shadowStyle: { shadowColor: '#000', shadowOffset: { width: 4, height: 4 }, shadowOpacity: 0.8, shadowRadius: 6, elevation: 10 }, textStyle: { fontSize: 16, color: '#fff', textAlign: 'center' } }) class ToastContainer extends Component { static displayName = 'ToastContainer' static propTypes = { ...ViewPropTypes, containerStyle: ViewPropTypes.style, duration: PropTypes.number, visible: PropTypes.bool, position: PropTypes.number, animation: PropTypes.bool, shadow: PropTypes.bool, backgroundColor: PropTypes.string, opacity: PropTypes.number, shadowColor: PropTypes.string, textColor: PropTypes.string, textStyle: Text.propTypes.style, closeSize: PropTypes.number, closeColor: PropTypes.string, closeStyle: ViewPropTypes.style, delay: PropTypes.number, hideOnPress: PropTypes.bool, showCloseButton: PropTypes.bool, onPressMessage: PropTypes.func, onHide: PropTypes.func, onHidden: PropTypes.func, onShow: PropTypes.func, onShown: PropTypes.func } static defaultProps = { containerStyle: {}, visible: false, duration: durations.SHORT, animation: true, shadow: true, position: positions.BOTTOM, opacity: 0.8, delay: 0, hideOnPress: true, showCloseButton: false, onPressMessage: null, closeSize: 24, closeColor: 'black', closeStyle: { opacity: 0.8, marginLeft: 5 }, onHide: null, onHidden: null, onShow: null, onShown: null } constructor(props) { super(props) const window = Dimensions.get('window') this.state = { visible: this.props.visible, opacity: new Animated.Value(0), windowWidth: window.width, windowHeight: window.height, keyboardScreenY: window.height } } componentWillMount() { Dimensions.addEventListener('change', this._windowChanged) Keyboard.addListener('keyboardDidChangeFrame', this._keyboardDidChangeFrame) } componentDidMount = () => { if (this.state.visible) { this._showTimeout = setTimeout(() => this._show(), this.props.delay) } } componentWillReceiveProps = nextProps => { if (nextProps.visible !== this.props.visible) { if (nextProps.visible) { clearTimeout(this._showTimeout) clearTimeout(this._hideTimeout) this._showTimeout = setTimeout(() => this._show(), this.props.delay) } else { this._hide() } this.setState({ visible: nextProps.visible }) } } componentWillUpdate() { const { windowHeight, keyboardScreenY } = this.state this._keyboardHeight = Math.max(windowHeight - keyboardScreenY, 0) } componentWillUnmount = () => { Dimensions.removeEventListener('change', this._windowChanged) Keyboard.removeListener('keyboardDidChangeFrame', this._keyboardDidChangeFrame) this._hide() } _animating = false _root = null _hideTimeout = null _showTimeout = null _keyboardHeight = 0 _windowChanged = ({ window }) => { this.setState({ windowWidth: window.width, windowHeight: window.height }) } _keyboardDidChangeFrame = ({ endCoordinates }) => { this.setState({ keyboardScreenY: endCoordinates.screenY }) } _show = () => { clearTimeout(this._showTimeout) if (!this._animating) { clearTimeout(this._hideTimeout) this._animating = true this._root.setNativeProps({ pointerEvents: 'auto' }) this.props.onShow && this.props.onShow(this.props.siblingManager) Animated.timing(this.state.opacity, { toValue: this.props.containerStyle.opacity || this.props.opacity, duration: this.props.animation ? TOAST_ANIMATION_DURATION : 0, easing: Easing.out(Easing.ease) }).start(({ finished }) => { if (finished) { this._animating = false this.props.onShown && this.props.onShown(this.props.siblingManager) if (this.props.duration > 0) { this._hideTimeout = setTimeout(() => this._hide(), this.props.duration) } } }) } } _hide = () => { clearTimeout(this._showTimeout) clearTimeout(this._hideTimeout) if (!this._animating) { this._root.setNativeProps({ pointerEvents: 'none' }) this.props.onHide && this.props.onHide(this.props.siblingManager) Animated.timing(this.state.opacity, { toValue: 0, duration: this.props.animation ? TOAST_ANIMATION_DURATION : 0, easing: Easing.in(Easing.ease) }).start(({ finished }) => { if (finished) { this._animating = false this.props.onHidden && this.props.onHidden(this.props.siblingManager) } }) } } _onPressMessage = () => { const { onPressMessage, hideOnPress } = this.props if (onPressMessage) { onPressMessage() this._hide() } else if (hideOnPress) { this._hide() } } render() { const { props } = this const { showCloseButton, closeSize, closeColor, closeStyle } = this.props const { windowWidth } = this.state const offset = props.position const position = offset ? { [offset < 0 ? 'bottom' : 'top']: offset < 0 ? this._keyboardHeight - offset : offset } : { top: 0, bottom: this._keyboardHeight } return this.state.visible || this._animating ? ( <View style={[styles.defaultStyle, position]} pointerEvents="box-none"> <TouchableWithoutFeedback onPress={this._onPressMessage}> <Animated.View style={[ styles.containerStyle, { marginHorizontal: windowWidth * ((1 - TOAST_MAX_WIDTH) / 2) }, props.containerStyle, props.backgroundColor && { backgroundColor: props.backgroundColor }, { opacity: this.state.opacity }, props.shadow && styles.shadowStyle, props.shadowColor && { shadowColor: props.shadowColor } ]} pointerEvents="none" ref={ele => { this._root = ele }} > <Text style={[ styles.textStyle, props.textStyle, props.textColor && { color: props.textColor } ]} > {this.props.children} </Text> {showCloseButton && ( <TouchableWithoutFeedback onPress={this._hide}> <EvilIcons name="close" size={closeSize} color={closeColor} style={closeStyle} /> </TouchableWithoutFeedback> )} </Animated.View> </TouchableWithoutFeedback> </View> ) : null } } export default ToastContainer export { positions, durations }