Add MenuWrapper component

This commit is contained in:
Jesús Espino 2020-10-15 01:18:23 +02:00
parent a4ff9466e2
commit 962c035eab
5 changed files with 128 additions and 61 deletions

View file

@ -6,6 +6,7 @@ import { IPropertyOption } from "../board"
import { BoardTree } from "../boardTree"
import { CardFilter } from "../cardFilter"
import ViewMenu from "../components/viewMenu"
import MenuWrapper from "../widgets/menuWrapper"
import { Constants } from "../constants"
import { Menu as OldMenu } from "../menu"
import { Mutator } from "../mutator"
@ -29,7 +30,6 @@ type Props = {
type State = {
isHoverOnCover: boolean
isSearching: boolean
viewMenu: boolean
}
class BoardComponent extends React.Component<Props, State> {
@ -39,7 +39,7 @@ class BoardComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { isHoverOnCover: false, isSearching: !!this.props.boardTree?.getSearchText(), viewMenu: false }
this.state = { isHoverOnCover: false, isSearching: !!this.props.boardTree?.getSearchText()}
}
componentDidUpdate(prevPros: Props, prevState: State) {
@ -93,21 +93,20 @@ class BoardComponent extends React.Component<Props, State> {
<div className="octo-board">
<div className="octo-controls">
<Editable style={{ color: "#000000", fontWeight: 600 }} text={activeView.title} placeholderText="Untitled View" onChanged={(text) => { mutator.changeTitle(activeView, text) }} />
<div
className="octo-button"
style={{ color: "#000000", fontWeight: 600 }}
onClick={() => this.setState({ viewMenu: true })}
>
{this.state.viewMenu &&
<ViewMenu
board={board}
onClose={() => this.setState({ viewMenu: false })}
mutator={mutator}
boardTree={boardTree}
showView={showView}
/>}
<div className="imageDropdown"></div>
</div>
<MenuWrapper>
<div
className="octo-button"
style={{ color: "#000000", fontWeight: 600 }}
>
<div className="imageDropdown"></div>
</div>
<ViewMenu
board={board}
mutator={mutator}
boardTree={boardTree}
showView={showView}
/>
</MenuWrapper>
<div className="octo-spacer"></div>
<div className="octo-button" onClick={(e) => { this.propertiesClicked(e) }}>Properties</div>
<div className="octo-button" id="groupByButton" onClick={(e) => { this.groupByClicked(e) }}>

View file

@ -6,6 +6,7 @@ import { IPropertyTemplate } from "../board"
import { BoardTree } from "../boardTree"
import { CsvExporter } from "../csvExporter"
import ViewMenu from "../components/viewMenu"
import MenuWrapper from "../widgets/menuWrapper"
import { Menu as OldMenu } from "../menu"
import { Mutator } from "../mutator"
import { IBlock } from "../octoTypes"
@ -90,21 +91,20 @@ class TableComponent extends React.Component<Props, State> {
<div className="octo-table">
<div className="octo-controls">
<Editable style={{ color: "#000000", fontWeight: 600 }} text={activeView.title} placeholderText="Untitled View" onChanged={(text) => { mutator.changeTitle(activeView, text) }} />
<div
className="octo-button"
style={{ color: "#000000", fontWeight: 600 }}
onClick={() => this.setState({ viewMenu: true })}
>
{this.state.viewMenu &&
<ViewMenu
board={board}
onClose={() => this.setState({ viewMenu: false })}
mutator={mutator}
boardTree={boardTree}
showView={showView}
/>}
<div className="imageDropdown"></div>
</div>
<MenuWrapper>
<div
className="octo-button"
style={{ color: "#000000", fontWeight: 600 }}
>
<div className="imageDropdown"></div>
</div>
<ViewMenu
board={board}
mutator={mutator}
boardTree={boardTree}
showView={showView}
/>
</MenuWrapper>
<div className="octo-spacer"></div>
<div className="octo-button" onClick={(e) => { this.propertiesClicked(e) }}>Properties</div>
<div className={hasFilter ? "octo-button active" : "octo-button"} onClick={(e) => { this.filterClicked(e) }}>Filter</div>

View file

@ -11,7 +11,6 @@ type Props = {
boardTree?: BoardTree
board: Board,
showView: (id: string) => void
onClose: () => void,
}
export default class ViewMenu extends React.Component<Props> {
@ -68,9 +67,9 @@ export default class ViewMenu extends React.Component<Props> {
}
render() {
const { onClose, boardTree } = this.props
const { boardTree } = this.props
return (
<Menu onClose={onClose}>
<Menu>
{boardTree.views.map((view) => (<Menu.Text key={view.id} id={view.id} name={view.title} onClick={this.handleViewClick} />))}
<Menu.Separator />
{boardTree.views.length > 1 && <Menu.Text id="__deleteView" name="Delete View" onClick={this.handleDeleteView} />}

View file

@ -40,7 +40,7 @@ class SubMenuOption extends React.Component<SubMenuOptionProps, SubMenuState> {
<div className='name menu-name'>{this.props.name}</div>
<div className="imageSubmenuTriangle" style={{float: 'right'}}></div>
{this.state.isOpen &&
<Menu onClose={this.close}>
<Menu>
{this.props.children}
</Menu>
}
@ -113,7 +113,6 @@ class TextOption extends React.Component<TextOptionProps> {
type MenuProps = {
children: React.ReactNode
onClose: () => void
}
export default class Menu extends React.Component<MenuProps> {
@ -123,30 +122,6 @@ export default class Menu extends React.Component<MenuProps> {
static Separator = SeparatorOption
static Text = TextOption
onBodyClick = (e: MouseEvent) => {
this.props.onClose()
}
onBodyKeyDown = (e: KeyboardEvent) => {
// Ignore keydown events on other elements
if (e.target !== document.body) { return }
if (e.keyCode === 27) {
// ESC
this.props.onClose()
e.stopPropagation()
}
}
componentDidMount() {
document.addEventListener("click", this.onBodyClick)
document.addEventListener("keydown", this.onBodyKeyDown)
}
componentWillUnmount() {
document.removeEventListener("click", this.onBodyClick)
document.removeEventListener("keydown", this.onBodyKeyDown)
}
render() {
return (
<div className="Menu menu noselect">

View file

@ -0,0 +1,94 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
type Props = {
children?: React.ReactNode;
onToggle?: (open: boolean) => void;
isDisabled?: boolean;
stopPropagationOnToggle?: boolean;
}
type State = {
open: boolean;
}
export default class MenuWrapper extends React.PureComponent<Props, State> {
private node: React.RefObject<HTMLDivElement>;
public constructor(props: Props) {
super(props);
if (!Array.isArray(props.children) || props.children.length !== 2) {
throw new Error('MenuWrapper needs exactly 2 children');
}
this.state = {
open: false,
};
this.node = React.createRef();
}
public componentDidMount() {
document.addEventListener('click', this.closeOnBlur, true);
document.addEventListener('keyup', this.keyboardClose, true);
}
public componentWillUnmount() {
document.removeEventListener('click', this.closeOnBlur, true);
document.removeEventListener('keyup', this.keyboardClose, true);
}
private keyboardClose = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
this.close();
}
if (e.key === 'Tab') {
this.closeOnBlur(e);
}
}
private closeOnBlur = (e: Event) => {
if (this.node && this.node.current && e.target && this.node.current.contains(e.target as Node)) {
return;
}
this.close();
}
public close = () => {
if (this.state.open) {
this.setState({open: false});
}
}
toggle = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
/**
* This is only here so that we can toggle the menus in the sidebar, because the default behavior of the mobile
* version (ie the one that uses a modal) needs propagation to close the modal after selecting something
* We need to refactor this so that the modal is explicitly closed on toggle, but for now I am aiming to preserve the existing logic
* so as to not break other things
**/
if (this.props.stopPropagationOnToggle) {
e.preventDefault();
e.stopPropagation();
}
const newState = !this.state.open;
this.setState({open: newState});
}
public render() {
const {children} = this.props;
return (
<div
className={'MenuWrapper'}
onClick={this.toggle}
ref={this.node}
>
{children && !this.state.open ? Object.values(children)[0] : null}
{children && this.state.open ? Object.values(children)[1] : null}
</div>
);
}
}