Repository: callstack/react-native-paper
Pull request #1176 (2019-07-04 07:20:01)

fix: consistent elevation behavior across platforms
### Motivation

This is the follow up for https://github.com/callstack/react-native-paper/pull/785 based on Typescript.

### Test plan

Open example app on web and press "Pressable Chameleon" card. Before this PR, the card elevation does not appear. After this PR, the card elevation does appear and is animated. See screenshots at https://github.com/callstack/react-native-paper/pull/785.

jaulz: @Trancever here we go. Unfortunately, I faced some issues on iOS and web during testing but both seem to be unrelated. On both platforms the example app did not work at all. I need to figure out what's going wrong in my environment but maybe you can already test the current changes.
callstack-bot: Hey @jaulz, thank you for your pull request 🤗. The documentation from this branch can be viewed [here](https://11441-71323749-gh.circle-artifacts.com/0/docs/index.html).

Patch:
diff --git a/example/src/ExampleList.tsx b/example/src/ExampleList.tsx
index c97589be9..5d161e956 100644
--- a/example/src/ExampleList.tsx
+++ b/example/src/ExampleList.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { FlatList } from 'react-native';
 import { List, Divider, withTheme, Theme } from 'react-native-paper';
diff --git a/example/src/Examples/CheckboxExample.tsx b/example/src/Examples/CheckboxExample.tsx
index afe677e5f..a12aa5222 100644
--- a/example/src/Examples/CheckboxExample.tsx
+++ b/example/src/Examples/CheckboxExample.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { View, StyleSheet } from 'react-native';
 import {
diff --git a/example/src/Examples/RadioButtonExample.tsx b/example/src/Examples/RadioButtonExample.tsx
index acd8c03ee..db00f5b23 100644
--- a/example/src/Examples/RadioButtonExample.tsx
+++ b/example/src/Examples/RadioButtonExample.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { View, StyleSheet } from 'react-native';
 import {
diff --git a/example/src/Examples/ToggleButtonExample.tsx b/example/src/Examples/ToggleButtonExample.tsx
index bee858021..38f443600 100644
--- a/example/src/Examples/ToggleButtonExample.tsx
+++ b/example/src/Examples/ToggleButtonExample.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { View, StyleSheet, ImageBackground } from 'react-native';
 import { withTheme, ToggleButton, List, Theme } from 'react-native-paper';
@@ -34,7 +32,7 @@ class ToggleButtonExample extends React.Component<Props, State> {
     return (
       <View style={[styles.container, { backgroundColor: background }]}>
         <List.Section title="Single">
-          <View style={styles.row}>
+          <View style={styles.padding}>
             <ToggleButton
               icon="android"
               value="android"
@@ -48,24 +46,23 @@ class ToggleButtonExample extends React.Component<Props, State> {
           </View>
         </List.Section>
         <List.Section title="Group">
-          <View style={styles.toggleGroup}>
-            <ToggleButton.Group
-              value={this.state.first}
-              onValueChange={(value: string) =>
-                this.setState({
-                  first: value,
-                })
-              }
-            >
-              <ToggleButton disabled icon="format-italic" value="italic" />
-              <ToggleButton icon="format-bold" value="bold" />
-              <ToggleButton icon="format-underlined" value="underlined" />
-              <ToggleButton icon="format-color-text" value="format-color" />
-            </ToggleButton.Group>
-          </View>
+          <ToggleButton.Row
+            value={this.state.first}
+            onValueChange={(value: string) =>
+              this.setState({
+                first: value,
+              })
+            }
+            style={styles.padding}
+          >
+            <ToggleButton disabled icon="format-italic" value="italic" />
+            <ToggleButton icon="format-bold" value="bold" />
+            <ToggleButton icon="format-underlined" value="underlined" />
+            <ToggleButton icon="format-color-text" value="format-color" />
+          </ToggleButton.Row>
         </List.Section>
         <List.Section title="Custom">
-          <View style={styles.group}>
+          <View style={[styles.padding, styles.row]}>
             <ToggleButton.Group
               value={this.state.fruit}
               onValueChange={(value: string) =>
@@ -138,18 +135,11 @@ const styles = StyleSheet.create({
   container: {
     flex: 1,
   },
-  toggleGroup: {
-    flexDirection: 'row',
+  padding: {
     paddingHorizontal: 16,
   },
-  group: {
-    flexDirection: 'row',
-    marginLeft: 5,
-  },
   row: {
     flexDirection: 'row',
-    flexWrap: 'wrap',
-    paddingHorizontal: 16,
   },
 });
 
diff --git a/example/webpack.config.js b/example/webpack.config.js
index d63d50e7f..171533efe 100644
--- a/example/webpack.config.js
+++ b/example/webpack.config.js
@@ -9,7 +9,7 @@ module.exports = {
   },
   devtool: 'source-map',
   resolve: {
-    extensions: ['.tsx', 'ts', '.js'],
+    extensions: ['.tsx', '.ts', '.js'],
   },
   module: {
     rules: [
diff --git a/src/components/Appbar/AppbarBackAction.tsx b/src/components/Appbar/AppbarBackAction.tsx
index f985e0e1e..55b40e82a 100644
--- a/src/components/Appbar/AppbarBackAction.tsx
+++ b/src/components/Appbar/AppbarBackAction.tsx
@@ -8,10 +8,10 @@ import {
   StyleProp,
   ViewStyle,
 } from 'react-native';
-
+import { $Omit } from './../../types';
 import AppbarAction from './AppbarAction';
 
-type Props = Omit<
+type Props = $Omit<
   React.ComponentProps<typeof AppbarAction> & {
     /**
      *  Custom color for back icon.
diff --git a/src/components/Appbar/AppbarHeader.tsx b/src/components/Appbar/AppbarHeader.tsx
index 8b8a18982..53aa6e4a8 100644
--- a/src/components/Appbar/AppbarHeader.tsx
+++ b/src/components/Appbar/AppbarHeader.tsx
@@ -34,7 +34,6 @@ type Props = React.ComponentProps<typeof Appbar> & {
    */
   theme: Theme;
   style?: StyleProp<ViewStyle>;
-  __expo?: any;
 };
 
 /**
diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx
index ee0b67c1d..99c6394cc 100644
--- a/src/components/Banner.tsx
+++ b/src/components/Banner.tsx
@@ -8,6 +8,7 @@ import { Theme, $RemoveChildren } from '../types';
 import shadow from '../styles/shadow';
 
 const ELEVATION = 1;
+const DEFAULT_MAX_WIDTH = 960;
 
 type Props = $RemoveChildren<typeof Surface> & {
   /**
@@ -35,6 +36,11 @@ type Props = $RemoveChildren<typeof Surface> & {
     label: string;
     onPress: () => void;
   }>;
+  /**
+   * Style of banner's inner content.
+   * Use this prop to apply custom width for wide layouts.
+   */
+  contentStyle?: StyleProp<ViewStyle>;
   style?: StyleProp<ViewStyle>;
   /**
    * @optional
@@ -159,6 +165,7 @@ class Banner extends React.Component<Props, State> {
       image,
       children,
       actions,
+      contentStyle,
       style,
       // eslint-disable-next-line @typescript-eslint/no-unused-vars
       theme,
@@ -185,7 +192,7 @@ class Banner extends React.Component<Props, State> {
         {...rest}
         style={[styles.container, shadow(ELEVATION) as ViewStyle, style]}
       >
-        <View style={styles.wrapper}>
+        <View style={[styles.wrapper, contentStyle]}>
           <Animated.View style={{ height }} />
           <Animated.View
             onLayout={this._handleLayout}
@@ -235,6 +242,9 @@ const styles = StyleSheet.create({
   },
   wrapper: {
     overflow: 'hidden',
+    alignSelf: 'center',
+    width: '100%',
+    maxWidth: DEFAULT_MAX_WIDTH,
   },
   absolute: {
     position: 'absolute',
diff --git a/src/components/HelperText.tsx b/src/components/HelperText.tsx
index b588e6f55..2a092b342 100644
--- a/src/components/HelperText.tsx
+++ b/src/components/HelperText.tsx
@@ -9,10 +9,10 @@ import {
 } from 'react-native';
 import AnimatedText from './Typography/AnimatedText';
 import { withTheme } from '../core/theming';
-import { Theme } from '../types';
+import { Theme, $Omit } from '../types';
 
-type Props = Omit<
-  Omit<React.ComponentProps<typeof Animated.Text>, 'padding'>,
+type Props = $Omit<
+  $Omit<React.ComponentProps<typeof Animated.Text>, 'padding'>,
   'type'
 > & {
   /**
diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx
index 75703749e..f1d0aef7b 100644
--- a/src/components/Menu/Menu.tsx
+++ b/src/components/Menu/Menu.tsx
@@ -16,7 +16,7 @@ import {
 } from 'react-native';
 
 import { withTheme } from '../../core/theming';
-import { Theme } from '../../types';
+import { Theme, $Omit } from '../../types';
 import Portal from '../Portal/Portal';
 import Surface from '../Surface';
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -54,7 +54,7 @@ type Props = {
   theme: Theme;
 };
 
-type Layout = Omit<Omit<LayoutRectangle, 'x'>, 'y'>;
+type Layout = $Omit<$Omit<LayoutRectangle, 'x'>, 'y'>;
 
 type State = {
   rendered: boolean;
diff --git a/src/components/Surface.tsx b/src/components/Surface.tsx
index 3e81c6a21..c530d1d7c 100644
--- a/src/components/Surface.tsx
+++ b/src/components/Surface.tsx
@@ -56,14 +56,14 @@ class Surface extends React.Component<Props> {
   render() {
     const { style, theme, ...rest } = this.props;
     const flattenedStyles = StyleSheet.flatten(style) || {};
-    const { elevation }: any = flattenedStyles;
+    const { elevation }: ViewStyle = flattenedStyles;
 
     return (
       <Animated.View
         {...rest}
         style={[
           { backgroundColor: theme.colors.surface },
-          elevation && shadow(elevation),
+          shadow(elevation),
           style,
         ]}
       />
diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx
index c746c2af8..9739567dc 100644
--- a/src/components/Switch.tsx
+++ b/src/components/Switch.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { grey400, grey800, grey50, white, black } from '../styles/colors';
 import {
diff --git a/src/components/TextInput/helpers.tsx b/src/components/TextInput/helpers.tsx
index 534139f3c..2bb876663 100644
--- a/src/components/TextInput/helpers.tsx
+++ b/src/components/TextInput/helpers.tsx
@@ -1,5 +1,3 @@
-// @flow
-
 type PaddingProps = {
   height: number | null;
   labelHalfHeight: number;
diff --git a/src/components/TextInput/types.tsx b/src/components/TextInput/types.tsx
index be470f018..03ed6576d 100644
--- a/src/components/TextInput/types.tsx
+++ b/src/components/TextInput/types.tsx
@@ -1,5 +1,6 @@
 import { TextInput as NativeTextInput, Animated } from 'react-native';
 import { TextInputProps } from './TextInput';
+import { $Omit } from './../../types';
 
 export type RenderProps = {
   ref: (a: NativeTextInput | null | undefined) => void;
@@ -17,7 +18,7 @@ export type RenderProps = {
   value?: string;
   adjustsFontSizeToFit?: boolean;
 };
-type TextInputTypesWithoutMode = Omit<TextInputProps, 'mode'>;
+type TextInputTypesWithoutMode = $Omit<TextInputProps, 'mode'>;
 export type State = {
   labeled: Animated.Value;
   error: Animated.Value;
diff --git a/src/components/ToggleButton/ToggleButton.tsx b/src/components/ToggleButton/ToggleButton.tsx
index f658c072a..baa71f13c 100644
--- a/src/components/ToggleButton/ToggleButton.tsx
+++ b/src/components/ToggleButton/ToggleButton.tsx
@@ -11,6 +11,7 @@ import IconButton from '../IconButton';
 import ToggleButtonGroup, {
   ToggleButtonGroupContext,
 } from './ToggleButtonGroup';
+import ToggleButtonRow from './ToggleButtonRow';
 import { black, white } from '../../styles/colors';
 import { IconSource } from '../Icon';
 import { Theme } from '../../types';
@@ -94,6 +95,9 @@ class ToggleButton extends React.Component<Props> {
   // @component ./ToggleButtonGroup.tsx
   static Group = ToggleButtonGroup;
 
+  // @component ./ToggleButtonRow.tsx
+  static Row = ToggleButtonRow;
+
   render() {
     const {
       icon,
diff --git a/src/components/ToggleButton/ToggleButtonGroup.tsx b/src/components/ToggleButton/ToggleButtonGroup.tsx
index 983b3363e..1dfc89fc4 100644
--- a/src/components/ToggleButton/ToggleButtonGroup.tsx
+++ b/src/components/ToggleButton/ToggleButtonGroup.tsx
@@ -1,6 +1,4 @@
 import * as React from 'react';
-import { StyleSheet } from 'react-native';
-import ToggleButton from './ToggleButton';
 
 type Props = {
   /**
@@ -27,7 +25,8 @@ export const ToggleButtonGroupContext = React.createContext<
 >(null as any);
 
 /**
- * Toggle group allows to control a group of toggle buttons.
+ * Toggle group allows to control a group of toggle buttons.</br>
+ * It doesn't change the appearance of the toggle buttons. If you want to group them in a row, checkout <a href="/toggle-button-row.html">`ToggleButton.Row`</a>.
  *
  * ## Usage
  * ```js
@@ -59,7 +58,6 @@ class ToggleButtonGroup extends React.Component<Props> {
 
   render() {
     const { value, onValueChange, children } = this.props;
-    const count = React.Children.count(children);
 
     return (
       <ToggleButtonGroupContext.Provider
@@ -68,51 +66,10 @@ class ToggleButtonGroup extends React.Component<Props> {
           onValueChange,
         }}
       >
-        {React.Children.map(children, (child, i) => {
-          // @ts-ignore
-          if (child && child.type === ToggleButton) {
-            // @ts-ignore
-            return React.cloneElement(child, {
-              style: [
-                styles.button,
-                i === 0
-                  ? styles.first
-                  : i === count - 1
-                  ? styles.last
-                  : styles.middle,
-                // @ts-ignore
-                child.props.style,
-              ],
-            });
-          }
-
-          return child;
-        })}
+        {children}
       </ToggleButtonGroupContext.Provider>
     );
   }
 }
 
-const styles = StyleSheet.create({
-  button: {
-    borderWidth: StyleSheet.hairlineWidth,
-  },
-
-  first: {
-    borderTopRightRadius: 0,
-    borderBottomRightRadius: 0,
-  },
-
-  middle: {
-    borderRadius: 0,
-    borderLeftWidth: 0,
-  },
-
-  last: {
-    borderLeftWidth: 0,
-    borderTopLeftRadius: 0,
-    borderBottomLeftRadius: 0,
-  },
-});
-
 export default ToggleButtonGroup;
diff --git a/src/components/ToggleButton/ToggleButtonRow.tsx b/src/components/ToggleButton/ToggleButtonRow.tsx
new file mode 100644
index 000000000..158f3ee60
--- /dev/null
+++ b/src/components/ToggleButton/ToggleButtonRow.tsx
@@ -0,0 +1,110 @@
+import * as React from 'react';
+import { StyleSheet, View, StyleProp, ViewStyle } from 'react-native';
+import ToggleButton from './ToggleButton';
+
+type Props = {
+  /**
+   * Function to execute on selection change.
+   */
+  onValueChange: (value: string) => void;
+  /**
+   * Value of the currently selected toggle button.
+   */
+  value: string;
+  /**
+   * React elements containing toggle buttons.
+   */
+  children: React.ReactNode;
+  style?: StyleProp<ViewStyle>;
+};
+
+/**
+ * Toggle button row renders a group of toggle buttons in a row.
+ *
+ * ## Usage
+ * ```js
+ * import * as React from 'react';
+ * import { View } from 'react-native';
+ * import { ToggleButton } from 'react-native-paper';
+ *
+ * export default class MyComponent extends React.Component {
+ *   state = {
+ *     value: 'left',
+ *   };
+ *
+ *   render() {
+ *     return(
+ *       <ToggleButton.Row
+ *         onValueChange={value => this.setState({ value })}
+ *         value={this.state.value}
+ *       >
+ *           <ToggleButton icon="format-align-left" value="left" />
+ *           <ToggleButton icon="format-align-right" value="right" />
+ *       </ToggleButton.Row>
+ *     )
+ *   }
+ * }
+ *```
+ */
+class ToggleButtonRow extends React.Component<Props> {
+  static displayName = 'ToggleButton.Row';
+
+  render() {
+    const { value, onValueChange, children, style } = this.props;
+    const count = React.Children.count(children);
+
+    return (
+      <ToggleButton.Group value={value} onValueChange={onValueChange}>
+        <View style={[styles.row, style]}>
+          {React.Children.map(children, (child, i) => {
+            // @ts-ignore
+            if (child && child.type === ToggleButton) {
+              // @ts-ignore
+              return React.cloneElement(child, {
+                style: [
+                  styles.button,
+                  i === 0
+                    ? styles.first
+                    : i === count - 1
+                    ? styles.last
+                    : styles.middle,
+                  // @ts-ignore
+                  child.props.style,
+                ],
+              });
+            }
+
+            return child;
+          })}
+        </View>
+      </ToggleButton.Group>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  row: {
+    flexDirection: 'row',
+  },
+  button: {
+    borderWidth: StyleSheet.hairlineWidth,
+  },
+
+  first: {
+    borderTopRightRadius: 0,
+    borderBottomRightRadius: 0,
+  },
+
+  middle: {
+    borderRadius: 0,
+    borderLeftWidth: 0,
+  },
+
+  last: {
+    borderLeftWidth: 0,
+    borderTopLeftRadius: 0,
+    borderBottomLeftRadius: 0,
+  },
+});
+
+export default ToggleButtonRow;
diff --git a/src/components/Typography/Caption.tsx b/src/components/Typography/Caption.tsx
index 1c3cbd367..843b85605 100644
--- a/src/components/Typography/Caption.tsx
+++ b/src/components/Typography/Caption.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { Text, TextStyle, StyleSheet, StyleProp } from 'react-native';
 import StyledText from './StyledText';
diff --git a/src/components/Typography/Headline.tsx b/src/components/Typography/Headline.tsx
index c02fe94eb..5cc51e710 100644
--- a/src/components/Typography/Headline.tsx
+++ b/src/components/Typography/Headline.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { Text, TextStyle, StyleSheet, StyleProp } from 'react-native';
 
diff --git a/src/components/Typography/StyledText.tsx b/src/components/Typography/StyledText.tsx
index 98d775895..434d36823 100644
--- a/src/components/Typography/StyledText.tsx
+++ b/src/components/Typography/StyledText.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import color from 'color';
 import * as React from 'react';
 import { I18nManager, StyleProp, TextStyle } from 'react-native';
diff --git a/src/components/Typography/Title.tsx b/src/components/Typography/Title.tsx
index d886d9c2b..a49a1727f 100644
--- a/src/components/Typography/Title.tsx
+++ b/src/components/Typography/Title.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 import { Text, StyleSheet } from 'react-native';
 import StyledText from './StyledText';
diff --git a/src/constants.ts b/src/constants.ts
index 28a38381a..29db1f17e 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,5 +1,3 @@
-/* @flow */
-
 import { Platform } from 'react-native';
 
 // @ts-ignore
diff --git a/src/index.tsx b/src/index.tsx
index 32c62781e..dcfdc8ea8 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -10,6 +10,7 @@ export { withTheme, ThemeProvider } from './core/theming';
 export { default as Provider } from './core/Provider';
 export { default as DefaultTheme } from './styles/DefaultTheme';
 export { default as DarkTheme } from './styles/DarkTheme';
+export { default as shadow } from './styles/shadow';
 
 import * as Avatar from './components/Avatar/Avatar';
 import * as List from './components/List/List';
diff --git a/src/styles/shadow.tsx b/src/styles/shadow.tsx
index f278a63d7..36a09d0f5 100644
--- a/src/styles/shadow.tsx
+++ b/src/styles/shadow.tsx
@@ -1,20 +1,34 @@
 import * as Colors from './colors';
 import { Animated } from 'react-native';
 
-export default function shadow(elevation: number | Animated.Value) {
-  let height, radius;
+const SHADOW_COLOR = Colors.black;
+const SHADOW_OPACITY = 0.24;
 
+export default function shadow(elevation: number | Animated.Value = 0) {
   if (elevation instanceof Animated.Value) {
-    height = elevation.interpolate({
-      inputRange: [0, 1, 2, 3, 8, 24],
-      outputRange: [0, 0.5, 0.75, 2, 7, 23],
-    });
+    const inputRange = [0, 1, 2, 3, 8, 24];
 
-    radius = elevation.interpolate({
-      inputRange: [0, 1, 2, 3, 8, 24],
-      outputRange: [0, 0.75, 1.5, 3, 8, 24],
-    });
+    return {
+      shadowColor: SHADOW_COLOR,
+      shadowOffset: {
+        width: new Animated.Value(0),
+        height: elevation.interpolate({
+          inputRange,
+          outputRange: [0, 0.5, 0.75, 2, 7, 23],
+        }),
+      },
+      shadowOpacity: new Animated.Value(SHADOW_OPACITY),
+      shadowRadius: elevation.interpolate({
+        inputRange,
+        outputRange: [0, 0.75, 1.5, 3, 8, 24],
+      }),
+    };
   } else {
+    if (elevation === 0) {
+      return {};
+    }
+
+    let height, radius;
     switch (elevation) {
       case 1:
         height = 0.5;
@@ -28,15 +42,15 @@ export default function shadow(elevation: number | Animated.Value) {
         height = elevation - 1;
         radius = elevation;
     }
-  }
 
-  return {
-    shadowColor: Colors.black,
-    shadowOffset: {
-      width: 0,
-      height,
-    },
-    shadowOpacity: 0.24,
-    shadowRadius: radius,
-  };
+    return {
+      shadowColor: SHADOW_COLOR,
+      shadowOffset: {
+        width: 0,
+        height,
+      },
+      shadowOpacity: SHADOW_OPACITY,
+      shadowRadius: radius,
+    };
+  }
 }
diff --git a/src/types.tsx b/src/types.tsx
index e8de00b35..9f2dc0901 100644
--- a/src/types.tsx
+++ b/src/types.tsx
@@ -1,5 +1,3 @@
-/* @flow */
-
 import * as React from 'react';
 
 export type Font = {
@@ -46,7 +44,8 @@ export type Theme = {
   };
 };
 
-export type $RemoveChildren<T extends React.ComponentType<any>> = Omit<
+export type $Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
+export type $RemoveChildren<T extends React.ComponentType<any>> = $Omit<
   React.ComponentProps<T>,
   'children'
 >;


Test patch:
diff --git a/src/components/__tests__/ListItem.test.js b/src/components/__tests__/ListItem.test.js
index 425158e2f..ab0c77a59 100644
--- a/src/components/__tests__/ListItem.test.js
+++ b/src/components/__tests__/ListItem.test.js
@@ -1,8 +1,9 @@
 import * as React from 'react';
 import renderer from 'react-test-renderer';
-import { Text } from 'react-native';
+import { Text, View } from 'react-native';
 import ListItem from '../List/ListItem.tsx';
 import ListIcon from '../List/ListIcon.tsx';
+import Chip from '../Chip';
 
 it('renders list item with title and description', () => {
   const tree = renderer
@@ -64,3 +65,32 @@ it('renders list item with custom title and description styles', () => {
 
   expect(tree).toMatchSnapshot();
 });
+
+it('renders list item with custom description', () => {
+  const tree = renderer
+    .create(
+      <ListItem
+        title="List Item with custom description"
+        description={({ ellipsizeMode, color: descriptionColor, fontSize }) => (
+          <View>
+            <Text
+              numberOfLines={2}
+              ellipsizeMode={ellipsizeMode}
+              style={{ color: descriptionColor, fontSize }}
+            >
+              React Native Paper is a high-quality, standard-compliant Design
+              Design library that has you covered in all major use-cases.
+            </Text>
+            <View>
+              <Chip icon="picture-as-pdf" onPress={() => {}}>
+                DOCS.pdf
+              </Chip>
+            </View>
+          </View>
+        )}
+      />
+    )
+    .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 a6bc36490..6be3f901b 100644
--- a/src/components/__tests__/__snapshots__/Banner.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Banner.test.js.snap
@@ -18,9 +18,15 @@ exports[`renders hidden banner, without action buttons and without image 1`] = `
 >
   <View
     style={
-      Object {
-        "overflow": "hidden",
-      }
+      Array [
+        Object {
+          "alignSelf": "center",
+          "maxWidth": 960,
+          "overflow": "hidden",
+          "width": "100%",
+        },
+        undefined,
+      ]
     }
   >
     <View
@@ -109,9 +115,15 @@ exports[`renders visible banner, with action buttons and with image 1`] = `
 >
   <View
     style={
-      Object {
-        "overflow": "hidden",
-      }
+      Array [
+        Object {
+          "alignSelf": "center",
+          "maxWidth": 960,
+          "overflow": "hidden",
+          "width": "100%",
+        },
+        undefined,
+      ]
     }
   >
     <View
@@ -296,9 +308,15 @@ exports[`renders visible banner, with action buttons and without image 1`] = `
 >
   <View
     style={
-      Object {
-        "overflow": "hidden",
-      }
+      Array [
+        Object {
+          "alignSelf": "center",
+          "maxWidth": 960,
+          "overflow": "hidden",
+          "width": "100%",
+        },
+        undefined,
+      ]
     }
   >
     <View
@@ -548,9 +566,15 @@ exports[`renders visible banner, without action buttons and with image 1`] = `
 >
   <View
     style={
-      Object {
-        "overflow": "hidden",
-      }
+      Array [
+        Object {
+          "alignSelf": "center",
+          "maxWidth": 960,
+          "overflow": "hidden",
+          "width": "100%",
+        },
+        undefined,
+      ]
     }
   >
     <View
@@ -648,9 +672,15 @@ exports[`renders visible banner, without action buttons and without image 1`] =
 >
   <View
     style={
-      Object {
-        "overflow": "hidden",
-      }
+      Array [
+        Object {
+          "alignSelf": "center",
+          "maxWidth": 960,
+          "overflow": "hidden",
+          "width": "100%",
+        },
+        undefined,
+      ]
     }
   >
     <View
diff --git a/src/components/__tests__/__snapshots__/ListItem.test.js.snap b/src/components/__tests__/__snapshots__/ListItem.test.js.snap
index 296892037..23093e960 100644
--- a/src/components/__tests__/__snapshots__/ListItem.test.js.snap
+++ b/src/components/__tests__/__snapshots__/ListItem.test.js.snap
@@ -1,5 +1,215 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`renders list item with custom description 1`] = `
+<View
+  accessible={true}
+  isTVSelectable={true}
+  onResponderGrant={[Function]}
+  onResponderMove={[Function]}
+  onResponderRelease={[Function]}
+  onResponderTerminate={[Function]}
+  onResponderTerminationRequest={[Function]}
+  onStartShouldSetResponder={[Function]}
+  style={
+    Array [
+      false,
+      Array [
+        Object {
+          "padding": 8,
+        },
+        undefined,
+      ],
+    ]
+  }
+>
+  <View
+    style={
+      Object {
+        "flexDirection": "row",
+      }
+    }
+  >
+    <View
+      pointerEvents="none"
+      style={
+        Array [
+          Object {
+            "marginVertical": 6,
+            "paddingLeft": 8,
+          },
+          Object {
+            "flex": 1,
+            "justifyContent": "center",
+          },
+        ]
+      }
+    >
+      <Text
+        numberOfLines={1}
+        style={
+          Array [
+            Object {
+              "color": "#000000",
+              "fontFamily": "System",
+              "fontWeight": "400",
+              "textAlign": "left",
+              "writingDirection": "ltr",
+            },
+            Array [
+              Object {
+                "fontSize": 16,
+              },
+              Object {
+                "color": "rgba(0, 0, 0, 0.87)",
+              },
+              undefined,
+            ],
+          ]
+        }
+      >
+        List Item with custom description
+      </Text>
+      <View>
+        <Text
+          numberOfLines={2}
+          style={
+            Object {
+              "color": "rgba(0, 0, 0, 0.54)",
+              "fontSize": 14,
+            }
+          }
+        >
+          React Native Paper is a high-quality, standard-compliant Design Design library that has you covered in all major use-cases.
+        </Text>
+        <View>
+          <View
+            style={
+              Object {
+                "backgroundColor": "#ebebeb",
+                "borderColor": "#ebebeb",
+                "borderRadius": 16,
+                "borderStyle": "solid",
+                "borderWidth": 0.5,
+                "elevation": 0,
+              }
+            }
+          >
+            <View
+              accessibilityRole="button"
+              accessibilityStates={Array []}
+              accessible={true}
+              isTVSelectable={true}
+              onResponderGrant={[Function]}
+              onResponderMove={[Function]}
+              onResponderRelease={[Function]}
+              onResponderTerminate={[Function]}
+              onResponderTerminationRequest={[Function]}
+              onStartShouldSetResponder={[Function]}
+              style={
+                Array [
+                  Object {
+                    "overflow": "hidden",
+                  },
+                  Object {
+                    "borderRadius": 16,
+                  },
+                ]
+              }
+            >
+              <View
+                style={
+                  Object {
+                    "alignItems": "center",
+                    "flexDirection": "row",
+                    "paddingHorizontal": 4,
+                  }
+                }
+              >
+                <View
+                  style={
+                    Array [
+                      Object {
+                        "padding": 4,
+                      },
+                      null,
+                    ]
+                  }
+                >
+                  <Text
+                    accessibilityElementsHidden={true}
+                    allowFontScaling={false}
+                    importantForAccessibility="no-hide-descendants"
+                    pointerEvents="none"
+                    style={
+                      Array [
+                        Object {
+                          "color": "rgba(0, 0, 0, 0.54)",
+                          "fontSize": 18,
+                        },
+                        Array [
+                          Object {
+                            "transform": Array [
+                              Object {
+                                "scaleX": 1,
+                              },
+                            ],
+                          },
+                          Object {
+                            "backgroundColor": "transparent",
+                          },
+                        ],
+                        Object {
+                          "fontFamily": "Material Icons",
+                          "fontStyle": "normal",
+                          "fontWeight": "normal",
+                        },
+                        Object {},
+                      ]
+                    }
+                  >
+                    
+                  </Text>
+                </View>
+                <Text
+                  numberOfLines={1}
+                  style={
+                    Array [
+                      Object {
+                        "color": "#000000",
+                        "fontFamily": "System",
+                        "fontWeight": "400",
+                        "textAlign": "left",
+                        "writingDirection": "ltr",
+                      },
+                      Array [
+                        Object {
+                          "lineHeight": 24,
+                          "marginVertical": 4,
+                          "minHeight": 24,
+                          "textAlignVertical": "center",
+                        },
+                        Object {
+                          "color": "rgba(0, 0, 0, 0.87)",
+                          "marginLeft": 4,
+                          "marginRight": 8,
+                        },
+                        undefined,
+                      ],
+                    ]
+                  }
+                >
+                  DOCS.pdf
+                </Text>
+              </View>
+            </View>
+          </View>
+        </View>
+      </View>
+    </View>
+  </View>
+</View>
+`;
+
 exports[`renders list item with custom title and description styles 1`] = `
 <View
   accessible={true}