@@ -20,6 +20,7 @@ import Portal from '../Portal/Portal';
2020import Surface from '../Surface' ;
2121// eslint-disable-next-line @typescript-eslint/no-unused-vars
2222import MenuItem , { MenuItem as _MenuItem } from './MenuItem' ;
23+ import { APPROX_STATUSBAR_HEIGHT } from '../../constants' ;
2324
2425type Props = {
2526 /**
@@ -30,6 +31,13 @@ type Props = {
3031 * The anchor to open the menu from. In most cases, it will be a button that opens the menu.
3132 */
3233 anchor : React . ReactNode ;
34+ /**
35+ * Extra margin to add at the top of the menu to account for translucent status bar on Android.
36+ * If you are using Expo, we assume translucent status bar and set a height for status bar automatically.
37+ * Pass `0` or a custom value to and customize it.
38+ * This is automatically handled on iOS.
39+ */
40+ statusBarHeight ?: number ;
3341 /**
3442 * Callback called when Menu is dismissed. The `visible` prop needs to be updated when this is called.
3543 */
@@ -45,11 +53,14 @@ type Props = {
4553 theme : Theme ;
4654} ;
4755
56+ type Layout = Omit < Omit < LayoutRectangle , 'x' > , 'y' > ;
57+
4858type State = {
4959 top : number ;
5060 left : number ;
51- menuLayout : LayoutRectangle ;
52- anchorLayout : LayoutRectangle ;
61+ windowLayout : Layout ;
62+ menuLayout : Layout ;
63+ anchorLayout : Layout ;
5364 opacityAnimation : Animated . Value ;
5465 scaleAnimation : Animated . ValueXY ;
5566} ;
@@ -116,11 +127,16 @@ class Menu extends React.Component<Props, State> {
116127 // @component ./MenuItem.tsx
117128 static Item = MenuItem ;
118129
130+ static defaultProps = {
131+ statusBarHeight : APPROX_STATUSBAR_HEIGHT ,
132+ } ;
133+
119134 state = {
120135 top : 0 ,
121136 left : 0 ,
122- menuLayout : { width : 0 , height : 0 , x : 0 , y : 0 } ,
123- anchorLayout : { width : 0 , height : 0 , x : 0 , y : 0 } ,
137+ windowLayout : { width : 0 , height : 0 } ,
138+ menuLayout : { width : 0 , height : 0 } ,
139+ anchorLayout : { width : 0 , height : 0 } ,
124140 opacityAnimation : new Animated . Value ( 0 ) ,
125141 scaleAnimation : new Animated . ValueXY ( { x : 0 , y : 0 } ) ,
126142 } ;
@@ -132,7 +148,8 @@ class Menu extends React.Component<Props, State> {
132148 }
133149
134150 componentWillUnmount ( ) {
135- BackHandler . removeEventListener ( 'hardwareBackPress' , this . props . onDismiss ) ;
151+ BackHandler . removeEventListener ( 'hardwareBackPress' , this . _handleDismiss ) ;
152+ Dimensions . removeEventListener ( 'change' , this . _handleDismiss ) ;
136153 }
137154
138155 _anchor ?: View | null ;
@@ -164,8 +181,17 @@ class Menu extends React.Component<Props, State> {
164181 }
165182 } ;
166183
184+ _handleDismiss = ( ) => {
185+ if ( this . props . visible ) {
186+ this . props . onDismiss ( ) ;
187+ }
188+ } ;
189+
167190 _show = async ( ) => {
168- BackHandler . addEventListener ( 'hardwareBackPress' , this . props . onDismiss ) ;
191+ BackHandler . addEventListener ( 'hardwareBackPress' , this . _handleDismiss ) ;
192+ Dimensions . addEventListener ( 'change' , this . _handleDismiss ) ;
193+
194+ const windowLayout = Dimensions . get ( 'window' ) ;
169195 const [ menuLayout , anchorLayout ] = await Promise . all ( [
170196 this . _measureMenuLayout ( ) ,
171197 this . _measureAnchorLayout ( ) ,
@@ -178,34 +204,31 @@ class Menu extends React.Component<Props, State> {
178204 // so we have to wait until views are ready
179205 // and rerun this function to show menu
180206 if (
207+ ! windowLayout . width ||
208+ ! windowLayout . height ||
181209 ! menuLayout . width ||
182210 ! menuLayout . height ||
183211 ! anchorLayout . width ||
184212 ! anchorLayout . height
185213 ) {
186- BackHandler . removeEventListener (
187- 'hardwareBackPress' ,
188- this . props . onDismiss
189- ) ;
190- setTimeout ( ( ) => {
191- this . _show ( ) ;
192- } , ANIMATION_DURATION ) ;
214+ BackHandler . removeEventListener ( 'hardwareBackPress' , this . _handleDismiss ) ;
215+ setTimeout ( this . _show , ANIMATION_DURATION ) ;
193216 return ;
194217 }
195218
196219 this . setState (
197- state => ( {
220+ ( ) => ( {
198221 left : anchorLayout . x ,
199222 top : anchorLayout . y ,
223+ windowLayout : {
224+ height : windowLayout . height ,
225+ width : windowLayout . width ,
226+ } ,
200227 anchorLayout : {
201- x : state . anchorLayout . x ,
202- y : state . anchorLayout . y ,
203228 height : anchorLayout . height ,
204229 width : anchorLayout . width ,
205230 } ,
206231 menuLayout : {
207- x : state . menuLayout . x ,
208- y : state . menuLayout . y ,
209232 width : menuLayout . width ,
210233 height : menuLayout . height ,
211234 } ,
@@ -230,7 +253,8 @@ class Menu extends React.Component<Props, State> {
230253 } ;
231254
232255 _hide = ( ) => {
233- BackHandler . removeEventListener ( 'hardwareBackPress' , this . props . onDismiss ) ;
256+ BackHandler . removeEventListener ( 'hardwareBackPress' , this . _handleDismiss ) ;
257+ Dimensions . removeEventListener ( 'change' , this . _handleDismiss ) ;
234258
235259 Animated . timing ( this . state . opacityAnimation , {
236260 toValue : 0 ,
@@ -245,9 +269,18 @@ class Menu extends React.Component<Props, State> {
245269 } ;
246270
247271 render ( ) {
248- const { visible, anchor, style, children, theme, onDismiss } = this . props ;
272+ const {
273+ visible,
274+ anchor,
275+ style,
276+ children,
277+ theme,
278+ statusBarHeight,
279+ onDismiss,
280+ } = this . props ;
249281
250282 const {
283+ windowLayout,
251284 menuLayout,
252285 anchorLayout,
253286 opacityAnimation,
@@ -256,7 +289,7 @@ class Menu extends React.Component<Props, State> {
256289
257290 // I don't know why but on Android measure function is wrong by 24
258291 const additionalVerticalValue = Platform . select ( {
259- android : 24 ,
292+ android : statusBarHeight ,
260293 default : 0 ,
261294 } ) ;
262295
@@ -280,12 +313,8 @@ class Menu extends React.Component<Props, State> {
280313 // We need to translate menu while animating scale to imitate transform origin for scale animation
281314 const positionTransforms = [ ] ;
282315
283- const { width : screenWidth , height : screenHeight } = Dimensions . get (
284- 'screen'
285- ) ;
286-
287316 // Check if menu fits horizontally and if not align it to right.
288- if ( left <= screenWidth - menuLayout . width - SCREEN_INDENT ) {
317+ if ( left <= windowLayout . width - menuLayout . width - SCREEN_INDENT ) {
289318 positionTransforms . push ( {
290319 translateX : scaleAnimation . x . interpolate ( {
291320 inputRange : [ 0 , menuLayout . width ] ,
@@ -309,13 +338,16 @@ class Menu extends React.Component<Props, State> {
309338
310339 const right = left + menuLayout . width ;
311340 // Check if menu position has enough space from right side
312- if ( right <= screenWidth && right > screenWidth - SCREEN_INDENT ) {
313- left = screenWidth - SCREEN_INDENT - menuLayout . width ;
341+ if (
342+ right <= windowLayout . width &&
343+ right > windowLayout . width - SCREEN_INDENT
344+ ) {
345+ left = windowLayout . width - SCREEN_INDENT - menuLayout . width ;
314346 }
315347 }
316348
317349 // Check if menu fits vertically and if not align it to bottom.
318- if ( top <= screenHeight - menuLayout . height - SCREEN_INDENT ) {
350+ if ( top <= windowLayout . width - menuLayout . height - SCREEN_INDENT ) {
319351 positionTransforms . push ( {
320352 translateY : scaleAnimation . y . interpolate ( {
321353 inputRange : [ 0 , menuLayout . height ] ,
@@ -339,9 +371,12 @@ class Menu extends React.Component<Props, State> {
339371
340372 const bottom = top + menuLayout . height + additionalVerticalValue ;
341373 // Check if menu position has enough space from bottom side
342- if ( bottom <= screenHeight && bottom > screenHeight - SCREEN_INDENT ) {
374+ if (
375+ bottom <= windowLayout . height &&
376+ bottom > windowLayout . height - SCREEN_INDENT
377+ ) {
343378 top =
344- screenHeight -
379+ windowLayout . height -
345380 SCREEN_INDENT -
346381 menuLayout . height -
347382 additionalVerticalValue ;
0 commit comments