feat: hide tab bar when keyboard is shown (#112)

Closes #16

When the statusbar is not translucent, the view resizes when the keyboard is shown on Android. The tab bar stays above the keyboard. This PR makes the tab bar hide automatically when the keyboard is shown.

The behaviour is enabled by default and can be disabled with `keyboardHidesTabBar: false` in `tabBarOptions`
This commit is contained in:
Satyajit Sahoo
2019-03-31 00:05:50 +01:00
parent 7c77f60398
commit 70a479da84

View File

@@ -2,18 +2,20 @@
import React from 'react';
import {
Animated,
TouchableWithoutFeedback,
StyleSheet,
View,
Keyboard,
Platform,
} from 'react-native';
import { SafeAreaView } from '@react-navigation/native';
import Animated from 'react-native-reanimated';
import CrossFadeIcon from './CrossFadeIcon';
import withDimensions from '../utils/withDimensions';
export type TabBarOptions = {
keyboardHidesTabBar: boolean,
activeTintColor?: string,
inactiveTintColor?: string,
activeBackgroundColor?: string,
@@ -45,6 +47,12 @@ type Props = TabBarOptions & {
safeAreaInset: { top: string, right: string, bottom: string, left: string },
};
type State = {
layout: { height: number, width: number },
keyboard: boolean,
visible: Animated.Value,
};
const majorVersion = parseInt(Platform.Version, 10);
const isIos = Platform.OS === 'ios';
const isIOS11 = majorVersion >= 11 && isIos;
@@ -79,8 +87,9 @@ class TouchableWithoutFeedbackWrapper extends React.Component<*> {
}
}
class TabBarBottom extends React.Component<Props> {
class TabBarBottom extends React.Component<Props, State> {
static defaultProps = {
keyboardHidesTabBar: true,
activeTintColor: '#007AFF',
activeBackgroundColor: 'transparent',
inactiveTintColor: '#8E8E93',
@@ -92,6 +101,66 @@ class TabBarBottom extends React.Component<Props> {
safeAreaInset: { bottom: 'always', top: 'never' },
};
state = {
layout: { height: 0, width: 0 },
keyboard: false,
visible: new Animated.Value(1),
};
componentDidMount() {
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', this._handleKeyboardShow);
Keyboard.addListener('keyboardWillHide', this._handleKeyboardHide);
} else {
Keyboard.addListener('keyboardDidShow', this._handleKeyboardShow);
Keyboard.addListener('keyboardDidHide', this._handleKeyboardHide);
}
}
componentWillUnmount() {
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', this._handleKeyboardShow);
Keyboard.removeListener('keyboardWillHide', this._handleKeyboardHide);
} else {
Keyboard.removeListener('keyboardDidShow', this._handleKeyboardShow);
Keyboard.removeListener('keyboardDidHide', this._handleKeyboardHide);
}
}
_handleKeyboardShow = () =>
this.setState({ keyboard: true }, () =>
Animated.timing(this.state.visible, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}).start()
);
_handleKeyboardHide = () =>
Animated.timing(this.state.visible, {
toValue: 1,
duration: 100,
useNativeDriver: true,
}).start(() => {
this.setState({ keyboard: false });
});
_handleLayout = e => {
const { layout } = this.state;
const { height, width } = e.nativeEvent.layout;
if (height === layout.height && width === layout.width) {
return;
}
this.setState({
layout: {
height,
width,
},
});
};
_renderLabel = ({ route, focused }) => {
const {
activeTintColor,
@@ -202,6 +271,7 @@ class TabBarBottom extends React.Component<Props> {
render() {
const {
navigation,
keyboardHidesTabBar,
activeBackgroundColor,
inactiveBackgroundColor,
onTabPress,
@@ -222,62 +292,71 @@ class TabBarBottom extends React.Component<Props> {
];
return (
<SafeAreaView style={tabBarStyle} forceInset={safeAreaInset}>
{routes.map((route, index) => {
const focused = index === navigation.state.index;
const scene = { route, focused };
const accessibilityLabel = this.props.getAccessibilityLabel({
route,
});
const accessibilityRole =
this.props.getAccessibilityRole({
<Animated.View
style={[
styles.container,
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
transform: [
{
translateY: this.state.visible.interpolate({
inputRange: [0, 1],
outputRange: [this.state.layout.height, 0],
}),
},
],
// Absolutely position the tab bar so that the content is below it
// This is needed to avoid gap at bottom when the tab bar is hidden
position: this.state.keyboard ? 'absolute' : null,
}
: null,
]}
pointerEvents={
keyboardHidesTabBar && this.state.keyboard ? 'none' : 'auto'
}
onLayout={this._handleLayout}
>
<SafeAreaView style={tabBarStyle} forceInset={safeAreaInset}>
{routes.map((route, index) => {
const focused = index === navigation.state.index;
const scene = { route, focused };
const accessibilityLabel = this.props.getAccessibilityLabel({
route,
}) || 'button';
});
const testID = this.props.getTestID({ route });
let accessibilityStates = this.props.getAccessibilityStates({
route,
});
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
if (!accessibilityStates) {
accessibilityStates = focused ? ['selected'] : [];
}
const ButtonComponent =
this.props.getButtonComponent({ route }) ||
TouchableWithoutFeedbackWrapper;
const testID = this.props.getTestID({ route });
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const ButtonComponent =
this.props.getButtonComponent({ route }) ||
TouchableWithoutFeedbackWrapper;
return (
<ButtonComponent
key={route.key}
onPress={() => onTabPress({ route })}
onLongPress={() => onTabLongPress({ route })}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessibilityStates={accessibilityStates}
style={[
styles.tab,
{ backgroundColor },
this._shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this._renderIcon(scene)}
{this._renderLabel(scene)}
</ButtonComponent>
);
})}
</SafeAreaView>
return (
<ButtonComponent
key={route.key}
onPress={() => onTabPress({ route })}
onLongPress={() => onTabLongPress({ route })}
testID={testID}
accessibilityLabel={accessibilityLabel}
style={[
styles.tab,
{ backgroundColor },
this._shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this._renderIcon(scene)}
{this._renderLabel(scene)}
</ButtonComponent>
);
})}
</SafeAreaView>
</Animated.View>
);
}
}
@@ -292,6 +371,12 @@ const styles = StyleSheet.create({
borderTopColor: 'rgba(0, 0, 0, .3)',
flexDirection: 'row',
},
container: {
left: 0,
right: 0,
bottom: 0,
elevation: 8,
},
tabBarCompact: {
height: COMPACT_HEIGHT,
},