Skip to content

Commit 85cb5bd

Browse files
committed
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.
1 parent 720f9b2 commit 85cb5bd

File tree

2 files changed

+237
-121
lines changed

2 files changed

+237
-121
lines changed

src/components/Snackbar.js

+54-56
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
/* @flow */
22

33
import * as React from 'react';
4-
import {
5-
StyleSheet,
6-
Animated,
7-
Text,
8-
View,
9-
TouchableWithoutFeedback,
10-
} from 'react-native';
4+
import { StyleSheet, Animated } from 'react-native';
115

6+
import Text from './Typography/Text';
127
import ThemedPortal from './Portal/ThemedPortal';
138
import withTheme from '../core/withTheme';
149
import { white } from '../styles/colors';
@@ -48,8 +43,10 @@ type Props = {
4843
};
4944

5045
type State = {
51-
rendered: boolean,
52-
height: number,
46+
layout: {
47+
height: number,
48+
measured: boolean,
49+
},
5350
opacity: Animated.Value,
5451
translateY: Animated.Value,
5552
};
@@ -129,25 +126,17 @@ class Snackbar extends React.Component<Props, State> {
129126
};
130127

131128
state = {
132-
rendered: false,
133-
height: 0,
129+
layout: {
130+
height: 0,
131+
measured: false,
132+
},
134133
opacity: new Animated.Value(0),
135134
translateY: new Animated.Value(0),
136135
};
137136

138-
componentDidMount() {
139-
if (this.props.visible) {
140-
this._show();
141-
}
142-
}
143-
144137
componentDidUpdate(prevProps) {
145138
if (prevProps.visible !== this.props.visible) {
146-
if (this.props.visible) {
147-
this._show();
148-
} else {
149-
this._hide();
150-
}
139+
this._toggle();
151140
}
152141
}
153142

@@ -159,16 +148,38 @@ class Snackbar extends React.Component<Props, State> {
159148

160149
_handleLayout = e => {
161150
const { height } = e.nativeEvent.layout;
151+
const { measured } = this.state.layout;
162152

163-
this.setState({
164-
height,
165-
rendered: true,
153+
this.setState({ layout: { height, measured: true } }, () => {
154+
if (measured) {
155+
if (!this.props.visible) {
156+
// If height changed and Snackbar was hidden, adjust the translate to keep it hidden
157+
this.state.translateY.setValue(height);
158+
}
159+
} else {
160+
// Set the appropriate initial values if height was previously unknown
161+
this.state.translateY.setValue(height);
162+
this.state.opacity.setValue(0);
163+
164+
// Perform the animation only if we're showing
165+
if (this.props.visible) {
166+
this._show();
167+
}
168+
}
166169
});
170+
};
167171

168-
this.state.translateY.setValue(height);
172+
_toggle = () => {
173+
if (this.props.visible) {
174+
this._show();
175+
} else {
176+
this._hide();
177+
}
169178
};
170179

171180
_show = () => {
181+
clearTimeout(this._hideTimeout);
182+
172183
Animated.parallel([
173184
Animated.timing(this.state.opacity, {
174185
toValue: 1,
@@ -199,7 +210,7 @@ class Snackbar extends React.Component<Props, State> {
199210
useNativeDriver: true,
200211
}),
201212
Animated.timing(this.state.translateY, {
202-
toValue: this.state.height,
213+
toValue: this.state.layout.height,
203214
duration: SNACKBAR_ANIMATION_DURATION,
204215
useNativeDriver: true,
205216
}),
@@ -210,17 +221,14 @@ class Snackbar extends React.Component<Props, State> {
210221
const { children, action, onDismiss, theme, style } = this.props;
211222
const { fonts, colors } = theme;
212223

213-
const buttonMargin = action ? 24 : 0;
214-
const contentRightMargin = action ? 0 : 24;
215-
216224
return (
217225
<ThemedPortal>
218226
<Animated.View
219227
onLayout={this._handleLayout}
220228
style={[
221229
styles.wrapper,
222230
{
223-
opacity: this.state.rendered ? 1 : 0,
231+
opacity: this.state.layout.measured ? 1 : 0,
224232
transform: [
225233
{
226234
translateY: this.state.translateY,
@@ -241,36 +249,22 @@ class Snackbar extends React.Component<Props, State> {
241249
},
242250
]}
243251
>
244-
<Text
245-
style={[
246-
styles.content,
247-
{
248-
fontFamily: fonts.regular,
249-
marginRight: contentRightMargin,
250-
},
251-
]}
252-
>
252+
<Text style={[styles.content, { marginRight: action ? 0 : 24 }]}>
253253
{children}
254254
</Text>
255255
{action ? (
256-
<View
257-
style={{
258-
marginHorizontal: buttonMargin,
256+
<Text
257+
style={[
258+
styles.button,
259+
{ color: colors.accent, fontFamily: fonts.medium },
260+
]}
261+
onPress={() => {
262+
action.onPress();
263+
onDismiss();
259264
}}
260265
>
261-
<TouchableWithoutFeedback
262-
onPress={() => {
263-
action.onPress();
264-
onDismiss();
265-
}}
266-
>
267-
<View>
268-
<Text style={{ color: colors.accent }}>
269-
{action.label.toUpperCase()}
270-
</Text>
271-
</View>
272-
</TouchableWithoutFeedback>
273-
</View>
266+
{action.label.toUpperCase()}
267+
</Text>
274268
) : null}
275269
</Animated.View>
276270
</Animated.View>
@@ -299,6 +293,10 @@ const styles = StyleSheet.create({
299293
flexWrap: 'wrap',
300294
flex: 1,
301295
},
296+
button: {
297+
paddingHorizontal: 24,
298+
paddingVertical: 14,
299+
},
302300
});
303301

304302
export default withTheme(Snackbar);

0 commit comments

Comments
 (0)