Repository: callstack/react-native-paper
Pull request #743 (2019-01-21 17:18:32)

fix: fix elevation not animating in some components
On Android P, if a view with elevation is inside another view which animates the opacity, the elevation doesn't animate when the opacity changes.
This PR refactors components to animate opacity on the same views those have elevation to workaround this behaviour.

callstack-bot: Hey @satya164, thank you for your pull request 🤗. The documentation from this branch can be viewed [here](https://6965-71323749-gh.circle-artifacts.com/0/docs/index.html). Please remember to update Typescript types if you changed API.

Patch:
diff --git a/src/components/Chip.js b/src/components/Chip.js
index 653315cc3..7ad502d58 100644
--- a/src/components/Chip.js
+++ b/src/components/Chip.js
@@ -168,8 +168,10 @@ class Chip extends React.Component<Props, State> {
           .alpha(0.54)
           .rgb()
           .string();
-    const selectedBackgroundColor = color(dark ? white : black)
-      .alpha(mode === 'outlined' ? 0.12 : 0.24)
+    const selectedBackgroundColor = (dark
+      ? color(backgroundColor).lighten(mode === 'outlined' ? 0.2 : 0.4)
+      : color(backgroundColor).darken(mode === 'outlined' ? 0.08 : 0.2)
+    )
       .rgb()
       .string();
 
diff --git a/src/components/Dialog/Dialog.js b/src/components/Dialog/Dialog.js
index 4710f1b06..d52a0eb9d 100644
--- a/src/components/Dialog/Dialog.js
+++ b/src/components/Dialog/Dialog.js
@@ -3,7 +3,6 @@
 import * as React from 'react';
 import { StyleSheet, Platform } from 'react-native';
 import Modal from '../Modal';
-import Surface from '../Surface';
 import DialogContent from './DialogContent';
 import DialogActions from './DialogActions';
 import DialogTitle from './DialogTitle';
@@ -108,27 +107,35 @@ class Dialog extends React.Component<Props, void> {
     } = this.props;
 
     return (
-      <Modal dismissable={dismissable} onDismiss={onDismiss} visible={visible}>
-        <Surface
-          style={[styles.container, { borderRadius: theme.roundness }, style]}
-        >
-          {React.Children.toArray(children)
-            .filter(child => child != null && typeof child !== 'boolean')
-            .map((child, i) => {
-              if (
-                i === 0 &&
-                React.isValidElement(child) &&
-                child.type === DialogContent
-              ) {
-                // Dialog content is the first item, so we add a top padding
-                return React.cloneElement(child, {
-                  style: [{ paddingTop: 24 }, child.props.style],
-                });
-              }
+      <Modal
+        dismissable={dismissable}
+        onDismiss={onDismiss}
+        visible={visible}
+        contentContainerStyle={[
+          {
+            borderRadius: theme.roundness,
+            backgroundColor: theme.colors.surface,
+          },
+          styles.container,
+          style,
+        ]}
+      >
+        {React.Children.toArray(children)
+          .filter(child => child != null && typeof child !== 'boolean')
+          .map((child, i) => {
+            if (
+              i === 0 &&
+              React.isValidElement(child) &&
+              child.type === DialogContent
+            ) {
+              // Dialog content is the first item, so we add a top padding
+              return React.cloneElement(child, {
+                style: [{ paddingTop: 24 }, child.props.style],
+              });
+            }
 
-              return child;
-            })}
-        </Surface>
+            return child;
+          })}
       </Modal>
     );
   }
@@ -146,6 +153,7 @@ const styles = StyleSheet.create({
     marginVertical: Platform.OS === 'android' ? 44 : 0,
     marginHorizontal: 26,
     elevation: 24,
+    justifyContent: 'flex-start',
   },
 });
 
diff --git a/src/components/FAB/FABGroup.js b/src/components/FAB/FABGroup.js
index b35526836..c18231283 100644
--- a/src/components/FAB/FABGroup.js
+++ b/src/components/FAB/FABGroup.js
@@ -243,64 +243,62 @@ class FABGroup extends React.Component<Props, State> {
         </TouchableWithoutFeedback>
         <View pointerEvents={open ? 'box-none' : 'none'}>
           {actions.map((it, i) => (
-            <Animated.View
+            <View
               key={i} // eslint-disable-line react/no-array-index-key
-              style={[
-                {
-                  opacity: opacities[i],
-                },
-              ]}
+              style={styles.item}
               pointerEvents="box-none"
             >
-              <View style={styles.item} pointerEvents="box-none">
-                {it.label && (
-                  <Card
-                    style={[
-                      styles.label,
-                      { transform: [{ scale: scales[i] }] },
-                    ]}
-                    onPress={() => {
-                      it.onPress();
-                      this._close();
-                    }}
-                    accessibilityLabel={
-                      it.accessibilityLabel !== 'undefined'
-                        ? it.accessibilityLabel
-                        : it.label
-                    }
-                    accessibilityTraits="button"
-                    accessibilityComponentType="button"
-                    accessibilityRole="button"
-                  >
-                    <Text style={{ color: labelColor }}>{it.label}</Text>
-                  </Card>
-                )}
-                <FAB
-                  small
-                  icon={it.icon}
-                  color={it.color}
+              {it.label && (
+                <Card
                   style={[
+                    styles.label,
                     {
                       transform: [{ scale: scales[i] }],
-                      backgroundColor: theme.colors.surface,
+                      opacity: opacities[i],
                     },
-                    it.style,
                   ]}
                   onPress={() => {
                     it.onPress();
                     this._close();
                   }}
                   accessibilityLabel={
-                    typeof it.accessibilityLabel !== 'undefined'
+                    it.accessibilityLabel !== 'undefined'
                       ? it.accessibilityLabel
                       : it.label
                   }
                   accessibilityTraits="button"
                   accessibilityComponentType="button"
                   accessibilityRole="button"
-                />
-              </View>
-            </Animated.View>
+                >
+                  <Text style={{ color: labelColor }}>{it.label}</Text>
+                </Card>
+              )}
+              <FAB
+                small
+                icon={it.icon}
+                color={it.color}
+                style={[
+                  {
+                    transform: [{ scale: scales[i] }],
+                    opacity: opacities[i],
+                    backgroundColor: theme.colors.surface,
+                  },
+                  it.style,
+                ]}
+                onPress={() => {
+                  it.onPress();
+                  this._close();
+                }}
+                accessibilityLabel={
+                  typeof it.accessibilityLabel !== 'undefined'
+                    ? it.accessibilityLabel
+                    : it.label
+                }
+                accessibilityTraits="button"
+                accessibilityComponentType="button"
+                accessibilityRole="button"
+              />
+            </View>
           ))}
         </View>
         <FAB
diff --git a/src/components/Modal.js b/src/components/Modal.js
index 3bcd5bb68..fd47a294d 100644
--- a/src/components/Modal.js
+++ b/src/components/Modal.js
@@ -10,6 +10,7 @@ import {
   BackHandler,
 } from 'react-native';
 import { polyfill } from 'react-lifecycles-compat';
+import Surface from './Surface';
 import { withTheme } from '../core/theming';
 import type { Theme } from '../types';
 
@@ -30,6 +31,10 @@ type Props = {|
    * Content of the `Modal`.
    */
   children: React.Node,
+  /**
+   * Style for the content of the modal
+   */
+  contentContainerStyle?: any,
   /**
    * @optional
    */
@@ -142,23 +147,35 @@ class Modal extends React.Component<Props, State> {
   render() {
     if (!this.state.rendered) return null;
 
-    const { children, dismissable, theme } = this.props;
+    const { children, dismissable, theme, contentContainerStyle } = this.props;
     const { colors } = theme;
     return (
       <Animated.View
         accessibilityViewIsModal
         accessibilityLiveRegion="polite"
-        style={[{ opacity: this.state.opacity }, StyleSheet.absoluteFill]}
+        style={StyleSheet.absoluteFill}
       >
         <TouchableWithoutFeedback
           onPress={dismissable ? this._hideModal : undefined}
         >
-          <View
-            style={[styles.backdrop, { backgroundColor: colors.backdrop }]}
+          <Animated.View
+            style={[
+              styles.backdrop,
+              { backgroundColor: colors.backdrop, opacity: this.state.opacity },
+            ]}
           />
         </TouchableWithoutFeedback>
-        <View pointerEvents="box-none" style={styles.content}>
-          {children}
+        <View style={styles.wrapper}>
+          <Surface
+            pointerEvents="box-none"
+            style={[
+              { opacity: this.state.opacity },
+              styles.content,
+              contentContainerStyle,
+            ]}
+          >
+            {children}
+          </Surface>
         </View>
       </Animated.View>
     );
@@ -173,8 +190,12 @@ const styles = StyleSheet.create({
   backdrop: {
     flex: 1,
   },
-  content: {
+  wrapper: {
     ...StyleSheet.absoluteFillObject,
     justifyContent: 'center',
   },
+  content: {
+    backgroundColor: 'transparent',
+    justifyContent: 'center',
+  },
 });
diff --git a/src/components/Snackbar.js b/src/components/Snackbar.js
index 6ca51b673..85bded913 100644
--- a/src/components/Snackbar.js
+++ b/src/components/Snackbar.js
@@ -1,9 +1,10 @@
 /* @flow */
 
 import * as React from 'react';
-import { StyleSheet, Animated, View, SafeAreaView } from 'react-native';
+import { StyleSheet, Animated, SafeAreaView } from 'react-native';
 
 import Button from './Button';
+import Surface from './Surface';
 import Text from './Typography/Text';
 import { withTheme } from '../core/theming';
 import { white } from '../styles/colors';
@@ -182,50 +183,53 @@ class Snackbar extends React.Component<Props, State> {
   render() {
     const { children, visible, action, onDismiss, theme, style } = this.props;
     const { colors, roundness } = theme;
+
+    if (this.state.hidden) {
+      return null;
+    }
+
     return (
       <SafeAreaView pointerEvents="box-none" style={styles.wrapper}>
-        <Animated.View
+        <Surface
           pointerEvents="box-none"
           accessibilityLiveRegion="polite"
-          style={{
-            opacity: this.state.opacity,
-            transform: [
-              {
-                scale: visible
-                  ? this.state.opacity.interpolate({
-                      inputRange: [0, 1],
-                      outputRange: [0.9, 1],
-                    })
-                  : 1,
-              },
-            ],
-          }}
+          style={[
+            styles.container,
+            {
+              orderRadius: roundness,
+              opacity: this.state.opacity,
+              transform: [
+                {
+                  scale: visible
+                    ? this.state.opacity.interpolate({
+                        inputRange: [0, 1],
+                        outputRange: [0.9, 1],
+                      })
+                    : 1,
+                },
+              ],
+            },
+            style,
+          ]}
         >
-          {!this.state.hidden ? (
-            <View
-              pointerEvents="box-none"
-              style={[styles.container, { borderRadius: roundness }, style]}
+          <Text style={[styles.content, { marginRight: action ? 0 : 16 }]}>
+            {children}
+          </Text>
+          {action ? (
+            <Button
+              onPress={() => {
+                action.onPress();
+                onDismiss();
+              }}
+              style={styles.button}
+              color={colors.accent}
+              compact
+              mode="text"
             >
-              <Text style={[styles.content, { marginRight: action ? 0 : 16 }]}>
-                {children}
-              </Text>
-              {action ? (
-                <Button
-                  onPress={() => {
-                    action.onPress();
-                    onDismiss();
-                  }}
-                  style={styles.button}
-                  color={colors.accent}
-                  compact
-                  mode="text"
-                >
-                  {action.label.toUpperCase()}
-                </Button>
-              ) : null}
-            </View>
+              {action.label.toUpperCase()}
+            </Button>
           ) : null}
-        </Animated.View>
+        </Surface>
       </SafeAreaView>
     );
   }
diff --git a/src/components/TouchableRipple.js b/src/components/TouchableRipple.js
index fad338f4f..7b68d882d 100644
--- a/src/components/TouchableRipple.js
+++ b/src/components/TouchableRipple.js
@@ -86,6 +86,9 @@ class TouchableRipple extends React.Component<Props, void> {
   static supported =
     Platform.OS === 'android' &&
     Platform.Version >= ANDROID_VERSION_LOLLIPOP &&
+    // Ripple effect doesn't work properly with border radius on Android P
+    // So we disable it temporarily
+    // https://github.com/facebook/react-native/issues/6480
     Platform.Version <= ANDROID_VERSION_NOUGAT;
 
   render() {


Test patch:
diff --git a/src/components/__tests__/__snapshots__/Chip.test.js.snap b/src/components/__tests__/__snapshots__/Chip.test.js.snap
index 74b03d0fa..ccac02083 100644
--- a/src/components/__tests__/__snapshots__/Chip.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Chip.test.js.snap
@@ -437,7 +437,7 @@ exports[`renders selected chip 1`] = `
 <View
   style={
     Object {
-      "backgroundColor": "rgba(0, 0, 0, 0.24)",
+      "backgroundColor": "rgb(188, 188, 188)",
       "borderColor": "#ebebeb",
       "borderRadius": 16,
       "borderStyle": "solid",
diff --git a/src/components/__tests__/__snapshots__/Snackbar.test.js.snap b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
index e095f95e5..a25e7c07e 100644
--- a/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
@@ -1,32 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`renders not visible snackbar with content wrapper but no actual content 1`] = `
-<RCTSafeAreaView
-  pointerEvents="box-none"
-  style={
-    Object {
-      "bottom": 0,
-      "position": "absolute",
-      "width": "100%",
-    }
-  }
->
-  <View
-    accessibilityLiveRegion="polite"
-    pointerEvents="box-none"
-    style={
-      Object {
-        "opacity": 0,
-        "transform": Array [
-          Object {
-            "scale": 1,
-          },
-        ],
-      }
-    }
-  />
-</RCTSafeAreaView>
-`;
+exports[`renders not visible snackbar with content wrapper but no actual content 1`] = `null`;
 
 exports[`renders snackbar with Text as a child 1`] = `
 <RCTSafeAreaView
@@ -44,7 +18,22 @@ exports[`renders snackbar with Text as a child 1`] = `
     pointerEvents="box-none"
     style={
       Object {
+        "alignItems": "center",
+        "backgroundColor": "#323232",
+        "borderRadius": 4,
+        "elevation": 6,
+        "flexDirection": "row",
+        "justifyContent": "space-between",
+        "margin": 8,
         "opacity": 1,
+        "orderRadius": 4,
+        "shadowColor": "#000000",
+        "shadowOffset": Object {
+          "height": 5,
+          "width": 0,
+        },
+        "shadowOpacity": 0.24,
+        "shadowRadius": 6,
         "transform": Array [
           Object {
             "scale": 1,
@@ -53,55 +42,34 @@ exports[`renders snackbar with Text as a child 1`] = `
       }
     }
   >
-    <View
-      pointerEvents="box-none"
+    <Text
       style={
         Array [
           Object {
-            "alignItems": "center",
-            "backgroundColor": "#323232",
-            "borderRadius": 4,
-            "elevation": 6,
-            "flexDirection": "row",
-            "justifyContent": "space-between",
-            "margin": 8,
-          },
-          Object {
-            "borderRadius": 4,
+            "color": "#000000",
+            "fontFamily": "Helvetica Neue",
+            "textAlign": "left",
+            "writingDirection": "ltr",
           },
-          undefined,
-        ]
-      }
-    >
-      <Text
-        style={
           Array [
             Object {
-              "color": "#000000",
-              "fontFamily": "Helvetica Neue",
-              "textAlign": "left",
-              "writingDirection": "ltr",
+              "color": "#ffffff",
+              "flex": 1,
+              "flexWrap": "wrap",
+              "marginLeft": 16,
+              "marginVertical": 14,
             },
-            Array [
-              Object {
-                "color": "#ffffff",
-                "flex": 1,
-                "flexWrap": "wrap",
-                "marginLeft": 16,
-                "marginVertical": 14,
-              },
-              Object {
-                "marginRight": 16,
-              },
-            ],
-          ]
-        }
-      >
-        <Text>
-          Snackbar content
-        </Text>
+            Object {
+              "marginRight": 16,
+            },
+          ],
+        ]
+      }
+    >
+      <Text>
+        Snackbar content
       </Text>
-    </View>
+    </Text>
   </View>
 </RCTSafeAreaView>
 `;
@@ -122,7 +90,22 @@ exports[`renders snackbar with action button 1`] = `
     pointerEvents="box-none"
     style={
       Object {
+        "alignItems": "center",
+        "backgroundColor": "#323232",
+        "borderRadius": 4,
+        "elevation": 6,
+        "flexDirection": "row",
+        "justifyContent": "space-between",
+        "margin": 8,
         "opacity": 1,
+        "orderRadius": 4,
+        "shadowColor": "#000000",
+        "shadowOffset": Object {
+          "height": 5,
+          "width": 0,
+        },
+        "shadowOpacity": 0.24,
+        "shadowRadius": 6,
         "transform": Array [
           Object {
             "scale": 1,
@@ -131,133 +114,112 @@ exports[`renders snackbar with action button 1`] = `
       }
     }
   >
-    <View
-      pointerEvents="box-none"
+    <Text
       style={
         Array [
           Object {
-            "alignItems": "center",
-            "backgroundColor": "#323232",
-            "borderRadius": 4,
-            "elevation": 6,
-            "flexDirection": "row",
-            "justifyContent": "space-between",
-            "margin": 8,
-          },
-          Object {
-            "borderRadius": 4,
+            "color": "#000000",
+            "fontFamily": "Helvetica Neue",
+            "textAlign": "left",
+            "writingDirection": "ltr",
           },
-          undefined,
-        ]
-      }
-    >
-      <Text
-        style={
           Array [
             Object {
-              "color": "#000000",
-              "fontFamily": "Helvetica Neue",
-              "textAlign": "left",
-              "writingDirection": "ltr",
+              "color": "#ffffff",
+              "flex": 1,
+              "flexWrap": "wrap",
+              "marginLeft": 16,
+              "marginVertical": 14,
+            },
+            Object {
+              "marginRight": 0,
             },
-            Array [
-              Object {
-                "color": "#ffffff",
-                "flex": 1,
-                "flexWrap": "wrap",
-                "marginLeft": 16,
-                "marginVertical": 14,
-              },
-              Object {
-                "marginRight": 0,
-              },
-            ],
-          ]
+          ],
+        ]
+      }
+    >
+      Snackbar content
+    </Text>
+    <View
+      style={
+        Object {
+          "backgroundColor": "transparent",
+          "borderColor": "transparent",
+          "borderRadius": 4,
+          "borderStyle": "solid",
+          "borderWidth": 0,
+          "elevation": 0,
+          "marginHorizontal": 8,
+          "marginVertical": 6,
+          "minWidth": "auto",
+          "shadowColor": "#000000",
+          "shadowOffset": Object {
+            "height": NaN,
+            "width": 0,
+          },
+          "shadowOpacity": 0.24,
+          "shadowRadius": 0,
         }
-      >
-        Snackbar content
-      </Text>
+      }
+    >
       <View
+        accessibilityRole="button"
+        accessible={true}
+        isTVSelectable={true}
+        onResponderGrant={[Function]}
+        onResponderMove={[Function]}
+        onResponderRelease={[Function]}
+        onResponderTerminate={[Function]}
+        onResponderTerminationRequest={[Function]}
+        onStartShouldSetResponder={[Function]}
         style={
           Object {
-            "backgroundColor": "transparent",
-            "borderColor": "transparent",
             "borderRadius": 4,
-            "borderStyle": "solid",
-            "borderWidth": 0,
-            "elevation": 0,
-            "marginHorizontal": 8,
-            "marginVertical": 6,
-            "minWidth": "auto",
-            "shadowColor": "#000000",
-            "shadowOffset": Object {
-              "height": NaN,
-              "width": 0,
-            },
-            "shadowOpacity": 0.24,
-            "shadowRadius": 0,
           }
         }
       >
         <View
-          accessibilityRole="button"
-          accessible={true}
-          isTVSelectable={true}
-          onResponderGrant={[Function]}
-          onResponderMove={[Function]}
-          onResponderRelease={[Function]}
-          onResponderTerminate={[Function]}
-          onResponderTerminationRequest={[Function]}
-          onStartShouldSetResponder={[Function]}
           style={
             Object {
-              "borderRadius": 4,
+              "alignItems": "center",
+              "flexDirection": "row",
+              "justifyContent": "center",
             }
           }
         >
-          <View
+          <Text
+            numberOfLines={1}
             style={
-              Object {
-                "alignItems": "center",
-                "flexDirection": "row",
-                "justifyContent": "center",
-              }
-            }
-          >
-            <Text
-              numberOfLines={1}
-              style={
+              Array [
+                Object {
+                  "color": "#000000",
+                  "fontFamily": "Helvetica Neue",
+                  "textAlign": "left",
+                  "writingDirection": "ltr",
+                },
                 Array [
                   Object {
-                    "color": "#000000",
-                    "fontFamily": "Helvetica Neue",
-                    "textAlign": "left",
-                    "writingDirection": "ltr",
+                    "letterSpacing": 1,
+                    "marginHorizontal": 16,
+                    "marginVertical": 9,
+                    "textAlign": "center",
+                  },
+                  Object {
+                    "marginHorizontal": 8,
+                  },
+                  Object {
+                    "color": "#03dac4",
+                    "fontFamily": "HelveticaNeue-Medium",
                   },
-                  Array [
-                    Object {
-                      "letterSpacing": 1,
-                      "marginHorizontal": 16,
-                      "marginVertical": 9,
-                      "textAlign": "center",
-                    },
-                    Object {
-                      "marginHorizontal": 8,
-                    },
-                    Object {
-                      "color": "#03dac4",
-                      "fontFamily": "HelveticaNeue-Medium",
-                    },
-                    Object {
-                      "fontFamily": "HelveticaNeue-Medium",
-                    },
-                  ],
-                ]
-              }
-            >
-              UNDO
-            </Text>
-          </View>
+                  Object {
+                    "fontFamily": "HelveticaNeue-Medium",
+                  },
+                ],
+              ]
+            }
+          >
+            UNDO
+          </Text>
         </View>
       </View>
     </View>
@@ -281,7 +243,22 @@ exports[`renders snackbar with content 1`] = `
     pointerEvents="box-none"
     style={
       Object {
+        "alignItems": "center",
+        "backgroundColor": "#323232",
+        "borderRadius": 4,
+        "elevation": 6,
+        "flexDirection": "row",
+        "justifyContent": "space-between",
+        "margin": 8,
         "opacity": 1,
+        "orderRadius": 4,
+        "shadowColor": "#000000",
+        "shadowOffset": Object {
+          "height": 5,
+          "width": 0,
+        },
+        "shadowOpacity": 0.24,
+        "shadowRadius": 6,
         "transform": Array [
           Object {
             "scale": 1,
@@ -290,53 +267,32 @@ exports[`renders snackbar with content 1`] = `
       }
     }
   >
-    <View
-      pointerEvents="box-none"
+    <Text
       style={
         Array [
           Object {
-            "alignItems": "center",
-            "backgroundColor": "#323232",
-            "borderRadius": 4,
-            "elevation": 6,
-            "flexDirection": "row",
-            "justifyContent": "space-between",
-            "margin": 8,
-          },
-          Object {
-            "borderRadius": 4,
+            "color": "#000000",
+            "fontFamily": "Helvetica Neue",
+            "textAlign": "left",
+            "writingDirection": "ltr",
           },
-          undefined,
-        ]
-      }
-    >
-      <Text
-        style={
           Array [
             Object {
-              "color": "#000000",
-              "fontFamily": "Helvetica Neue",
-              "textAlign": "left",
-              "writingDirection": "ltr",
+              "color": "#ffffff",
+              "flex": 1,
+              "flexWrap": "wrap",
+              "marginLeft": 16,
+              "marginVertical": 14,
             },
-            Array [
-              Object {
-                "color": "#ffffff",
-                "flex": 1,
-                "flexWrap": "wrap",
-                "marginLeft": 16,
-                "marginVertical": 14,
-              },
-              Object {
-                "marginRight": 16,
-              },
-            ],
-          ]
-        }
-      >
-        Snackbar content
-      </Text>
-    </View>
+            Object {
+              "marginRight": 16,
+            },
+          ],
+        ]
+      }
+    >
+      Snackbar content
+    </Text>
   </View>
 </RCTSafeAreaView>
 `;