Fixed issue with sidebar menu position (#3426)

* Fixed issue with sidebar menu position

* Removed duplicate code

* Handled small screens as well

* Fixed a class name
This commit is contained in:
Harshil Sharma 2022-07-28 21:06:09 +05:30 committed by GitHub
parent 4f7ce070bc
commit b9ac00ef4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 130 additions and 16 deletions

View file

@ -163,6 +163,19 @@
right: calc(100% - 480px + 50px);
left: calc(240px - 50px);
}
.boardMoveToCategorySubmenu {
.menu-options {
max-height: 600px;
overflow-y: auto;
}
@media only screen and (max-height: 768px) {
.menu-options {
max-height: min(350px, 50vh);
}
}
}
}
.team-sidebar + .product-wrapper {

View file

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useState} from 'react'
import React, {useCallback, useRef, useState} from 'react'
import {useIntl} from 'react-intl'
import {useHistory, useRouteMatch} from "react-router-dom"
@ -106,12 +106,15 @@ const SidebarBoardItem = (props: Props) => {
}, [board.id])
const boardItemRef = useRef<HTMLDivElement>(null)
const title = board.title || intl.formatMessage({id: 'Sidebar.untitled-board', defaultMessage: '(Untitled Board)'})
return (
<>
<div
className={`SidebarBoardItem subitem ${props.isActive ? 'active' : ''}`}
onClick={() => props.showBoard(board.id)}
ref={boardItemRef}
>
<div className='octo-sidebar-icon'>
{board.icon || <BoardIcon/>}
@ -136,7 +139,8 @@ const SidebarBoardItem = (props: Props) => {
<IconButton icon={<OptionsIcon/>}/>
<Menu
fixed={true}
position='left'
position='auto'
parentRef={boardItemRef}
>
<BoardPermissionGate
boardId={board.id}
@ -155,9 +159,10 @@ const SidebarBoardItem = (props: Props) => {
<Menu.SubMenu
key={`moveBlock-${board.id}`}
id='moveBlock'
className='boardMoveToCategorySubmenu'
name={intl.formatMessage({id: 'SidebarCategories.BlocksMenu.Move', defaultMessage: 'Move To...'})}
icon={<CreateNewFolder/>}
position='bottom'
position='auto'
>
{generateMoveToCategoryOptions(board.id)}
</Menu.SubMenu>

View file

@ -154,9 +154,12 @@
}
}
.Menu.noselect.left {
.Menu.noselect:not(.SubMenu) {
position: fixed;
right: calc(100% - 480px + 50px);
left: calc(240px - 50px);
> .left {
right: calc(100% - 480px - 64px + 50px);
left: calc(64px + 240px - 50px);
}
}
}

View file

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useState} from 'react'
import React, {useCallback, useRef, useState} from 'react'
import {FormattedMessage, useIntl} from 'react-intl'
import {generatePath, useHistory, useRouteMatch} from 'react-router-dom'
@ -59,6 +59,8 @@ const SidebarCategory = (props: Props) => {
const team = useAppSelector(getCurrentTeam)
const teamID = team?.id || ''
const menuWrapperRef = useRef<HTMLDivElement>(null)
const showBoard = useCallback((boardId) => {
Utils.showBoard(boardId, match, history)
props.hideSidebar()
@ -138,7 +140,7 @@ const SidebarCategory = (props: Props) => {
}, [showBoard, deleteBoard, props.boards])
return (
<div className='SidebarCategory'>
<div className='SidebarCategory' ref={menuWrapperRef}>
<div
className={`octo-sidebar-item category ' ${collapsed ? 'collapsed' : 'expanded'} ${props.categoryBoards.id === props.activeCategoryId ? 'active' : ''}`}
>
@ -156,7 +158,10 @@ const SidebarCategory = (props: Props) => {
onToggle={(open) => setCategoryMenuOpen(open)}
>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu
position='auto'
parentRef={menuWrapperRef}
>
<Menu.Text
id='createNewCategory'
name={intl.formatMessage({id: 'SidebarCategories.CategoryMenu.CreateNew', defaultMessage: 'Create New Category'})}

View file

@ -1,6 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import React, {CSSProperties} from 'react'
import SeparatorOption from './separatorOption'
import SwitchOption from './switchOption'
@ -11,13 +11,16 @@ import LabelOption from './labelOption'
import './menu.scss'
import textInputOption from './textInputOption'
import MenuUtil from "./menuUtil"
type Props = {
children: React.ReactNode
position?: 'top' | 'bottom' | 'left' | 'right'
position?: 'top' | 'bottom' | 'left' | 'right' | 'auto'
fixed?: boolean
parentRef?: React.RefObject<any>
}
export default class Menu extends React.PureComponent<Props> {
static Color = ColorOption
static SubMenu = SubMenuOption
@ -27,14 +30,33 @@ export default class Menu extends React.PureComponent<Props> {
static TextInput = textInputOption
static Label = LabelOption
menuRef: React.RefObject<HTMLDivElement>
constructor(props: Props) {
super(props)
this.menuRef = React.createRef<HTMLDivElement>()
}
public state = {
hovering: null,
menuStyle: {},
}
public render(): JSX.Element {
const {position, fixed, children} = this.props
let style: CSSProperties = {}
if (position === 'auto' && this.props.parentRef) {
style = MenuUtil.openUp(this.props.parentRef).style
}
return (
<div className={`Menu noselect ${position || 'bottom'} ${fixed ? ' fixed' : ''}`}>
<div
className={`Menu noselect ${position || 'bottom'} ${fixed ? ' fixed' : ''}`}
style={style}
ref={this.menuRef}
>
<div className='menu-contents'>
<div className='menu-options'>
{React.Children.map(children, (child) => (

View file

@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {CSSProperties} from 'react'
/**
* Calculates if a menu should open aligned down or up around the `anchorRef` element.
* This should be used to make sure the menues are always fullly visible in cases
* when opening them close to the edges of screen.
* @param anchorRef ref of the element with respect to which the menu position is to be calculated.
* @param menuMargin a safe margin value to be ensured around the menu in the calculations.
* this ensures the menu stick to the edges of the screen ans has some space around for ease of use.
*/
function openUp(anchorRef: React.RefObject<HTMLElement>, menuMargin = 40): {openUp: boolean , style: CSSProperties} {
const ret = {
openUp: false,
style: {} as CSSProperties,
}
if (!anchorRef.current) {
return ret
}
const boundingRect = anchorRef.current.getBoundingClientRect()
const y = typeof boundingRect?.y === 'undefined' ? boundingRect?.top : boundingRect.y
const windowHeight = window.innerHeight
const totalSpace = windowHeight - menuMargin
const spaceOnTop = y || 0
const spaceOnBottom = totalSpace - spaceOnTop
ret.openUp = spaceOnTop > spaceOnBottom
if (ret.openUp) {
ret.style.bottom = spaceOnBottom + menuMargin
} else {
ret.style.top = spaceOnTop + menuMargin
}
return ret
}
export default {
openUp,
}

View file

@ -1,11 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useState, useContext} from 'react'
import React, {useEffect, useState, useContext, CSSProperties, useRef} from 'react'
import SubmenuTriangleIcon from '../icons/submenuTriangle'
import MenuUtil from './menuUtil'
import Menu from '.'
import './subMenuOption.scss'
export const HoveringContext = React.createContext(false)
@ -13,9 +16,10 @@ export const HoveringContext = React.createContext(false)
type SubMenuOptionProps = {
id: string
name: string
position?: 'bottom' | 'top' | 'left' | 'left-bottom'
position?: 'bottom' | 'top' | 'left' | 'left-bottom' | 'auto'
icon?: React.ReactNode
children: React.ReactNode
className?: string
}
function SubMenuOption(props: SubMenuOptionProps): JSX.Element {
@ -30,22 +34,44 @@ function SubMenuOption(props: SubMenuOptionProps): JSX.Element {
}
}, [isHovering])
const ref = useRef<HTMLDivElement>(null)
const styleRef = useRef<CSSProperties>({})
useEffect(() => {
const newStyle: CSSProperties = {}
if (props.position === 'auto' && ref.current) {
const openUp = MenuUtil.openUp(ref)
if (openUp.openUp) {
newStyle.bottom = 0
} else {
newStyle.top = 0
}
}
styleRef.current = newStyle
}, [ref.current])
return (
<div
id={props.id}
className={`MenuOption SubMenuOption menu-option${openLeftClass}${isOpen ? ' menu-option-active' : ''}`}
className={`MenuOption SubMenuOption menu-option${openLeftClass}${isOpen ? ' menu-option-active' : ''}${props.className ? ' ' + props.className : ''}`}
onClick={(e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setIsOpen((open) => !open)
}}
ref={ref}
>
{(props.position === 'left' || props.position === 'left-bottom') && <SubmenuTriangleIcon/>}
{props.icon ?? <div className='noicon'/>}
<div className='menu-name'>{props.name}</div>
{props.position !== 'left' && props.position !== 'left-bottom' && <SubmenuTriangleIcon/>}
{isOpen &&
<div className={'SubMenu Menu noselect ' + (props.position || 'bottom')}>
<div
className={'SubMenu Menu noselect ' + (props.position || 'bottom')}
style={styleRef.current}
>
<div className='menu-contents'>
<div className='menu-options'>
{props.children}