Skip to content

Commit

Permalink
feat: hide tab bar when keyboard is shown (facebook#112)
Browse files Browse the repository at this point in the history
Closes facebook#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`
  • Loading branch information
satya164 committed Aug 18, 2019
1 parent 73e9b4c commit ccb2d38
Showing 1 changed file with 142 additions and 57 deletions.
199 changes: 142 additions & 57 deletions packages/bottom-tabs/src/views/BottomTabBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand All @@ -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,
Expand Down Expand Up @@ -202,6 +271,7 @@ class TabBarBottom extends React.Component<Props> {
render() {
const {
navigation,
keyboardHidesTabBar,
activeBackgroundColor,
inactiveBackgroundColor,
onTabPress,
Expand All @@ -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';

let accessibilityStates = this.props.getAccessibilityStates({
route,
});

if (!accessibilityStates) {
accessibilityStates = focused ? ['selected'] : [];
}

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>
});
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}
style={[
styles.tab,
{ backgroundColor },
this._shouldUseHorizontalLabels()
? styles.tabLandscape
: styles.tabPortrait,
tabStyle,
]}
>
{this._renderIcon(scene)}
{this._renderLabel(scene)}
</ButtonComponent>
);
})}
</SafeAreaView>
</Animated.View>
);
}
}
Expand All @@ -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,
},
Expand Down

0 comments on commit ccb2d38

Please sign in to comment.