UNPKG

react-native-root-toast

Version:

react native toast like component, pure javascript solution

276 lines (253 loc) 8.09 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { ViewPropTypes, StyleSheet, View, Text, Animated, Dimensions, TouchableWithoutFeedback, Easing, Keyboard } from 'react-native'; 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 }; let styles = StyleSheet.create({ defaultStyle: { position: 'absolute', left: 0, right: 0, justifyContent: 'center', alignItems: 'center' }, containerStyle: { 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, keyboardAvoiding: PropTypes.bool, backgroundColor: PropTypes.string, opacity: PropTypes.number, shadowColor: PropTypes.string, textColor: PropTypes.string, textStyle: Text.propTypes.style, delay: PropTypes.number, hideOnPress: PropTypes.bool, onPress: PropTypes.func, onHide: PropTypes.func, onHidden: PropTypes.func, onShow: PropTypes.func, onShown: PropTypes.func }; static defaultProps = { visible: false, duration: durations.SHORT, animation: true, shadow: true, position: positions.BOTTOM, opacity: 0.8, delay: 0, hideOnPress: true, keyboardAvoiding: true }; constructor() { super(...arguments); 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 }; } componentDidMount = () => { this.dimensionListener = Dimensions.addEventListener('change', this._windowChanged); if (this.props.keyboardAvoiding) { this.keyboardListener = Keyboard.addListener('keyboardDidChangeFrame', this._keyboardDidChangeFrame); } if (this.state.visible) { this._showTimeout = setTimeout(() => this._show(), this.props.delay); } }; componentDidUpdate = prevProps => { if (this.props.visible !== prevProps.visible) { if (this.props.visible) { clearTimeout(this._showTimeout); clearTimeout(this._hideTimeout); this._showTimeout = setTimeout(() => this._show(), this.props.delay); } else { this._hide(); } this.setState({ visible: this.props.visible }); } }; componentWillUnmount = () => { this._hide(); this.dimensionListener?.remove(); this.keyboardListener?.remove(); }; _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.opacity, duration: this.props.animation ? TOAST_ANIMATION_DURATION : 0, easing: Easing.out(Easing.ease), useNativeDriver: true }).start(({finished}) => { if (finished) { this._animating = !finished; 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) { if (this._root) { this._root.setNativeProps({ pointerEvents: 'none' }); } if (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), useNativeDriver: true }).start(({finished}) => { if (finished) { this._animating = false; this.props.onHidden && this.props.onHidden(this.props.siblingManager); } }); } }; render() { let {props} = this; const { windowWidth } = this.state; let offset = props.position; const { windowHeight, keyboardScreenY } = this.state; const keyboardHeight = Math.max(windowHeight - keyboardScreenY, 0); let position = offset ? { [offset < 0 ? 'bottom' : 'top']: offset < 0 ? (keyboardHeight - offset) : offset } : { top: 0, bottom: keyboardHeight }; return (this.state.visible || this._animating) ? <View style={[ styles.defaultStyle, position ]} pointerEvents="box-none" > <TouchableWithoutFeedback onPress={() => { typeof this.props.onPress === 'function' ? this.props.onPress() : null this.props.hideOnPress ? this._hide() : null }} > <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> </Animated.View> </TouchableWithoutFeedback> </View> : null; } } export default ToastContainer; export { positions, durations }