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.
This commit is contained in:
Satyajit Sahoo
2018-05-06 14:50:28 +02:00
parent 720f9b235d
commit 85cb5bdb48
2 changed files with 237 additions and 121 deletions

View File

@@ -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<Props, State> {
};
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<Props, State> {
_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<Props, State> {
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<Props, State> {
const { children, action, onDismiss, theme, style } = this.props;
const { fonts, colors } = theme;
const buttonMargin = action ? 24 : 0;
const contentRightMargin = action ? 0 : 24;
return (
<ThemedPortal>
<Animated.View
@@ -220,7 +228,7 @@ class Snackbar extends React.Component<Props, State> {
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<Props, State> {
},
]}
>
<Text
style={[
styles.content,
{
fontFamily: fonts.regular,
marginRight: contentRightMargin,
},
]}
>
<Text style={[styles.content, { marginRight: action ? 0 : 24 }]}>
{children}
</Text>
{action ? (
<View
style={{
marginHorizontal: buttonMargin,
<Text
style={[
styles.button,
{ color: colors.accent, fontFamily: fonts.medium },
]}
onPress={() => {
action.onPress();
onDismiss();
}}
>
<TouchableWithoutFeedback
onPress={() => {
action.onPress();
onDismiss();
}}
>
<View>
<Text style={{ color: colors.accent }}>
{action.label.toUpperCase()}
</Text>
</View>
</TouchableWithoutFeedback>
</View>
{action.label.toUpperCase()}
</Text>
) : null}
</Animated.View>
</Animated.View>
@@ -299,6 +293,10 @@ const styles = StyleSheet.create({
flexWrap: 'wrap',
flex: 1,
},
button: {
paddingHorizontal: 24,
paddingVertical: 14,
},
});
export default withTheme(Snackbar);

View File

@@ -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
</Text>
@@ -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,
}
}
>
<Text
accessible={true}
@@ -231,59 +285,96 @@ exports[`renders snackbar with action button 1`] = `
style={
Array [
Object {
"color": "#ffffff",
"flex": 1,
"flexWrap": "wrap",
"marginLeft": 24,
"marginVertical": 14,
},
Object {
"color": "#000000",
"fontFamily": "Helvetica Neue",
"marginRight": 0,
},
Array [
Object {
"color": "#ffffff",
"flex": 1,
"flexWrap": "wrap",
"marginLeft": 24,
"marginVertical": 14,
},
Object {
"marginRight": 0,
},
],
]
}
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
</Text>
<View
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
onPress={[Function]}
style={
Array [
Object {
"color": "#000000",
"fontFamily": "Helvetica Neue",
},
Array [
Object {
"paddingHorizontal": 24,
"paddingVertical": 14,
},
Object {
"color": "#ff4081",
"fontFamily": "HelveticaNeue-Medium",
},
],
]
}
theme={
Object {
"marginHorizontal": 24,
"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,
}
}
>
<View
accessibilityComponentType={undefined}
accessibilityLabel={undefined}
accessibilityTraits={undefined}
accessible={true}
hitSlop={undefined}
nativeID={undefined}
onLayout={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={undefined}
testID={undefined}
>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
style={
Object {
"color": "#ff4081",
}
}
>
UNDO
</Text>
</View>
</View>
UNDO
</Text>
</View>
</View>
</View>
@@ -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
</Text>