diff --git a/example/src/Examples/ProgressBarExample.js b/example/src/Examples/ProgressBarExample.js index 860c7eb..71b4d32 100644 --- a/example/src/Examples/ProgressBarExample.js +++ b/example/src/Examples/ProgressBarExample.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { View, StyleSheet } from 'react-native'; import { + Button, ProgressBar, Paragraph, Colors, @@ -16,43 +17,73 @@ type Props = { type State = { progress: number, + visible: boolean, }; class ProgressBarExample extends React.Component { static title = 'Progress Bar'; state = { - progress: 0, + progress: 0.3, + visible: true, }; - componentDidMount() { - this._interval = setInterval( - () => - this.setState(state => ({ - progress: state.progress < 1 ? state.progress + 0.01 : 0, - })), - 16 - ); - } - - componentWillUnmount() { - clearInterval(this._interval); - } - - _interval: IntervalID; - render() { const { theme: { colors: { background }, }, } = this.props; + return ( - ProgressBar primary color - - ProgressBar custom color - + + + + + Default ProgressBar + + + + + Indeterminate ProgressBar + + + + + ProgressBar with custom color + + + + + ProgressBar with custom background color + + + + + ProgressBar with custom height + + ); } @@ -62,6 +93,10 @@ const styles = StyleSheet.create({ flex: 1, padding: 16, }, + + row: { + marginVertical: 10, + }, }); export default withTheme(ProgressBarExample); diff --git a/src/components/ProgressBar.js b/src/components/ProgressBar.js new file mode 100644 index 0000000..612ca8c --- /dev/null +++ b/src/components/ProgressBar.js @@ -0,0 +1,234 @@ +/* @flow */ + +import * as React from 'react'; +import { Animated, Platform, StyleSheet, View } from 'react-native'; +import setColor from 'color'; +import { withTheme } from '../core/theming'; +import type { Theme } from '../types'; + +type Props = {| + /** + * Progress value (between 0 and 1). + */ + progress?: number, + /** + * Color of the progress bar. The background color will be calculated based on this but you can change it by passing `backgroundColor` to `style` prop. + */ + color?: string, + /** + * If the progress bar will show indeterminate progress. + */ + indeterminate?: boolean, + /** + * Whether to show the ProgressBar (true, the default) or hide it (false). + */ + visible?: boolean, + style?: any, + /** + * @optional + */ + theme: Theme, +|}; + +type State = { + width: number, + fade: Animated.Value, + timer: Animated.Value, +}; + +const INDETERMINATE_DURATION = 2000; +const INDETERMINATE_MAX_WIDTH = 0.6; + +/** + * Progress bar is an indicator used to present progress of some activity in the app. + * + *
+ * + *
+ * + * ## Usage + * ```js + * import * as React from 'react'; + * import { ProgressBar, Colors } from 'react-native-paper'; + * + * const MyComponent = () => ( + * + * ); + * + * export default MyComponent; + * ``` + */ +class ProgressBar extends React.Component { + static defaultProps = { + visible: true, + progress: 0, + }; + + state = { + width: 0, + timer: new Animated.Value(0), + fade: new Animated.Value(0), + }; + + indeterminateAnimation = null; + + componentDidUpdate() { + const { visible } = this.props; + + if (visible) { + this._startAnimation(); + } else { + this._stopAnimation(); + } + } + + _onLayout = event => { + const { visible } = this.props; + const { width: previousWidth } = this.state; + + this.setState({ width: event.nativeEvent.layout.width }, () => { + // Start animation the very first time when previously the width was unclear + if (visible && previousWidth === 0) { + this._startAnimation(); + } + }); + }; + + _startAnimation() { + const { indeterminate, progress } = this.props; + const { fade, timer } = this.state; + + // Show progress bar + Animated.timing(fade, { + duration: 200, + toValue: 1, + useNativeDriver: true, + isInteraction: false, + }).start(); + + // Animate progress bar + if (indeterminate) { + if (!this.indeterminateAnimation) { + this.indeterminateAnimation = Animated.timing(timer, { + duration: INDETERMINATE_DURATION, + toValue: 1, + // Animated.loop does not work if useNativeDriver is true on web + useNativeDriver: Platform.OS !== 'web', + isInteraction: false, + }); + } + + // Reset timer to the beginning + timer.setValue(0); + + // $FlowFixMe + Animated.loop(this.indeterminateAnimation).start(); + } else { + Animated.timing(timer, { + duration: 200, + // $FlowFixMe + toValue: progress, + useNativeDriver: true, + isInteraction: false, + }).start(); + } + } + + _stopAnimation() { + const { fade } = this.state; + + // Stop indeterminate animation + if (this.indeterminateAnimation) { + this.indeterminateAnimation.stop(); + } + + Animated.timing(fade, { + duration: 200, + toValue: 0, + useNativeDriver: true, + isInteraction: false, + }).start(); + } + + render() { + const { color, indeterminate, style, theme } = this.props; + const { fade, timer, width } = this.state; + const tintColor = color || theme.colors.primary; + const trackTintColor = setColor(tintColor) + .alpha(0.38) + .rgb() + .string(); + + return ( + + + + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + height: 4, + overflow: 'hidden', + }, + + progressBar: { + flex: 1, + }, +}); + +export default withTheme(ProgressBar); diff --git a/src/components/ProgressBar/ProgressBar.js b/src/components/ProgressBar/ProgressBar.js deleted file mode 100644 index ba71aa4..0000000 --- a/src/components/ProgressBar/ProgressBar.js +++ /dev/null @@ -1,71 +0,0 @@ -/* @flow */ - -import * as React from 'react'; -import { StyleSheet } from 'react-native'; -import setColor from 'color'; -import ProgressBarComponent from './ProgressBarComponent'; -import { withTheme } from '../../core/theming'; -import type { Theme } from '../../types'; - -type Props = {| - /** - * Progress value (between 0 and 1). - */ - progress: number, - /** - * Color of the progress bar. - */ - color?: string, - style?: any, - /** - * @optional - */ - theme: Theme, -|}; - -/** - * Progress bar is an indicator used to present progress of some activity in the app. - * - *
- * - *
- * - * ## Usage - * ```js - * import * as React from 'react'; - * import { ProgressBar, Colors } from 'react-native-paper'; - * - * const MyComponent = () => ( - * - * ); - * - * export default MyComponent; - * ``` - */ -class ProgressBar extends React.Component { - render() { - const { progress, color, style, theme } = this.props; - const tintColor = color || theme.colors.primary; - const trackTintColor = setColor(tintColor) - .alpha(0.38) - .rgb() - .string(); - - return ( - - ); - } -} - -const styles = StyleSheet.create({ - progressBarHeight: { - paddingVertical: 10, - }, -}); - -export default withTheme(ProgressBar); diff --git a/src/components/ProgressBar/ProgressBarComponent.android.js b/src/components/ProgressBar/ProgressBarComponent.android.js deleted file mode 100644 index c72b04d..0000000 --- a/src/components/ProgressBar/ProgressBarComponent.android.js +++ /dev/null @@ -1,18 +0,0 @@ -/* @flow */ - -import * as React from 'react'; -import { ProgressBarAndroid } from 'react-native'; - -export default function BaseProgressBarAndroid({ - progressTintColor, - ...rest -}: *) { - return ( - - ); -} diff --git a/src/components/ProgressBar/ProgressBarComponent.ios.js b/src/components/ProgressBar/ProgressBarComponent.ios.js deleted file mode 100644 index fc3ff17..0000000 --- a/src/components/ProgressBar/ProgressBarComponent.ios.js +++ /dev/null @@ -1,3 +0,0 @@ -/* @flow */ - -export { ProgressViewIOS as default } from 'react-native'; diff --git a/src/components/ProgressBar/ProgressBarComponent.js b/src/components/ProgressBar/ProgressBarComponent.js deleted file mode 100644 index 9e53308..0000000 --- a/src/components/ProgressBar/ProgressBarComponent.js +++ /dev/null @@ -1 +0,0 @@ -export { ProgressBar as default } from 'react-native'; diff --git a/src/components/ProgressBar/ProgressBarComponent.macos.js b/src/components/ProgressBar/ProgressBarComponent.macos.js deleted file mode 100644 index fc3ff17..0000000 --- a/src/components/ProgressBar/ProgressBarComponent.macos.js +++ /dev/null @@ -1,3 +0,0 @@ -/* @flow */ - -export { ProgressViewIOS as default } from 'react-native'; diff --git a/src/components/ProgressBar/ProgressBarComponent.windows.js b/src/components/ProgressBar/ProgressBarComponent.windows.js deleted file mode 100644 index 2c6cc22..0000000 --- a/src/components/ProgressBar/ProgressBarComponent.windows.js +++ /dev/null @@ -1,3 +0,0 @@ -/* @flow */ - -export { View as default } from 'react-native'; diff --git a/src/components/__tests__/ProgressBar.test.js b/src/components/__tests__/ProgressBar.test.js new file mode 100644 index 0000000..cfb5738 --- /dev/null +++ b/src/components/__tests__/ProgressBar.test.js @@ -0,0 +1,44 @@ +/* @flow */ + +import * as React from 'react'; +import { View } from 'react-native'; +import renderer from 'react-test-renderer'; +import ProgressBar from '../ProgressBar'; + +jest.useFakeTimers(); + +const layoutEvent = { + nativeEvent: { + layout: { + width: 100, + }, + }, +}; + +it('renders progress bar with specific progress', () => { + const tree = renderer.create(); + tree.root.findByType(View).props.onLayout(layoutEvent); + + expect(tree.toJSON()).toMatchSnapshot(); +}); + +it('renders hidden progress bar', () => { + const tree = renderer.create(); + tree.root.findByType(View).props.onLayout(layoutEvent); + + expect(tree.toJSON()).toMatchSnapshot(); +}); + +it('renders colored progress bar', () => { + const tree = renderer.create(); + tree.root.findByType(View).props.onLayout(layoutEvent); + + expect(tree.toJSON()).toMatchSnapshot(); +}); + +it('renders indeterminate progress bar', () => { + const tree = renderer.create(); + tree.root.findByType(View).props.onLayout(layoutEvent); + + expect(tree.toJSON()).toMatchSnapshot(); +}); diff --git a/src/components/__tests__/__snapshots__/ProgressBar.test.js.snap b/src/components/__tests__/__snapshots__/ProgressBar.test.js.snap new file mode 100644 index 0000000..1e39cf6 --- /dev/null +++ b/src/components/__tests__/__snapshots__/ProgressBar.test.js.snap @@ -0,0 +1,141 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders colored progress bar 1`] = ` + + + + + +`; + +exports[`renders hidden progress bar 1`] = ` + + + + + +`; + +exports[`renders indeterminate progress bar 1`] = ` + + + + + +`; + +exports[`renders progress bar with specific progress 1`] = ` + + + + + +`; diff --git a/src/index.js b/src/index.js index 3c404a1..ff6c203 100644 --- a/src/index.js +++ b/src/index.js @@ -36,7 +36,7 @@ export { default as IconButton } from './components/IconButton'; export { default as Menu } from './components/Menu/Menu'; export { default as Modal } from './components/Modal'; export { default as Portal } from './components/Portal/Portal'; -export { default as ProgressBar } from './components/ProgressBar/ProgressBar'; +export { default as ProgressBar } from './components/ProgressBar'; export { default as RadioButton } from './components/RadioButton'; export { default as Searchbar } from './components/Searchbar'; export { default as Snackbar } from './components/Snackbar'; diff --git a/typings/components/ProgressBar.d.ts b/typings/components/ProgressBar.d.ts index 9b22cf2..f5bea2b 100644 --- a/typings/components/ProgressBar.d.ts +++ b/typings/components/ProgressBar.d.ts @@ -2,8 +2,8 @@ import * as React from 'react'; import { ThemeShape } from '../types'; export interface ProgressBarProps { - progress: number; - animating?: boolean; + progress?: number; + visible?: boolean; indeterminate?: boolean; color?: string; style?: any;