├── .gitignore
├── src
├── index.js
├── utils
│ └── index.js
└── Touchable.js
├── example
└── react-native-snackbar-component-demo
│ ├── babel.config.js
│ ├── assets
│ ├── icon.png
│ └── splash.png
│ ├── .expo-shared
│ └── assets.json
│ ├── .gitignore
│ ├── metro.config.js
│ ├── package.json
│ ├── app.json
│ └── App.js
├── .eslintrc.json
├── .github
└── workflows
│ └── npmpublish.yml
├── LICENSE
├── package.json
├── README.md
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /npm-debug.log
2 | node_modules/
3 | .idea/
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export { default as Touchable } from './Touchable';
3 |
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidevesh/react-native-snackbar-component/HEAD/example/react-native-snackbar-component-demo/assets/icon.png
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidevesh/react-native-snackbar-component/HEAD/example/react-native-snackbar-component-demo/assets/splash.png
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true,
3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true
4 | }
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 | web-report/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import { Platform } from 'react-native';
2 |
3 | const {
4 | Version,
5 | OS,
6 | } = Platform;
7 |
8 | export const IS_ANDROID = OS === 'android';
9 | export const IS_LT_LOLLIPOP = Version < 21;
10 | export const noop = () => {};
11 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "rules": {
5 | "no-confusing-arrow": "off",
6 | "no-shadow": "off",
7 | "no-param-reassign": "off",
8 | "import/no-unresolved": "off",
9 | "import/extensions": "off",
10 | "import/no-extraneous-dependencies": "off",
11 | "react/jsx-filename-extension": "off",
12 | "react/sort-comp": "off",
13 | "no-use-before-define": "off",
14 | "react/destructuring-assignment": "off",
15 | "jsx-a11y/img-has-alt": "off"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/metro.config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const getDevPaths = require('get-dev-paths');
4 |
5 | const projectRoot = __dirname;
6 | module.exports = {
7 | resolver: {
8 | extraNodeModules: {
9 | react: path.resolve(__dirname, 'node_modules/react'),
10 | 'react-native': path.resolve(__dirname, 'node_modules/react-native'),
11 | },
12 | },
13 | // Old way
14 | // eslint-disable-next-line max-len
15 | // getProjectRoots: () => Array.from(new Set(getDevPaths(projectRoot).map($ => fs.realpathSync($)))),
16 | // New way
17 | watchFolders: Array.from(
18 | new Set(getDevPaths(projectRoot).map($ => fs.realpathSync($))),
19 | ),
20 | };
21 |
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject"
9 | },
10 | "dependencies": {
11 | "expo": "~49.0.16",
12 | "react": "~16.9.0",
13 | "react-dom": "~16.9.0",
14 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz",
15 | "react-native-snackbar-component": "file:../..",
16 | "react-native-web": "~0.11.7"
17 | },
18 | "devDependencies": {
19 | "@babel/core": "^7.0.0",
20 | "babel-preset-expo": "~9.5.2",
21 | "get-dev-paths": "^0.1.1"
22 | },
23 | "private": true
24 | }
25 |
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "Snackbar demo",
4 | "slug": "react-native-snackbar-component-demo",
5 | "privacy": "public",
6 | "sdkVersion": "36.0.0",
7 | "packagerOpts": {
8 | "config": "metro.config.js"
9 | },
10 | "platforms": [
11 | "ios",
12 | "android",
13 | "web"
14 | ],
15 | "version": "1.0.0",
16 | "orientation": "portrait",
17 | "icon": "./assets/icon.png",
18 | "splash": {
19 | "image": "./assets/splash.png",
20 | "resizeMode": "contain",
21 | "backgroundColor": "#ffffff"
22 | },
23 | "updates": {
24 | "fallbackToCacheTimeout": 0
25 | },
26 | "assetBundlePatterns": [
27 | "**/*"
28 | ],
29 | "ios": {
30 | "supportsTablet": true
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.github/workflows/npmpublish.yml:
--------------------------------------------------------------------------------
1 | name: NPM Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | npm-publish:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v1
14 | - name: Setup node
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: 10
18 | registry-url: https://registry.npmjs.org/
19 | - name: Install dependencies
20 | run: npm install
21 | - name: Publish package
22 | run: |
23 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
24 | LATEST=`npm view . version`
25 | CURRENT=`cat package.json | jq -r .version`
26 | if [ "$LATEST" != "$CURRENT" ]
27 | then
28 | npm publish
29 | fi
30 |
--------------------------------------------------------------------------------
/example/react-native-snackbar-component-demo/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | StyleSheet,
4 | View,
5 | Button,
6 | } from 'react-native';
7 | import Snackbar from 'react-native-snackbar-component';
8 |
9 | export default function App() {
10 | const [isVisible, setIsVisible] = React.useState(false);
11 | return (
12 |
13 |
25 | );
26 | }
27 |
28 | const styles = StyleSheet.create({
29 | container: {
30 | flex: 1,
31 | alignItems: 'center',
32 | justifyContent: 'center',
33 | },
34 | });
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 sidevesh
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 |
--------------------------------------------------------------------------------
/src/Touchable.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | View,
5 | TouchableOpacity,
6 | TouchableNativeFeedback,
7 | ViewPropTypes,
8 | } from 'react-native';
9 | import {
10 | IS_ANDROID,
11 | IS_LT_LOLLIPOP,
12 | noop,
13 | } from './utils';
14 |
15 | const Touchable = ({ onPress, style, children }) => {
16 | if (IS_ANDROID && !IS_LT_LOLLIPOP) {
17 | return (
18 |
22 |
25 | {children}
26 |
27 |
28 | );
29 | }
30 |
31 | return (
32 |
36 | {children}
37 |
38 | );
39 | };
40 |
41 | Touchable.propTypes = {
42 | onPress: PropTypes.func,
43 | style: ViewPropTypes.style,
44 | children: PropTypes.node.isRequired,
45 | };
46 |
47 | Touchable.defaultProps = {
48 | onPress: noop,
49 | style: {},
50 | };
51 |
52 | export default Touchable;
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-snackbar-component",
3 | "version": "1.1.12",
4 | "description": "A snackbar component for Android and iOS",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "eslint": "eslint --fix ./index.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/sidevesh/react-native-snackbar-component.git"
13 | },
14 | "keywords": [
15 | "snackbar",
16 | "material",
17 | "design",
18 | "android",
19 | "ios",
20 | "notifications",
21 | "react-native"
22 | ],
23 | "author": "sidevesh",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/sidevesh/react-native-snackbar-component/issues"
27 | },
28 | "homepage": "https://github.com/sidevesh/react-native-snackbar-component#readme",
29 | "devDependencies": {
30 | "babel-eslint": "^7.2.3",
31 | "eslint": "^5.11.0",
32 | "eslint-config-airbnb": "^17.1.0",
33 | "eslint-plugin-import": "^2.14.0",
34 | "eslint-plugin-jsx-a11y": "^6.1.2",
35 | "eslint-plugin-react": "^7.11.1"
36 | },
37 | "peerDependencies": {
38 | "react": ">=15.3.1",
39 | "react-native": "*"
40 | },
41 | "dependencies": {
42 | "prop-types": "^15.6.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-snackbar-component
2 | A snackbar component for Android and iOS, customizable and simple.
3 |
4 | 
5 | 
6 |
7 | See [Google Material Design](https://material.io/guidelines/components/snackbars-toasts.html) for more info on Snackbars.
8 |
9 | ## Installation
10 |
11 | ```sh
12 | npm install --save react-native-snackbar-component
13 | ```
14 |
15 | ## Basic Usage
16 |
17 | ```javascript
18 | import SnackBar from 'react-native-snackbar-component'
19 | ```
20 |
21 | ## Code
22 |
23 | ```js
24 | {console.log("snackbar button clicked!")}} actionText="let's go"/>
25 | ```
26 | ## Options
27 | | Prop | Type | Effect | Default Value |
28 | | ------------- |-------------| -----| -----|
29 | | visible | boolean | Show or hide the snackbar | none |
30 | | textMessage | string / function | The main message text, can also supply a function returning JSX to render custom message UI | none |
31 | | actionHandler | function | Function to be called when button is pressed, if absent no action button is shown | none |
32 | | actionText | message | The text of action button, will be uppercased automatically | none |
33 | | backgroundColor | color | The background color of snackbar | #484848 |
34 | | accentColor | color | The color of action button text | orange |
35 | | messageColor | color | The color of main message text | #FFFFFF |
36 | | distanceCallback | function | Function to be caled whenever snackbar moves in and out or changes layout, the function will be supplied a number indicating distance taken up by snackbar on bottom or top, based on position. | (distance) => {} |
37 | | position | string | The position of the snackbar: top, bottom | bottom |
38 | | top / bottom / left / right | number | Use these to position the snackbar | 0 |
39 | | autoHidingTime | number | How many milliseconds the snackbar will be hidden | 0 (Do not hide automatically) |
40 | | containerStyle | object | Override or add style to the root container View | {} |
41 | | messageStyle | object | Override or add style to the message Text | {} |
42 | | actionStyle | object | Override or add style to the action button Text | {} |
43 |
44 | ## Note
45 |
46 | * When visible prop is changed, the snackbar will be animated in/out of screen
47 | * The snackbar will not auto-dismiss by itself, for auto-dismiss use setTimeout() and change value passed to prop to false,
48 | **OR** you can set `autoHidingTime` to any particular value to hide the snackbar itself, although you will need to reset `visible` to false manually after the `autoHidingTime` period is over, since `visible` is controlled from outside.
49 | * This works great together with [react-native-fab](https://github.com/sidevesh/react-native-fab). See [demo](https://github.com/sidevesh/snackbar-and-fab-demo) for example and instructions how to.
50 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | StyleSheet,
5 | Text,
6 | Animated,
7 | Easing,
8 | ViewPropTypes,
9 | } from 'react-native';
10 | import { Touchable } from './src';
11 |
12 | /* Values are from https://material.io/guidelines/motion/duration-easing.html#duration-easing-dynamic-durations */
13 | const easingValues = {
14 | entry: Easing.bezier(0.0, 0.0, 0.2, 1),
15 | exit: Easing.bezier(0.4, 0.0, 1, 1),
16 | };
17 |
18 | const durationValues = {
19 | entry: 225,
20 | exit: 195,
21 | };
22 |
23 | class SnackbarComponent extends Component {
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | translateValue: new Animated.Value(0),
28 | hideDistance: 9999,
29 | };
30 | }
31 |
32 | render() {
33 | return (
34 |
48 | this.setState({ hideDistance: event.nativeEvent.layout.height })}
65 | >
66 | {typeof this.props.textMessage === 'function'
67 | ? this.props.textMessage()
68 | : (
69 |
76 | {this.props.textMessage}
77 |
78 | )
79 | }
80 | {this.props.actionHandler !== null && !!this.props.actionText
81 | ? (
82 |
83 |
90 | {this.props.actionText.toUpperCase()}
91 |
92 |
93 | )
94 | : null
95 | }
96 |
97 |
98 | );
99 | }
100 |
101 | componentDidMount() {
102 | if (this.props.visible) {
103 | this.state.translateValue.setValue(1);
104 | } else {
105 | this.state.translateValue.setValue(0);
106 | }
107 | }
108 |
109 | // eslint-disable-next-line camelcase
110 | UNSAFE_componentWillReceiveProps(nextProps) {
111 | if (nextProps.visible && !this.props.visible) {
112 | Animated.timing(this.state.translateValue, {
113 | duration: durationValues.entry,
114 | toValue: 1,
115 | easing: easingValues.entry,
116 | useNativeDriver: false
117 | }).start();
118 | if (nextProps.autoHidingTime) {
119 | const hideFunc = this.hideSnackbar.bind(this);
120 | setTimeout(hideFunc, nextProps.autoHidingTime);
121 | }
122 | } else if (!nextProps.visible && this.props.visible) {
123 | this.hideSnackbar();
124 | }
125 | }
126 |
127 | // eslint-disable-next-line camelcase
128 | UNSAFE_componentWillUpdate(nextProps, nextState) {
129 | if (
130 | this.props.distanceCallback !== null
131 | && (
132 | nextProps.visible !== this.props.visible
133 | || nextState.hideDistance !== this.state.hideDistance
134 | )
135 | ) {
136 | if (nextProps.visible) {
137 | this.props.distanceCallback(nextState.hideDistance + this.props[this.props.position]);
138 | } else {
139 | this.props.distanceCallback(this.props[this.props.position]);
140 | }
141 | }
142 | }
143 |
144 | /**
145 | * Starting the animation to hide the snackbar.
146 | * @return {null} No return.
147 | */
148 | hideSnackbar() {
149 | Animated.timing(this.state.translateValue, {
150 | duration: durationValues.exit,
151 | toValue: 0,
152 | easing: easingValues.exit,
153 | useNativeDriver: false
154 | }).start();
155 | }
156 | }
157 |
158 | SnackbarComponent.defaultProps = {
159 | accentColor: 'orange',
160 | messageColor: '#FFFFFF',
161 | backgroundColor: '#484848',
162 | distanceCallback: null,
163 | actionHandler: null,
164 | left: 0,
165 | right: 0,
166 | top: 0,
167 | bottom: 0,
168 | visible: false,
169 | position: 'bottom',
170 | actionText: '',
171 | textMessage: '',
172 | autoHidingTime: 0, // Default value will not auto hide the snack bar as the old version.
173 | containerStyle: {},
174 | messageStyle: {},
175 | actionStyle: {},
176 | };
177 |
178 | SnackbarComponent.propTypes = {
179 | accentColor: PropTypes.string,
180 | messageColor: PropTypes.string,
181 | backgroundColor: PropTypes.string,
182 | distanceCallback: PropTypes.func,
183 | actionHandler: PropTypes.func,
184 | left: PropTypes.number,
185 | right: PropTypes.number,
186 | top: PropTypes.number,
187 | bottom: PropTypes.number,
188 | visible: PropTypes.bool,
189 | actionText: PropTypes.string,
190 | textMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
191 | position: PropTypes.oneOf(['bottom', 'top']), // bottom (default), top
192 | // eslint-disable-next-line react/no-unused-prop-types
193 | autoHidingTime: PropTypes.number, // How long (in milliseconds) the snack bar will be hidden.
194 | containerStyle: ViewPropTypes.style,
195 | messageStyle: Text.propTypes.style,
196 | actionStyle: Text.propTypes.style,
197 | };
198 |
199 | const styles = StyleSheet.create({
200 | limitContainer: {
201 | position: 'absolute',
202 | overflow: 'hidden',
203 | left: 0,
204 | right: 0,
205 | zIndex: 9999,
206 | backgroundColor: 'rgba(0, 0, 0, 0)',
207 | },
208 | container: {
209 | flexDirection: 'row',
210 | justifyContent: 'space-between',
211 | alignItems: 'center',
212 | position: 'absolute',
213 | },
214 | textMessage: {
215 | fontSize: 14,
216 | flex: 1,
217 | textAlign: 'left',
218 | paddingStart: 20,
219 | paddingTop: 14,
220 | paddingBottom: 14,
221 | },
222 | actionText: {
223 | fontSize: 14,
224 | fontWeight: '600',
225 | paddingEnd: 20,
226 | paddingTop: 14,
227 | paddingBottom: 14,
228 | },
229 | });
230 |
231 | export default SnackbarComponent;
232 |
--------------------------------------------------------------------------------