Repository: callstack/react-native-paper (ts)

Issue:
FAB.Group actions accepts a IconSource, not only a string
## Environment

 `"react-native-paper": "2.15.0"` and Typescript

## Description

I was looking at the docs to see if i could use a custom icon source on the `FAB.Group` `actions`, but the type for the actions icon is specified as a string:
https://github.com/callstack/react-native-paper/blob/5935fb4c8010f884b407a91bef30a472dafe58a8/src/components/FAB/FABGroup.js#L32-L39
  
But the signature is wrong, looking at the `FAB.Group` source, the action list actually can receive a `IconSource` just like the normal `FAB`:
https://github.com/callstack/react-native-paper/blob/5935fb4c8010f884b407a91bef30a472dafe58a8/src/components/FAB/FABGroup.js#L283-L287

## Reproducible Demo

I think that just a simple component using typescript and a icon source on the actions will
give you the compile error:

```typescript

const MyComponent = () => <Portal>
                <FAB.Group
                    open={ true }
                    icon={ props => <Icon name='add' { ...props }/> }
                    actions={ [
                        {
                            icon: props => <Icon name='add' { ...props }/>,
                            label: 'Enviar',
                            onPress: () => console.log('Pressed add')
                        }
                    ] }
                    onStateChange={ () => {  } }
                    onPress={ () => {  } }
                />
            </Portal>
```

I managed to make my code compile making a cast (`as unknown as FABGroupAction[]`) on the list before using it on the component


Pull request:
Change actions icon type
Fix #1055 


Patch:
diff --git a/example/src/Examples/FABExample.js b/example/src/Examples/FABExample.js
index efeea873f..c667dff9c 100644
--- a/example/src/Examples/FABExample.js
+++ b/example/src/Examples/FABExample.js
@@ -65,6 +65,15 @@ class ButtonExample extends React.Component<Props, State> {
             visible={this.state.visible}
             disabled
           />
+
+          <FAB
+            icon="cancel"
+            label="Loading FAB"
+            style={styles.fab}
+            onPress={() => {}}
+            visible={this.state.visible}
+            loading
+          />
           <Portal>
             <FAB.Group
               open={this.state.open}
diff --git a/src/components/Button.js b/src/components/Button.js
index 6625fab5a..8b595c7e4 100644
--- a/src/components/Button.js
+++ b/src/components/Button.js
@@ -221,7 +221,8 @@ class Button extends React.Component<Props, State> {
     };
     const touchableStyle = { borderRadius: roundness };
     const textStyle = { color: textColor, fontFamily };
-    const elevation = disabled ? 0 : this.state.elevation;
+    const elevation =
+      disabled || mode !== 'contained' ? 0 : this.state.elevation;
 
     return (
       <Surface
diff --git a/src/components/Card/CardTitle.js b/src/components/Card/CardTitle.js
index 24701ebf9..0604845b2 100644
--- a/src/components/Card/CardTitle.js
+++ b/src/components/Card/CardTitle.js
@@ -6,10 +6,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,
   /**
@@ -17,7 +18,7 @@ type Props = React.ElementConfig<typeof View> & {|
    */
   titleStyle?: any,
   /**
-   * Text for the subtitle.
+   * Text for the subtitle. Note that this will only accept a string or `<Text>`-based node.
    */
   subtitle?: React.Node,
   /**
@@ -28,10 +29,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
    */
@@ -79,7 +88,9 @@ class CardTitle extends React.Component<Props> {
   render() {
     const {
       left,
+      leftStyle,
       right,
+      rightStyle,
       subtitle,
       subtitleStyle,
       style,
@@ -96,7 +107,7 @@ class CardTitle extends React.Component<Props> {
         ]}
       >
         {left ? (
-          <View style={[styles.left]}>
+          <View style={[styles.left, leftStyle]}>
             {left({
               size: LEFT_SIZE,
             })}
@@ -124,7 +135,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/FAB.js b/src/components/FAB/FAB.js
index 77730af4f..f328ce674 100644
--- a/src/components/FAB/FAB.js
+++ b/src/components/FAB/FAB.js
@@ -3,6 +3,7 @@
 import color from 'color';
 import * as React from 'react';
 import { Animated, View, StyleSheet } from 'react-native';
+import ActivityIndicator from '../ActivityIndicator';
 import FABGroup from './FABGroup';
 import Surface from '../Surface';
 import CrossFadeIcon from '../CrossFadeIcon';
@@ -43,6 +44,10 @@ type Props = $RemoveChildren<typeof Surface> & {|
    * Whether `FAB` is currently visible.
    */
   visible: boolean,
+  /**
+   * Whether to show a loading indicator.
+   */
+  loading?: boolean,
   /**
    * Function to execute on press.
    */
@@ -137,6 +142,7 @@ class FAB extends React.Component<Props, State> {
       theme,
       style,
       visible,
+      loading,
       ...rest
     } = this.props;
     const { visibility } = this.state;
@@ -207,7 +213,12 @@ class FAB extends React.Component<Props, State> {
             ]}
             pointerEvents="none"
           >
-            <CrossFadeIcon source={icon} size={24} color={foregroundColor} />
+            {icon && loading !== true ? (
+              <CrossFadeIcon source={icon} size={24} color={foregroundColor} />
+            ) : null}
+            {loading && label ? (
+              <ActivityIndicator size={18} color={foregroundColor} />
+            ) : null}
             {label ? (
               <Text
                 style={[
diff --git a/src/components/FAB/FABGroup.js b/src/components/FAB/FABGroup.js
index d3a98533c..48ef6f505 100644
--- a/src/components/FAB/FABGroup.js
+++ b/src/components/FAB/FABGroup.js
@@ -30,7 +30,7 @@ type Props = {|
    * - `onPress`: callback that is called when `FAB` is pressed (required)
    */
   actions: Array<{
-    icon: string,
+    icon: IconSource,
     label?: string,
     color?: string,
     accessibilityLabel?: string,
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/FAB.d.ts b/typings/components/FAB.d.ts
index c763b77e8..6dff77316 100644
--- a/typings/components/FAB.d.ts
+++ b/typings/components/FAB.d.ts
@@ -3,7 +3,7 @@ import { ViewProps } from 'react-native';
 import { ThemeShape, IconSource } from '../types';
 
 export interface FABGroupAction {
-  icon: string;
+  icon: IconSource;
   label?: string;
   color?: string;
   accessibilityLabel?: string;
@@ -33,6 +33,7 @@ export interface FABProps extends ViewProps {
   color?: string;
   disabled?: boolean;
   visible?: boolean;
+  loading?: boolean;
   onPress?: () => any;
   theme?: ThemeShape;
 }
diff --git a/typings/components/Modal.d.ts b/typings/components/Modal.d.ts
index b1f1564ff..d58048a20 100644
--- a/typings/components/Modal.d.ts
+++ b/typings/components/Modal.d.ts
@@ -1,4 +1,5 @@
 import * as React from 'react';
+import { StyleProp, ViewStyle } from 'react-native';
 import { ThemeShape } from '../types';
 
 export interface ModalProps {
@@ -6,6 +7,7 @@ export interface ModalProps {
   onDismiss?: () => any;
   visible: boolean;
   children: React.ReactNode;
+  contentContainerStyle?: StyleProp<ViewStyle>;
   theme?: ThemeShape;
 }
 


Test patch:
diff --git a/src/components/__tests__/__snapshots__/Banner.test.js.snap b/src/components/__tests__/__snapshots__/Banner.test.js.snap
index f1255747d..50efb250a 100644
--- a/src/components/__tests__/__snapshots__/Banner.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Banner.test.js.snap
@@ -195,13 +195,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,
             }
           }
         >
@@ -364,13 +357,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,
             }
           }
         >
@@ -454,13 +440,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 0f99c199f..7072a3874 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,
     }
   }
 >
@@ -101,13 +94,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,
     }
   }
 >
@@ -413,13 +399,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,
     }
   }
 >
@@ -516,13 +495,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,
     }
   }
 >
@@ -606,13 +578,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,
     }
   }
 >
@@ -696,13 +661,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__/Menu.test.js.snap b/src/components/__tests__/__snapshots__/Menu.test.js.snap
index 57226cbcd..0e298c723 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,
       }
     }
   >
@@ -108,13 +101,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__/Snackbar.test.js.snap b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
index 08ceece21..000431ed9 100644
--- a/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
+++ b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap
@@ -150,13 +150,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,
         }
       }
     >