Merge pull request #3611 from matrix-org/t3chguy/context_menus

ARIA compliant context menus
This commit is contained in:
Michael Telatynski 2019-12-04 17:17:47 +00:00 committed by GitHub
commit be6da03348
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1093 additions and 853 deletions

View file

@ -17,8 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
@ -26,10 +25,9 @@ import dis from '../../../dispatcher';
import MatrixClientPeg from '../../../MatrixClientPeg';
import DMRoomMap from '../../../utils/DMRoomMap';
import sdk from '../../../index';
import {createMenu} from '../../structures/ContextualMenu';
import {ContextMenu, ContextMenuButton, toRightOf} from '../../structures/ContextMenu';
import * as RoomNotifs from '../../../RoomNotifs';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import AccessibleButton from '../elements/AccessibleButton';
import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
@ -147,6 +145,8 @@ module.exports = createReactClass({
},
componentDidMount: function() {
this._contextMenuButton = createRef();
const cli = MatrixClientPeg.get();
cli.on("accountData", this.onAccountData);
cli.on("Room.name", this.onRoomName);
@ -229,32 +229,6 @@ module.exports = createReactClass({
this.badgeOnMouseLeave();
},
_showContextMenu: function(x, y, chevronOffset) {
const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
createMenu(RoomTileContextMenu, {
chevronOffset,
left: x,
top: y,
room: this.props.room,
onFinished: () => {
this.setState({ menuDisplayed: false });
this.props.refreshSubList();
},
});
this.setState({ menuDisplayed: true });
},
onContextMenu: function(e) {
// Prevent the RoomTile onClick event firing as well
e.preventDefault();
// Only allow non-guests to access the context menu
if (MatrixClientPeg.get().isGuest()) return;
const chevronOffset = 12;
this._showContextMenu(e.clientX, e.clientY - (chevronOffset + 8), chevronOffset);
},
badgeOnMouseEnter: function() {
// Only allow non-guests to access the context menu
// and only change it if it needs to change
@ -267,26 +241,31 @@ module.exports = createReactClass({
this.setState( { badgeHover: false } );
},
onOpenMenu: function(e) {
// Prevent the RoomTile onClick event firing as well
e.stopPropagation();
openMenu: function(e) {
// Only allow non-guests to access the context menu
if (MatrixClientPeg.get().isGuest()) return;
// Prevent the RoomTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
const state = {
menuDisplayed: true,
};
// If the badge is clicked, then no longer show tooltip
if (this.props.collapsed) {
this.setState({ hover: false });
state.hover = false;
}
const elementRect = e.target.getBoundingClientRect();
this.setState(state);
},
// The window X and Y offsets are to adjust position when zoomed in to page
const x = elementRect.right + window.pageXOffset + 3;
const chevronOffset = 12;
let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
this._showContextMenu(x, y, chevronOffset);
closeMenu: function() {
this.setState({
menuDisplayed: false,
});
this.props.refreshSubList();
},
render: function() {
@ -360,9 +339,18 @@ module.exports = createReactClass({
// incomingCallBox = <IncomingCallBox incomingCall={ this.props.incomingCall }/>;
//}
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let contextMenuButton;
if (!MatrixClientPeg.get().isGuest()) {
contextMenuButton = <AccessibleButton className="mx_RoomTile_menuButton" onClick={this.onOpenMenu} />;
contextMenuButton = (
<ContextMenuButton
className="mx_RoomTile_menuButton"
inputRef={this._contextMenuButton}
label={_t("Options")}
isExpanded={this.state.menuDisplayed}
onClick={this.openMenu} />
);
}
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
@ -393,32 +381,48 @@ module.exports = createReactClass({
ariaLabel += " " + _t("Unread messages.");
}
return <AccessibleButton tabIndex="0"
className={classes}
onClick={this.onClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
onContextMenu={this.onContextMenu}
aria-label={ariaLabel}
aria-selected={this.state.selected}
role="treeitem"
>
<div className={avatarClasses}>
<div className="mx_RoomTile_avatar_container">
<RoomAvatar room={this.props.room} width={24} height={24} />
{ dmIndicator }
let contextMenu;
if (this.state.menuDisplayed && this._contextMenuButton.current) {
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
contextMenu = (
<ContextMenu {...toRightOf(elementRect)} onFinished={this.closeMenu}>
<RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} />
</ContextMenu>
);
}
return <React.Fragment>
<AccessibleButton
tabIndex="0"
className={classes}
onClick={this.onClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
onContextMenu={this.openMenu}
aria-label={ariaLabel}
aria-selected={this.state.selected}
role="treeitem"
>
<div className={avatarClasses}>
<div className="mx_RoomTile_avatar_container">
<RoomAvatar room={this.props.room} width={24} height={24} />
{ dmIndicator }
</div>
</div>
</div>
<div className="mx_RoomTile_nameContainer">
<div className="mx_RoomTile_labelContainer">
{ label }
{ subtextLabel }
<div className="mx_RoomTile_nameContainer">
<div className="mx_RoomTile_labelContainer">
{ label }
{ subtextLabel }
</div>
{ contextMenuButton }
{ badge }
</div>
{ contextMenuButton }
{ badge }
</div>
{ /* { incomingCallBox } */ }
{ tooltip }
</AccessibleButton>;
{ /* { incomingCallBox } */ }
{ tooltip }
</AccessibleButton>
{ contextMenu }
</React.Fragment>;
},
});

View file

@ -25,6 +25,7 @@ import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import PersistedElement from "../elements/PersistedElement";
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import {ContextMenu} from "../../structures/ContextMenu";
const widgetType = 'm.stickerpicker';
@ -371,26 +372,8 @@ export default class Stickerpicker extends React.Component {
}
render() {
const ContextualMenu = sdk.getComponent('structures.ContextualMenu');
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
let stickerPicker;
let stickersButton;
const stickerPicker = <ContextualMenu
elementClass={GenericElementContextMenu}
chevronOffset={this.state.stickerPickerChevronOffset}
chevronFace={'bottom'}
left={this.state.stickerPickerX}
top={this.state.stickerPickerY}
menuWidth={this.popoverWidth}
menuHeight={this.popoverHeight}
element={this._getStickerpickerContent()}
onFinished={this._onFinished}
menuPaddingTop={0}
menuPaddingLeft={0}
menuPaddingRight={0}
zIndex={STICKERPICKER_Z_INDEX}
/>;
if (this.state.showStickers) {
// Show hide-stickers button
stickersButton =
@ -402,6 +385,23 @@ export default class Stickerpicker extends React.Component {
title={_t("Hide Stickers")}
>
</AccessibleButton>;
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
stickerPicker = <ContextMenu
chevronOffset={this.state.stickerPickerChevronOffset}
chevronFace="bottom"
left={this.state.stickerPickerX}
top={this.state.stickerPickerY}
menuWidth={this.popoverWidth}
menuHeight={this.popoverHeight}
onFinished={this._onFinished}
menuPaddingTop={0}
menuPaddingLeft={0}
menuPaddingRight={0}
zIndex={STICKERPICKER_Z_INDEX}
>
<GenericElementContextMenu element={this._getStickerpickerContent()} onResize={this._onFinished} />
</ContextMenu>;
} else {
// Show show-stickers button
stickersButton =
@ -415,8 +415,8 @@ export default class Stickerpicker extends React.Component {
</AccessibleButton>;
}
return <React.Fragment>
{stickersButton}
{this.state.showStickers && stickerPicker}
{ stickersButton }
{ stickerPicker }
</React.Fragment>;
}
}