[change] ActivityIndicator using CSS animations

Ref #299
This commit is contained in:
Nicolas Gallagher
2017-01-01 19:47:01 -08:00
parent b1cd92a65d
commit cfc56a1354
8 changed files with 291 additions and 57 deletions
@@ -50,7 +50,8 @@ const ToggleAnimatingActivityIndicator = React.createClass({
return (
<ActivityIndicator
animating={this.state.animating}
style={[styles.centering, {height: 80}]}
style={styles.centering}
hidesWhenStopped={this.props.hidesWhenStopped}
size="large"
/>
);
@@ -121,7 +122,12 @@ const examples = [
{
title: 'Start/stop',
render() {
return <ToggleAnimatingActivityIndicator />;
return (
<View style={[styles.horizontal, styles.centering]}>
<ToggleAnimatingActivityIndicator />
<ToggleAnimatingActivityIndicator hidesWhenStopped={false} />
</View>
);
}
},
{
@@ -5,6 +5,7 @@ body{margin:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,input::-webkit-search-cancel-button,input::-webkit-search-decoration,input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}
.rn_pointerEvents\\:auto, .rn_pointerEvents\\:box-only, .rn_pointerEvents\\:box-none * {pointer-events:auto}.rn_pointerEvents\\:none, .rn_pointerEvents\\:box-only *, .rn_pointerEvents\\:box-none {pointer-events:none}
@keyframes rn-ActivityIndicator-animation {0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }}
.rn-bottom\\:0px{bottom:0px;}
.rn-left\\:0px{left:0px;}
.rn-position\\:absolute{position:absolute;}
+8
View File
@@ -33,6 +33,14 @@ const initialize = () => {
'.rn_pointerEvents\\:none, .rn_pointerEvents\\:box-only *, .rn_pointerEvents\\:box-none {pointer-events:none}'
);
injector.addRule(
'activity-indicator-animation',
'@keyframes rn-ActivityIndicator-animation {' +
'0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }' +
'100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }' +
'}'
);
StyleRegistry.initialize();
};
@@ -0,0 +1,226 @@
exports[`components/ActivityIndicator default render 1`] = `
<div
aria-valuemax="1"
aria-valuemin="0"
className="
rn-alignItems:center
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-justifyContent:center
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
role="progressbar"
style={Object {}}>
<div
className="rn-ActivityIndicator-animation
rn-alignItems:stretch
rn-animationDuration:0.75s
rn-animationIterationCount:infinite
rn-animationName:rn-ActivityIndicator-animation
rn-animationTimingFunction:linear
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-height:20px
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none
rn-width:20px"
style={Object {}}>
<svg
height="100%"
viewBox="0 0 32 32"
width="100%">
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"opacity": 0.2,
"stroke": "#1976D2",
}
} />
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"stroke": "#1976D2",
"strokeDasharray": 80,
"strokeDashoffset": 60,
}
} />
</svg>
</div>
</div>
`;
exports[`components/ActivityIndicator other render 1`] = `
<div
aria-valuemax="1"
aria-valuemin="0"
className="
rn-alignItems:center
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-justifyContent:center
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none"
role="progressbar"
style={Object {}}>
<div
className="rn-ActivityIndicator-animation
rn-alignItems:stretch
rn-animationDuration:0.75s
rn-animationIterationCount:infinite
rn-animationName:rn-ActivityIndicator-animation
rn-animationPlayState:paused
rn-animationTimingFunction:linear
rn-backgroundColor:transparent
rn-borderTopStyle:solid
rn-borderRightStyle:solid
rn-borderBottomStyle:solid
rn-borderLeftStyle:solid
rn-borderTopWidth:0px
rn-borderRightWidth:0px
rn-borderBottomWidth:0px
rn-borderLeftWidth:0px
rn-boxSizing:border-box
rn-color:inherit
rn-display:flex
rn-flexBasis:auto
rn-flexDirection:column
rn-flexShrink:0
rn-font:inherit
rn-height:36px
rn-listStyle:none
rn-marginTop:0px
rn-marginRight:0px
rn-marginBottom:0px
rn-marginLeft:0px
rn-minHeight:0px
rn-minWidth:0px
rn-paddingTop:0px
rn-paddingRight:0px
rn-paddingBottom:0px
rn-paddingLeft:0px
rn-position:relative
rn-textAlign:inherit
rn-textDecoration:none
rn-width:36px"
style={Object {}}>
<svg
height="100%"
viewBox="0 0 32 32"
width="100%">
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"opacity": 0.2,
"stroke": "#1976D2",
}
} />
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={
Object {
"stroke": "#1976D2",
"strokeDasharray": 80,
"strokeDashoffset": 60,
}
} />
</svg>
</div>
</div>
`;
@@ -1,5 +1,17 @@
/* eslint-env jasmine, jest */
import ActivityIndicator from '..';
import React from 'react';
import renderer from 'react-test-renderer';
describe('components/ActivityIndicator', () => {
test.skip('NO TEST COVERAGE', () => {});
test('default render', () => {
const component = renderer.create(<ActivityIndicator />);
expect(component.toJSON()).toMatchSnapshot();
});
test('other render', () => {
const component = renderer.create(<ActivityIndicator animating={false} hidesWhenStopped={false} size='large' />);
expect(component.toJSON()).toMatchSnapshot();
});
});
+17 -54
View File
@@ -1,12 +1,8 @@
import Animated from '../../apis/Animated';
import applyNativeMethods from '../../modules/applyNativeMethods';
import Easing from 'animated/lib/Easing';
import StyleSheet from '../../apis/StyleSheet';
import View from '../View';
import React, { Component, PropTypes } from 'react';
const rotationInterpolation = { inputRange: [ 0, 1 ], outputRange: [ '0deg', '360deg' ] };
class ActivityIndicator extends Component {
static displayName = 'ActivityIndicator';
@@ -25,21 +21,6 @@ class ActivityIndicator extends Component {
size: 'small'
};
constructor(props) {
super(props);
this.state = {
animation: new Animated.Value(0)
};
}
componentDidMount() {
this._manageAnimation();
}
componentDidUpdate() {
this._manageAnimation();
}
render() {
const {
animating,
@@ -50,8 +31,6 @@ class ActivityIndicator extends Component {
...other
} = this.props;
const { animation } = this.state;
const svg = (
<svg height='100%' viewBox='0 0 32 32' width='100%'>
<circle
@@ -88,47 +67,22 @@ class ActivityIndicator extends Component {
style={[
styles.container,
style,
size && { height: size, width: size }
typeof size === 'number' && { height: size, width: size }
]}
>
<Animated.View
<View
children={svg}
className='rn-ActivityIndicator-animation'
style={[
indicatorStyles[size],
hidesWhenStopped && !animating && styles.hidesWhenStopped,
{
transform: [
{ rotate: animation.interpolate(rotationInterpolation) }
]
}
indicatorSizes[size],
styles.animation,
!animating && styles.animationPause,
!animating && hidesWhenStopped && styles.hidesWhenStopped
]}
/>
</View>
);
}
_manageAnimation() {
const { animation } = this.state;
const cycleAnimation = () => {
animation.setValue(0);
Animated.timing(animation, {
duration: 750,
easing: Easing.inOut(Easing.linear),
toValue: 1
}).start((event) => {
if (event.finished) {
cycleAnimation();
}
});
};
if (this.props.animating) {
cycleAnimation();
} else {
animation.stopAnimation();
}
}
}
const styles = StyleSheet.create({
@@ -138,10 +92,19 @@ const styles = StyleSheet.create({
},
hidesWhenStopped: {
visibility: 'hidden'
},
animation: {
animationDuration: '0.75s',
animationName: 'rn-ActivityIndicator-animation',
animationTimingFunction: 'linear',
animationIterationCount: 'infinite'
},
animationPause: {
animationPlayState: 'paused'
}
});
const indicatorStyles = StyleSheet.create({
const indicatorSizes = StyleSheet.create({
small: {
width: 20,
height: 20
@@ -1,3 +1,4 @@
import AnimationPropTypes from '../../propTypes/AnimationPropTypes';
import BorderPropTypes from '../../propTypes/BorderPropTypes';
import ColorPropType from '../../propTypes/ColorPropType';
import LayoutPropTypes from '../../propTypes/LayoutPropTypes';
@@ -10,6 +11,7 @@ const autoOrHiddenOrVisible = oneOf([ 'auto', 'hidden', 'visible' ]);
const hiddenOrVisible = oneOf([ 'hidden', 'visible' ]);
module.exports = process.env.NODE_ENV !== 'production' ? {
...AnimationPropTypes,
...BorderPropTypes,
...LayoutPropTypes,
...ShadowPropTypes,
+16
View File
@@ -0,0 +1,16 @@
import { PropTypes } from 'react';
const { number, oneOf, oneOfType, string } = PropTypes;
const AnimationPropTypes = process.env.NODE_ENV !== 'production' ? {
animationDelay: string,
animationDirection: oneOf([ 'alternate', 'alternate-reverse', 'normal', 'reverse' ]),
animationDuration: string,
animationFillMode: oneOf([ 'none', 'forwards', 'backwards', 'both' ]),
animationIterationCount: oneOfType([ number, oneOf([ 'infinite' ]) ]),
animationName: string,
animationPlayState: oneOf([ 'paused', 'running' ]),
animationTimingFunction: string
} : {};
module.exports = AnimationPropTypes;