diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.js index 7438d1e35..65809a323 100644 --- a/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -10,19 +10,15 @@ 'use strict'; -const ColorPropType = require('ColorPropType'); -const NativeMethodsMixin = require('NativeMethodsMixin'); const Platform = require('Platform'); const ProgressBarAndroid = require('ProgressBarAndroid'); -const PropTypes = require('prop-types'); const React = require('React'); const StyleSheet = require('StyleSheet'); const View = require('View'); -const ViewPropTypes = require('ViewPropTypes'); -const createReactClass = require('create-react-class'); const requireNativeComponent = require('requireNativeComponent'); +import type {NativeComponent} from 'ReactNative'; import type {ViewProps} from 'ViewPropTypes'; let RCTActivityIndicator; @@ -31,113 +27,103 @@ const GRAY = '#999999'; type IndicatorSize = number | 'small' | 'large'; +type IOSProps = $ReadOnly<{| + /** + * Whether the indicator should hide when not animating (true by default). + * + * See http://facebook.github.io/react-native/docs/activityindicator.html#hideswhenstopped + */ + hidesWhenStopped?: ?boolean, +|}>; type Props = $ReadOnly<{| ...ViewProps, + ...IOSProps, + /** + * Whether to show the indicator (true, the default) or hide it (false). + * + * See http://facebook.github.io/react-native/docs/activityindicator.html#animating + */ animating?: ?boolean, + + /** + * The foreground color of the spinner (default is gray). + * + * See http://facebook.github.io/react-native/docs/activityindicator.html#color + */ color?: ?string, - hidesWhenStopped?: ?boolean, + + /** + * Size of the indicator (default is 'small'). + * Passing a number to the size prop is only supported on Android. + * + * See http://facebook.github.io/react-native/docs/activityindicator.html#size + */ size?: ?IndicatorSize, |}>; -type DefaultProps = { - animating: boolean, - color: ?string, - hidesWhenStopped: boolean, - size: IndicatorSize, -}; - /** * Displays a circular loading indicator. * * See http://facebook.github.io/react-native/docs/activityindicator.html */ -const ActivityIndicator = ((createReactClass({ - displayName: 'ActivityIndicator', - mixins: [NativeMethodsMixin], +const ActivityIndicator = ( + props: $ReadOnly<{| + ...Props, + forwardedRef?: ?React.Ref<'RCTActivityIndicatorView'>, + |}>, +) => { + const {onLayout, style, forwardedRef, ...restProps} = props; + let sizeStyle; - propTypes: { - ...ViewPropTypes, - /** - * Whether to show the indicator (true, the default) or hide it (false). - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#animating - */ - animating: PropTypes.bool, - /** - * The foreground color of the spinner (default is gray). - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#color - */ - color: ColorPropType, - /** - * Size of the indicator (default is 'small'). - * Passing a number to the size prop is only supported on Android. - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#size - */ - size: PropTypes.oneOfType([ - PropTypes.oneOf(['small', 'large']), - PropTypes.number, - ]), - /** - * Whether the indicator should hide when not animating (true by default). - * - * @platform ios - * - * See http://facebook.github.io/react-native/docs/activityindicator.html#hideswhenstopped - */ - hidesWhenStopped: PropTypes.bool, - }, + switch (props.size) { + case 'small': + sizeStyle = styles.sizeSmall; + break; + case 'large': + sizeStyle = styles.sizeLarge; + break; + default: + sizeStyle = {height: props.size, width: props.size}; + break; + } - getDefaultProps(): DefaultProps { - return { - animating: true, - color: Platform.OS === 'ios' ? GRAY : null, - hidesWhenStopped: true, - size: 'small', - }; - }, + const nativeProps = { + ...restProps, + ref: forwardedRef, + style: sizeStyle, + styleAttr: 'Normal', + indeterminate: true, + }; - render() { - const {onLayout, style, ...props} = this.props; - let sizeStyle; + return ( + + {Platform.OS === 'ios' ? ( + + ) : ( + + )} + + ); +}; - switch (props.size) { - case 'small': - sizeStyle = styles.sizeSmall; - break; - case 'large': - sizeStyle = styles.sizeLarge; - break; - default: - sizeStyle = {height: props.size, width: props.size}; - break; - } +// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. +const ActivityIndicatorWithRef = React.forwardRef((props: Props, ref) => { + return ; +}); - const nativeProps = { - ...props, - style: sizeStyle, - styleAttr: 'Normal', - indeterminate: true, - }; - - return ( - - {Platform.OS === 'ios' ? ( - - ) : ( - - )} - - ); - }, -}): any): React.ComponentType); +ActivityIndicatorWithRef.defaultProps = { + animating: true, + color: Platform.OS === 'ios' ? GRAY : null, + hidesWhenStopped: true, + size: 'small', +}; +ActivityIndicatorWithRef.displayName = 'ActivityIndicator'; if (Platform.OS === 'ios') { RCTActivityIndicator = requireNativeComponent( 'RCTActivityIndicatorView', - ActivityIndicator, + null, {nativeOnly: {activityIndicatorViewStyle: true}}, ); } @@ -157,4 +143,4 @@ const styles = StyleSheet.create({ }, }); -module.exports = ActivityIndicator; +module.exports = (ActivityIndicatorWithRef: Class>); diff --git a/jest/mockComponent.js b/jest/mockComponent.js index e22c34cca..6d5a80b47 100644 --- a/jest/mockComponent.js +++ b/jest/mockComponent.js @@ -20,9 +20,24 @@ module.exports = (moduleName, instanceMethods) => { render() { const name = RealComponent.displayName || RealComponent.name; + const props = Object.assign({}, RealComponent.defaultProps); + + if (this.props) { + Object.keys(this.props).forEach(prop => { + // We can't just assign props on top of defaultProps + // because React treats undefined as special and different from null. + // If a prop is specified but set to undefined it is ignored and the + // default prop is used instead. If it is set to null, then the + // null value overwrites the default value. + if (this.props[prop] !== undefined) { + props[prop] = this.props[prop]; + } + }); + } + return React.createElement( name.replace(/^(RCT|RK)/, ''), - this.props, + props, this.props.children, ); }