From 85cb5bdb48d2d7ea9e20d580c98f849dff21acb3 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 6 May 2018 14:50:28 +0200 Subject: [PATCH] fix: fix visibility and touch target of action in Snackbar - Animate the Snackbar on layout instead of on mount because we need the layout for animation, this fixes snackbar not being visible on initial mount if visible=true. - Fix the touch target of the action button to be larger. Previously the touch target was as small as the action text and it was difficult to tap on it. --- src/components/Snackbar.js | 110 ++++---- .../__snapshots__/Snackbar.test.js.snap | 248 +++++++++++++----- 2 files changed, 237 insertions(+), 121 deletions(-) diff --git a/src/components/Snackbar.js b/src/components/Snackbar.js index d09141d..2500061 100644 --- a/src/components/Snackbar.js +++ b/src/components/Snackbar.js @@ -1,14 +1,9 @@ /* @flow */ import * as React from 'react'; -import { - StyleSheet, - Animated, - Text, - View, - TouchableWithoutFeedback, -} from 'react-native'; +import { StyleSheet, Animated } from 'react-native'; +import Text from './Typography/Text'; import ThemedPortal from './Portal/ThemedPortal'; import withTheme from '../core/withTheme'; import { white } from '../styles/colors'; @@ -48,8 +43,10 @@ type Props = { }; type State = { - rendered: boolean, - height: number, + layout: { + height: number, + measured: boolean, + }, opacity: Animated.Value, translateY: Animated.Value, }; @@ -129,25 +126,17 @@ class Snackbar extends React.Component { }; state = { - rendered: false, - height: 0, + layout: { + height: 0, + measured: false, + }, opacity: new Animated.Value(0), translateY: new Animated.Value(0), }; - componentDidMount() { - if (this.props.visible) { - this._show(); - } - } - componentDidUpdate(prevProps) { if (prevProps.visible !== this.props.visible) { - if (this.props.visible) { - this._show(); - } else { - this._hide(); - } + this._toggle(); } } @@ -159,16 +148,38 @@ class Snackbar extends React.Component { _handleLayout = e => { const { height } = e.nativeEvent.layout; + const { measured } = this.state.layout; - this.setState({ - height, - rendered: true, + this.setState({ layout: { height, measured: true } }, () => { + if (measured) { + if (!this.props.visible) { + // If height changed and Snackbar was hidden, adjust the translate to keep it hidden + this.state.translateY.setValue(height); + } + } else { + // Set the appropriate initial values if height was previously unknown + this.state.translateY.setValue(height); + this.state.opacity.setValue(0); + + // Perform the animation only if we're showing + if (this.props.visible) { + this._show(); + } + } }); + }; - this.state.translateY.setValue(height); + _toggle = () => { + if (this.props.visible) { + this._show(); + } else { + this._hide(); + } }; _show = () => { + clearTimeout(this._hideTimeout); + Animated.parallel([ Animated.timing(this.state.opacity, { toValue: 1, @@ -199,7 +210,7 @@ class Snackbar extends React.Component { useNativeDriver: true, }), Animated.timing(this.state.translateY, { - toValue: this.state.height, + toValue: this.state.layout.height, duration: SNACKBAR_ANIMATION_DURATION, useNativeDriver: true, }), @@ -210,9 +221,6 @@ class Snackbar extends React.Component { const { children, action, onDismiss, theme, style } = this.props; const { fonts, colors } = theme; - const buttonMargin = action ? 24 : 0; - const contentRightMargin = action ? 0 : 24; - return ( { style={[ styles.wrapper, { - opacity: this.state.rendered ? 1 : 0, + opacity: this.state.layout.measured ? 1 : 0, transform: [ { translateY: this.state.translateY, @@ -241,36 +249,22 @@ class Snackbar extends React.Component { }, ]} > - + {children} {action ? ( - { + action.onPress(); + onDismiss(); }} > - { - action.onPress(); - onDismiss(); - }} - > - - - {action.label.toUpperCase()} - - - - + {action.label.toUpperCase()} + ) : null} @@ -299,6 +293,10 @@ const styles = StyleSheet.create({ flexWrap: 'wrap', flex: 1, }, + button: { + paddingHorizontal: 24, + paddingVertical: 14, + }, }); export default withTheme(Snackbar); diff --git a/src/components/__tests__/__snapshots__/Snackbar.test.js.snap b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap index 570792c..853ea08 100644 --- a/src/components/__tests__/__snapshots__/Snackbar.test.js.snap +++ b/src/components/__tests__/__snapshots__/Snackbar.test.js.snap @@ -61,18 +61,45 @@ exports[`renders not visible snackbar with content 1`] = ` style={ Array [ Object { - "color": "#ffffff", - "flex": 1, - "flexWrap": "wrap", - "marginLeft": 24, - "marginVertical": 14, - }, - Object { + "color": "#000000", "fontFamily": "Helvetica Neue", - "marginRight": 24, }, + Array [ + Object { + "color": "#ffffff", + "flex": 1, + "flexWrap": "wrap", + "marginLeft": 24, + "marginVertical": 14, + }, + Object { + "marginRight": 24, + }, + ], ] } + theme={ + Object { + "colors": Object { + "accent": "#ff4081", + "background": "#fafafa", + "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", + "paper": "#ffffff", + "placeholder": "rgba(0, 0, 0, 0.38)", + "primary": "#3f51b5", + "text": "#000000", + }, + "dark": false, + "fonts": Object { + "light": "HelveticaNeue-Light", + "medium": "HelveticaNeue-Medium", + "regular": "Helvetica Neue", + "thin": "HelveticaNeue-Thin", + }, + "roundness": 2, + } + } > Snackbar content @@ -143,18 +170,45 @@ exports[`renders snackbar with Text as a child 1`] = ` style={ Array [ Object { - "color": "#ffffff", - "flex": 1, - "flexWrap": "wrap", - "marginLeft": 24, - "marginVertical": 14, - }, - Object { + "color": "#000000", "fontFamily": "Helvetica Neue", - "marginRight": 24, }, + Array [ + Object { + "color": "#ffffff", + "flex": 1, + "flexWrap": "wrap", + "marginLeft": 24, + "marginVertical": 14, + }, + Object { + "marginRight": 24, + }, + ], ] } + theme={ + Object { + "colors": Object { + "accent": "#ff4081", + "background": "#fafafa", + "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", + "paper": "#ffffff", + "placeholder": "rgba(0, 0, 0, 0.38)", + "primary": "#3f51b5", + "text": "#000000", + }, + "dark": false, + "fonts": Object { + "light": "HelveticaNeue-Light", + "medium": "HelveticaNeue-Medium", + "regular": "Helvetica Neue", + "thin": "HelveticaNeue-Thin", + }, + "roundness": 2, + } + } > Snackbar content - - - - UNDO - - - + UNDO + @@ -351,18 +442,45 @@ exports[`renders snackbar with content 1`] = ` style={ Array [ Object { - "color": "#ffffff", - "flex": 1, - "flexWrap": "wrap", - "marginLeft": 24, - "marginVertical": 14, - }, - Object { + "color": "#000000", "fontFamily": "Helvetica Neue", - "marginRight": 24, }, + Array [ + Object { + "color": "#ffffff", + "flex": 1, + "flexWrap": "wrap", + "marginLeft": 24, + "marginVertical": 14, + }, + Object { + "marginRight": 24, + }, + ], ] } + theme={ + Object { + "colors": Object { + "accent": "#ff4081", + "background": "#fafafa", + "disabled": "rgba(0, 0, 0, 0.26)", + "error": "#ff1744", + "paper": "#ffffff", + "placeholder": "rgba(0, 0, 0, 0.38)", + "primary": "#3f51b5", + "text": "#000000", + }, + "dark": false, + "fonts": Object { + "light": "HelveticaNeue-Light", + "medium": "HelveticaNeue-Medium", + "regular": "Helvetica Neue", + "thin": "HelveticaNeue-Thin", + }, + "roundness": 2, + } + } > Snackbar content