├── .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 | ![npm](https://img.shields.io/npm/dy/react-native-progress-webview.svg) ![npm](https://img.shields.io/npm/v/react-native-progress-webview.svg) [![star this repo](http://githubbadges.com/star.svg?user=wangdicoder&repo=react-native-progress-webview&style=flat)](https://github.com/wangdicoder/react-native-progress-webview) ![GitHub](https://img.shields.io/github/license/wangdicoder/react-native-progress-webview.svg) 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 | --------------------------------------------------------------------------------