├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── MessageBar.js ├── README.md ├── custom-messages.gif ├── default-messages.gif ├── demo ├── .babelrc ├── .buckconfig ├── .expo │ └── packager-info.json ├── .gitattributes ├── .gitignore ├── .watchmanconfig ├── App.js ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── messagesexample │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── app.json ├── index.js ├── ios │ ├── MessagesExample-tvOS │ │ └── Info.plist │ ├── MessagesExample-tvOSTests │ │ └── Info.plist │ ├── MessagesExample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── MessagesExample-tvOS.xcscheme │ │ │ └── MessagesExample.xcscheme │ ├── MessagesExample │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m │ └── MessagesExampleTests │ │ ├── Info.plist │ │ └── MessagesExampleTests.m ├── package-lock.json ├── package.json └── rn-cli-config.js ├── index.js ├── messageManager.js ├── package-lock.json └── package.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "qlean-react-native", 3 | "rules": { 4 | "react/prop-types": 0, 5 | "react-native/no-inline-styles": 0, 6 | "react-native/no-color-literals": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo 2 | *.gif 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present, Qlean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MessageBar.js: -------------------------------------------------------------------------------- 1 | // Peer dependecies 2 | /* eslint-disable import/no-unresolved, import/extensions */ 3 | import React, { Component } from 'react'; 4 | import { View, Text, Animated, PanResponder, StyleSheet } from 'react-native'; 5 | /* eslint-enable import/no-unresolved, import/extensions */ 6 | 7 | import messageManager from './messageManager'; 8 | 9 | const MIN_SWIPE_DISTANCE = 20; 10 | const MIN_SWIPE_VELOCITY = 0.15; 11 | 12 | const styles = StyleSheet.create({ 13 | root: { 14 | position: 'absolute', 15 | top: 0, 16 | left: 0, 17 | right: 0, 18 | }, 19 | message: { 20 | paddingVertical: 15, 21 | paddingHorizontal: 20, 22 | justifyContent: 'center', 23 | backgroundColor: 'grey', 24 | }, 25 | messageText: { 26 | color: 'white', 27 | }, 28 | }); 29 | 30 | function Message({ message }) { 31 | return ( 32 | 33 | {message} 34 | 35 | ); 36 | } 37 | 38 | export default class MessageBar extends Component { 39 | constructor(props) { 40 | super(props); 41 | this.state = { 42 | isVisibleAnimValue: new Animated.Value(0), 43 | isAnimatingHide: false, 44 | message: null, 45 | config: {}, 46 | }; 47 | } 48 | componentWillMount() { 49 | this.panResponder = PanResponder.create({ 50 | onMoveShouldSetPanResponder: (e, gesture) => ( 51 | this.getConfig().closeOnSwipe 52 | && gesture.dy < -MIN_SWIPE_DISTANCE 53 | && gesture.vy < -MIN_SWIPE_VELOCITY 54 | ), 55 | onPanResponderMove: (e, gesture) => { 56 | if (!this.state.isAnimatingHide) { 57 | this.hideMessage(this.state.message); 58 | } 59 | }, 60 | onShouldBlockNativeResponder: () => true, 61 | }); 62 | } 63 | componentDidMount() { 64 | messageManager.registerMessageBar(this); 65 | } 66 | componentWillUnmount() { 67 | messageManager.unregisterMessageBar(); 68 | } 69 | getConfig() { 70 | return Object.assign({}, this.props, this.state.config); 71 | } 72 | pushMessage(message, config) { 73 | this.setState({ message, config }, () => this.showMessage(message)); 74 | } 75 | showMessage(message) { 76 | const { duration, showAnimationDuration } = this.getConfig(); 77 | this.state.isVisibleAnimValue.setValue(0); 78 | this.setState({ isAnimatingHide: false }); 79 | Animated.timing( 80 | this.state.isVisibleAnimValue, 81 | { toValue: 1, duration: showAnimationDuration, useNativeDriver: true }, 82 | ).start(() => setTimeout(() => this.hideMessage(message), duration)); 83 | } 84 | hideMessage(message) { 85 | if (message === this.state.message) { 86 | const { hideAnimationDuration } = this.getConfig(); 87 | this.setState({ isAnimatingHide: true }); 88 | Animated.timing( 89 | this.state.isVisibleAnimValue, 90 | { toValue: 0, duration: hideAnimationDuration, useNativeDriver: true }, 91 | ).start(() => { 92 | if (message === this.state.message) { 93 | this.setState({ message: null, config: {}, isAnimatingHide: false }); 94 | } 95 | }); 96 | } 97 | } 98 | render() { 99 | const { messageComponent: MessageComponent, slideAnimationOffset } = this.getConfig(); 100 | const translateY = this.state.isVisibleAnimValue.interpolate({ 101 | inputRange: [0, 1], 102 | outputRange: [-slideAnimationOffset, 0], 103 | }); 104 | const opacity = this.state.isVisibleAnimValue.interpolate({ 105 | inputRange: [0, 1], 106 | outputRange: [0, 1], 107 | }); 108 | return ( 109 | 116 | 117 | {this.state.message && ( 118 | this.hideMessage(this.state.message)} 121 | /> 122 | ) 123 | } 124 | 125 | 126 | ); 127 | } 128 | } 129 | 130 | MessageBar.defaultProps = { 131 | messageComponent: Message, 132 | duration: 1000, 133 | slideAnimationOffset: 40, 134 | showAnimationDuration: 255, 135 | hideAnimationDuration: 255, 136 | closeOnSwipe: true, 137 | }; 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-messages 2 | 3 | React Native notification-like messages 4 | 5 | ![Default messages](./default-messages.gif) 6 | 7 | ## Features 8 | - Customizable message component 9 | - Fast native driver animations 10 | - Auto-hide current message to display a next one 11 | - Robust implementation, used in production code 12 | 13 | ## Installation 14 | ``` 15 | $ npm install react-native-messages 16 | ``` 17 | 18 | ## Usage 19 | 1. Add `` to the top of your view hierarchy, as a last component. If you are using `react-navigation`, root navigator should be in place of ``. Root view should have `flex: 1`: 20 | ```jsx 21 | import { MessageBar } from 'react-native-messages'; 22 | 23 | 24 | 25 | 26 | 27 | ``` 28 | 29 | 2. Call `showMessage` in any other component: 30 | ```jsx 31 | import { showMessage } from 'react-native-messages'; 32 | 33 |