22
33import React from 'react' ;
44import {
5+ Animated ,
56 TouchableWithoutFeedback ,
67 StyleSheet ,
78 View ,
9+ Keyboard ,
810 Platform ,
911} from 'react-native' ;
1012import { SafeAreaView } from '@react-navigation/native' ;
11- import Animated from 'react-native-reanimated' ;
1213
1314import CrossFadeIcon from './CrossFadeIcon' ;
1415import withDimensions from '../utils/withDimensions' ;
1516
1617export type TabBarOptions = {
18+ keyboardHidesTabBar : boolean ,
1719 activeTintColor ?: string ,
1820 inactiveTintColor ?: string ,
1921 activeBackgroundColor ?: string ,
@@ -45,6 +47,12 @@ type Props = TabBarOptions & {
4547 safeAreaInset : { top : string , right : string , bottom : string , left : string } ,
4648} ;
4749
50+ type State = {
51+ layout : { height : number , width : number } ,
52+ keyboard : boolean ,
53+ visible : Animated . Value ,
54+ } ;
55+
4856const majorVersion = parseInt ( Platform . Version , 10 ) ;
4957const isIos = Platform . OS === 'ios' ;
5058const isIOS11 = majorVersion >= 11 && isIos ;
@@ -79,8 +87,9 @@ class TouchableWithoutFeedbackWrapper extends React.Component<*> {
7987 }
8088}
8189
82- class TabBarBottom extends React . Component < Props > {
90+ class TabBarBottom extends React . Component < Props , State > {
8391 static defaultProps = {
92+ keyboardHidesTabBar : true ,
8493 activeTintColor : '#007AFF' ,
8594 activeBackgroundColor : 'transparent' ,
8695 inactiveTintColor : '#8E8E93' ,
@@ -92,6 +101,66 @@ class TabBarBottom extends React.Component<Props> {
92101 safeAreaInset : { bottom : 'always' , top : 'never' } ,
93102 } ;
94103
104+ state = {
105+ layout : { height : 0 , width : 0 } ,
106+ keyboard : false ,
107+ visible : new Animated . Value ( 1 ) ,
108+ } ;
109+
110+ componentDidMount ( ) {
111+ if ( Platform . OS === 'ios' ) {
112+ Keyboard . addListener ( 'keyboardWillShow' , this . _handleKeyboardShow ) ;
113+ Keyboard . addListener ( 'keyboardWillHide' , this . _handleKeyboardHide ) ;
114+ } else {
115+ Keyboard . addListener ( 'keyboardDidShow' , this . _handleKeyboardShow ) ;
116+ Keyboard . addListener ( 'keyboardDidHide' , this . _handleKeyboardHide ) ;
117+ }
118+ }
119+
120+ componentWillUnmount ( ) {
121+ if ( Platform . OS === 'ios ') {
122+ Keyboard . removeListener ( 'keyboardWillShow ', this . _handleKeyboardShow ) ;
123+ Keyboard . removeListener ( 'keyboardWillHide ', this . _handleKeyboardHide ) ;
124+ } else {
125+ Keyboard . removeListener ( 'keyboardDidShow ', this . _handleKeyboardShow ) ;
126+ Keyboard . removeListener ( 'keyboardDidHide ', this . _handleKeyboardHide ) ;
127+ }
128+ }
129+
130+ _handleKeyboardShow = ( ) =>
131+ this . setState ( { keyboard : true } , ( ) =>
132+ Animated . timing ( this . state . visible , {
133+ toValue : 0 ,
134+ duration : 150 ,
135+ useNativeDriver : true ,
136+ } ) . start ( )
137+ ) ;
138+
139+ _handleKeyboardHide = ( ) =>
140+ Animated . timing ( this . state . visible , {
141+ toValue : 1 ,
142+ duration : 100 ,
143+ useNativeDriver : true ,
144+ } ) . start ( ( ) => {
145+ this . setState ( { keyboard : false } ) ;
146+ } ) ;
147+
148+ _handleLayout = e => {
149+ const { layout } = this . state ;
150+ const { height, width } = e . nativeEvent . layout ;
151+
152+ if ( height === layout . height && width === layout . width ) {
153+ return ;
154+ }
155+
156+ this . setState ( {
157+ layout : {
158+ height,
159+ width,
160+ } ,
161+ } ) ;
162+ } ;
163+
95164 _renderLabel = ( { route, focused } ) = > {
96165 const {
97166 activeTintColor,
@@ -202,6 +271,7 @@ class TabBarBottom extends React.Component<Props> {
202271 render ( ) {
203272 const {
204273 navigation ,
274+ keyboardHidesTabBar ,
205275 activeBackgroundColor ,
206276 inactiveBackgroundColor ,
207277 onTabPress ,
@@ -222,62 +292,71 @@ class TabBarBottom extends React.Component<Props> {
222292 ] ;
223293
224294 return (
225- < SafeAreaView style = { tabBarStyle } forceInset = { safeAreaInset } >
226- { routes . map ( ( route , index ) => {
227- const focused = index === navigation . state . index ;
228- const scene = { route, focused } ;
229-
230- const accessibilityLabel = this . props . getAccessibilityLabel ( {
231- route,
232- } ) ;
233-
234- const accessibilityRole =
235- this . props . getAccessibilityRole ( {
295+ < Animated . View
296+ style = { [
297+ styles . container ,
298+ keyboardHidesTabBar
299+ ? {
300+ // When the keyboard is shown, slide down the tab bar
301+ transform : [
302+ {
303+ translateY : this . state . visible . interpolate ( {
304+ inputRange : [ 0 , 1 ] ,
305+ outputRange : [ this . state . layout . height , 0 ] ,
306+ } ) ,
307+ } ,
308+ ] ,
309+ // Absolutely position the tab bar so that the content is below it
310+ // This is needed to avoid gap at bottom when the tab bar is hidden
311+ position : this . state . keyboard ? 'absolute' : null ,
312+ }
313+ : null ,
314+ ] }
315+ pointerEvents = {
316+ keyboardHidesTabBar && this . state . keyboard ? 'none' : 'auto'
317+ }
318+ onLayout = { this . _handleLayout }
319+ >
320+ < SafeAreaView style = { tabBarStyle } forceInset = { safeAreaInset } >
321+ { routes . map ( ( route , index ) => {
322+ const focused = index === navigation . state . index ;
323+ const scene = { route, focused } ;
324+ const accessibilityLabel = this . props . getAccessibilityLabel ( {
236325 route,
237- } ) || 'button' ;
238-
239- let accessibilityStates = this . props . getAccessibilityStates ( {
240- route,
241- } ) ;
242-
243- if ( ! accessibilityStates ) {
244- accessibilityStates = focused ? [ 'selected' ] : [ ] ;
245- }
246-
247- const testID = this . props . getTestID ( { route } ) ;
248-
249- const backgroundColor = focused
250- ? activeBackgroundColor
251- : inactiveBackgroundColor ;
252-
253- const ButtonComponent =
254- this . props . getButtonComponent ( { route } ) ||
255- TouchableWithoutFeedbackWrapper ;
256-
257- return (
258- < ButtonComponent
259- key = { route . key }
260- onPress = { ( ) => onTabPress ( { route } ) }
261- onLongPress = { ( ) => onTabLongPress ( { route } ) }
262- testID = { testID }
263- accessibilityLabel = { accessibilityLabel }
264- accessibilityRole = { accessibilityRole }
265- accessibilityStates = { accessibilityStates }
266- style = { [
267- styles . tab ,
268- { backgroundColor } ,
269- this . _shouldUseHorizontalLabels ( )
270- ? styles . tabLandscape
271- : styles . tabPortrait ,
272- tabStyle ,
273- ] }
274- >
275- { this . _renderIcon ( scene ) }
276- { this . _renderLabel ( scene ) }
277- </ ButtonComponent >
278- ) ;
279- } ) }
280- </ SafeAreaView >
326+ } ) ;
327+ const testID = this . props . getTestID ( { route } ) ;
328+
329+ const backgroundColor = focused
330+ ? activeBackgroundColor
331+ : inactiveBackgroundColor ;
332+
333+ const ButtonComponent =
334+ this . props . getButtonComponent ( { route } ) ||
335+ TouchableWithoutFeedbackWrapper ;
336+
337+ return (
338+ < ButtonComponent
339+ key = { route . key }
340+ onPress = { ( ) => onTabPress ( { route } ) }
341+ onLongPress = { ( ) => onTabLongPress ( { route } ) }
342+ testID = { testID }
343+ accessibilityLabel = { accessibilityLabel }
344+ style = { [
345+ styles . tab ,
346+ { backgroundColor } ,
347+ this . _shouldUseHorizontalLabels ( )
348+ ? styles . tabLandscape
349+ : styles . tabPortrait ,
350+ tabStyle ,
351+ ] }
352+ >
353+ { this . _renderIcon ( scene ) }
354+ { this . _renderLabel ( scene ) }
355+ </ ButtonComponent >
356+ ) ;
357+ } ) }
358+ </ SafeAreaView >
359+ </ Animated . View >
281360 ) ;
282361 }
283362}
@@ -292,6 +371,12 @@ const styles = StyleSheet.create({
292371 borderTopColor : 'rgba(0, 0, 0, .3)' ,
293372 flexDirection : 'row' ,
294373 } ,
374+ container : {
375+ left : 0 ,
376+ right : 0 ,
377+ bottom : 0 ,
378+ elevation : 8 ,
379+ } ,
295380 tabBarCompact : {
296381 height : COMPACT_HEIGHT ,
297382 } ,
0 commit comments