UNPKG

react-native-root-toast

Version:

react native toast like component, pure javascript solution

293 lines (270 loc) 7.4 kB
import React, {Component} from 'react'; import { StyleSheet, Text, Animated, Dimensions, Pressable, Easing, Keyboard, TouchableWithoutFeedback, Platform, } from 'react-native'; import {SafeAreaView} from 'react-native-safe-area-context'; 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: { 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', }, }); const Touchable = Pressable || TouchableWithoutFeedback; const Wrapper = SafeAreaView; const window = Dimensions.get('window'); class ToastContainer extends Component { static displayName = 'ToastContainer'; static defaultProps = { visible: false, duration: durations.SHORT, animation: true, shadow: true, position: positions.BOTTOM, opacity: 0.8, delay: 0, hideOnPress: true, keyboardAvoiding: true, accessible: true, accessibilityLabel: undefined, accessibilityHint: undefined, accessibilityRole: 'alert', }; 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?.(); }; _platform = Platform.OS; _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, }); }; _setPointerEvents = value => { if (this._platform !== 'web') { this._root.setNativeProps({ pointerEvents: value, }); } else { this._root.style.pointerEvents = value; } }; _show = () => { clearTimeout(this._showTimeout); if (!this._animating) { clearTimeout(this._hideTimeout); this._animating = true; this._setPointerEvents('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._setPointerEvents('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() { const {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 ? ( <Wrapper style={[styles.defaultStyle, position]} pointerEvents="box-none" accessible={props.accessible ? props.accessible : true} accessibilityLabel={ this.props.accessibilityLabel ? this.props.accessibilityLabel : undefined } accessibilityHint={ this.props.accessibilityHint ? this.props.accessibilityHint : undefined } accessibilityRole={ this.props.accessibilityRole ? this.props.accessibilityRole : 'alert' }> <Touchable 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)}> {typeof props.children === 'string' ? ( <Text style={[ styles.textStyle, props.textStyle, props.textColor && {color: props.textColor}, ]}> {this.props.children} </Text> ) : ( props.children )} </Animated.View> </Touchable> </Wrapper> ) : null; } } export default ToastContainer; export {positions, durations};