1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge pull request #3866 from lensapp/issue-3383-menu-position-bug

Fix menu rendering on when slightly off screen
This commit is contained in:
Juho Heikka 2021-09-29 09:36:25 +03:00 committed by GitHub
commit b41f923931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 49 additions and 37 deletions

View File

@ -35,14 +35,6 @@
&.portal { &.portal {
left: -1000px; left: -1000px;
top: -1000px; top: -1000px;
&.top {
margin-top: -$margin;
}
&.bottom {
margin-top: $margin;
}
} }
&:not(.portal) { &:not(.portal) {

View File

@ -26,6 +26,7 @@ import { createPortal } from "react-dom";
import { autoBind, cssNames, noop } from "../../utils"; import { autoBind, cssNames, noop } from "../../utils";
import { Animate } from "../animate"; import { Animate } from "../animate";
import { Icon, IconProps } from "../icon"; import { Icon, IconProps } from "../icon";
import isEqual from "lodash/isEqual";
export const MenuContext = React.createContext<MenuContextValue>(null); export const MenuContext = React.createContext<MenuContextValue>(null);
export type MenuContextValue = Menu; export type MenuContextValue = Menu;
@ -37,6 +38,10 @@ export interface MenuPosition {
bottom?: boolean; bottom?: boolean;
} }
export interface MenuStyle {
top: string;
left: string;
}
export interface MenuProps { export interface MenuProps {
isOpen?: boolean; isOpen?: boolean;
open(): void; open(): void;
@ -56,6 +61,7 @@ export interface MenuProps {
interface State { interface State {
position?: MenuPosition; position?: MenuPosition;
menuStyle?: MenuStyle
} }
const defaultPropsMenu: Partial<MenuProps> = { const defaultPropsMenu: Partial<MenuProps> = {
@ -75,7 +81,6 @@ export class Menu extends React.Component<MenuProps, State> {
super(props); super(props);
autoBind(this); autoBind(this);
} }
public opener: HTMLElement; public opener: HTMLElement;
public elem: HTMLUListElement; public elem: HTMLUListElement;
protected items: { [index: number]: MenuItem } = {}; protected items: { [index: number]: MenuItem } = {};
@ -119,6 +124,12 @@ export class Menu extends React.Component<MenuProps, State> {
window.removeEventListener("scroll", this.onScrollOutside, true); window.removeEventListener("scroll", this.onScrollOutside, true);
} }
componentDidUpdate(prevProps: MenuProps) {
if (!isEqual(prevProps.children, this.props.children)) {
this.refreshPosition();
}
}
protected get focusableItems() { protected get focusableItems() {
return Object.values(this.items).filter(item => item.isFocusable); return Object.values(this.items).filter(item => item.isFocusable);
} }
@ -150,39 +161,44 @@ export class Menu extends React.Component<MenuProps, State> {
return; return;
} }
const { width, height } = this.opener.getBoundingClientRect(); const openerClientRect = this.opener.getBoundingClientRect();
let { left, top, bottom, right } = this.opener.getBoundingClientRect(); let { left: openerLeft, top: openerTop, bottom: openerBottom, right: openerRight } = this.opener.getBoundingClientRect();
const withScroll = window.getComputedStyle(this.elem).position !== "fixed"; const withScroll = window.getComputedStyle(this.elem).position !== "fixed";
// window global scroll corrections // window global scroll corrections
if (withScroll) { if (withScroll) {
left += window.pageXOffset; openerLeft += window.pageXOffset;
top += window.pageYOffset; openerTop += window.pageYOffset;
right = left + width; openerRight = openerLeft + openerClientRect.width;
bottom = top + height; openerBottom = openerTop + openerClientRect.height;
} }
// setup initial position const extraMargin = this.props.usePortal ? 8 : 0;
const position: MenuPosition = { left: true, bottom: true };
this.elem.style.left = `${left}px`; const { width: menuWidth, height: menuHeight } = this.elem.getBoundingClientRect();
this.elem.style.top = `${bottom}px`;
// correct position if menu doesn't fit to viewport const rightSideOfMenu = openerLeft + menuWidth;
const menuPos = this.elem.getBoundingClientRect(); const renderMenuLeft = rightSideOfMenu > window.innerWidth;
const menuOnLeftSidePosition = `${openerRight - this.elem.offsetWidth}px`;
const menuOnRightSidePosition = `${openerLeft}px`;
if (menuPos.right > window.innerWidth) { const bottomOfMenu = openerBottom + extraMargin + menuHeight;
this.elem.style.left = `${right - this.elem.offsetWidth}px`; const renderMenuOnTop = bottomOfMenu > window.innerHeight;
position.right = true; const menuOnTopPosition = `${openerTop - this.elem.offsetHeight - extraMargin}px`;
delete position.left; const menuOnBottomPosition = `${openerBottom + extraMargin}px`;
}
if (menuPos.bottom > window.innerHeight) { this.setState({
this.elem.style.top = `${top - this.elem.offsetHeight}px`; position: {
position.top = true; top: renderMenuOnTop,
delete position.bottom; bottom: !renderMenuOnTop,
} left: renderMenuLeft,
this.setState({ position }); right: !renderMenuLeft
},
menuStyle: {
top: renderMenuOnTop ? menuOnTopPosition : menuOnBottomPosition,
left: renderMenuLeft ? menuOnLeftSidePosition : menuOnRightSidePosition,
}
});
}; };
open() { open() {
@ -276,10 +292,6 @@ export class Menu extends React.Component<MenuProps, State> {
} }
render() { render() {
if (this.isOpen) {
setImmediate(() => this.refreshPosition());
}
const { position, id } = this.props; const { position, id } = this.props;
let { className, usePortal } = this.props; let { className, usePortal } = this.props;
@ -305,7 +317,15 @@ export class Menu extends React.Component<MenuProps, State> {
const menu = ( const menu = (
<MenuContext.Provider value={this}> <MenuContext.Provider value={this}>
<Animate enter={this.isOpen}> <Animate enter={this.isOpen}>
<ul id={id} className={className} ref={this.bindRef}> <ul
id={id}
ref={this.bindRef}
className={className}
style={{
left: this.state?.menuStyle?.left,
top: this.state?.menuStyle?.top
}}
>
{menuItems} {menuItems}
</ul> </ul>
</Animate> </Animate>