diff --git a/app/components/DropdownMenu/DropdownMenu.js b/app/components/DropdownMenu/DropdownMenu.js index cfeeeab3..a1cbc6a3 100644 --- a/app/components/DropdownMenu/DropdownMenu.js +++ b/app/components/DropdownMenu/DropdownMenu.js @@ -28,9 +28,15 @@ type Props = { @observer class DropdownMenu extends React.Component { - @observable top: number; - @observable right: number; - @observable left: number; + @observable top: ?number; + @observable bottom: ?number; + @observable right: ?number; + @observable left: ?number; + @observable position: 'left' | 'right' | 'center'; + @observable fixed: ?boolean; + @observable bodyRect: ClientRect; + @observable labelRect: ClientRect; + @observable dropdownRef: { current: null | HTMLElement } = React.createRef(); handleOpen = ( openPortal: (SyntheticEvent<>) => void, @@ -42,18 +48,25 @@ class DropdownMenu extends React.Component { invariant(document.body, 'why you not here'); if (currentTarget instanceof HTMLDivElement) { - const bodyRect = document.body.getBoundingClientRect(); - const targetRect = currentTarget.getBoundingClientRect(); - this.top = targetRect.bottom - bodyRect.top; + this.bodyRect = document.body.getBoundingClientRect(); + this.labelRect = currentTarget.getBoundingClientRect(); + this.top = this.labelRect.bottom - this.bodyRect.top; + this.bottom = undefined; + this.position = this.props.position || 'left'; - if (this.props.position === 'left') { - this.left = targetRect.left; - } else if (this.props.position === 'center') { - this.left = targetRect.left + targetRect.width / 2; - } else { - this.right = bodyRect.width - targetRect.left - targetRect.width; + if (currentTarget.parentElement) { + const triggerParentStyle = getComputedStyle( + currentTarget.parentElement + ); + + if (triggerParentStyle.position === 'static') { + this.fixed = true; + this.top = this.labelRect.bottom; + } } + this.initPosition(); + // attempt to keep only one flyout menu open at once if (previousClosePortal) { previousClosePortal(); @@ -64,13 +77,65 @@ class DropdownMenu extends React.Component { }; }; + initPosition() { + if (this.position === 'left') { + this.right = + this.bodyRect.width - this.labelRect.left - this.labelRect.width; + } else if (this.position === 'center') { + this.left = this.labelRect.left + this.labelRect.width / 2; + } else { + this.left = this.labelRect.left; + } + } + + onOpen(originalFunction?: () => void) { + if (typeof originalFunction === 'function') { + originalFunction(); + } + this.fitOnTheScreen(); + } + + fitOnTheScreen() { + if (!this.dropdownRef || !this.dropdownRef.current) return; + const el = this.dropdownRef.current; + + const sticksOutPastBottomEdge = + el.clientHeight + this.top > window.innerHeight; + if (sticksOutPastBottomEdge) { + this.top = undefined; + this.bottom = this.fixed ? 0 : -1 * window.pageYOffset; + } else { + this.bottom = undefined; + } + + if (this.position === 'left' || this.position === 'right') { + const totalWidth = + Math.sign(this.position === 'left' ? -1 : 1) * el.offsetLeft + + el.scrollWidth; + const isVisible = totalWidth < window.innerWidth; + + if (!isVisible) { + if (this.position === 'right') { + this.position = 'left'; + this.left = undefined; + } else if (this.position === 'left') { + this.position = 'right'; + this.right = undefined; + } + } + } + + this.initPosition(); + this.forceUpdate(); + } + render() { - const { className, label, position, children } = this.props; + const { className, label, children } = this.props; return (
{ {portal( @@ -125,10 +193,13 @@ const Label = styled(Flex).attrs({ `; const Position = styled.div` - position: absolute; + position: ${({ fixed }) => (fixed ? 'fixed' : 'absolute')}; + display: flex; ${({ left }) => (left !== undefined ? `left: ${left}px` : '')}; ${({ right }) => (right !== undefined ? `right: ${right}px` : '')}; - top: ${({ top }) => top}px; + ${({ top }) => (top !== undefined ? `top: ${top}px` : '')}; + ${({ bottom }) => (bottom !== undefined ? `bottom: ${bottom}px` : '')}; + max-height: 75%; z-index: 1000; transform: ${props => props.position === 'center' ? 'translateX(-50%)' : 'initial'}; @@ -142,6 +213,7 @@ const Menu = styled.div` padding: 0.5em 0; min-width: 180px; overflow: hidden; + overflow-y: auto; box-shadow: ${props => props.theme.menuShadow}; @media print { diff --git a/app/components/Sidebar/components/CollectionLink.js b/app/components/Sidebar/components/CollectionLink.js index af689923..ce76b78d 100644 --- a/app/components/Sidebar/components/CollectionLink.js +++ b/app/components/Sidebar/components/CollectionLink.js @@ -62,7 +62,7 @@ class CollectionLink extends React.Component { exact={false} menu={ (this.menuOpen = true)} onClose={() => (this.menuOpen = false)} diff --git a/app/components/Sidebar/components/DocumentLink.js b/app/components/Sidebar/components/DocumentLink.js index 3352ea5f..952ec0df 100644 --- a/app/components/Sidebar/components/DocumentLink.js +++ b/app/components/Sidebar/components/DocumentLink.js @@ -81,7 +81,7 @@ class DocumentLink extends React.Component { document ? ( (this.menuOpen = true)} onClose={() => (this.menuOpen = false)} diff --git a/app/scenes/Search/components/FilterOptions.js b/app/scenes/Search/components/FilterOptions.js index a2aa8b14..9218f7f4 100644 --- a/app/scenes/Search/components/FilterOptions.js +++ b/app/scenes/Search/components/FilterOptions.js @@ -83,7 +83,7 @@ const SearchFilter = props => { {props.label} } - position="left" + position="right" > {({ closePortal }) => (