2
2
3
3
import React from 'react' ;
4
4
import {
5
+ Animated ,
5
6
TouchableWithoutFeedback ,
6
7
StyleSheet ,
7
8
View ,
9
+ Keyboard ,
8
10
Platform ,
9
11
} from 'react-native' ;
10
12
import { SafeAreaView } from '@react-navigation/native' ;
11
- import Animated from 'react-native-reanimated' ;
12
13
13
14
import CrossFadeIcon from './CrossFadeIcon' ;
14
15
import withDimensions from '../utils/withDimensions' ;
15
16
16
17
export type TabBarOptions = {
18
+ keyboardHidesTabBar : boolean ,
17
19
activeTintColor ?: string ,
18
20
inactiveTintColor ?: string ,
19
21
activeBackgroundColor ?: string ,
@@ -45,6 +47,12 @@ type Props = TabBarOptions & {
45
47
safeAreaInset : { top : string , right : string , bottom : string , left : string } ,
46
48
} ;
47
49
50
+ type State = {
51
+ layout : { height : number , width : number } ,
52
+ keyboard : boolean ,
53
+ visible : Animated . Value ,
54
+ } ;
55
+
48
56
const majorVersion = parseInt ( Platform . Version , 10 ) ;
49
57
const isIos = Platform . OS === 'ios' ;
50
58
const isIOS11 = majorVersion >= 11 && isIos ;
@@ -79,8 +87,9 @@ class TouchableWithoutFeedbackWrapper extends React.Component<*> {
79
87
}
80
88
}
81
89
82
- class TabBarBottom extends React . Component < Props > {
90
+ class TabBarBottom extends React . Component < Props , State > {
83
91
static defaultProps = {
92
+ keyboardHidesTabBar : true ,
84
93
activeTintColor : '#007AFF' ,
85
94
activeBackgroundColor : 'transparent' ,
86
95
inactiveTintColor : '#8E8E93' ,
@@ -92,6 +101,66 @@ class TabBarBottom extends React.Component<Props> {
92
101
safeAreaInset : { bottom : 'always' , top : 'never' } ,
93
102
} ;
94
103
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
+
95
164
_renderLabel = ( { route, focused } ) = > {
96
165
const {
97
166
activeTintColor,
@@ -202,6 +271,7 @@ class TabBarBottom extends React.Component<Props> {
202
271
render ( ) {
203
272
const {
204
273
navigation ,
274
+ keyboardHidesTabBar ,
205
275
activeBackgroundColor ,
206
276
inactiveBackgroundColor ,
207
277
onTabPress ,
@@ -222,62 +292,71 @@ class TabBarBottom extends React.Component<Props> {
222
292
] ;
223
293
224
294
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 ( {
236
325
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 >
281
360
) ;
282
361
}
283
362
}
@@ -292,6 +371,12 @@ const styles = StyleSheet.create({
292
371
borderTopColor : 'rgba(0, 0, 0, .3)' ,
293
372
flexDirection : 'row' ,
294
373
} ,
374
+ container : {
375
+ left : 0 ,
376
+ right : 0 ,
377
+ bottom : 0 ,
378
+ elevation : 8 ,
379
+ } ,
295
380
tabBarCompact : {
296
381
height : COMPACT_HEIGHT ,
297
382
} ,
0 commit comments