From 0aef117733f1e64f9bf426a41d9c4c9f0b01e23a Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Thu, 1 Sep 2016 16:49:39 -0700 Subject: [PATCH] [add] ProgressBar component Ref #91 --- docs/components/ProgressBar.md | 23 ++++ .../ProgressBar/ProgressBarExample.js | 96 ++++++++++++++ .../ProgressBar/__tests__/index-test.js | 20 +++ src/components/ProgressBar/index.js | 119 ++++++++++++++++++ src/index.js | 2 + 5 files changed, 260 insertions(+) create mode 100644 docs/components/ProgressBar.md create mode 100644 examples/components/ProgressBar/ProgressBarExample.js create mode 100644 src/components/ProgressBar/__tests__/index-test.js create mode 100644 src/components/ProgressBar/index.js diff --git a/docs/components/ProgressBar.md b/docs/components/ProgressBar.md new file mode 100644 index 00000000..80064773 --- /dev/null +++ b/docs/components/ProgressBar.md @@ -0,0 +1,23 @@ +# ProgressBar + +Display an activity progress bar. + +## Props + +[...View props](./View.md) + +**color**: string = '#1976D2' + +Color of the progress bar. + +**indeterminate**: bool = true + +Whether the progress bar will show indeterminate progress. + +**progress**: number + +The progress value (between 0 and 1). + +(web) **trackColor**: string = 'transparent' + +Color of the track bar. diff --git a/examples/components/ProgressBar/ProgressBarExample.js b/examples/components/ProgressBar/ProgressBarExample.js new file mode 100644 index 00000000..8046b978 --- /dev/null +++ b/examples/components/ProgressBar/ProgressBarExample.js @@ -0,0 +1,96 @@ +import { ProgressBar, StyleSheet, View } from 'react-native' +import React from 'react'; +import { storiesOf, action } from '@kadira/storybook'; +import TimerMixin from 'react-timer-mixin'; + +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ + +var ProgressBarExample = React.createClass({ + mixins: [TimerMixin], + + getInitialState() { + return { + progress: 0, + }; + }, + + componentDidMount() { + this.updateProgress(); + }, + + updateProgress() { + var progress = this.state.progress + 0.01; + this.setState({ progress }); + this.requestAnimationFrame(() => this.updateProgress()); + }, + + getProgress(offset) { + var progress = this.state.progress + offset; + return Math.sin(progress % Math.PI) % 1; + }, + + render() { + return ( + + + + + + + ); + }, +}); + +const examples = [{ + title: 'progress', + render() { + return ( + + ); + }, +}, { + title: 'indeterminate', + render() { + return ( + + ); + } +}]; + +var styles = StyleSheet.create({ + container: { + minWidth: 200, + marginTop: -20, + backgroundColor: 'transparent', + }, + progressView: { + marginTop: 20, + minWidth: 200 + } +}); + +examples.forEach((example) => { + storiesOf('component: ProgressBar', module) + .add(example.title, () => example.render()) +}) diff --git a/src/components/ProgressBar/__tests__/index-test.js b/src/components/ProgressBar/__tests__/index-test.js new file mode 100644 index 00000000..1df0bf09 --- /dev/null +++ b/src/components/ProgressBar/__tests__/index-test.js @@ -0,0 +1,20 @@ +/* eslint-env mocha */ + +import assert from 'assert'; +import React from 'react'; +import { shallow } from 'enzyme'; +import ProgressBar from '..'; + +suite('components/ProgressBar', () => { + suite('progress', () => { + test('value as percentage is set to "aria-valuenow"', () => { + const component = shallow(); + assert(component.prop('aria-valuenow') === 50); + }); + + test('is ignored when "indeterminate" is "true"', () => { + const component = shallow(); + assert(component.prop('aria-valuenow') === null); + }); + }); +}); diff --git a/src/components/ProgressBar/index.js b/src/components/ProgressBar/index.js new file mode 100644 index 00000000..3a5c81e2 --- /dev/null +++ b/src/components/ProgressBar/index.js @@ -0,0 +1,119 @@ +import Animated from '../../apis/Animated'; +import applyNativeMethods from '../../modules/applyNativeMethods'; +import ColorPropType from '../../propTypes/ColorPropType'; +import StyleSheet from '../../apis/StyleSheet'; +import View from '../View'; +import React, { Component, PropTypes } from 'react'; + +const indeterminateWidth = '25%'; +const translateInterpolation = { inputRange: [ 0, 1 ], outputRange: [ '-100%', '400%' ] }; + +class ProgressBar extends Component { + static propTypes = { + ...View.propTypes, + color: ColorPropType, + indeterminate: PropTypes.bool, + progress: PropTypes.number, + trackColor: ColorPropType + }; + + static defaultProps = { + color: '#1976D2', + indeterminate: false, + progress: 0, + trackColor: 'transparent' + }; + + constructor(props) { + super(props); + this.state = { + animationScale: new Animated.Value(0), + animationTranslate: new Animated.Value(0) + }; + } + + componentDidMount() { + this._manageAnimation(); + } + + componentDidUpdate() { + this._manageAnimation(); + } + + render() { + const { + color, + indeterminate, + progress, + trackColor, + style, + ...other + } = this.props; + + const { animationTranslate } = this.state; + + const percentageProgress = indeterminate ? 50 : progress * 100; + + return ( + + + + ); + } + + _manageAnimation() { + const { animationTranslate } = this.state; + + const cycleAnimation = (animation) => { + animation.setValue(0); + Animated.timing(animation, { + duration: 1000, + toValue: 1 + }).start((event) => { + if (event.finished) { + cycleAnimation(animation); + } + }); + }; + + if (this.props.indeterminate) { + cycleAnimation(animationTranslate); + } else { + animationTranslate.stopAnimation(); + } + } +} + +const styles = StyleSheet.create({ + track: { + height: 5, + overflow: 'hidden', + userSelect: 'none' + }, + progress: { + height: '100%' + } +}); + +module.exports = applyNativeMethods(ProgressBar); diff --git a/src/index.js b/src/index.js index 21f6c71b..bb7e2102 100644 --- a/src/index.js +++ b/src/index.js @@ -25,6 +25,7 @@ import Vibration from './apis/Vibration'; import ActivityIndicator from './components/ActivityIndicator'; import Image from './components/Image'; import ListView from './components/ListView'; +import ProgressBar from './components/ProgressBar'; import ScrollView from './components/ScrollView'; import Switch from './components/Switch'; import Text from './components/Text'; @@ -75,6 +76,7 @@ const ReactNative = { ActivityIndicator, Image, ListView, + ProgressBar, ScrollView, Switch, Text,