Merge branch 'develop' into rav/base_dialog

This commit is contained in:
Richard van der Hoff 2017-01-24 21:03:08 +00:00
commit 79e1108564
12 changed files with 119 additions and 45 deletions

View file

@ -26,6 +26,7 @@ var UserSettingsStore = require('../../UserSettingsStore');
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
var Email = require('../../email'); var Email = require('../../email');
var AddThreepid = require('../../AddThreepid'); var AddThreepid = require('../../AddThreepid');
var AccessibleButton = require('../views/elements/AccessibleButton');
// if this looks like a release, use the 'version' from package.json; else use // if this looks like a release, use the 'version' from package.json; else use
// the git sha. // the git sha.
@ -531,9 +532,9 @@ module.exports = React.createClass({
return <div> return <div>
<h3>Deactivate Account</h3> <h3>Deactivate Account</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<button className="mx_UserSettings_button danger" <AccessibleButton className="mx_UserSettings_button danger"
onClick={this._onDeactivateAccountClicked}>Deactivate my account onClick={this._onDeactivateAccountClicked}>Deactivate my account
</button> </AccessibleButton>
</div> </div>
</div>; </div>;
}, },
@ -553,10 +554,10 @@ module.exports = React.createClass({
// bind() the invited rooms so any new invites that may come in as this button is clicked // bind() the invited rooms so any new invites that may come in as this button is clicked
// don't inadvertently get rejected as well. // don't inadvertently get rejected as well.
reject = ( reject = (
<button className="mx_UserSettings_button danger" <AccessibleButton className="mx_UserSettings_button danger"
onClick={this._onRejectAllInvitesClicked.bind(this, invitedRooms)}> onClick={this._onRejectAllInvitesClicked.bind(this, invitedRooms)}>
Reject all {invitedRooms.length} invites Reject all {invitedRooms.length} invites
</button> </AccessibleButton>
); );
} }
@ -724,9 +725,9 @@ module.exports = React.createClass({
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
<div className="mx_UserSettings_logout mx_UserSettings_button" onClick={this.onLogoutClicked}> <AccessibleButton className="mx_UserSettings_logout mx_UserSettings_button" onClick={this.onLogoutClicked}>
Sign out Sign out
</div> </AccessibleButton>
{accountJsx} {accountJsx}
</div> </div>

View file

@ -19,6 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var AvatarLogic = require("../../../Avatar"); var AvatarLogic = require("../../../Avatar");
import sdk from '../../../index'; import sdk from '../../../index';
var AccessibleButton = require('../elements/AccessibleButton');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'BaseAvatar', displayName: 'BaseAvatar',
@ -138,7 +139,7 @@ module.exports = React.createClass({
const { const {
name, idName, title, url, urls, width, height, resizeMethod, name, idName, title, url, urls, width, height, resizeMethod,
defaultToInitialLetter, defaultToInitialLetter, onClick,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -156,6 +157,17 @@ module.exports = React.createClass({
</span> </span>
); );
} }
if (onClick != null) {
return (
<AccessibleButton className="mx_BaseAvatar" onClick={onClick}>
<img className="mx_BaseAvatar_image" src={imageUrl}
onError={this.onError}
width={width} height={height}
title={title} alt=""
{...otherProps} />
</AccessibleButton>
);
} else {
return ( return (
<img className="mx_BaseAvatar mx_BaseAvatar_image" src={imageUrl} <img className="mx_BaseAvatar mx_BaseAvatar_image" src={imageUrl}
onError={this.onError} onError={this.onError}
@ -164,4 +176,5 @@ module.exports = React.createClass({
{...otherProps} /> {...otherProps} />
); );
} }
}
}); });

View file

@ -24,6 +24,7 @@ var DMRoomMap = require('../../../utils/DMRoomMap');
var rate_limited_func = require("../../../ratelimitedfunc"); var rate_limited_func = require("../../../ratelimitedfunc");
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
var Modal = require('../../../Modal'); var Modal = require('../../../Modal');
var AccessibleButton = require('../elements/AccessibleButton');
const TRUNCATE_QUERY_LIST = 40; const TRUNCATE_QUERY_LIST = 40;
@ -436,9 +437,9 @@ module.exports = React.createClass({
<div className="mx_Dialog_title"> <div className="mx_Dialog_title">
{this.props.title} {this.props.title}
</div> </div>
<div className="mx_ChatInviteDialog_cancel" onClick={this.onCancel} > <AccessibleButton className="mx_ChatInviteDialog_cancel" onClick={this.onCancel} >
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" /> <TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
</div> </AccessibleButton>
<div className="mx_ChatInviteDialog_label"> <div className="mx_ChatInviteDialog_label">
<label htmlFor="textinput">{ this.props.description }</label> <label htmlFor="textinput">{ this.props.description }</label>
</div> </div>

View file

@ -0,0 +1,50 @@
/*
Copyright 2016 Jani Mustonen
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
/**
* AccessibleButton is a generic wrapper for any element that should be treated as a button.
* Identifies the element as a button, setting proper tab indexing and keyboard activation behavior.
*/
export default function AccessibleButton(props) {
const {element, onClick, children, ...restProps} = props;
restProps.onClick = onClick;
restProps.onKeyDown = function(e) {
if (e.keyCode == 13 || e.keyCode == 32) return onClick();
};
restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
return React.createElement(element, restProps, children);
}
/**
* children: React's magic prop. Represents all children given to the element.
* element: (optional) The base element type. "div" by default.
* onClick: (required) Event handler for button activation. Should be
* implemented exactly like a normal onClick handler.
*/
AccessibleButton.propTypes = {
children: React.PropTypes.node,
element: React.PropTypes.string,
onClick: React.PropTypes.func.isRequired,
};
AccessibleButton.defaultProps = {
element: 'div'
};
AccessibleButton.displayName = "AccessibleButton";

View file

@ -69,6 +69,7 @@ var TintableSvg = React.createClass({
width={ this.props.width } width={ this.props.width }
height={ this.props.height } height={ this.props.height }
onLoad={ this.onLoad } onLoad={ this.onLoad }
tabIndex="-1"
/> />
); );
} }

View file

@ -20,6 +20,7 @@ var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index'); var sdk = require('../../../index');
var AccessibleButton = require('../elements/AccessibleButton');
var PRESENCE_CLASS = { var PRESENCE_CLASS = {
@ -152,7 +153,7 @@ module.exports = React.createClass({
var av = this.props.avatarJsx || <BaseAvatar name={this.props.name} width={36} height={36} />; var av = this.props.avatarJsx || <BaseAvatar name={this.props.name} width={36} height={36} />;
return ( return (
<div className={mainClassName} title={ this.props.title } <AccessibleButton className={mainClassName} title={ this.props.title }
onClick={ this.props.onClick } onMouseEnter={ this.mouseEnter } onClick={ this.props.onClick } onMouseEnter={ this.mouseEnter }
onMouseLeave={ this.mouseLeave }> onMouseLeave={ this.mouseLeave }>
<div className="mx_EntityTile_avatar"> <div className="mx_EntityTile_avatar">
@ -161,7 +162,7 @@ module.exports = React.createClass({
</div> </div>
{ nameEl } { nameEl }
{ inviteButton } { inviteButton }
</div> </AccessibleButton>
); );
} }
}); });

View file

@ -35,6 +35,7 @@ var DMRoomMap = require('../../../utils/DMRoomMap');
var Unread = require('../../../Unread'); var Unread = require('../../../Unread');
var Receipt = require('../../../utils/Receipt'); var Receipt = require('../../../utils/Receipt');
var WithMatrixClient = require('../../../wrappers/WithMatrixClient'); var WithMatrixClient = require('../../../wrappers/WithMatrixClient');
var AccessibleButton = require('../elements/AccessibleButton');
module.exports = WithMatrixClient(React.createClass({ module.exports = WithMatrixClient(React.createClass({
displayName: 'MemberInfo', displayName: 'MemberInfo',
@ -612,7 +613,7 @@ module.exports = WithMatrixClient(React.createClass({
mx_MemberInfo_createRoom_label: true, mx_MemberInfo_createRoom_label: true,
mx_RoomTile_name: true, mx_RoomTile_name: true,
}); });
const startNewChat = <div const startNewChat = <AccessibleButton
className="mx_MemberInfo_createRoom" className="mx_MemberInfo_createRoom"
onClick={this.onNewDMClick} onClick={this.onNewDMClick}
> >
@ -620,7 +621,7 @@ module.exports = WithMatrixClient(React.createClass({
<img src="img/create-big.svg" width="26" height="26" /> <img src="img/create-big.svg" width="26" height="26" />
</div> </div>
<div className={labelClasses}><i>Start new chat</i></div> <div className={labelClasses}><i>Start new chat</i></div>
</div>; </AccessibleButton>;
startChat = <div> startChat = <div>
<h3>Direct chats</h3> <h3>Direct chats</h3>
@ -635,26 +636,26 @@ module.exports = WithMatrixClient(React.createClass({
} }
if (this.state.can.kick) { if (this.state.can.kick) {
kickButton = <div className="mx_MemberInfo_field" onClick={this.onKick}> kickButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onKick}>
{ this.props.member.membership === "invite" ? "Disinvite" : "Kick" } { this.props.member.membership === "invite" ? "Disinvite" : "Kick" }
</div>; </AccessibleButton>;
} }
if (this.state.can.ban) { if (this.state.can.ban) {
banButton = <div className="mx_MemberInfo_field" onClick={this.onBan}> banButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onBan}>
Ban Ban
</div>; </AccessibleButton>;
} }
if (this.state.can.mute) { if (this.state.can.mute) {
var muteLabel = this.state.muted ? "Unmute" : "Mute"; var muteLabel = this.state.muted ? "Unmute" : "Mute";
muteButton = <div className="mx_MemberInfo_field" onClick={this.onMuteToggle}> muteButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onMuteToggle}>
{muteLabel} {muteLabel}
</div>; </AccessibleButton>;
} }
if (this.state.can.toggleMod) { if (this.state.can.toggleMod) {
var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator"; var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator";
giveModButton = <div className="mx_MemberInfo_field" onClick={this.onModToggle}> giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
{giveOpLabel} {giveOpLabel}
</div>; </AccessibleButton>;
} }
// TODO: we should have an invite button if this MemberInfo is showing a user who isn't actually in the current room yet // TODO: we should have an invite button if this MemberInfo is showing a user who isn't actually in the current room yet
@ -682,7 +683,7 @@ module.exports = WithMatrixClient(React.createClass({
const EmojiText = sdk.getComponent('elements.EmojiText'); const EmojiText = sdk.getComponent('elements.EmojiText');
return ( return (
<div className="mx_MemberInfo"> <div className="mx_MemberInfo">
<img className="mx_MemberInfo_cancel" src="img/cancel.svg" width="18" height="18" onClick={this.onCancel}/> <AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}> <img src="img/cancel.svg" width="18" height="18"/></AccessibleButton>
<div className="mx_MemberInfo_avatar"> <div className="mx_MemberInfo_avatar">
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} /> <MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />
</div> </div>

View file

@ -192,9 +192,9 @@ module.exports = React.createClass({
width={14} height={14} resizeMethod="crop" width={14} height={14} resizeMethod="crop"
style={style} style={style}
title={title} title={title}
onClick={this.props.onClick}
/> />
</Velociraptor> </Velociraptor>
); );
/* onClick={this.props.onClick} */
}, },
}); });

View file

@ -26,6 +26,7 @@ var rate_limited_func = require('../../../ratelimitedfunc');
var linkify = require('linkifyjs'); var linkify = require('linkifyjs');
var linkifyElement = require('linkifyjs/element'); var linkifyElement = require('linkifyjs/element');
var linkifyMatrix = require('../../../linkify-matrix'); var linkifyMatrix = require('../../../linkify-matrix');
var AccessibleButton = require('../elements/AccessibleButton');
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -182,8 +183,8 @@ module.exports = React.createClass({
'm.room.name', user_id 'm.room.name', user_id
); );
save_button = <div className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save</div>; save_button = <AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save</AccessibleButton>;
cancel_button = <div className="mx_RoomHeader_cancelButton mx_filterFlipColor" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </div>; cancel_button = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>;
} }
if (this.props.saving) { if (this.props.saving) {
@ -275,9 +276,9 @@ module.exports = React.createClass({
var settings_button; var settings_button;
if (this.props.onSettingsClick) { if (this.props.onSettingsClick) {
settings_button = settings_button =
<div className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title="Settings"> <AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title="Settings">
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/> <TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
</div>; </AccessibleButton>;
} }
// var leave_button; // var leave_button;
@ -291,17 +292,17 @@ module.exports = React.createClass({
var forget_button; var forget_button;
if (this.props.onForgetClick) { if (this.props.onForgetClick) {
forget_button = forget_button =
<div className="mx_RoomHeader_button" onClick={this.props.onForgetClick} title="Forget room"> <AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onForgetClick} title="Forget room">
<TintableSvg src="img/leave.svg" width="26" height="20"/> <TintableSvg src="img/leave.svg" width="26" height="20"/>
</div>; </AccessibleButton>;
} }
var rightPanel_buttons; var rightPanel_buttons;
if (this.props.collapsedRhs) { if (this.props.collapsedRhs) {
rightPanel_buttons = rightPanel_buttons =
<div className="mx_RoomHeader_button" onClick={this.onShowRhsClick} title="<"> <AccessibleButton className="mx_RoomHeader_button" onClick={this.onShowRhsClick} title="<">
<TintableSvg src="img/minimise.svg" width="10" height="16"/> <TintableSvg src="img/minimise.svg" width="10" height="16"/>
</div>; </AccessibleButton>;
} }
var right_row; var right_row;
@ -310,9 +311,9 @@ module.exports = React.createClass({
<div className="mx_RoomHeader_rightRow"> <div className="mx_RoomHeader_rightRow">
{ settings_button } { settings_button }
{ forget_button } { forget_button }
<div className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search"> <AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search">
<TintableSvg src="img/icons-search.svg" width="35" height="35"/> <TintableSvg src="img/icons-search.svg" width="35" height="35"/>
</div> </AccessibleButton>
{ rightPanel_buttons } { rightPanel_buttons }
</div>; </div>;
} }

View file

@ -26,6 +26,7 @@ var sdk = require('../../../index');
var ContextualMenu = require('../../structures/ContextualMenu'); var ContextualMenu = require('../../structures/ContextualMenu');
var RoomNotifs = require('../../../RoomNotifs'); var RoomNotifs = require('../../../RoomNotifs');
var FormattingUtils = require('../../../utils/FormattingUtils'); var FormattingUtils = require('../../../utils/FormattingUtils');
var AccessibleButton = require('../elements/AccessibleButton');
var UserSettingsStore = require('../../../UserSettingsStore'); var UserSettingsStore = require('../../../UserSettingsStore');
module.exports = React.createClass({ module.exports = React.createClass({
@ -288,8 +289,10 @@ module.exports = React.createClass({
var connectDragSource = this.props.connectDragSource; var connectDragSource = this.props.connectDragSource;
var connectDropTarget = this.props.connectDropTarget; var connectDropTarget = this.props.connectDropTarget;
let ret = ( let ret = (
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <div> { /* Only native elements can be wrapped in a DnD object. */}
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div className={avatarClasses}> <div className={avatarClasses}>
<div className="mx_RoomTile_avatar_menu" onClick={this.onAvatarClicked}> <div className="mx_RoomTile_avatar_menu" onClick={this.onAvatarClicked}>
<div className={avatarContainerClasses}> <div className={avatarContainerClasses}>
@ -304,6 +307,7 @@ module.exports = React.createClass({
</div> </div>
{/* { incomingCallBox } */} {/* { incomingCallBox } */}
{ tooltip } { tooltip }
</AccessibleButton>
</div> </div>
); );

View file

@ -19,6 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var sdk = require('../../../index'); var sdk = require('../../../index');
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
var AccessibleButton = require('../elements/AccessibleButton');
/* /*
* A stripped-down room header used for things like the user settings * A stripped-down room header used for things like the user settings
@ -44,7 +45,7 @@ module.exports = React.createClass({
var cancelButton; var cancelButton;
if (this.props.onCancelClick) { if (this.props.onCancelClick) {
cancelButton = <div className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </div>; cancelButton = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>;
} }
var showRhsButton; var showRhsButton;
@ -70,4 +71,3 @@ module.exports = React.createClass({
); );
}, },
}); });

View file

@ -19,6 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var sdk = require("../../../index"); var sdk = require("../../../index");
var AccessibleButton = require('../elements/AccessibleButton');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'ChangePassword', displayName: 'ChangePassword',
@ -136,9 +137,9 @@ module.exports = React.createClass({
<input id="password2" type="password" ref="confirm_input" /> <input id="password2" type="password" ref="confirm_input" />
</div> </div>
</div> </div>
<div className={buttonClassName} onClick={this.onClickChange}> <AccessibleButton className={buttonClassName} onClick={this.onClickChange}>
Change Password Change Password
</div> </AccessibleButton>
</div> </div>
); );
case this.Phases.Uploading: case this.Phases.Uploading: