Skip to content

Commit

Permalink
fix: fix position of menu when dimensions change (#3521)
Browse files Browse the repository at this point in the history
Fixes #3515
  • Loading branch information
mathias-berg authored Dec 18, 2022
1 parent a981d5c commit 031f0db
Showing 1 changed file with 58 additions and 22 deletions.
80 changes: 58 additions & 22 deletions src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
LayoutRectangle,
NativeEventSubscription,
Platform,
ScaledSize,
ScrollView,
ScrollViewProps,
StyleProp,
Expand Down Expand Up @@ -89,6 +90,7 @@ type State = {
anchorLayout: Layout;
opacityAnimation: Animated.Value;
scaleAnimation: Animated.ValueXY;
windowLayout: Layout;
};

// Minimum padding between the edge of the screen and the menu
Expand Down Expand Up @@ -176,6 +178,10 @@ class Menu extends React.Component<Props, State> {
anchorLayout: { width: 0, height: 0 },
opacityAnimation: new Animated.Value(0),
scaleAnimation: new Animated.ValueXY({ x: 0, y: 0 }),
windowLayout: {
width: WINDOW_LAYOUT.width,
height: WINDOW_LAYOUT.height,
},
};

componentDidMount() {
Expand All @@ -187,6 +193,10 @@ class Menu extends React.Component<Props, State> {
'keyboardDidHide',
this.keyboardDidHide
);
this.dimensionsWindowSubscription = Dimensions.addEventListener(
'change',
this.onWindowChangeHandler
);
}

componentDidUpdate(prevProps: Props) {
Expand All @@ -199,15 +209,16 @@ class Menu extends React.Component<Props, State> {
this.removeListeners();
this.keyboardDidShowListener?.remove();
this.keyboardDidHideListener?.remove();
this.dimensionsWindowSubscription?.remove();
}

private anchor?: View | null = null;
private menu?: View | null = null;
private backHandlerSubscription: NativeEventSubscription | undefined;
private dimensionsSubscription: NativeEventSubscription | undefined;
private dimensionsWindowSubscription: NativeEventSubscription | undefined;
private keyboardDidShowListener: EmitterSubscription | undefined;
private keyboardDidHideListener: EmitterSubscription | undefined;
private keyboardHeight = 0;

private isCoordinate = (anchor: any): anchor is { x: number; y: number } =>
!React.isValidElement(anchor) &&
Expand Down Expand Up @@ -384,11 +395,32 @@ class Menu extends React.Component<Props, State> {
};

private keyboardDidShow = (e: RNKeyboardEvent) => {
this.keyboardHeight = e.endCoordinates.height;
const keyboardHeight = e.endCoordinates.height;
const { height, width } = Dimensions.get('window');

this.setState({
windowLayout: {
height: height - keyboardHeight,
width,
},
});
};

private keyboardDidHide = () => {
this.keyboardHeight = 0;
const { height } = Dimensions.get('window');

this.setState((state) => ({
windowLayout: {
width: state.windowLayout.width,
height,
},
}));
};

private onWindowChangeHandler = ({ window }: { window: ScaledSize }) => {
this.setState({
windowLayout: { height: window.height, width: window.width },
});
};

render() {
Expand Down Expand Up @@ -441,14 +473,14 @@ class Menu extends React.Component<Props, State> {
},
];

const windowLayout = { ...WINDOW_LAYOUT };
windowLayout.height = windowLayout.height - this.keyboardHeight;

// We need to translate menu while animating scale to imitate transform origin for scale animation
const positionTransforms = [];

// Check if menu fits horizontally and if not align it to right.
if (left <= windowLayout.width - menuLayout.width - SCREEN_INDENT) {
if (
left <=
this.state.windowLayout.width - menuLayout.width - SCREEN_INDENT
) {
positionTransforms.push({
translateX: scaleAnimation.x.interpolate({
inputRange: [0, menuLayout.width],
Expand All @@ -472,8 +504,8 @@ class Menu extends React.Component<Props, State> {

const right = left + menuLayout.width;
// Check if menu position has enough space from right side
if (right > windowLayout.width - SCREEN_INDENT) {
left = windowLayout.width - SCREEN_INDENT - menuLayout.width;
if (right > this.state.windowLayout.width - SCREEN_INDENT) {
left = this.state.windowLayout.width - SCREEN_INDENT - menuLayout.width;
}
}

Expand All @@ -485,25 +517,28 @@ class Menu extends React.Component<Props, State> {
if (
// Check if the menu overflows from bottom side
top >=
windowLayout.height -
this.state.windowLayout.height -
menuLayout.height -
SCREEN_INDENT -
additionalVerticalValue &&
// And bottom side of the screen has more space than top side
top <= windowLayout.height - top
top <= this.state.windowLayout.height - top
) {
// Scrollable menu should be below the anchor (expands downwards)
scrollableMenuHeight =
windowLayout.height - top - SCREEN_INDENT - additionalVerticalValue;
this.state.windowLayout.height -
top -
SCREEN_INDENT -
additionalVerticalValue;
} else if (
// Check if the menu overflows from bottom side
top >=
windowLayout.height -
this.state.windowLayout.height -
menuLayout.height -
SCREEN_INDENT -
additionalVerticalValue &&
// And top side of the screen has more space than bottom side
top >= windowLayout.height - top &&
top >= this.state.windowLayout.height - top &&
// And menu overflows from top side
top <=
menuLayout.height -
Expand All @@ -518,27 +553,27 @@ class Menu extends React.Component<Props, State> {

// Scrollable menu max height
scrollableMenuHeight =
scrollableMenuHeight > windowLayout.height - 2 * SCREEN_INDENT
? windowLayout.height - 2 * SCREEN_INDENT
scrollableMenuHeight > this.state.windowLayout.height - 2 * SCREEN_INDENT
? this.state.windowLayout.height - 2 * SCREEN_INDENT
: scrollableMenuHeight;

// Menu is typically positioned below the element that generates it
// So first check if it fits below the anchor (expands downwards)
if (
// Check if menu fits vertically
top <=
windowLayout.height -
this.state.windowLayout.height -
menuLayout.height -
SCREEN_INDENT -
additionalVerticalValue ||
// Or if the menu overflows from bottom side
(top >=
windowLayout.height -
this.state.windowLayout.height -
menuLayout.height -
SCREEN_INDENT -
additionalVerticalValue &&
// And bottom side of the screen has more space than top side
top <= windowLayout.height - top)
top <= this.state.windowLayout.height - top)
) {
positionTransforms.push({
translateY: scaleAnimation.y.interpolate({
Expand Down Expand Up @@ -567,11 +602,12 @@ class Menu extends React.Component<Props, State> {
additionalVerticalValue;

// Check if menu position has enough space from bottom side
if (bottom > windowLayout.height - SCREEN_INDENT) {
if (bottom > this.state.windowLayout.height - SCREEN_INDENT) {
top =
scrollableMenuHeight === windowLayout.height - 2 * SCREEN_INDENT
scrollableMenuHeight ===
this.state.windowLayout.height - 2 * SCREEN_INDENT
? -SCREEN_INDENT * 2
: windowLayout.height -
: this.state.windowLayout.height -
menuLayout.height -
SCREEN_INDENT -
additionalVerticalValue;
Expand Down

0 comments on commit 031f0db

Please sign in to comment.