Repository: callstack/react-native-paper
Pull request #979 (2019-04-09 13:11:07)

fix: use own ActivityIndicator in Button
### Motivation

So far the default `ActivityIndicator` from react-native was used but since we provide a custom one with Material Design we can use it in the button instead.

### Test plan

n/a


callstack-bot: Hey @jaulz, thank you for your pull request 🤗. The documentation from this branch can be viewed [here](https://9515-71323749-gh.circle-artifacts.com/0/docs/index.html). Please remember to update Typescript types if you changed API.
ferrannp: @jaulz can you fix conflicts?
jaulz: @ferrannp done but it seems that there is still something wrong with the snapshots in the 3.0 branch
ferrannp: @jaulz tests are passing for me in 3.0. Did you rebase that branch with yours? Or maybe just even open a new PR if there is too much trouble :)
jaulz: @ferrannp not sure what went wrong before but here we go ;)

Patch:
diff --git a/example/src/Examples/ListSectionExample.js b/example/src/Examples/ListSectionExample.js
index a44097ac6..2fa17b5b3 100644
--- a/example/src/Examples/ListSectionExample.js
+++ b/example/src/Examples/ListSectionExample.js
@@ -29,6 +29,11 @@ class ListSectionExample extends React.Component<Props> {
             left={props => <List.Icon {...props} icon="redeem" />}
             title="List item 2"
           />
+          <List.Item
+            title="List item 3"
+            left={props => <List.Icon {...props} icon="folder" />}
+            right={props => <List.Icon {...props} icon="drag-handle" />}
+          />
         </List.Section>
         <Divider />
         <List.Section>
diff --git a/example/src/Examples/TextInputExample.js b/example/src/Examples/TextInputExample.js
index 084addf40..3c7e6533b 100644
--- a/example/src/Examples/TextInputExample.js
+++ b/example/src/Examples/TextInputExample.js
@@ -87,6 +87,24 @@ class TextInputExample extends React.Component<Props, State> {
               Error: Only letters are allowed
             </HelperText>
           </View>
+          <View style={styles.inputContainerStyle}>
+            <TextInput
+              label="Input with no padding"
+              style={{ backgroundColor: 'transparent' }}
+              padding="none"
+              placeholder="Enter username, only letters"
+              value={this.state.name}
+              error={!this._isUsernameValid()}
+              onChangeText={name => this.setState({ name })}
+            />
+            <HelperText
+              type="error"
+              padding="none"
+              visible={!this._isUsernameValid()}
+            >
+              Error: Only letters are allowed
+            </HelperText>
+          </View>
         </ScrollView>
       </KeyboardAvoidingView>
     );
diff --git a/src/components/Button.js b/src/components/Button.js
index c09d77fd5..c9fd3d00d 100644
--- a/src/components/Button.js
+++ b/src/components/Button.js
@@ -1,9 +1,10 @@
 /* @flow */
 
 import * as React from 'react';
-import { ActivityIndicator, Animated, View, StyleSheet } from 'react-native';
+import { Animated, View, StyleSheet } from 'react-native';
 import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
 import color from 'color';
+import ActivityIndicator from './ActivityIndicator';
 import Icon from './Icon';
 import Surface from './Surface';
 import Text from './Typography/Text';
@@ -222,7 +223,8 @@ class Button extends React.Component<Props, State> {
     };
     const touchableStyle = { borderRadius: roundness };
     const textStyle = { color: textColor, ...font };
-    const elevation = disabled ? 0 : this.state.elevation;
+    const elevation =
+      disabled || mode !== 'contained' ? 0 : this.state.elevation;
 
     return (
       <Surface
@@ -258,7 +260,7 @@ class Button extends React.Component<Props, State> {
             ) : null}
             {loading ? (
               <ActivityIndicator
-                size="small"
+                size={16}
                 color={textColor}
                 style={styles.icon}
               />
diff --git a/src/components/Card/CardTitle.js b/src/components/Card/CardTitle.js
index e2b959219..1b1cfe34e 100644
--- a/src/components/Card/CardTitle.js
+++ b/src/components/Card/CardTitle.js
@@ -10,10 +10,11 @@ import { withTheme } from '../../core/theming';
 import Caption from './../Typography/Caption';
 import Title from './../Typography/Title';
 import type { Theme } from '../../types';
+import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
 
 type Props = React.ElementConfig<typeof View> & {|
   /**
-   * Text for the title.
+   * Text for the title. Note that this will only accept a string or `<Text>`-based node.
    */
   title: React.Node,
   /**
@@ -21,7 +22,7 @@ type Props = React.ElementConfig<typeof View> & {|
    */
   titleStyle?: TextStyleProp,
   /**
-   * Text for the subtitle.
+   * Text for the subtitle. Note that this will only accept a string or `<Text>`-based node.
    */
   subtitle?: React.Node,
   /**
@@ -32,10 +33,18 @@ type Props = React.ElementConfig<typeof View> & {|
    * Callback which returns a React element to display on the left side.
    */
   left?: (props: { size: number }) => React.Node,
+  /**
+   * Style for the left element wrapper.
+   */
+  leftStyle?: ViewStyleProp,
   /**
    * Callback which returns a React element to display on the right side.
    */
   right?: (props: { size: number }) => React.Node,
+  /**
+   * Style for the right element wrapper.
+   */
+  rightStyle?: ViewStyleProp,
   /**
    * @internal
    */
@@ -83,7 +92,9 @@ class CardTitle extends React.Component<Props> {
   render() {
     const {
       left,
+      leftStyle,
       right,
+      rightStyle,
       subtitle,
       subtitleStyle,
       style,
@@ -100,7 +111,7 @@ class CardTitle extends React.Component<Props> {
         ]}
       >
         {left ? (
-          <View style={[styles.left]}>
+          <View style={[styles.left, leftStyle]}>
             {left({
               size: LEFT_SIZE,
             })}
@@ -128,7 +139,7 @@ class CardTitle extends React.Component<Props> {
           ) : null}
         </View>
 
-        <View>{right ? right({ size: 24 }) : null}</View>
+        <View style={rightStyle}>{right ? right({ size: 24 }) : null}</View>
       </View>
     );
   }
diff --git a/src/components/FAB/FABGroup.js b/src/components/FAB/FABGroup.js
index 341ca7281..31bb6922d 100644
--- a/src/components/FAB/FABGroup.js
+++ b/src/components/FAB/FABGroup.js
@@ -6,7 +6,6 @@ import {
   StyleSheet,
   Animated,
   TouchableWithoutFeedback,
-  StatusBar,
   SafeAreaView,
 } from 'react-native';
 import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
@@ -235,7 +234,6 @@ class FABGroup extends React.Component<Props, State> {
 
     return (
       <View pointerEvents="box-none" style={[styles.container, style]}>
-        {open ? <StatusBar barStyle="light-content" /> : null}
         <TouchableWithoutFeedback onPress={this._close}>
           <Animated.View
             pointerEvents={open ? 'auto' : 'none'}
diff --git a/src/components/HelperText.js b/src/components/HelperText.js
index f63cc1c50..5df8c1412 100644
--- a/src/components/HelperText.js
+++ b/src/components/HelperText.js
@@ -19,6 +19,10 @@ type Props = React.ElementConfig<typeof Text> & {
    * Whether to display the helper text.
    */
   visible?: boolean,
+  /**
+   * Whether to apply padding to the helper text.
+   */
+  padding?: 'none' | 'normal',
   /**
    * Text content of the HelperText.
    */
@@ -76,6 +80,7 @@ type State = {
 class HelperText extends React.PureComponent<Props, State> {
   static defaultProps = {
     type: 'info',
+    padding: 'normal',
     visible: true,
   };
 
@@ -121,7 +126,15 @@ class HelperText extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const { style, type, visible, theme, onLayout, ...rest } = this.props;
+    const {
+      style,
+      type,
+      visible,
+      theme,
+      onLayout,
+      padding,
+      ...rest
+    } = this.props;
     const { colors, dark } = theme;
 
     const textColor =
@@ -137,6 +150,7 @@ class HelperText extends React.PureComponent<Props, State> {
         onLayout={this._handleTextLayout}
         style={[
           styles.text,
+          padding !== 'none' ? styles.padding : {},
           {
             color: textColor,
             opacity: this.state.shown,
@@ -166,6 +180,8 @@ const styles = StyleSheet.create({
   text: {
     fontSize: 12,
     paddingVertical: 4,
+  },
+  padding: {
     paddingHorizontal: 12,
   },
 });
diff --git a/src/components/List/ListIcon.js b/src/components/List/ListIcon.js
index 83df2333a..b67dced72 100644
--- a/src/components/List/ListIcon.js
+++ b/src/components/List/ListIcon.js
@@ -48,7 +48,7 @@ export default class ListIcon extends React.Component<Props> {
 
 const styles = StyleSheet.create({
   item: {
-    marginRight: 16,
+    margin: 8,
     height: 40,
     width: 40,
     alignItems: 'center',
diff --git a/src/components/List/ListItem.js b/src/components/List/ListItem.js
index af825bf32..876a3fa41 100644
--- a/src/components/List/ListItem.js
+++ b/src/components/List/ListItem.js
@@ -121,7 +121,9 @@ class ListItem extends React.Component<Props> {
           {left
             ? left({
                 color: descriptionColor,
-                style: description ? styles.iconMargin : null,
+                style: description
+                  ? styles.iconMarginLeft
+                  : { ...styles.iconMarginLeft, ...styles.marginVerticalNone },
               })
             : null}
           <View style={[styles.item, styles.content]} pointerEvents="none">
@@ -151,7 +153,9 @@ class ListItem extends React.Component<Props> {
           {right
             ? right({
                 color: descriptionColor,
-                style: description ? styles.iconMargin : null,
+                style: description
+                  ? styles.iconMarginRight
+                  : { ...styles.iconMarginRight, ...styles.marginVerticalNone },
               })
             : null}
         </View>
@@ -173,7 +177,9 @@ const styles = StyleSheet.create({
   description: {
     fontSize: 14,
   },
-  iconMargin: { marginVertical: 8 },
+  marginVerticalNone: { marginVertical: 0 },
+  iconMarginLeft: { marginLeft: 0, marginRight: 16 },
+  iconMarginRight: { marginRight: 0 },
   item: {
     marginVertical: 6,
     paddingLeft: 8,
diff --git a/src/components/RadioButton.js b/src/components/RadioButton.js
index f293c46e6..39bb648c8 100644
--- a/src/components/RadioButton.js
+++ b/src/components/RadioButton.js
@@ -2,7 +2,7 @@
 
 import * as React from 'react';
 import { Platform } from 'react-native';
-import RadioButtonGroup from './RadioButtonGroup';
+import RadioButtonGroup, { RadioButtonContext } from './RadioButtonGroup';
 import RadioButtonAndroid from './RadioButtonAndroid';
 import RadioButtonIOS from './RadioButtonIOS';
 import { withTheme } from '../core/theming';
@@ -103,11 +103,32 @@ class RadioButton extends React.Component<Props> {
   // @component ./RadioButtonIOS.js
   static IOS = RadioButtonIOS;
 
+  handlePress = context => {
+    const { onPress } = this.props;
+    const { onValueChange } = context;
+
+    onPress || onValueChange(this.props.value);
+  };
+
+  isChecked = context =>
+    context.value === this.props.value ? 'checked' : 'unchecked';
+
   render() {
-    return Platform.OS === 'ios' ? (
-      <RadioButtonIOS {...this.props} />
-    ) : (
-      <RadioButtonAndroid {...this.props} />
+    const Button = Platform.select({
+      default: RadioButtonAndroid,
+      ios: RadioButtonIOS,
+    });
+
+    return (
+      <RadioButtonContext.Consumer>
+        {context => (
+          <Button
+            {...this.props}
+            status={this.props.status || (context && this.isChecked(context))}
+            onPress={() => context && this.handlePress(context)}
+          />
+        )}
+      </RadioButtonContext.Consumer>
     );
   }
 }
diff --git a/src/components/RadioButtonAndroid.js b/src/components/RadioButtonAndroid.js
index 1e7042d01..5c5c610ce 100644
--- a/src/components/RadioButtonAndroid.js
+++ b/src/components/RadioButtonAndroid.js
@@ -3,7 +3,6 @@
 import * as React from 'react';
 import { Animated, View, StyleSheet } from 'react-native';
 import color from 'color';
-import { RadioButtonContext } from './RadioButtonGroup';
 import TouchableRipple from './TouchableRipple';
 import { withTheme } from '../core/theming';
 import type { Theme, $RemoveChildren } from '../types';
@@ -92,89 +91,78 @@ class RadioButtonAndroid extends React.Component<Props, State> {
   }
 
   render() {
+    const { disabled, onPress, theme, ...rest } = this.props;
+    const checkedColor = this.props.color || theme.colors.accent;
+    const uncheckedColor =
+      this.props.uncheckedColor ||
+      color(theme.colors.text)
+        .alpha(theme.dark ? 0.7 : 0.54)
+        .rgb()
+        .string();
+
+    let rippleColor, radioColor;
+
+    const checked = this.props.status === 'checked';
+
+    if (disabled) {
+      rippleColor = color(theme.colors.text)
+        .alpha(0.16)
+        .rgb()
+        .string();
+      radioColor = theme.colors.disabled;
+    } else {
+      rippleColor = color(checkedColor)
+        .fade(0.32)
+        .rgb()
+        .string();
+      radioColor = checked ? checkedColor : uncheckedColor;
+    }
+
     return (
-      <RadioButtonContext.Consumer>
-        {context => {
-          const { disabled, onPress, theme, ...rest } = this.props;
-          const checkedColor = this.props.color || theme.colors.accent;
-          const uncheckedColor =
-            this.props.uncheckedColor ||
-            color(theme.colors.text)
-              .alpha(theme.dark ? 0.7 : 0.54)
-              .rgb()
-              .string();
-
-          let rippleColor, radioColor;
-
-          const checked = context
-            ? context.value === this.props.value
-            : this.props.status === 'checked';
-
-          if (disabled) {
-            rippleColor = color(theme.colors.text)
-              .alpha(0.16)
-              .rgb()
-              .string();
-            radioColor = theme.colors.disabled;
-          } else {
-            rippleColor = color(checkedColor)
-              .fade(0.32)
-              .rgb()
-              .string();
-            radioColor = checked ? checkedColor : uncheckedColor;
-          }
-
-          return (
-            <TouchableRipple
-              {...rest}
-              borderless
-              rippleColor={rippleColor}
-              onPress={
-                disabled
-                  ? undefined
-                  : () => {
-                      context && context.onValueChange(this.props.value);
-                      onPress && onPress();
-                    }
-              }
-              accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
-              accessibilityComponentType={
-                checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
+      <TouchableRipple
+        {...rest}
+        borderless
+        rippleColor={rippleColor}
+        onPress={
+          disabled
+            ? undefined
+            : () => {
+                onPress && onPress(this.props.value);
               }
-              accessibilityRole="button"
-              accessibilityStates={disabled ? ['disabled'] : undefined}
-              accessibilityLiveRegion="polite"
-              style={styles.container}
-            >
+        }
+        accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
+        accessibilityComponentType={
+          checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
+        }
+        accessibilityRole="button"
+        accessibilityStates={disabled ? ['disabled'] : undefined}
+        accessibilityLiveRegion="polite"
+        style={styles.container}
+      >
+        <Animated.View
+          style={[
+            styles.radio,
+            {
+              borderColor: radioColor,
+              borderWidth: this.state.borderAnim,
+            },
+          ]}
+        >
+          {checked ? (
+            <View style={[StyleSheet.absoluteFill, styles.radioContainer]}>
               <Animated.View
                 style={[
-                  styles.radio,
+                  styles.dot,
                   {
-                    borderColor: radioColor,
-                    borderWidth: this.state.borderAnim,
+                    backgroundColor: radioColor,
+                    transform: [{ scale: this.state.radioAnim }],
                   },
                 ]}
-              >
-                {checked ? (
-                  <View
-                    style={[StyleSheet.absoluteFill, styles.radioContainer]}
-                  >
-                    <Animated.View
-                      style={[
-                        styles.dot,
-                        {
-                          backgroundColor: radioColor,
-                          transform: [{ scale: this.state.radioAnim }],
-                        },
-                      ]}
-                    />
-                  </View>
-                ) : null}
-              </Animated.View>
-            </TouchableRipple>
-          );
-        }}
-      </RadioButtonContext.Consumer>
+              />
+            </View>
+          ) : null}
+        </Animated.View>
+      </TouchableRipple>
     );
   }
 }
diff --git a/src/components/RadioButtonIOS.js b/src/components/RadioButtonIOS.js
index 6417dd2d9..057795183 100644
--- a/src/components/RadioButtonIOS.js
+++ b/src/components/RadioButtonIOS.js
@@ -3,7 +3,6 @@
 import * as React from 'react';
 import { StyleSheet, View } from 'react-native';
 import color from 'color';
-import { RadioButtonContext } from './RadioButtonGroup';
 import Icon from './Icon';
 import TouchableRipple from './TouchableRipple';
 import { withTheme } from '../core/theming';
@@ -55,66 +54,57 @@ class RadioButtonIOS extends React.Component<Props> {
   static displayName = 'RadioButton.IOS';
 
   render() {
-    return (
-      <RadioButtonContext.Consumer>
-        {context => {
-          const { disabled, onPress, theme, ...rest } = this.props;
+    const { disabled, onPress, theme, ...rest } = this.props;
 
-          const checkedColor = disabled
-            ? theme.colors.disabled
-            : this.props.color || theme.colors.accent;
+    const checkedColor = disabled
+      ? theme.colors.disabled
+      : this.props.color || theme.colors.accent;
 
-          let rippleColor;
+    let rippleColor;
 
-          const checked = context
-            ? context.value === this.props.value
-            : this.props.status === 'checked';
+    const checked = this.props.status === 'checked';
 
-          if (disabled) {
-            rippleColor = color(theme.colors.text)
-              .alpha(0.16)
-              .rgb()
-              .string();
-          } else {
-            rippleColor = color(checkedColor)
-              .fade(0.32)
-              .rgb()
-              .string();
-          }
-          return (
-            <TouchableRipple
-              {...rest}
-              borderless
-              rippleColor={rippleColor}
-              onPress={
-                disabled
-                  ? undefined
-                  : () => {
-                      context && context.onValueChange(this.props.value);
-                      onPress && onPress();
-                    }
-              }
-              accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
-              accessibilityComponentType={
-                checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
+    if (disabled) {
+      rippleColor = color(theme.colors.text)
+        .alpha(0.16)
+        .rgb()
+        .string();
+    } else {
+      rippleColor = color(checkedColor)
+        .fade(0.32)
+        .rgb()
+        .string();
+    }
+    return (
+      <TouchableRipple
+        {...rest}
+        borderless
+        rippleColor={rippleColor}
+        onPress={
+          disabled
+            ? undefined
+            : () => {
+                onPress && onPress();
               }
-              accessibilityRole="button"
-              accessibilityStates={disabled ? ['disabled'] : undefined}
-              accessibilityLiveRegion="polite"
-              style={styles.container}
-            >
-              <View style={{ opacity: checked ? 1 : 0 }}>
-                <Icon
-                  allowFontScaling={false}
-                  source="done"
-                  size={24}
-                  color={checkedColor}
-                />
-              </View>
-            </TouchableRipple>
-          );
-        }}
-      </RadioButtonContext.Consumer>
+        }
+        accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
+        accessibilityComponentType={
+          checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
+        }
+        accessibilityRole="button"
+        accessibilityStates={disabled ? ['disabled'] : undefined}
+        accessibilityLiveRegion="polite"
+        style={styles.container}
+      >
+        <View style={{ opacity: checked ? 1 : 0 }}>
+          <Icon
+            allowFontScaling={false}
+            source="done"
+            size={24}
+            color={checkedColor}
+          />
+        </View>
+      </TouchableRipple>
     );
   }
 }
diff --git a/src/components/TextInput/TextInput.js b/src/components/TextInput/TextInput.js
index 0f9fc85a9..9c8081132 100644
--- a/src/components/TextInput/TextInput.js
+++ b/src/components/TextInput/TextInput.js
@@ -52,6 +52,10 @@ export type TextInputProps = {|
    * Underline color of the input.
    */
   underlineColor?: string,
+  /**
+   * Whether to apply padding to label and input.
+   */
+  padding?: 'none' | 'normal',
   /**
    * Whether the input can have multiple lines.
    */
@@ -148,6 +152,7 @@ export type TextInputProps = {|
 class TextInput extends React.Component<TextInputProps, State> {
   static defaultProps = {
     mode: 'flat',
+    padding: 'normal',
     disabled: false,
     error: false,
     multiline: false,
@@ -368,7 +373,7 @@ class TextInput extends React.Component<TextInputProps, State> {
   }
 
   render() {
-    const { mode, ...rest } = this.props;
+    const { mode, padding, ...rest } = this.props;
 
     return mode === 'outlined' ? (
       <TextInputOutlined
@@ -389,6 +394,7 @@ class TextInput extends React.Component<TextInputProps, State> {
         innerRef={ref => {
           this._root = ref;
         }}
+        padding={padding}
         onFocus={this._handleFocus}
         onBlur={this._handleBlur}
         onChangeText={this._handleChangeText}
diff --git a/src/components/TextInput/TextInputFlat.js b/src/components/TextInput/TextInputFlat.js
index ab6414f5e..d72285e98 100644
--- a/src/components/TextInput/TextInputFlat.js
+++ b/src/components/TextInput/TextInputFlat.js
@@ -37,6 +37,7 @@ class TextInputFlat extends React.Component<ChildTextInputProps, {}> {
       error,
       selectionColor,
       underlineColor,
+      padding,
       style,
       theme,
       render,
@@ -51,8 +52,9 @@ class TextInputFlat extends React.Component<ChildTextInputProps, {}> {
     } = this.props;
 
     const { colors, fonts } = theme;
-    const fontFamily = fonts.regular;
+    const font = fonts.regular;
     const hasActiveOutline = parentState.focused || error;
+    const paddingOffset = padding !== 'none' ? styles.paddingOffset : null;
 
     let inputTextColor, activeColor, underlineColorCustom, placeholderColor;
 
@@ -91,7 +93,7 @@ class TextInputFlat extends React.Component<ChildTextInputProps, {}> {
       labelHalfWidth;
 
     const labelStyle = {
-      fontFamily,
+      ...font,
       fontSize: MAXIMIZED_LABEL_FONT_SIZE,
       transform: [
         {
@@ -133,7 +135,7 @@ class TextInputFlat extends React.Component<ChildTextInputProps, {}> {
                   RANDOM_VALUE_TO_CENTER_LABEL
                 : baseLabelTranslateX -
                   labelHalfWidth / LABEL_PADDING_HORIZONTAL +
-                  RANDOM_VALUE_TO_CENTER_LABEL,
+                  (paddingOffset ? RANDOM_VALUE_TO_CENTER_LABEL : 0),
               0,
             ],
           }),
@@ -182,6 +184,7 @@ class TextInputFlat extends React.Component<ChildTextInputProps, {}> {
                 styles.placeholder,
                 styles.placeholderFlat,
                 labelStyle,
+                paddingOffset,
                 {
                   color: activeColor,
                   opacity: parentState.labeled.interpolate({
@@ -199,6 +202,7 @@ class TextInputFlat extends React.Component<ChildTextInputProps, {}> {
                 styles.placeholder,
                 styles.placeholderFlat,
                 labelStyle,
+                paddingOffset,
                 {
                   color: placeholderColor,
                   opacity: hasActiveOutline ? parentState.labeled : 1,
@@ -234,9 +238,10 @@ class TextInputFlat extends React.Component<ChildTextInputProps, {}> {
               this.props.label
                 ? styles.inputFlatWithLabel
                 : styles.inputFlatWithoutLabel,
+              paddingOffset,
               {
                 color: inputTextColor,
-                fontFamily,
+                ...font,
                 textAlignVertical: multiline ? 'top' : 'center',
               },
             ],
@@ -254,7 +259,6 @@ const styles = StyleSheet.create({
     position: 'absolute',
     left: 0,
     fontSize: 16,
-    paddingHorizontal: LABEL_PADDING_HORIZONTAL,
   },
   placeholderFlat: {
     top: 19,
@@ -268,7 +272,6 @@ const styles = StyleSheet.create({
   },
   input: {
     flexGrow: 1,
-    paddingHorizontal: 12,
     fontSize: 16,
     margin: 0,
     minHeight: 58,
@@ -282,4 +285,7 @@ const styles = StyleSheet.create({
   inputFlatWithoutLabel: {
     paddingVertical: 15,
   },
+  paddingOffset: {
+    paddingHorizontal: LABEL_PADDING_HORIZONTAL,
+  },
 });
diff --git a/src/components/TextInput/TextInputOutlined.js b/src/components/TextInput/TextInputOutlined.js
index 43ed8295c..38a16a478 100644
--- a/src/components/TextInput/TextInputOutlined.js
+++ b/src/components/TextInput/TextInputOutlined.js
@@ -51,7 +51,7 @@ class TextInputOutlined extends React.Component<ChildTextInputProps, {}> {
     } = this.props;
 
     const { colors, fonts } = theme;
-    const fontFamily = fonts.regular;
+    const font = fonts.regular;
     const hasActiveOutline = parentState.focused || error;
     const { backgroundColor = colors.background } =
       StyleSheet.flatten(style) || {};
@@ -81,7 +81,7 @@ class TextInputOutlined extends React.Component<ChildTextInputProps, {}> {
       labelHalfWidth;
 
     const labelStyle = {
-      fontFamily,
+      ...font,
       fontSize: MAXIMIZED_LABEL_FONT_SIZE,
       transform: [
         {
@@ -161,7 +161,7 @@ class TextInputOutlined extends React.Component<ChildTextInputProps, {}> {
               styles.outlinedLabelBackground,
               {
                 backgroundColor,
-                fontFamily,
+                ...font,
                 fontSize: MINIMIZED_LABEL_FONT_SIZE,
                 // Hide the background when scale will be 0
                 // There's a bug in RN which makes scale: 0 act weird
@@ -262,7 +262,7 @@ class TextInputOutlined extends React.Component<ChildTextInputProps, {}> {
               styles.inputOutlined,
               {
                 color: inputTextColor,
-                fontFamily,
+                ...font,
                 textAlignVertical: multiline ? 'top' : 'center',
               },
             ],
diff --git a/typings/components/Card.d.ts b/typings/components/Card.d.ts
index 6ec68ee27..83582b113 100644
--- a/typings/components/Card.d.ts
+++ b/typings/components/Card.d.ts
@@ -22,7 +22,10 @@ export interface CardTitleProps extends ViewProps {
   subtitleStyle?: StyleProp<TextStyle>;
   style?: StyleProp<ViewStyle>;
   left?: (props: { size:number }) => React.ReactNode;
+  leftStyle?: StyleProp<ViewStyle>;
   right?: (props: { size:number }) => React.ReactNode;
+  rightStyle?: StyleProp<ViewStyle>;
+
 }
 
 export interface CardProps {
diff --git a/typings/components/HelperText.d.ts b/typings/components/HelperText.d.ts
index 08f8ec932..9237bad03 100644
--- a/typings/components/HelperText.d.ts
+++ b/typings/components/HelperText.d.ts
@@ -4,6 +4,7 @@ import { ThemeShape } from '../types';
 export interface HelperTextProps {
   type?: 'error' | 'info';
   visible?: boolean;
+  padding?: 'none'|'normal',
   children: React.ReactNode;
   style?: any;
   theme?: ThemeShape;
diff --git a/typings/components/TextInput.d.ts b/typings/components/TextInput.d.ts
index 5cca1e0e5..e9773b80d 100644
--- a/typings/components/TextInput.d.ts
+++ b/typings/components/TextInput.d.ts
@@ -10,6 +10,7 @@ export interface TextInputProps extends NativeTextInputProps {
   error?: boolean;
   onChangeText?: (text: string) => void;
   underlineColor?: string;
+  padding?: 'none'|'normal',
   multiline?: boolean;
   numberOfLines?: number;
   onFocus?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;


Test patch:
diff --git a/src/components/__tests__/RadioButton.test.js b/src/components/__tests__/RadioButton.test.js
new file mode 100644
index 000000000..cbf678c68
--- /dev/null
+++ b/src/components/__tests__/RadioButton.test.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { RadioButtonContext } from '../RadioButtonGroup';
+import RadioButton from '../RadioButton';
+
+describe('RadioButton', () => {
+  afterEach(() => jest.resetModules());
+
+  describe('on default platform', () => {
+    beforeAll(() => {
+      jest.doMock('Platform', () => ({ select: objs => objs.default }));
+    });
+
+    it('renders properly', () => {
+      const tree = renderer.create(<RadioButton value="first" />).toJSON();
+
+      expect(tree).toMatchSnapshot();
+    });
+  });
+
+  describe('on ios platform', () => {
+    beforeAll(() => {
+      jest.doMock('Platform', () => ({ select: objs => objs.ios }));
+    });
+
+    it('renders properly', () => {
+      const tree = renderer.create(<RadioButton value="first" />).toJSON();
+
+      expect(tree).toMatchSnapshot();
+    });
+  });
+
+  describe('when RadioButton is wrapped by RadioButtonContext.Provider', () => {
+    it('renders properly', () => {
+      const tree = renderer
+        .create(
+          <RadioButtonContext.Provider value="first" onValueChange={() => {}}>
+            <RadioButton value="first" />
+          </RadioButtonContext.Provider>
+        )
+        .toJSON();
+
+      expect(tree).toMatchSnapshot();
+    });
+  });
+});
diff --git a/src/components/__tests__/__snapshots__/Banner.test.js.snap b/src/components/__tests__/__snapshots__/Banner.test.js.snap
index cc8b6b918..a6bc36490 100644
--- a/src/components/__tests__/__snapshots__/Banner.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Banner.test.js.snap
@@ -197,13 +197,6 @@ exports[`renders visible banner, with action buttons and with image 1`] = `
               "elevation": 0,
               "margin": 4,
               "minWidth": "auto",
-              "shadowColor": "#000000",
-              "shadowOffset": Object {
-                "height": 0,
-                "width": 0,
-              },
-              "shadowOpacity": 0.24,
-              "shadowRadius": 0,
             }
           }
         >
@@ -370,13 +363,6 @@ exports[`renders visible banner, with action buttons and without image 1`] = `
               "elevation": 0,
               "margin": 4,
               "minWidth": "auto",
-              "shadowColor": "#000000",
-              "shadowOffset": Object {
-                "height": 0,
-                "width": 0,
-              },
-              "shadowOpacity": 0.24,
-              "shadowRadius": 0,
             }
           }
         >
@@ -463,13 +449,6 @@ exports[`renders visible banner, with action buttons and without image 1`] = `
               "elevation": 0,
               "margin": 4,
               "minWidth": "auto",
-              "shadowColor": "#000000",
-              "shadowOffset": Object {
-                "height": 0,
-                "width": 0,
-              },
-              "shadowOpacity": 0.24,
-              "shadowRadius": 0,
             }
           }
         >
diff --git a/src/components/__tests__/__snapshots__/Button.test.js.snap b/src/components/__tests__/__snapshots__/Button.test.js.snap
index f1346751e..a29be47e6 100644
--- a/src/components/__tests__/__snapshots__/Button.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Button.test.js.snap
@@ -11,13 +11,6 @@ exports[`renders button with color 1`] = `
       "borderWidth": 0,
       "elevation": 0,
       "minWidth": 64,
-      "shadowColor": "#000000",
-      "shadowOffset": Object {
-        "height": 0,
-        "width": 0,
-      },
-      "shadowOpacity": 0.24,
-      "shadowRadius": 0,
     }
   }
 >
@@ -104,13 +97,6 @@ exports[`renders button with icon 1`] = `
       "borderWidth": 0,
       "elevation": 0,
       "minWidth": 64,
-      "shadowColor": "#000000",
-      "shadowOffset": Object {
-        "height": 0,
-        "width": 0,
-      },
-      "shadowOpacity": 0.24,
-      "shadowRadius": 0,
     }
   }
 >
@@ -425,13 +411,6 @@ exports[`renders loading button 1`] = `
       "borderWidth": 0,
       "elevation": 0,
       "minWidth": 64,
-      "shadowColor": "#000000",
-      "shadowOffset": Object {
-        "height": 0,
-        "width": 0,
-      },
-      "shadowOpacity": 0.24,
-      "shadowRadius": 0,
     }
   }
 >
@@ -468,19 +447,189 @@ exports[`renders loading button 1`] = `
         ]
       }
     >
-      <ActivityIndicator
-        animating={true}
-        color="#6200ee"
-        hidesWhenStopped={true}
-        size="small"
+      <View
         style={
-          Object {
-            "marginLeft": 12,
-            "marginRight": -4,
-            "width": 16,
-          }
+          Array [
+            Object {
+              "alignItems": "center",
+              "justifyContent": "center",
+            },
+            Object {
+              "marginLeft": 12,
+              "marginRight": -4,
+              "width": 16,
+            },
+          ]
         }
-      />
+      >
+        <View
+          style={
+            Object {
+              "height": 16,
+              "opacity": 1,
+              "width": 16,
+            }
+          }
+        >
+          <View
+            style={
+              Object {
+                "alignItems": "center",
+                "bottom": 0,
+                "justifyContent": "center",
+                "left": 0,
+                "position": "absolute",
+                "right": 0,
+                "top": 0,
+              }
+            }
+          >
+            <View
+              style={
+                Object {
+                  "height": 16,
+                  "transform": Array [
+                    Object {
+                      "rotate": "45deg",
+                    },
+                  ],
+                  "width": 16,
+                }
+              }
+            >
+              <View
+                collapsable={false}
+                style={
+                  Object {
+                    "height": 8,
+                    "overflow": "hidden",
+                    "width": 16,
+                  }
+                }
+              >
+                <View
+                  style={
+                    Object {
+                      "height": 16,
+                      "transform": Array [
+                        Object {
+                          "translateY": 0,
+                        },
+                        Object {
+                          "rotate": "-165deg",
+                        },
+                      ],
+                      "width": 16,
+                    }
+                  }
+                >
+                  <View
+                    collapsable={false}
+                    style={
+                      Object {
+                        "height": 8,
+                        "overflow": "hidden",
+                        "width": 16,
+                      }
+                    }
+                  >
+                    <View
+                      style={
+                        Object {
+                          "borderColor": "#6200ee",
+                          "borderRadius": 8,
+                          "borderWidth": 1.6,
+                          "height": 16,
+                          "width": 16,
+                        }
+                      }
+                    />
+                  </View>
+                </View>
+              </View>
+            </View>
+          </View>
+          <View
+            style={
+              Object {
+                "alignItems": "center",
+                "bottom": 0,
+                "justifyContent": "center",
+                "left": 0,
+                "position": "absolute",
+                "right": 0,
+                "top": 0,
+              }
+            }
+          >
+            <View
+              style={
+                Object {
+                  "height": 16,
+                  "transform": Array [
+                    Object {
+                      "rotate": "45deg",
+                    },
+                  ],
+                  "width": 16,
+                }
+              }
+            >
+              <View
+                collapsable={false}
+                style={
+                  Object {
+                    "height": 8,
+                    "overflow": "hidden",
+                    "top": 8,
+                    "width": 16,
+                  }
+                }
+              >
+                <View
+                  style={
+                    Object {
+                      "height": 16,
+                      "transform": Array [
+                        Object {
+                          "translateY": -8,
+                        },
+                        Object {
+                          "rotate": "345deg",
+                        },
+                      ],
+                      "width": 16,
+                    }
+                  }
+                >
+                  <View
+                    collapsable={false}
+                    style={
+                      Object {
+                        "height": 8,
+                        "overflow": "hidden",
+                        "width": 16,
+                      }
+                    }
+                  >
+                    <View
+                      style={
+                        Object {
+                          "borderColor": "#6200ee",
+                          "borderRadius": 8,
+                          "borderWidth": 1.6,
+                          "height": 16,
+                          "width": 16,
+                        }
+                      }
+                    />
+                  </View>
+                </View>
+              </View>
+            </View>
+          </View>
+        </View>
+      </View>
       <Text
         numberOfLines={1}
         style={
@@ -531,13 +680,6 @@ exports[`renders outlined button with mode 1`] = `
       "borderWidth": 0.5,
       "elevation": 0,
       "minWidth": 64,
-      "shadowColor": "#000000",
-      "shadowOffset": Object {
-        "height": 0,
-        "width": 0,
-      },
-      "shadowOpacity": 0.24,
-      "shadowRadius": 0,
     }
   }
 >
@@ -624,13 +766,6 @@ exports[`renders text button by default 1`] = `
       "borderWidth": 0,
       "elevation": 0,
       "minWidth": 64,
-      "shadowColor": "#000000",
-      "shadowOffset": Object {
-        "height": 0,
-        "width": 0,
-      },
-      "shadowOpacity": 0.24,
-      "shadowRadius": 0,
     }
   }
 >
@@ -717,13 +852,6 @@ exports[`renders text button with mode 1`] = `
       "borderWidth": 0,
       "elevation": 0,
       "minWidth": 64,
-      "shadowColor": "#000000",
-      "shadowOffset": Object {
-        "height": 0,
-        "width": 0,
-      },
-      "shadowOpacity": 0.24,
-      "shadowRadius": 0,
     }
   }
 >
diff --git a/src/components/__tests__/__snapshots__/ListAccordion.test.js.snap b/src/components/__tests__/__snapshots__/ListAccordion.test.js.snap
index 3c0df9cc4..3fb2b43e5 100644
--- a/src/components/__tests__/__snapshots__/ListAccordion.test.js.snap
+++ b/src/components/__tests__/__snapshots__/ListAccordion.test.js.snap
@@ -233,7 +233,7 @@ exports[`renders list accordion with children 1`] = `
               "alignItems": "center",
               "height": 40,
               "justifyContent": "center",
-              "marginRight": 16,
+              "margin": 8,
               "width": 40,
             },
             undefined,
@@ -404,7 +404,7 @@ exports[`renders list accordion with left items 1`] = `
               "alignItems": "center",
               "height": 40,
               "justifyContent": "center",
-              "marginRight": 16,
+              "margin": 8,
               "width": 40,
             },
             undefined,
diff --git a/src/components/__tests__/__snapshots__/ListItem.test.js.snap b/src/components/__tests__/__snapshots__/ListItem.test.js.snap
index 538686a2f..296892037 100644
--- a/src/components/__tests__/__snapshots__/ListItem.test.js.snap
+++ b/src/components/__tests__/__snapshots__/ListItem.test.js.snap
@@ -209,11 +209,11 @@ exports[`renders list item with left and right items 1`] = `
             "alignItems": "center",
             "height": 40,
             "justifyContent": "center",
-            "marginRight": 16,
+            "margin": 8,
             "width": 40,
           },
           Object {
-            "marginVertical": 8,
+            "marginRight": 0,
           },
         ]
       }
@@ -294,10 +294,14 @@ exports[`renders list item with left item 1`] = `
             "alignItems": "center",
             "height": 40,
             "justifyContent": "center",
-            "marginRight": 16,
+            "margin": 8,
             "width": 40,
           },
-          null,
+          Object {
+            "marginLeft": 0,
+            "marginRight": 16,
+            "marginVertical": 0,
+          },
         ]
       }
     >
diff --git a/src/components/__tests__/__snapshots__/ListSection.test.js.snap b/src/components/__tests__/__snapshots__/ListSection.test.js.snap
index 2937b4b4b..9817ddbf0 100644
--- a/src/components/__tests__/__snapshots__/ListSection.test.js.snap
+++ b/src/components/__tests__/__snapshots__/ListSection.test.js.snap
@@ -114,10 +114,14 @@ exports[`renders list section with subheader 1`] = `
               "alignItems": "center",
               "height": 40,
               "justifyContent": "center",
-              "marginRight": 16,
+              "margin": 8,
               "width": 40,
             },
-            null,
+            Object {
+              "marginLeft": 0,
+              "marginRight": 16,
+              "marginVertical": 0,
+            },
           ]
         }
       >
@@ -235,10 +239,14 @@ exports[`renders list section with subheader 1`] = `
               "alignItems": "center",
               "height": 40,
               "justifyContent": "center",
-              "marginRight": 16,
+              "margin": 8,
               "width": 40,
             },
-            null,
+            Object {
+              "marginLeft": 0,
+              "marginRight": 16,
+              "marginVertical": 0,
+            },
           ]
         }
       >
@@ -409,10 +417,14 @@ exports[`renders list section without subheader 1`] = `
               "alignItems": "center",
               "height": 40,
               "justifyContent": "center",
-              "marginRight": 16,
+              "margin": 8,
               "width": 40,
             },
-            null,
+            Object {
+              "marginLeft": 0,
+              "marginRight": 16,
+              "marginVertical": 0,
+            },
           ]
         }
       >
@@ -530,10 +542,14 @@ exports[`renders list section without subheader 1`] = `
               "alignItems": "center",
               "height": 40,
               "justifyContent": "center",
-              "marginRight": 16,
+              "margin": 8,
               "width": 40,
             },
-            null,
+            Object {
+              "marginLeft": 0,
+              "marginRight": 16,
+              "marginVertical": 0,
+            },
           ]
         }
       >
diff --git a/src/components/__tests__/__snapshots__/Menu.test.js.snap b/src/components/__tests__/__snapshots__/Menu.test.js.snap
index 592fe3303..fe503b170 100644
--- a/src/components/__tests__/__snapshots__/Menu.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Menu.test.js.snap
@@ -14,13 +14,6 @@ exports[`renders not visible menu 1`] = `
         "borderWidth": 0.5,
         "elevation": 0,
         "minWidth": 64,
-        "shadowColor": "#000000",
-        "shadowOffset": Object {
-          "height": 0,
-          "width": 0,
-        },
-        "shadowOpacity": 0.24,
-        "shadowRadius": 0,
       }
     }
   >
@@ -111,13 +104,6 @@ exports[`renders visible menu 1`] = `
         "borderWidth": 0.5,
         "elevation": 0,
         "minWidth": 64,
-        "shadowColor": "#000000",
-        "shadowOffset": Object {
-          "height": 0,
-          "width": 0,
-        },
-        "shadowOpacity": 0.24,
-        "shadowRadius": 0,
       }
     }
   >
diff --git a/src/components/__tests__/__snapshots__/RadioButton.test.js.snap b/src/components/__tests__/__snapshots__/RadioButton.test.js.snap
new file mode 100644
index 000000000..65babe805
--- /dev/null
+++ b/src/components/__tests__/__snapshots__/RadioButton.test.js.snap
@@ -0,0 +1,174 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RadioButton on default platform renders properly 1`] = `
+<View
+  accessibilityRole="button"
+  accessible={true}
+  isTVSelectable={true}
+  onResponderGrant={[Function]}
+  onResponderMove={[Function]}
+  onResponderRelease={[Function]}
+  onResponderTerminate={[Function]}
+  onResponderTerminationRequest={[Function]}
+  onStartShouldSetResponder={[Function]}
+  style={
+    Array [
+      Object {
+        "overflow": "hidden",
+      },
+      Object {
+        "borderRadius": 18,
+      },
+    ]
+  }
+>
+  <View
+    style={
+      Object {
+        "borderColor": "rgba(0, 0, 0, 0.54)",
+        "borderRadius": 10,
+        "borderWidth": 2,
+        "height": 20,
+        "margin": 8,
+        "width": 20,
+      }
+    }
+  />
+</View>
+`;
+
+exports[`RadioButton on ios platform renders properly 1`] = `
+<View
+  accessibilityRole="button"
+  accessible={true}
+  isTVSelectable={true}
+  onResponderGrant={[Function]}
+  onResponderMove={[Function]}
+  onResponderRelease={[Function]}
+  onResponderTerminate={[Function]}
+  onResponderTerminationRequest={[Function]}
+  onStartShouldSetResponder={[Function]}
+  style={
+    Array [
+      Object {
+        "overflow": "hidden",
+      },
+      Object {
+        "borderRadius": 18,
+        "padding": 6,
+      },
+    ]
+  }
+>
+  <View
+    style={
+      Object {
+        "opacity": 0,
+      }
+    }
+  >
+    <Text
+      accessibilityElementsHidden={true}
+      allowFontScaling={false}
+      importantForAccessibility="no-hide-descendants"
+      pointerEvents="none"
+      style={
+        Array [
+          Object {
+            "color": "#03dac4",
+            "fontSize": 24,
+          },
+          Array [
+            Object {
+              "transform": Array [
+                Object {
+                  "scaleX": 1,
+                },
+              ],
+            },
+            Object {
+              "backgroundColor": "transparent",
+            },
+          ],
+          Object {
+            "fontFamily": "Material Icons",
+            "fontStyle": "normal",
+            "fontWeight": "normal",
+          },
+          Object {},
+        ]
+      }
+    >
+      
+    </Text>
+  </View>
+</View>
+`;
+
+exports[`RadioButton when RadioButton is wrapped by RadioButtonContext.Provider renders properly 1`] = `
+<View
+  accessibilityRole="button"
+  accessible={true}
+  isTVSelectable={true}
+  onResponderGrant={[Function]}
+  onResponderMove={[Function]}
+  onResponderRelease={[Function]}
+  onResponderTerminate={[Function]}
+  onResponderTerminationRequest={[Function]}
+  onStartShouldSetResponder={[Function]}
+  style={
+    Array [
+      Object {
+        "overflow": "hidden",
+      },
+      Object {
+        "borderRadius": 18,
+        "padding": 6,
+      },
+    ]
+  }
+>
+  <View
+    style={
+      Object {
+        "opacity": 0,
+      }
+    }
+  >
+    <Text
+      accessibilityElementsHidden={true}
+      allowFontScaling={false}
+      importantForAccessibility="no-hide-descendants"
+      pointerEvents="none"
+      style={
+        Array [
+          Object {
+            "color": "#03dac4",
+            "fontSize": 24,
+          },
+          Array [
+            Object {
+              "transform": Array [
+                Object {
+                  "scaleX": 1,
+                },
+              ],
+            },
+            Object {
+              "backgroundColor": "transparent",
+            },
+          ],
+          Object {
+            "fontFamily": "Material Icons",
+            "fontStyle": "normal",
+            "fontWeight": "normal",
+          },
+          Object {},
+        ]
+      }
+    >
+      
+    </Text>
+  </View>
+</View>
+`;
diff --git a/src/components/__tests__/__snapshots__/Snackbar.test.js.snap b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
index c64f15257..7777e89b8 100644
--- a/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
@@ -152,13 +152,6 @@ exports[`renders snackbar with action button 1`] = `
           "marginHorizontal": 8,
           "marginVertical": 6,
           "minWidth": "auto",
-          "shadowColor": "#000000",
-          "shadowOffset": Object {
-            "height": 0,
-            "width": 0,
-          },
-          "shadowOpacity": 0.24,
-          "shadowRadius": 0,
         }
       }
     >