Skip to content

Commit

Permalink
Merge pull request #4870 from matrix-org/t3chguy/room-list/2
Browse files Browse the repository at this point in the history
Room List v2 context menu interactions
  • Loading branch information
t3chguy committed Jul 2, 2020
2 parents eac4eb7 + 4b27a67 commit c4bbdef
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 65 deletions.
21 changes: 18 additions & 3 deletions src/components/structures/ContextMenu.js
Expand Up @@ -116,6 +116,7 @@ export class ContextMenu extends React.Component {
this.props.onFinished();

e.preventDefault();
e.stopPropagation();
const x = e.clientX;
const y = e.clientY;

Expand All @@ -133,6 +134,12 @@ export class ContextMenu extends React.Component {
}
};

onContextMenuPreventBubbling = (e) => {
// stop propagation so that any context menu handlers don't leak out of this context menu
// but do not inhibit the default browser menu
e.stopPropagation();
};

_onMoveFocus = (element, up) => {
let descending = false; // are we currently descending or ascending through the DOM tree?

Expand Down Expand Up @@ -324,7 +331,7 @@ export class ContextMenu extends React.Component {
}

return (
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown} onContextMenu={this.onContextMenuPreventBubbling}>
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} role={this.props.managed ? "menu" : undefined}>
{ chevron }
{ props.children }
Expand All @@ -340,10 +347,18 @@ export class ContextMenu extends React.Component {
}

// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
export const ContextMenuButton = ({ label, isExpanded, children, ...props }) => {
export const ContextMenuButton = ({ label, isExpanded, children, onClick, onContextMenu, ...props }) => {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (
<AccessibleButton {...props} title={label} aria-label={label} aria-haspopup={true} aria-expanded={isExpanded}>
<AccessibleButton
{...props}
onClick={onClick}
onContextMenu={onContextMenu || onClick}
title={label}
aria-label={label}
aria-haspopup={true}
aria-expanded={isExpanded}
>
{ children }
</AccessibleButton>
);
Expand Down
40 changes: 27 additions & 13 deletions src/components/structures/UserMenu.tsx
Expand Up @@ -42,8 +42,10 @@ interface IProps {
isMinimized: boolean;
}

type PartialDOMRect = Pick<DOMRect, "width" | "left" | "top" | "height">;

interface IState {
menuDisplayed: boolean;
contextMenuPosition: PartialDOMRect;
isDarkTheme: boolean;
}

Expand All @@ -56,7 +58,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
super(props);

this.state = {
menuDisplayed: false,
contextMenuPosition: null,
isDarkTheme: this.isUserOnDarkTheme(),
};

Expand Down Expand Up @@ -106,13 +108,25 @@ export default class UserMenu extends React.Component<IProps, IState> {
private onOpenMenuClick = (ev: InputEvent) => {
ev.preventDefault();
ev.stopPropagation();
this.setState({menuDisplayed: true});
const target = ev.target as HTMLButtonElement;
this.setState({contextMenuPosition: target.getBoundingClientRect()});
};

private onCloseMenu = (ev: InputEvent) => {
private onContextMenu = (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
this.setState({menuDisplayed: false});
this.setState({
contextMenuPosition: {
left: ev.clientX,
top: ev.clientY,
width: 20,
height: 0,
},
});
};

private onCloseMenu = () => {
this.setState({contextMenuPosition: null});
};

private onSwitchThemeClick = () => {
Expand All @@ -129,7 +143,7 @@ export default class UserMenu extends React.Component<IProps, IState> {

const payload: OpenToTabPayload = {action: Action.ViewUserSettings, initialTabId: tabId};
defaultDispatcher.dispatch(payload);
this.setState({menuDisplayed: false}); // also close the menu
this.setState({contextMenuPosition: null}); // also close the menu
};

private onShowArchived = (ev: ButtonEvent) => {
Expand All @@ -145,15 +159,15 @@ export default class UserMenu extends React.Component<IProps, IState> {
ev.stopPropagation();

Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
this.setState({menuDisplayed: false}); // also close the menu
this.setState({contextMenuPosition: null}); // also close the menu
};

private onSignOutClick = (ev: ButtonEvent) => {
ev.preventDefault();
ev.stopPropagation();

Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog);
this.setState({menuDisplayed: false}); // also close the menu
this.setState({contextMenuPosition: null}); // also close the menu
};

private onHomeClick = (ev: ButtonEvent) => {
Expand All @@ -164,7 +178,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
};

private renderContextMenu = (): React.ReactNode => {
if (!this.state.menuDisplayed) return null;
if (!this.state.contextMenuPosition) return null;

let hostingLink;
const signupLink = getHostingLink("user-context-menu");
Expand Down Expand Up @@ -198,13 +212,12 @@ export default class UserMenu extends React.Component<IProps, IState> {
);
}

const elementRect = this.buttonRef.current.getBoundingClientRect();
return (
<ContextMenu
chevronFace="none"
// -20 to overlap the context menu by just over the width of the `...` icon and make it look connected
left={elementRect.width + elementRect.left - 20}
top={elementRect.top + elementRect.height}
left={this.state.contextMenuPosition.width + this.state.contextMenuPosition.left - 20}
top={this.state.contextMenuPosition.top + this.state.contextMenuPosition.height}
onFinished={this.onCloseMenu}
>
<div className="mx_IconizedContextMenu mx_UserMenu_contextMenu">
Expand Down Expand Up @@ -290,7 +303,8 @@ export default class UserMenu extends React.Component<IProps, IState> {
onClick={this.onOpenMenuClick}
inputRef={this.buttonRef}
label={_t("Account settings")}
isExpanded={this.state.menuDisplayed}
isExpanded={!!this.state.contextMenuPosition}
onContextMenu={this.onContextMenu}
>
<div className="mx_UserMenu_row">
<span className="mx_UserMenu_userAvatarContainer">
Expand Down
42 changes: 27 additions & 15 deletions src/components/views/rooms/RoomSublist2.tsx
Expand Up @@ -69,22 +69,21 @@ interface IProps {
// TODO: Account for https://github.com/vector-im/riot-web/issues/14179
}

type PartialDOMRect = Pick<DOMRect, "left" | "top" | "height">;

interface IState {
notificationState: ListNotificationState;
menuDisplayed: boolean;
contextMenuPosition: PartialDOMRect;
isResizing: boolean;
}

export default class RoomSublist2 extends React.Component<IProps, IState> {
private headerButton = createRef();
private menuButtonRef: React.RefObject<HTMLButtonElement> = createRef();

constructor(props: IProps) {
super(props);

this.state = {
notificationState: new ListNotificationState(this.props.isInvite, this.props.tagId),
menuDisplayed: false,
contextMenuPosition: null,
isResizing: false,
};
this.state.notificationState.setRooms(this.props.rooms);
Expand Down Expand Up @@ -141,11 +140,24 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
private onOpenMenuClick = (ev: InputEvent) => {
ev.preventDefault();
ev.stopPropagation();
this.setState({menuDisplayed: true});
const target = ev.target as HTMLButtonElement;
this.setState({contextMenuPosition: target.getBoundingClientRect()});
};

private onContextMenu = (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
this.setState({
contextMenuPosition: {
left: ev.clientX,
top: ev.clientY,
height: 0,
},
});
};

private onCloseMenu = () => {
this.setState({menuDisplayed: false});
this.setState({contextMenuPosition: null});
};

private onUnreadFirstChanged = async () => {
Expand Down Expand Up @@ -227,15 +239,14 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
}

let contextMenu = null;
if (this.state.menuDisplayed) {
const elementRect = this.menuButtonRef.current.getBoundingClientRect();
if (this.state.contextMenuPosition) {
const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic;
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
contextMenu = (
<ContextMenu
chevronFace="none"
left={elementRect.left}
top={elementRect.top + elementRect.height}
left={this.state.contextMenuPosition.left}
top={this.state.contextMenuPosition.top + this.state.contextMenuPosition.height}
onFinished={this.onCloseMenu}
>
<div className="mx_RoomSublist2_contextMenu">
Expand Down Expand Up @@ -286,9 +297,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
<ContextMenuButton
className="mx_RoomSublist2_menuButton"
onClick={this.onOpenMenuClick}
inputRef={this.menuButtonRef}
label={_t("List options")}
isExpanded={this.state.menuDisplayed}
isExpanded={!!this.state.contextMenuPosition}
/>
{contextMenu}
</React.Fragment>
Expand All @@ -297,7 +307,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {

private renderHeader(): React.ReactElement {
return (
<RovingTabIndexWrapper inputRef={this.headerButton}>
<RovingTabIndexWrapper>
{({onFocus, isActive, ref}) => {
// TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180
const tabIndex = isActive ? 0 : -1;
Expand Down Expand Up @@ -342,12 +352,14 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
<div className={classes}>
<div className='mx_RoomSublist2_stickable'>
<AccessibleButton
onFocus={onFocus}
inputRef={ref}
tabIndex={tabIndex}
className={"mx_RoomSublist2_headerText"}
role="treeitem"
aria-level={1}
onClick={this.onHeaderClick}
onContextMenu={this.onContextMenu}
>
<span className={collapseClasses} />
<span>{this.props.label}</span>
Expand All @@ -372,7 +384,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {

const classes = classNames({
'mx_RoomSublist2': true,
'mx_RoomSublist2_hasMenuOpen': this.state.menuDisplayed,
'mx_RoomSublist2_hasMenuOpen': !!this.state.contextMenuPosition,
'mx_RoomSublist2_minimized': this.props.isMinimized,
});

Expand Down

0 comments on commit c4bbdef

Please sign in to comment.