Skip to content

Commit

Permalink
Improving UX: replace videos and links with SnackPlayers, update exam…
Browse files Browse the repository at this point in the history
…ples
  • Loading branch information
Simek committed Jul 29, 2022
1 parent da4bfe8 commit ca43ddb
Show file tree
Hide file tree
Showing 2 changed files with 672 additions and 16 deletions.
344 changes: 336 additions & 8 deletions docs/improvingux.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,361 @@ Entering text on touch phone is a challenge - small screen, software keyboard. B

Check out [`TextInput` docs](textinput.md) for more configuration options.

<video src="/img/textinput.mp4" muted autoplay loop width="320" height="430"></video>
```SnackPlayer name=TextInput%20form%20example
import React, { useState, useRef } from 'react';
import { Text, StatusBar, TextInput, View, StyleSheet } from 'react-native';
import { Constants } from 'expo';
[Try it on your phone](https://snack.expo.io/H1iGt2vSW)
const App = () => {
const emailInput = useRef(null);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const submit = () => {
alert(`Welcome, ${name}! Confirmation email has been sent to ${email}`);
};
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<Text style={styles.description}>
This demo shows how using available TextInput customizations can make
forms much easier to use. Try completing the form and notice that
different fields have specific optimizations and the return key
changes from focusing next input to submitting the form.
</Text>
</View>
<TextInput
style={styles.input}
value={name}
onChangeText={(name) => setName(name)}
placeholder="Full Name"
autoFocus={true}
autoCapitalize="words"
autoCorrect={true}
keyboardType="default"
returnKeyType="next"
onSubmitEditing={() => emailInput.current.focus()}
blurOnSubmit={false}
/>
<TextInput
style={styles.input}
value={email}
onChangeText={(email) => setEmail(email)}
ref={emailInput}
placeholder="email@example.com"
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
returnKeyType="send"
onSubmitEditing={submit}
blurOnSubmit={true}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingTop: 64,
padding: 20,
backgroundColor: '#282c34',
},
description: {
fontSize: 14,
color: 'white',
},
input: {
margin: 20,
marginBottom: 0,
height: 34,
paddingHorizontal: 10,
borderRadius: 4,
borderColor: '#ccc',
borderWidth: 1,
fontSize: 16,
},
});
export default App;
```

## Manage layout when keyboard is visible

Software keyboard takes almost half of the screen. If you have interactive elements that can get covered by the keyboard, make sure they are still accessible by using the [`KeyboardAvoidingView` component](keyboardavoidingview.md).

<video src="/img/keyboardavoidingview.mp4" muted autoplay loop width="320" height="448"></video>
```SnackPlayer name=KeyboardAvoidingView%20example
import React, { useState, useRef } from 'react';
import {
Text,
Button,
StatusBar,
TextInput,
KeyboardAvoidingView,
View,
StyleSheet,
} from 'react-native';
const App = () => {
const emailInput = useRef(null);
const [email, setEmail] = useState('');
const submit = () => {
emailInput.current.blur();
alert(`Confirmation email has been sent to ${email}`);
};
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<Text style={styles.description}>
This demo shows how to avoid covering important UI elements with the
software keyboard. Focus the email input below and notice that the
Sign Up button and the text adjusted positions to make sure they are
not hidden under the keyboard.
</Text>
</View>
<KeyboardAvoidingView behavior="padding" style={styles.form}>
<TextInput
style={styles.input}
value={email}
onChangeText={(email) => setEmail(email)}
ref={emailInput}
placeholder="email@example.com"
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
returnKeyType="send"
onSubmitEditing={submit}
blurOnSubmit={true}
/>
<View>
<Button title="Sign Up" onPress={submit} />
<Text style={styles.legal}>Some important legal fine print here</Text>
</View>
</KeyboardAvoidingView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingTop: 64,
padding: 20,
backgroundColor: '#282c34',
},
description: {
fontSize: 14,
color: 'white',
},
input: {
margin: 20,
marginBottom: 0,
height: 34,
paddingHorizontal: 10,
borderRadius: 4,
borderColor: '#ccc',
borderWidth: 1,
fontSize: 16,
},
legal: {
margin: 10,
color: '#333',
fontSize: 12,
textAlign: 'center',
},
form: {
flex: 1,
justifyContent: 'space-between',
},
});
[Try it on your phone](https://snack.expo.io/ryxRkwnrW)
export default App;
```

## Make tappable areas larger

On mobile phones it's hard to be very precise when pressing buttons. Make sure all interactive elements are 44x44 or larger. One way to do this is to leave enough space for the element, `padding`, `minWidth` and `minHeight` style values can be useful for that. Alternatively, you can use [`hitSlop` prop](touchablewithoutfeedback.md#hitslop) to increase interactive area without affecting the layout. Here's a demo:

<video src="/img/hitslop.mp4" muted autoplay loop width="320" height="120"></video>
```SnackPlayer name=HitSlop%20example
import React, { Component } from 'react';
import {
Text,
StatusBar,
TouchableOpacity,
View,
StyleSheet,
} from 'react-native';
[Try it on your phone](https://snack.expo.io/rJPwCt4HZ)
const App = () => {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<View style={styles.header}>
<Text style={styles.description}>
This demo shows how using hitSlop can make interactive elements much
easier to tap without changing their layout and size. Try pressing
each button quickly multiple times and notice which one is easier to
hit.
</Text>
</View>
<View style={styles.content}>
<TouchableOpacity>
<Text style={styles.label}>Without hitSlop</Text>
</TouchableOpacity>
<View style={styles.separator} />
<View style={styles.preview}>
<TouchableOpacity
hitSlop={{ top: 20, left: 20, bottom: 20, right: 20 }}>
<Text style={styles.label}>With hitSlop</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingTop: 64,
padding: 20,
backgroundColor: '#282c34',
},
description: {
fontSize: 14,
color: 'white',
},
content: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
label: {
fontSize: 18,
color: '#336699',
textAlign: 'center',
borderColor: '#ddd',
borderWidth: 1,
},
separator: {
height: 50,
},
preview: {
padding: 20,
backgroundColor: '#f6f6f6',
},
});
export default App;
```

## Use Android Ripple

Android API 21+ uses the material design ripple to provide user with feedback when they touch an interactable area on the screen. React Native exposes this through the [`TouchableNativeFeedback` component](touchablenativefeedback.md). Using this touchable effect instead of opacity or highlight will often make your app feel much more fitting on the platform. That said, you need to be careful when using it because it doesn't work on iOS or on Android API < 21, so you will need to fallback to using one of the other Touchable components on iOS. You can use a library like [react-native-platform-touchable](/~https://github.com/react-community/react-native-platform-touchable) to handle the platform differences for you.

<video src="/img/ripple.mp4" muted autoplay loop width="320"></video>
```SnackPlayer name=Android%20Ripple%20example&supportedPlatforms=android
import React from 'react';
import {
TouchableNativeFeedback,
TouchableOpacity,
TouchableHighlight,
Platform,
Text,
View,
StyleSheet,
} from 'react-native';
const SUPPORTS_NATIVE_FEEDBACK =
Platform.OS === 'android' && Platform.Version >= 21;
const noop = () => {};
const defaultHitSlop = { top: 15, bottom: 15, right: 15, left: 15 };
const ButtonsWithNativeFeedback = () => (
<View style={styles.buttonContainer}>
<TouchableNativeFeedback
onPress={noop}
background={TouchableNativeFeedback.Ripple('#06bcee', false)}
hitSlop={defaultHitSlop}>
<View style={styles.button}>
<Text style={styles.text}>This is a ripple respecting borders</Text>
</View>
</TouchableNativeFeedback>
<TouchableNativeFeedback
onPress={noop}
background={TouchableNativeFeedback.Ripple('#06bcee', true)}
hitSlop={defaultHitSlop}>
<View style={styles.button}>
<Text style={styles.text}>
This is ripple without borders, this is more useful for icons, eg: in
tab bar
</Text>
</View>
</TouchableNativeFeedback>
</View>
);
const Buttons = () => (
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.button}
onPress={noop}
hitSlop={defaultHitSlop}>
<Text style={styles.text}>This is opacity</Text>
</TouchableOpacity>
<TouchableHighlight
style={styles.button}
onPress={noop}
hitSlop={defaultHitSlop}
underlayColor="#06bcee">
<Text style={styles.text}>This is highlight</Text>
</TouchableHighlight>
</View>
);
const App = () => (
<View style={styles.container}>
{SUPPORTS_NATIVE_FEEDBACK ? <ButtonsWithNativeFeedback /> : <Buttons />}
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: 64,
backgroundColor: '#fff',
},
buttonContainer: {
margin: 24,
},
text: {
fontSize: 20,
color: '#fff',
fontWeight: 'bold',
},
button: {
padding: 25,
borderRadius: 5,
backgroundColor: '#000',
marginBottom: 30,
},
});
[Try it on your phone](https://snack.expo.io/SJywqe3rZ)
export default App;
```

## Screen orientation lock

Expand Down
Loading

0 comments on commit ca43ddb

Please sign in to comment.