├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.js
├── lib
├── loading-bar.js
└── webview.js
├── package-lock.json
├── package.json
├── screenshot.gif
└── test
├── __snapshots__
├── loading-bar.test.js.snap
└── webview.test.js.snap
├── loading-bar.test.js
└── webview.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | /node_modules
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | screenshot.gif
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Di Wang
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Progress WebView
2 |
3 |   [](https://github.com/wangdicoder/react-native-progress-webview) 
4 |
5 | **React Native Progress WebView** is a wrapper of [React Native WebView](https://github.com/react-native-community/react-native-webview) to provide a loading bar on the top of the browser.
6 |
7 |
8 |
9 | ## Platforms Supported
10 |
11 | - [x] iOS (both UIWebView and WKWebView)
12 | - [x] Android
13 |
14 | ## Getting Started
15 |
16 | Firstly, read the [Getting Started Guide](https://github.com/react-native-community/react-native-webview) on React Native WebView page to configure the webview component.
17 |
18 | Then
19 | ```bash
20 | $ npm install --save react-native-progress-webview
21 | ```
22 |
23 | ## Usage
24 |
25 | Import the `ProgressWebView` component from `react-native-progress-webview` and use it like so:
26 |
27 | ```jsx
28 | import React, { Component } from 'react';
29 | import { StyleSheet, Text, View } from 'react-native';
30 | import ProgressWebView from "react-native-progress-webview";
31 |
32 | // ...
33 | class App extends Component {
34 | render() {
35 | return (
36 |
37 | );
38 | }
39 | }
40 | ```
41 |
42 | ## API
43 |
44 | **React Native Progress Webview** supports all React Native Webview props. Read the [API Reference](https://github.com/react-native-community/react-native-webview/blob/master/docs/Reference.md) to explore. There are a couple of exclusive props related to the loading bar.
45 |
46 | | prop | type | default | description |
47 | | ----------------- | ------ | ------- | ----------------------------------------------------------- |
48 | | height | number | 3 | the height of loading bar |
49 | | color | string | #3B78E7 | the normal color of loading bar |
50 | | errorColor | string | #f30 | the error color of loading bar |
51 | | disappearDuration | number | 300 | the visible duration after the webview finishes the loading |
52 |
53 | ## Common issues
54 |
55 | #### How can I use `ref` to get WebView methods?
56 |
57 | It's same as using raw WebView. You can just pass a `ref` prop to get the instance.
58 |
59 | ```jsx
60 | class App extends Component {
61 | constructor(props){
62 | super(props);
63 | this.ref = React.createRef();
64 | }
65 |
66 | componentDidMount() {
67 | console.log(this.ref.current);
68 | }
69 |
70 | render() {
71 | return (
72 |
76 | );
77 | }
78 | }
79 | ```
80 |
81 | ## License
82 |
83 | MIT
84 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import ProgressBarWebView from "./lib/webview";
2 |
3 | export default ProgressBarWebView;
4 |
--------------------------------------------------------------------------------
/lib/loading-bar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet } from 'react-native';
3 | import * as PropTypes from 'prop-types';
4 |
5 | const LoadingBar = ({ color, percent, height }) => {
6 | const style = {
7 | backgroundColor: color,
8 | width: `${percent * 100}%`,
9 | height,
10 | };
11 | return ;
12 | };
13 |
14 | LoadingBar.propTypes = {
15 | color: PropTypes.string
16 | };
17 |
18 | const styles = StyleSheet.create({
19 | container: {
20 | position: 'absolute',
21 | zIndex: 10,
22 | top: 0,
23 | left: 0,
24 | },
25 | });
26 |
27 | export default LoadingBar;
28 |
--------------------------------------------------------------------------------
/lib/webview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View, StyleSheet } from 'react-native';
3 | import { WebView } from 'react-native-webview';
4 | import * as PropTypes from 'prop-types';
5 | import LoadingBar from "./loading-bar";
6 |
7 | class ProgressBarWebView extends React.PureComponent {
8 |
9 | static propTypes = {
10 | height: PropTypes.number,
11 | color: PropTypes.string,
12 | errorColor: PropTypes.string,
13 | disappearDuration: PropTypes.number,
14 | onLoadProgress: PropTypes.func,
15 | onError: PropTypes.func,
16 | onLoadStart: PropTypes.func,
17 | onLoadEnd: PropTypes.func,
18 | };
19 |
20 | static defaultProps = {
21 | height: 3,
22 | color: '#3B78E7',
23 | errorColor: '#f30',
24 | disappearDuration: 300,
25 | };
26 |
27 | state = {
28 | percent: 0, //range: 0 - 1
29 | color: this.props.color,
30 | visible: false,
31 | };
32 |
33 | _onLoadProgress = (syntheticEvent) => {
34 | this.setState({ percent: syntheticEvent.nativeEvent.progress });
35 | const { onLoadProgress } = this.props;
36 | onLoadProgress && onLoadProgress(syntheticEvent);
37 | };
38 |
39 | _onError = (syntheticEvent) => {
40 | this.setState({ color: this.props.errorColor, percent: 1 });
41 | const { onError } = this.props;
42 | onError && onError(syntheticEvent);
43 | };
44 |
45 | _onLoadStart = (syntheticEvent) => {
46 | this.setState({ visible: true });
47 | const { onLoadStart } = this.props;
48 | onLoadStart && onLoadStart(syntheticEvent);
49 | };
50 |
51 | _onLoadEnd = (syntheticEvent) => {
52 | const { onLoadEnd, disappearDuration } = this.props;
53 | this.timer = setTimeout(() => {
54 | this.setState({ visible: false });
55 | }, disappearDuration);
56 | onLoadEnd && onLoadEnd(syntheticEvent);
57 | };
58 |
59 | componentWillUnmount(): void {
60 | clearTimeout(this.timer);
61 | }
62 |
63 | render() {
64 | const { height, forwardedRef } = this.props;
65 | const { percent, color, visible } = this.state;
66 | return (
67 |
68 | {visible && }
69 |
77 |
78 | );
79 | }
80 | }
81 |
82 | const styles = StyleSheet.create({
83 | container: {
84 | flex: 1,
85 | position: 'relative',
86 | },
87 | });
88 |
89 | export default React.forwardRef((props, ref) => (
90 |
91 | ));
92 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-progress-webview",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "escape-string-regexp": {
8 | "version": "1.0.5",
9 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
10 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
11 | },
12 | "invariant": {
13 | "version": "2.2.4",
14 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
15 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
16 | "requires": {
17 | "loose-envify": "^1.0.0"
18 | }
19 | },
20 | "js-tokens": {
21 | "version": "4.0.0",
22 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
23 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
24 | },
25 | "loose-envify": {
26 | "version": "1.4.0",
27 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
28 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
29 | "requires": {
30 | "js-tokens": "^3.0.0 || ^4.0.0"
31 | }
32 | },
33 | "object-assign": {
34 | "version": "4.1.1",
35 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
36 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
37 | },
38 | "prop-types": {
39 | "version": "15.7.2",
40 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
41 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
42 | "requires": {
43 | "loose-envify": "^1.4.0",
44 | "object-assign": "^4.1.1",
45 | "react-is": "^16.8.1"
46 | }
47 | },
48 | "react-is": {
49 | "version": "16.8.6",
50 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
51 | "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
52 | },
53 | "react-native-webview": {
54 | "version": "5.11.0",
55 | "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-5.11.0.tgz",
56 | "integrity": "sha512-8hqq7Gr5tP6seWUJ5G1qmSmSo53hljhXIkZT09SX07pfAw1pbEoIMg7uYvZGk7BVkYiwIO4nkyg+M501E8CL8g==",
57 | "requires": {
58 | "escape-string-regexp": "1.0.5",
59 | "invariant": "2.2.4"
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-progress-webview",
3 | "version": "1.2.0",
4 | "description": "React Native Progress WebView is a wrapper of React Native WebView to provide the loading status.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest"
8 | },
9 | "keywords": [
10 | "react",
11 | "react-native",
12 | "react-native-progress-webview",
13 | "webview",
14 | "progress webview",
15 | "loading",
16 | "loading bar"
17 | ],
18 | "author": "Di Wang",
19 | "license": "MIT",
20 | "repository": "https://github.com/wangdicoder/react-native-progress-webview",
21 | "dependencies": {
22 | "prop-types": "^15.7.2"
23 | },
24 | "peerDependencies": {
25 | "react-native-webview": ">=7.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangdicoder/react-native-progress-webview/e848bd19642032bf2a8911727dcc18cdbffa1866/screenshot.gif
--------------------------------------------------------------------------------
/test/__snapshots__/loading-bar.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`LoadingBar Rendering should match to snapshot 1`] = `ShallowWrapper {}`;
4 |
--------------------------------------------------------------------------------
/test/__snapshots__/webview.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`WebView Rendering should match to snapshot 1`] = `ShallowWrapper {}`;
4 |
--------------------------------------------------------------------------------
/test/loading-bar.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {shallow} from 'enzyme';
3 | import LoadingBar from '../lib/loading-bar';
4 |
5 | describe('LoadingBar', () => {
6 | describe('Rendering', () => {
7 | it('should match to snapshot', () => {
8 | const component = shallow();
9 | expect(component).toMatchSnapshot()
10 | });
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/test/webview.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 | import WebView from '../lib/webview';
4 |
5 | describe('WebView', () => {
6 | describe('Rendering', () => {
7 | it('should match to snapshot', () => {
8 | const component = shallow();
9 | expect(component).toMatchSnapshot()
10 | });
11 | });
12 |
13 | describe('function callback', () => {
14 | it('should call onLoadProgress', () => {
15 | const onLoadProgress = jest.fn();
16 | shallow(
17 |
21 | );
22 |
23 | expect(onLoadProgress).toBeCalled();
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------