diff --git a/src/components/BottomNavigation.js b/src/components/BottomNavigation.js index 953f930..29552ff 100644 --- a/src/components/BottomNavigation.js +++ b/src/components/BottomNavigation.js @@ -9,6 +9,7 @@ import { SafeAreaView, StyleSheet, Platform, + Keyboard, } from 'react-native'; import { polyfill } from 'react-lifecycles-compat'; import color from 'color'; @@ -168,6 +169,11 @@ type Props = { * Custom color for icon and label in the inactive tab. */ inactiveColor?: string, + /** + * Whether the bottom navigation bar is hidden when keyboard is shown. + * On Android, this works best when [`windowSoftInputMode`](https://developer.android.com/guide/topics/manifest/activity-element#wsoft) is set to `adjustResize`. + */ + keyboardHidesNavigationBar?: boolean, /** * Style for the bottom navigation bar. * You can set a bottom padding here if you have a translucent navigation bar on Android: @@ -185,6 +191,10 @@ type Props = { }; type State = { + /** + * Visibility of the navigation bar, visible state is 1 and invisible is 0. + */ + visible: Animated.Value, /** * Active state of individual tab items, active state is 1 and inactve state is 0. */ @@ -205,7 +215,7 @@ type State = { */ ripple: Animated.Value, /** - * Layout of the tab bar. The width is used to determine the size and position of the ripple. + * Layout of the navigation bar. The width is used to determine the size and position of the ripple. */ layout: { height: number, width: number, measured: boolean }, /** @@ -220,6 +230,10 @@ type State = { * List of loaded tabs, tabs will be loaded when navigated to. */ loaded: number[], + /** + * Trak whether the keyboard is visible to show and hide the navigation bar. + */ + keyboard: boolean, }; const MIN_RIPPLE_SCALE = 0.001; // Minimum scale is not 0 due to bug with animation @@ -245,7 +259,7 @@ class SceneComponent extends React.PureComponent<*> { } /** - * Bottom navigation provides quick navigation between top-level views of an app with a bottom tab bar. + * Bottom navigation provides quick navigation between top-level views of an app with a bottom navigation bar. * It is primarily designed for use on mobile. * * For integration with React Navigation, you can use [react-navigation-material-bottom-tab-navigator](https://github.com/react-navigation/react-navigation-material-bottom-tab-navigator). @@ -319,6 +333,7 @@ class BottomNavigation extends React.Component, State> { static defaultProps = { labeled: true, + keyboardHidesNavigationBar: true, }; static getDerivedStateFromProps(nextProps, prevState) { @@ -362,6 +377,7 @@ class BottomNavigation extends React.Component, State> { const { index } = this.props.navigationState; this.state = { + visible: new Animated.Value(1), tabs: [], offsets: [], index: new Animated.Value(index), @@ -371,6 +387,7 @@ class BottomNavigation extends React.Component, State> { current: index, previous: 0, loaded: [index], + keyboard: false, }; } @@ -378,6 +395,14 @@ class BottomNavigation extends React.Component, State> { // Workaround for native animated bug in react-native@^0.57 // Context: https://github.com/callstack/react-native-paper/pull/637 this._animateToCurrentIndex(); + + 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); + } } componentDidUpdate(prevProps) { @@ -398,6 +423,34 @@ class BottomNavigation extends React.Component, State> { this._animateToCurrentIndex(); } + 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 }); + }); + _animateToCurrentIndex = () => { const shifting = this._isShifting(); const { routes, index } = this.props.navigationState; @@ -488,11 +541,13 @@ class BottomNavigation extends React.Component, State> { getTestID = ({ route }: Object) => route.testID, activeColor, inactiveColor, + keyboardHidesNavigationBar, barStyle, labeled, style, theme, } = this.props; + const { layout, loaded } = this.state; const { routes } = navigationState; const { colors } = theme; @@ -539,7 +594,6 @@ class BottomNavigation extends React.Component, State> { return ( @@ -583,209 +637,233 @@ class BottomNavigation extends React.Component, State> { ); })} - - - {shifting ? ( - - ) : null} - {routes.map((route, index) => { - const focused = navigationState.index === index; - const active = this.state.tabs[index]; + + ); @@ -804,9 +882,13 @@ const styles = StyleSheet.create({ flex: 1, }, bar: { + left: 0, + right: 0, + bottom: 0, elevation: 8, + }, + barContent: { overflow: 'hidden', - alignItems: 'center', }, items: { flexDirection: 'row', diff --git a/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap b/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap index 5b8bb72..9a624df 100644 --- a/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap +++ b/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap @@ -2,7 +2,6 @@ exports[`hides labels in non-shifting bottom navigation 1`] = ` - - - -  - + +  + + + + +  + + - -  - - + /> - - - - -  - + +  + + + + +  + + - -  - - + /> - - - - -  - + +  + + + + +  + + - -  - - + /> - - - + + `; exports[`hides labels in shifting bottom navigation 1`] = ` - - - - - - -  - - - - -  - - - - - - - + /> - - - -  - - - - -  - - - - - - - - - -  - + +  + + + + +  + + + + + + + - -  - + +  + + + + +  + + - - + /> + - - + + + + + +  + + + + +  + + + + + + + + `; exports[`renders custom icon and label in non-shifting bottom navigation 1`] = ` - - - - - - 3d-rotation - - - - - 3d-rotation - - - - - - - - - - - - - - - - - - - ac-unit - - - - - ac-unit - - - - - - - - - - - - - - - - - access-alarm - - - - - access-alarm - - - - - - + > + + 3d-rotation + + + + + 3d-rotation + + - + + + + + + - - + + + + + + ac-unit + + + + + ac-unit + + + + + + + + + + + + + + + + + + + access-alarm + + + + + access-alarm + + + + + + + + + + + + + + + `; exports[`renders custom icon and label in shifting bottom navigation 1`] = ` - - - - - - - 3d-rotation - - - - - 3d-rotation - - - - - - - - - - - + /> - - - - ac-unit - - - - - ac-unit - - - - - - - - - - - - - - - - access-alarm - - - - - access-alarm - - - - - - - - - - - - - - - - access-alarms - - - - - access-alarms - - - - - - - - - - - - - - access-time - - - - - access-time - - - - + "alignItems": "center", + "bottom": 0, + "left": 0, + "opacity": 1, + "position": "absolute", + "right": 0, + "top": 0, + } + } + > + + 3d-rotation + + + + + 3d-rotation + + + - + + + - - + + + + + + ac-unit + + + + + ac-unit + + + + + + + + + + + + + + + + access-alarm + + + + + access-alarm + + + + + + + + + + + + + + + + access-alarms + + + + + access-alarms + + + + + + + + + + + + + + + + access-time + + + + + access-time + + + + + + + + + + + + `; exports[`renders custom icon and label with custom colors in non-shifting bottom navigation 1`] = ` - - - - - -  - - - - -  - - - - - - - Route: 0 - - - - - Route: 0 - - - - - - - - - - -  - - - - -  - - - - - - - Route: 1 - - - - - Route: 1 - - - - - - - - -  - - - - -  - - - - - - - Route: 2 - + +  + + + + +  + + - - Route: 2 - + + Route: 0 + + + + + Route: 0 + + - - + + + + + +  + + + + +  + + + + + + + Route: 1 + + + + + Route: 1 + + + + + + + + + + +  + + + + +  + + + + + + + Route: 2 + + + + + Route: 2 + + + + + + + `; exports[`renders custom icon and label with custom colors in shifting bottom navigation 1`] = ` - - - - - - -  - - - - -  - - - - - - - Route: 0 - - - - - - + /> - - - -  - - - - -  - - - - - - - Route: 1 - - - - - - - - -  - - - - -  - - - - + "alignItems": "center", + "bottom": 0, + "left": 0, + "opacity": 1, + "position": "absolute", + "right": 0, + "top": 0, + } + } + > + +  + + + + +  + + + - - Route: 2 - + + Route: 0 + + - - + + + + + +  + + + + +  + + + + + + + Route: 1 + + + + + + + + + + +  + + + + +  + + + + + + + Route: 2 + + + + + + + `; exports[`renders non-shifting bottom navigation 1`] = ` - - - - - -  - - - - -  - - - - - - - Route: 0 - - - - - Route: 0 - - - - - - - - - - -  - - - - -  - - - - - - - Route: 1 - - - - - Route: 1 - - - - - - - - -  - - - - -  - - - - - - - Route: 2 - + +  + + + + +  + + - - Route: 2 - + + Route: 0 + + + + + Route: 0 + + - - + + + + + +  + + + + +  + + + + + + + Route: 1 + + + + + Route: 1 + + + + + + + + + + +  + + + + +  + + + + + + + Route: 2 + + + + + Route: 2 + + + + + + + `; exports[`renders shifting bottom navigation 1`] = ` - - - - - - -  - - - - -  - - - - - - - Route: 0 - - - - - - + /> - - - -  - - - - -  - - - - - - - Route: 1 - - - - - - - - - - -  - - - - -  - - - - - - - Route: 2 - - - - - - - - - - -  - - - - -  - - - - - - - Route: 3 - - - - - - - - -  - - - - -  - - - - + "alignItems": "center", + "bottom": 0, + "left": 0, + "opacity": 1, + "position": "absolute", + "right": 0, + "top": 0, + } + } + > + +  + + + + +  + + + - - Route: 4 - + + Route: 0 + + - - + + + + + +  + + + + +  + + + + + + + Route: 1 + + + + + + + + + + +  + + + + +  + + + + + + + Route: 2 + + + + + + + + + + +  + + + + +  + + + + + + + Route: 3 + + + + + + + + + + +  + + + + +  + + + + + + + Route: 4 + + + + + + + `; diff --git a/typings/components/BottomNavigation.d.ts b/typings/components/BottomNavigation.d.ts index bd78578..4290696 100644 --- a/typings/components/BottomNavigation.d.ts +++ b/typings/components/BottomNavigation.d.ts @@ -40,6 +40,7 @@ export interface BottomNavigationProps { onTabPress?: (props: { route: T }) => any; activeColor?: string; inactiveColor?: string; + keyboardHidesNavigationBar?: boolean; barStyle?: any; style?: StyleProp; theme?: ThemeShape;