diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 4a1332be8c..ba5ab49bbc 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -26,6 +26,7 @@ var UserSettingsStore = require('../../UserSettingsStore');
var GeminiScrollbar = require('react-gemini-scrollbar');
var Email = require('../../email');
var AddThreepid = require('../../AddThreepid');
+var AccessibleButton = require('../views/elements/AccessibleButton');
// if this looks like a release, use the 'version' from package.json; else use
// the git sha.
@@ -531,9 +532,9 @@ module.exports = React.createClass({
return
Deactivate Account
-
+
;
},
@@ -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
// don't inadvertently get rejected as well.
reject = (
-
+
);
}
@@ -724,9 +725,9 @@ module.exports = React.createClass({
diff --git a/src/components/views/elements/AccessibleButton.js b/src/components/views/elements/AccessibleButton.js
new file mode 100644
index 0000000000..3ff5d7a38a
--- /dev/null
+++ b/src/components/views/elements/AccessibleButton.js
@@ -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";
diff --git a/src/components/views/elements/TintableSvg.js b/src/components/views/elements/TintableSvg.js
index 0157131506..401a11c1cb 100644
--- a/src/components/views/elements/TintableSvg.js
+++ b/src/components/views/elements/TintableSvg.js
@@ -69,6 +69,7 @@ var TintableSvg = React.createClass({
width={ this.props.width }
height={ this.props.height }
onLoad={ this.onLoad }
+ tabIndex="-1"
/>
);
}
diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js
index d29137ffc2..64de431d9d 100644
--- a/src/components/views/rooms/EntityTile.js
+++ b/src/components/views/rooms/EntityTile.js
@@ -20,6 +20,7 @@ var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
+var AccessibleButton = require('../elements/AccessibleButton');
var PRESENCE_CLASS = {
@@ -152,7 +153,7 @@ module.exports = React.createClass({
var av = this.props.avatarJsx || ;
return (
-
;
+ ;
}
if (this.state.can.mute) {
var muteLabel = this.state.muted ? "Unmute" : "Mute";
- muteButton =
+ muteButton =
{muteLabel}
-
;
+ ;
}
if (this.state.can.toggleMod) {
var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator";
- giveModButton =
+ giveModButton =
{giveOpLabel}
-
;
+ ;
}
// 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');
return (
);
diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.js
index 4c63be5b99..cabd0f27a4 100644
--- a/src/components/views/rooms/SimpleRoomHeader.js
+++ b/src/components/views/rooms/SimpleRoomHeader.js
@@ -19,6 +19,7 @@ limitations under the License.
var React = require('react');
var sdk = require('../../../index');
var dis = require("../../../dispatcher");
+var AccessibleButton = require('../elements/AccessibleButton');
/*
* A stripped-down room header used for things like the user settings
@@ -44,7 +45,7 @@ module.exports = React.createClass({
var cancelButton;
if (this.props.onCancelClick) {
- cancelButton =
;
+ cancelButton = ;
}
var showRhsButton;
@@ -70,4 +71,3 @@ module.exports = React.createClass({
);
},
});
-
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js
index a011d5262e..2bbf5420c0 100644
--- a/src/components/views/settings/ChangePassword.js
+++ b/src/components/views/settings/ChangePassword.js
@@ -19,6 +19,7 @@ limitations under the License.
var React = require('react');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var sdk = require("../../../index");
+var AccessibleButton = require('../elements/AccessibleButton');
module.exports = React.createClass({
displayName: 'ChangePassword',
@@ -136,9 +137,9 @@ module.exports = React.createClass({