├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── LoaderHandler.js
├── README.md
├── circle.yml
├── demo.gif
├── index.js
├── package.json
├── scripts
└── test-helper.js
└── tests
├── LoaderHandler.test.js
└── index.test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "node": true,
5 | "es6": true,
6 | "mocha": true
7 | },
8 | "plugins": [
9 | "react",
10 | "react-native"
11 | ],
12 | "root": true,
13 | "rules": {
14 | "jsx-quotes": 2,
15 | "no-unused-vars": [2, {"vars": "all", "args": "none"}],
16 | "react/jsx-boolean-value": 2,
17 | "react/jsx-curly-spacing": 2,
18 | "react/jsx-no-duplicate-props": 2,
19 | "react/jsx-no-undef": 2,
20 | "react/jsx-sort-prop-types": 2,
21 | "react/jsx-sort-props": 2,
22 | "react/jsx-uses-react": 2,
23 | "react/jsx-uses-vars": 2,
24 | "react/no-danger": 2,
25 | "react/no-did-mount-set-state": 2,
26 | "react/no-did-update-set-state": 2,
27 | "react/no-direct-mutation-state": 2,
28 | "react/no-multi-comp": 2,
29 | "react/no-unknown-property": 2,
30 | "react/prefer-es6-class": 1,
31 | "react/react-in-jsx-scope": 2,
32 | "react/require-extension": 2,
33 | "react/self-closing-comp": 2,
34 | "react/sort-comp": 2,
35 | "react/wrap-multilines": 2,
36 | "react-native/no-unused-styles": 2
37 | },
38 | "extends": "eslint:recommended"
39 | }
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | /Example/node_modules
22 | *.ipa
23 | *.xcuserstate
24 | project.xcworkspace
25 |
26 | # Android/IJ
27 | #
28 | .idea
29 | .gradle
30 | local.properties
31 |
32 | # node.js
33 | #
34 | node_modules/
35 | npm-debug.log
36 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | .eslintrc
3 | demo.gif
4 | circle.yml
5 | tests/
6 | scripts/
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Durgaprasad
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 |
23 |
--------------------------------------------------------------------------------
/LoaderHandler.js:
--------------------------------------------------------------------------------
1 | import { DeviceEventEmitter } from 'react-native';
2 |
3 | const loaderHandler = {
4 | hideLoader () {
5 | DeviceEventEmitter.emit('changeLoadingEffect', {isVisible: false});
6 | },
7 | showLoader (title) {
8 | DeviceEventEmitter.emit('changeLoadingEffect', {title, isVisible: true});
9 | }
10 | };
11 |
12 | module.exports = loaderHandler;
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Native Busy Indicator
2 | [](https://badge.fury.io/js/react-native-busy-indicator)
3 | [](https://circleci.com/gh/RealOrangeOne/react-native-busy-indicator)
4 | [](https://www.npmjs.com/package/react-native-busy-indicator)
5 |
6 | ## Install
7 | ```shell
8 | npm install react-native-busy-indicator --save
9 | ```
10 |
11 | ## Usage
12 | Place the indicator component as close to the root as possible, outside your other view components
13 |
14 | ```js
15 | const BusyIndicator = require('react-native-busy-indicator');
16 |
17 | const YourComponent = React.createClass({
18 | render() {
19 | return (
20 |
21 | ...
22 |
23 |
24 | );
25 | }
26 | ```
27 |
28 | ### Show / Hide loader
29 | Toggling the component can be done from any file, provided the component has already been placed and rendered.
30 |
31 | ```js
32 | const loaderHandler = require('react-native-busy-indicator/LoaderHandler');
33 |
34 | loaderHandler.showLoader("Loading"); // Show indicator with message 'Loading'
35 | loaderHandler.showLoader("Loading More"); // Show indicator with message 'Loading More'
36 |
37 | loaderHandler.hideLoader(); // Hide the loader
38 | ```
39 |
40 | ## Component Properties
41 |
42 | | Prop | Type | Description |
43 | |---|---|---|
44 | |**`color`**|`number`| color of the indicator. Default gray|
45 | |**`overlayWidth`**|`number`|overlay width|
46 | |**`overlayHeight`**|`number`|overlay height|
47 | |**`overlayColor`**|`string`|overlay color|
48 | |**`text`**|`string`|text. Default: `Please wait...`|
49 | |**`textColor`**|`string`| text color|
50 | |**`textFontSize`**|`number`|text font size|
51 | |**`textNumberOfLines`**|`number`| total number of lines does not exceed this number. Default: 1|
52 | |**`Size`**|`small/large`| Size of the spinner. Default: small|
53 |
54 |
55 | ## Demo
56 |
57 | 
58 |
59 | __Note__: The spinner moves much smoother than shown in recording!
60 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 5.11.1
4 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RealOrangeOne/react-native-busy-indicator/9680b3b59502155e8b315651bc1dbb7d2b2b6d00/demo.gif
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | StyleSheet,
5 | View,
6 | Text,
7 | DeviceEventEmitter,
8 | ActivityIndicator
9 | } from 'react-native';
10 |
11 | import PropTypes from 'prop-types';
12 |
13 | const styles = StyleSheet.create({
14 | container: {
15 | position: 'absolute',
16 | backgroundColor: 'rgba(0,0,0,0.3)',
17 | justifyContent: 'center',
18 | alignItems: 'center',
19 | top: 0,
20 | bottom: 0,
21 | left: 0,
22 | right: 0,
23 | flex: 1
24 | },
25 |
26 | progressBar: {
27 | margin: 10,
28 | justifyContent: 'center',
29 | alignItems: 'center',
30 | padding: 10
31 | },
32 |
33 | nocontainer: {
34 | position: 'absolute',
35 | top: 0,
36 | left: 0,
37 | width: 0.001,
38 | height: 0.001
39 | },
40 |
41 | overlay: {
42 | alignItems: 'center',
43 | justifyContent: 'center',
44 | borderRadius: 10,
45 | padding: 10
46 | },
47 |
48 | text: {
49 | marginTop: 8
50 | }
51 | });
52 |
53 | class BusyIndicator extends React.Component {
54 | constructor(props) {
55 | super(props);
56 | this.state = {
57 | isVisible: props.startVisible
58 | };
59 | }
60 |
61 | componentDidMount () {
62 | this.emitter = DeviceEventEmitter.addListener('changeLoadingEffect', this.changeLoadingEffect.bind(this));
63 | }
64 |
65 | componentWillUnmount() {
66 | this.emitter.remove();
67 | }
68 |
69 | changeLoadingEffect(state) {
70 | this.setState({
71 | isVisible: state.isVisible,
72 | text: state.title ? state.title : this.props.text
73 | });
74 | }
75 |
76 | render() {
77 | if (!this.state.isVisible) {
78 | return ();
79 | }
80 |
81 | const customStyles = StyleSheet.create({
82 | overlay: {
83 | backgroundColor: this.props.overlayColor,
84 | width: this.props.overlayWidth,
85 | height: this.props.overlayHeight
86 | },
87 |
88 | text: {
89 | color: this.props.textColor,
90 | fontSize: this.props.textFontSize
91 | }
92 | });
93 |
94 | return (
95 |
96 |
97 |
101 |
102 |
105 | {this.state.text}
106 |
107 |
108 |
109 | );
110 | }
111 | }
112 |
113 | BusyIndicator.propTypes = {
114 | color: PropTypes.string,
115 | overlayColor: PropTypes.string,
116 | overlayHeight: PropTypes.number,
117 | overlayWidth: PropTypes.number,
118 | size: PropTypes.oneOf(['small', 'large']),
119 | startVisible: PropTypes.bool,
120 | text: PropTypes.string,
121 | textColor: PropTypes.string,
122 | textFontSize: PropTypes.number,
123 | textNumberOfLines: PropTypes.number
124 | };
125 |
126 | BusyIndicator.defaultProps = {
127 | isDismissible: false,
128 | overlayWidth: 120,
129 | overlayHeight: 100,
130 | overlayColor: '#333333',
131 | color: '#f5f5f5',
132 | size: 'small',
133 | startVisible: false,
134 | text: 'Please wait...',
135 | textColor: '#f5f5f5',
136 | textFontSize: 14,
137 | textNumberOfLines: 1
138 | };
139 |
140 | module.exports = BusyIndicator;
141 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-busy-indicator",
3 | "version": "1.1.0",
4 | "main": "index.js",
5 | "description": "A simple busy indicator which can be trigger from any child or navigated page in react native application",
6 | "scripts": {
7 | "lint": "eslint index.js LoaderHandler.js scripts/ tests/",
8 | "mocha": "mocha --require babel-core/register scripts/test-helper.js --bail 'tests/**/*.test.js'",
9 | "test": "npm run lint && npm run mocha"
10 | },
11 | "keywords": [
12 | "react",
13 | "native",
14 | "busy",
15 | "indicator",
16 | "loading",
17 | "indicator",
18 | "ios",
19 | "android",
20 | "react-component",
21 | "react-native"
22 | ],
23 | "devDependencies": {
24 | "babel-cli": "=6.11.4",
25 | "babel-eslint": "=4.1.7",
26 | "babel-polyfill": "=6.9.1",
27 | "babel-preset-react-native": "=1.8.0",
28 | "chai": "=3.5.0",
29 | "enzyme": "=2.4.1",
30 | "eslint": "=1.10.3",
31 | "eslint-plugin-react": "=3.14.0",
32 | "eslint-plugin-react-native": "=1.0.2",
33 | "jsdom": "=9.4.1",
34 | "mocha": "=2.5.3",
35 | "react": "=15.1.0",
36 | "react-dom": "=15.1.0",
37 | "react-native": "=0.26.0",
38 | "react-native-mock": "=0.2.5",
39 | "sinon": "=1.17.4",
40 | "sinon-chai": "=2.8.0",
41 | "prop-types": "^15.6.0"
42 | },
43 | "repository": {
44 | "type": "git",
45 | "url": "https://github.com/RealOrangeOne/react-native-busy-indicator"
46 | },
47 | "author": "Durgaprasad Budhwani",
48 | "license": "MIT",
49 | "peerDependencies": {
50 | "react-native": ">=0.25.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/scripts/test-helper.js:
--------------------------------------------------------------------------------
1 | import jsdom from 'jsdom';
2 | import chai from 'chai';
3 | import sinonChai from 'sinon-chai';
4 | import { assert } from 'sinon';
5 |
6 | require('react-native-mock/mock');
7 | require('babel-polyfill');
8 |
9 |
10 | // Setup mocks
11 | // Jsdom document & window
12 | const doc = jsdom.jsdom('
');
13 | const win = doc.defaultView;
14 |
15 | // Add to global
16 | global.document = doc;
17 | global.window = win;
18 |
19 |
20 | // Add window keys to global window
21 | Object.keys(global.window).forEach((key) => {
22 | if (!(key in global)) {
23 | global[key] = global.window[key];
24 | }
25 | });
26 |
27 | global.window.localStorage = { getItem: () => null };
28 |
29 | chai.expect();
30 | chai.use(sinonChai);
31 |
32 | assert.expose(chai.expect, { prefix: '' });
33 |
--------------------------------------------------------------------------------
/tests/LoaderHandler.test.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai';
2 | import { DeviceEventEmitter } from 'react-native';
3 | import LoaderHandler from '../LoaderHandler';
4 | import sinon from 'sinon';
5 |
6 | describe('Loader Handler', function () {
7 | let eventHandler;
8 |
9 | beforeEach(function () {
10 | eventHandler = sinon.spy();
11 | DeviceEventEmitter.addListener('changeLoadingEffect', eventHandler);
12 | });
13 |
14 | it('Should expose the right functions', function () {
15 | expect(LoaderHandler.showLoader).to.be.a('function');
16 | expect(LoaderHandler.hideLoader).to.be.a('function');
17 | });
18 |
19 | it('Should emit event when showing', function () {
20 | LoaderHandler.showLoader('Loading');
21 | expect(eventHandler).to.have.been.calledWithExactly({ isVisible: true, title: 'Loading' });
22 | });
23 |
24 | it('Should emit event when hiding', function () {
25 | LoaderHandler.hideLoader();
26 | expect(eventHandler).to.have.been.calledWithExactly({ isVisible: false });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'; // eslint-disable-line no-unused-vars
2 | import { expect } from 'chai';
3 | import { shallow, mount } from 'enzyme';
4 | import BusyIndicator from '../index';
5 |
6 | describe('Main Component', function () {
7 | it('Should render empty initially', function () {
8 | const instance = shallow();
9 | expect(instance.find('View')).to.have.lengthOf(1);
10 | expect(instance.find('ActivityIndicator')).to.have.lengthOf(0);
11 | });
12 |
13 | it('Should render properly if starting visible', function () {
14 | const instance = shallow();
15 | expect(instance.find('ActivityIndicator')).to.have.lengthOf(1);
16 | });
17 |
18 | describe('Update Event', function () {
19 | it('Should show if event handler called', function () {
20 | const instance = shallow();
21 | expect(instance.find('ActivityIndicator')).to.have.lengthOf(0);
22 | instance.instance().changeLoadingEffect({ isVisible: true });
23 | instance.update();
24 | expect(instance.find('ActivityIndicator')).to.have.lengthOf(1);
25 | });
26 |
27 | it('Should create event emitter on mount', function () {
28 | const instance = mount();
29 | expect(instance.instance().emitter).to.exist;
30 | });
31 | });
32 |
33 | it('Should show correct text', function () {
34 | const instance = shallow();
35 | instance.setState({ text: 'Loading...' });
36 | instance.update();
37 | expect(instance.find('Text').prop('children')).to.equal('Loading...');
38 | });
39 | });
40 |
--------------------------------------------------------------------------------