From e1c795e7de2aa706b267598b77d9eeed95f9a6fe Mon Sep 17 00:00:00 2001 From: Luke Walczak Date: Thu, 24 May 2018 18:58:37 +0200 Subject: [PATCH] feat: add FABGroup component for FAB with speed dial (#218) Fixes #210 --- docs/assets/screenshots/fab-group.png | Bin 0 -> 20736 bytes example/src/FABExample.js | 40 ++- src/components/CrossFadeIcon.js | 143 ++++++++ src/components/FAB.js | 16 +- src/components/FABGroup.js | 310 ++++++++++++++++++ src/components/Icon.js | 21 +- .../__tests__/__snapshots__/FAB.test.js.snap | 243 +++++++++----- src/index.js | 1 + 8 files changed, 681 insertions(+), 93 deletions(-) create mode 100644 docs/assets/screenshots/fab-group.png create mode 100644 src/components/CrossFadeIcon.js create mode 100644 src/components/FABGroup.js diff --git a/docs/assets/screenshots/fab-group.png b/docs/assets/screenshots/fab-group.png new file mode 100644 index 0000000000000000000000000000000000000000..118de10d0f93c9d84ca60b66dfa8abcfeb027ace GIT binary patch literal 20736 zcmaI6Wmp?g*YBNR!L_)1ahIaSp+G6_?jA~U3r>r*P~3||8JHk|645owahb|Y_UG({X)~) z$O~DmCTQaHi>+k54iTbtaM-E=wFn{YFZ)JE6RaJcj=`!QWzq;Sc2*=uwpdtu$&~KQ z_W-d=%WqtF=OI(to$guO9eio*vm2(k28^pTfE6km--od^bpWt%{v$-D2suM|2tT&o z#j;S1HuO0c*>72-X$aWg+_t1LNFdcVzB%yMDuW>`csA^uaMMwuZk#Yuip|2L6DcE9G8yc^EB$iIomyk zC-$^SySSftgOwO0Lxa_z^M6t$u@xbo5*}xno-}Y1+hOX=XJV2lh!>s10W~ywPPPfj zeDFA%)I}4F0O)YPG!cj7WnH-K^wWmpM>7hVi$l8YM3@V~;?VeqsuMMo@PxmrxrF#u z$3PyE)owqfjNd+n;X5NLI&k2@A(!ncZL(tP3+HlqBLFEGGdJ*H%d~w|z?-RK{5xv+ zyqKTy8sPFY?K{wj;jKebU1|X<6ADL^N(vdimCF7X+ab54qs9TR)Ei4K4&Z^lRX4>~ z2Q7cQ`sN5CKtkcTjea*|9lJ?Jib&>#F`ok#SE?Epx;0v!cc2BpW%NPppj&K?!s zC1=BD@^ARjj`$=A{A>o+x@M<tL$UwigId=c`0)Toe)Iw!)FpI$ztt?n=ciT+T2q%a)Si75)Gg8$B|_?pk_6=WIJ3iEtwN~U0k`o*X}gaiLpFs? zZ5t%v;h#SqrWv*-I{pn~p5ipMGFf?W+*}LVG}-DA?fYDV9!0^|!6Q0@iP~mvYMPI@ z+cBTNBTRF9E4o+Qr6y;cu;WpRey{vap55UcAgqEZlpap}X_N13;?8SV1YtADEzjRn za�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 N002ovPDHLkV1hg&>>mIC literal 0 HcmV?d00001 diff --git a/example/src/FABExample.js b/example/src/FABExample.js index c94e125aef..5db8dfb608 100644 --- a/example/src/FABExample.js +++ b/example/src/FABExample.js @@ -2,17 +2,23 @@ import * as React from 'react'; import { View, StyleSheet } from 'react-native'; -import { Colors, FAB, withTheme } from 'react-native-paper'; +import { Colors, FAB, FABGroup, withTheme } from 'react-native-paper'; import type { Theme } from 'react-native-paper/types'; type Props = { theme: Theme, }; -class ButtonExample extends React.Component { +type State = { + open: boolean, +}; + +class ButtonExample extends React.Component { static title = 'Floating Action Button'; - _handlePress = () => {}; + state = { + open: false, + }; render() { const { @@ -20,21 +26,33 @@ class ButtonExample extends React.Component { colors: { background }, }, } = this.props; + return ( - - + {}} /> + {}} /> {}} + /> + {} }, + { icon: 'star', label: 'Star', onPress: () => {} }, + { icon: 'email', label: 'Email', onPress: () => {} }, + { icon: 'notifications', label: 'Remind', onPress: () => {} }, + ]} + onStateChange={({ open }) => this.setState({ open })} + onPress={() => { + if (this.state.open) { + // do something if the speed dial is open + } + }} /> diff --git a/src/components/CrossFadeIcon.js b/src/components/CrossFadeIcon.js new file mode 100644 index 0000000000..6037a39e45 --- /dev/null +++ b/src/components/CrossFadeIcon.js @@ -0,0 +1,143 @@ +/* @flow */ + +import * as React from 'react'; +import { Animated, StyleSheet, View } from 'react-native'; +import { polyfill } from 'react-lifecycles-compat'; +import Icon, { isValidIcon, isEqualIcon } from './Icon'; +import type { IconSource } from './Icon'; + +type Props = { + /** + * Icon to display for the `CrossFadeIcon`. + */ + source: IconSource, + /** + * Color of the icon. + */ + color: string, + /** + * Size of the icon. + */ + size: number, +}; + +type State = { + currentIcon: IconSource, + previousIcon: ?IconSource, + fade: Animated.Value, +}; + +class CrossFadeIcon extends React.Component { + static getDerivedStateFromProps(nextProps: Props, nextState: State) { + if (nextState.currentIcon === nextProps.source) { + return null; + } + + return { + currentIcon: nextProps.source, + previousIcon: nextState.currentIcon, + }; + } + + state = { + currentIcon: this.props.source, + previousIcon: null, + fade: new Animated.Value(1), + }; + + componentDidUpdate(prevProps: Props, prevState: State) { + const { previousIcon } = this.state; + + if ( + !isValidIcon(previousIcon) || + isEqualIcon(previousIcon, prevState.previousIcon) + ) { + return; + } + + this.state.fade.setValue(1); + + Animated.timing(this.state.fade, { + duration: 200, + toValue: 0, + }).start(); + } + + render() { + const { color, size } = this.props; + const opacityPrev = this.state.fade; + const opacityNext = this.state.previousIcon + ? this.state.fade.interpolate({ + inputRange: [0, 1], + outputRange: [1, 0], + }) + : 1; + + const rotatePrev = this.state.fade.interpolate({ + inputRange: [0, 1], + outputRange: ['-90deg', '0deg'], + }); + + const rotateNext = this.state.previousIcon + ? this.state.fade.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '-180deg'], + }) + : '0deg'; + + return ( + + {this.state.previousIcon ? ( + + + + ) : null} + + + + + ); + } +} + +polyfill(CrossFadeIcon); + +export default CrossFadeIcon; + +const styles = StyleSheet.create({ + content: { + alignItems: 'center', + justifyContent: 'center', + }, + icon: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, +}); diff --git a/src/components/FAB.js b/src/components/FAB.js index ed255d5077..fef345e195 100644 --- a/src/components/FAB.js +++ b/src/components/FAB.js @@ -2,9 +2,9 @@ import color from 'color'; import * as React from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet, View, Animated } from 'react-native'; import Paper from './Paper'; -import Icon from './Icon'; +import CrossFadeIcon from './CrossFadeIcon'; import Text from './Typography/Text'; import TouchableRipple from './TouchableRipple'; import { white } from '../styles/colors'; @@ -12,6 +12,8 @@ import withTheme from '../core/withTheme'; import type { Theme } from '../types'; import type { IconSource } from './Icon'; +const AnimatedPaper = Animated.createAnimatedComponent(Paper); + type Props = { /** * Icon to display for the `FAB`. @@ -80,7 +82,8 @@ class FAB extends React.Component { ...rest } = this.props; - const backgroundColor = theme.colors.accent; + const { backgroundColor = theme.colors.accent } = + StyleSheet.flatten(style) || {}; const isDark = typeof dark === 'boolean' ? dark : !color(backgroundColor).light(); const textColor = iconColor || (isDark ? white : 'rgba(0, 0, 0, .54)'); @@ -90,7 +93,7 @@ class FAB extends React.Component { .string(); return ( - @@ -105,8 +108,9 @@ class FAB extends React.Component { styles.content, label ? styles.extended : small ? styles.small : styles.standard, ]} + pointerEvents="none" > - + {label ? ( { ) : null} - + ); } } diff --git a/src/components/FABGroup.js b/src/components/FABGroup.js new file mode 100644 index 0000000000..177247966f --- /dev/null +++ b/src/components/FABGroup.js @@ -0,0 +1,310 @@ +/* @flow */ + +import * as React from 'react'; +import { + View, + StyleSheet, + Animated, + TouchableWithoutFeedback, + StatusBar, +} from 'react-native'; +import { polyfill } from 'react-lifecycles-compat'; +import color from 'color'; +import Text from './Typography/Text'; +import Card from './Card/Card'; +import ThemedPortal from './Portal/ThemedPortal'; +import FAB from './FAB'; +import withTheme from '../core/withTheme'; +import type { Theme } from '../types'; +import type { IconSource } from './Icon'; + +type Props = { + /** + * Action items to display in the form of a speed dial. + * An action item should contain the following properties: + * - `icon`: icon to display (required) + * - `label`: optional label text + * - `color`: custom icon color of the action item + * - `onPress`: callback that is called when `FAB` is pressed (required) + */ + actions: Array<{ + icon: string, + label?: string, + color?: string, + onPress: () => mixed, + }>, + /** + * Icon to display for the `FAB`. + * You can toggle it based on whether the speed dial is open to display a different icon. + */ + icon: IconSource, + /** + * Custom icon color for the `FAB`. + */ + color?: string, + /** + * Function to execute on pressing the `FAB`. + */ + onPress?: () => mixed, + /** + * Whether the speed dial is open. + */ + open: boolean, + /** + * Callback which is called on opening and closing the speed dial. + * The open state needs to be updated when it's called, otherwise the change is dropped. + */ + onStateChange: (state: { open: boolean }) => mixed, + /** + * @optional + */ + theme: Theme, +}; + +type State = { + backdrop: Animated.Value, + animations: Animated.Value[], +}; + +/** + * FABGroup displays a stack of FABs with related actions in a speed dial. + * + *
+ * + *
+ * + * ## Usage + * ```js + * import React from 'react'; + * import { FABGroup, StyleSheet } from 'react-native-paper'; + * + * export default class MyComponent extends React.Component { + * state = { + * open: false, + * }; + * + * render() { + * return ( + * {} }, + * { icon: 'star', label: 'Star', onPress: () => {} }, + * { icon: 'email', label: 'Email', onPress: () => {} }, + * { icon: 'notifications', label: 'Remind', onPress: () => {} }, + * ]} + * onStateChange={({ open }) => this.setState({ open })} + * onPress={() => { + * if (this.state.open) { + * // do something if the speed dial is open + * } + * }} + * /> + * ); + * } + * } + * ``` + */ +class FABGroup extends React.Component { + static getDerivedStateFromProps(nextProps, prevState) { + return { + animations: nextProps.actions.map( + (_, i) => + prevState.animations[i] || new Animated.Value(nextProps.open ? 1 : 0) + ), + }; + } + + state = { + backdrop: new Animated.Value(0), + animations: [], + }; + + componentDidUpdate(prevProps) { + if (this.props.open === prevProps.open) { + return; + } + + if (this.props.open) { + Animated.parallel([ + Animated.timing(this.state.backdrop, { + toValue: 1, + duration: 250, + useNativeDriver: true, + }), + Animated.stagger( + 50, + this.state.animations + .map(animation => + Animated.timing(animation, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }) + ) + .reverse() + ), + ]).start(); + } else { + Animated.parallel([ + Animated.timing(this.state.backdrop, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }), + ...this.state.animations.map(animation => + Animated.timing(animation, { + toValue: 0, + duration: 150, + useNativeDriver: true, + }) + ), + ]).start(); + } + } + + _toggleOpen = () => this.props.onStateChange({ open: !this.props.open }); + + render() { + const { actions, icon, open, onPress, theme } = this.props; + + const labelColor = theme.dark + ? theme.colors.text + : color(theme.colors.text) + .fade(0.54) + .rgb() + .string(); + const backdropOpacity = open + ? this.state.backdrop.interpolate({ + inputRange: [0, 0.5, 1], + // $FlowFixMe + outputRange: [0, 1, 1], + }) + : this.state.backdrop; + + const opacities = this.state.animations; + const scales = opacities.map( + opacity => + open + ? opacity.interpolate({ + inputRange: [0, 1], + // $FlowFixMe + outputRange: [0.8, 1], + }) + : 1 + ); + + return ( + + {open ? ( + + ) : null} + + + + + + {actions.map((it, i) => { + if (it.primary) { + return null; + } + return ( + + + {it.label && ( + + {it.label} + + )} + + + + ); + })} + + { + onPress && onPress(); + this._toggleOpen(); + }} + icon={icon} + color={this.props.color} + style={styles.fab} + /> + + + ); + } +} + +polyfill(FABGroup); + +export default withTheme(FABGroup); + +const styles = StyleSheet.create({ + container: { + ...StyleSheet.absoluteFillObject, + alignItems: 'flex-end', + justifyContent: 'flex-end', + }, + fab: { + marginHorizontal: 16, + marginBottom: 16, + marginTop: 0, + }, + backdrop: { + ...StyleSheet.absoluteFillObject, + }, + label: { + borderRadius: 5, + paddingHorizontal: 12, + paddingVertical: 6, + marginVertical: 8, + marginHorizontal: 16, + elevation: 2, + }, + item: { + marginHorizontal: 24, + marginBottom: 16, + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + }, +}); diff --git a/src/components/Icon.js b/src/components/Icon.js index b6622c90b5..18d013c6a8 100644 --- a/src/components/Icon.js +++ b/src/components/Icon.js @@ -44,7 +44,7 @@ type Props = IconProps & { name: IconSource, }; -const isImageSource = source => +const isImageSource = (source: any) => // source is an object with uri (typeof source === 'object' && source !== null && @@ -53,6 +53,25 @@ const isImageSource = source => // source is a module, e.g. - require('image') typeof source === 'number'; +const getIconId = (source: any) => { + if ( + typeof source === 'object' && + source !== null && + (Object.prototype.hasOwnProperty.call(source, 'uri') && + typeof source.uri === 'string') + ) { + return source.uri; + } + + return source; +}; + +export const isValidIcon = (source: any) => + typeof source === 'string' || isImageSource(source); + +export const isEqualIcon = (a: any, b: any) => + a === b || getIconId(a) === getIconId(b); + const Icon = ({ name, color, size, ...rest }: Props) => { if (typeof name === 'string') { return ( diff --git a/src/components/__tests__/__snapshots__/FAB.test.js.snap b/src/components/__tests__/__snapshots__/FAB.test.js.snap index 9c582e7897..69ab9f6407 100644 --- a/src/components/__tests__/__snapshots__/FAB.test.js.snap +++ b/src/components/__tests__/__snapshots__/FAB.test.js.snap @@ -2,6 +2,7 @@ exports[`renders extended FAB 1`] = ` @@ -57,6 +53,7 @@ exports[`renders extended FAB 1`] = ` tvParallaxProperties={undefined} > - -  - + + +  + + + @@ -203,6 +230,7 @@ exports[`renders normal FAB 1`] = ` tvParallaxProperties={undefined} > - -  - + + +  + + + @@ -248,6 +310,7 @@ exports[`renders normal FAB 1`] = ` exports[`renders small FAB 1`] = ` @@ -303,6 +361,7 @@ exports[`renders small FAB 1`] = ` tvParallaxProperties={undefined} > - -  - + + +  + + + diff --git a/src/index.js b/src/index.js index 3171854def..e58d9a8696 100644 --- a/src/index.js +++ b/src/index.js @@ -42,6 +42,7 @@ export { default as Searchbar } from './components/Searchbar'; export { default as SearchBar } from './components/Searchbar'; export { default as Snackbar } from './components/Snackbar'; export { default as Switch } from './components/Switch'; +export { default as FABGroup } from './components/FABGroup'; export { default as Toolbar } from './components/Toolbar/Toolbar'; export { default as ToolbarAction } from './components/Toolbar/ToolbarAction'; export {