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
committed by satyajit.happy
parent 73e9b4cb8f
commit ccb2d38089

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,
},