From cb301da477f2600a7862831900b671b47acaeba0 Mon Sep 17 00:00:00 2001 From: Mike Pennisi Date: Fri, 19 Jun 2020 16:56:57 -0400 Subject: [PATCH 1/4] Create component for Message Actions Signed-off-by: Mike Pennisi --- res/css/_components.scss | 1 + res/css/views/rooms/_MessageActions.scss | 62 +++++ res/css/views/rooms/_MessageComposer.scss | 44 ---- src/components/views/rooms/MessageActions.js | 224 ++++++++++++++++++ src/components/views/rooms/MessageComposer.js | 199 +--------------- 5 files changed, 294 insertions(+), 236 deletions(-) create mode 100644 res/css/views/rooms/_MessageActions.scss create mode 100644 src/components/views/rooms/MessageActions.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 66eb98ea9da..c3564531c4f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -171,6 +171,7 @@ @import "./views/rooms/_LinkPreviewWidget.scss"; @import "./views/rooms/_MemberInfo.scss"; @import "./views/rooms/_MemberList.scss"; +@import "./views/rooms/_MessageActions.scss"; @import "./views/rooms/_MessageComposer.scss"; @import "./views/rooms/_MessageComposerFormatBar.scss"; @import "./views/rooms/_NotificationBadge.scss"; diff --git a/res/css/views/rooms/_MessageActions.scss b/res/css/views/rooms/_MessageActions.scss new file mode 100644 index 00000000000..5131ecb438f --- /dev/null +++ b/res/css/views/rooms/_MessageActions.scss @@ -0,0 +1,62 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2018 New Vector Ltd + +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. +*/ + +.mx_MessageComposer_button { + position: relative; + margin-right: 12px; + cursor: pointer; + height: 20px; + width: 20px; + + &::before { + content: ''; + position: absolute; + + height: 20px; + width: 20px; + background-color: $composer-button-color; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + } +} + +.mx_MessageComposer_upload::before { + mask-image: url('$(res)/img/feather-customised/paperclip.svg'); +} + +.mx_MessageComposer_hangup::before { + mask-image: url('$(res)/img/hangup.svg'); +} + +.mx_MessageComposer_voicecall::before { + mask-image: url('$(res)/img/feather-customised/phone.svg'); +} + +.mx_MessageComposer_videocall::before { + mask-image: url('$(res)/img/feather-customised/video.svg'); +} + +.mx_MessageComposer_emoji::before { + mask-image: url('$(res)/img/feather-customised/emoji3.custom.svg'); +} + +.mx_MessageComposer_stickers::before { + mask-image: url('$(res)/img/feather-customised/sticker.custom.svg'); +} + + diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index c1cda7bf24c..629ab07937f 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -178,50 +178,6 @@ limitations under the License. color: $accent-color; } -.mx_MessageComposer_button { - position: relative; - margin-right: 12px; - cursor: pointer; - height: 20px; - width: 20px; - - &::before { - content: ''; - position: absolute; - - height: 20px; - width: 20px; - background-color: $composer-button-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - } -} - -.mx_MessageComposer_upload::before { - mask-image: url('$(res)/img/feather-customised/paperclip.svg'); -} - -.mx_MessageComposer_hangup::before { - mask-image: url('$(res)/img/hangup.svg'); -} - -.mx_MessageComposer_voicecall::before { - mask-image: url('$(res)/img/feather-customised/phone.svg'); -} - -.mx_MessageComposer_videocall::before { - mask-image: url('$(res)/img/feather-customised/video.svg'); -} - -.mx_MessageComposer_emoji::before { - mask-image: url('$(res)/img/feather-customised/emoji3.custom.svg'); -} - -.mx_MessageComposer_stickers::before { - mask-image: url('$(res)/img/feather-customised/sticker.custom.svg'); -} - .mx_MessageComposer_formatting { cursor: pointer; margin: 0 11px; diff --git a/src/components/views/rooms/MessageActions.js b/src/components/views/rooms/MessageActions.js new file mode 100644 index 00000000000..365c009663d --- /dev/null +++ b/src/components/views/rooms/MessageActions.js @@ -0,0 +1,224 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017, 2018 New Vector Ltd + +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, {createRef} from 'react'; +import PropTypes from 'prop-types'; +import { _t } from '../../../languageHandler'; +import CallHandler from '../../../CallHandler'; +import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import dis from '../../../dispatcher/dispatcher'; +import * as sdk from '../../../index'; +import ContentMessages from '../../../ContentMessages'; +import Stickerpicker from './Stickerpicker'; +import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu"; + +function CallButton(props) { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const onVoiceCallClick = (ev) => { + dis.dispatch({ + action: 'place_call', + type: "voice", + room_id: props.roomId, + }); + }; + + return (); +} + +CallButton.propTypes = { + roomId: PropTypes.string.isRequired, +}; + +const EmojiButton = ({addEmoji}) => { + const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); + + let contextMenu; + if (menuDisplayed) { + const buttonRect = button.current.getBoundingClientRect(); + const EmojiPicker = sdk.getComponent('emojipicker.EmojiPicker'); + contextMenu = + + ; + } + + return + + + + + { contextMenu } + ; +}; + +const addEmoji = (emoji) => { + dis.dispatch({ + action: "insert_emoji", + emoji, + }); +}; + +function HangupButton(props) { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const onHangupClick = () => { + const call = CallHandler.getCallForRoom(props.roomId); + if (!call) { + return; + } + dis.dispatch({ + action: 'hangup', + // hangup the call for this room, which may not be the room in props + // (e.g. conferences which will hangup the 1:1 room instead) + room_id: call.roomId, + }); + }; + return (); +} + +HangupButton.propTypes = { + roomId: PropTypes.string.isRequired, +}; + +class UploadButton extends React.Component { + static propTypes = { + roomId: PropTypes.string.isRequired, + } + + constructor(props) { + super(props); + this.onUploadClick = this.onUploadClick.bind(this); + this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this); + + this._uploadInput = createRef(); + this._dispatcherRef = dis.register(this.onAction); + } + + componentWillUnmount() { + dis.unregister(this._dispatcherRef); + } + + onAction = payload => { + if (payload.action === "upload_file") { + this.onUploadClick(); + } + }; + + onUploadClick(ev) { + if (MatrixClientPeg.get().isGuest()) { + dis.dispatch({action: 'require_registration'}); + return; + } + this._uploadInput.current.click(); + } + + onUploadFileInputChange(ev) { + if (ev.target.files.length === 0) return; + + // take a copy so we can safely reset the value of the form control + // (Note it is a FileList: we can't use slice or sensible iteration). + const tfiles = []; + for (let i = 0; i < ev.target.files.length; ++i) { + tfiles.push(ev.target.files[i]); + } + + ContentMessages.sharedInstance().sendContentListToRoom( + tfiles, this.props.roomId, MatrixClientPeg.get(), + ); + + // This is the onChange handler for a file form control, but we're + // not keeping any state, so reset the value of the form control + // to empty. + // NB. we need to set 'value': the 'files' property is immutable. + ev.target.value = ''; + } + + render() { + const uploadInputStyle = {display: 'none'}; + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + return ( + + + + ); + } +} + +function VideoCallButton(props) { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const onCallClick = (ev) => { + dis.dispatch({ + action: 'place_call', + type: ev.shiftKey ? "screensharing" : "video", + room_id: props.roomId, + }); + }; + + return ; +} + +VideoCallButton.propTypes = { + roomId: PropTypes.string.isRequired, +}; + +export default function MessageActions(props) { + const callInProgress = props.callState && props.callState !== 'ended'; + const controls = [ + , + , + , + ]; + + if (props.showCallButtons) { + if (callInProgress) { + controls.push( + , + ); + } else { + controls.push( + , + , + ); + } + } + + return ( + + {controls} + + ); +} diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 84a5a3a9a05..9441d6b8b5b 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -14,20 +14,17 @@ 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, {createRef} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; -import CallHandler from '../../../CallHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import RoomViewStore from '../../../stores/RoomViewStore'; -import Stickerpicker from './Stickerpicker'; import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks'; -import ContentMessages from '../../../ContentMessages'; import E2EIcon from './E2EIcon'; +import MessageActions from "./MessageActions"; import SettingsStore from "../../../settings/SettingsStore"; -import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -40,169 +37,6 @@ ComposerAvatar.propTypes = { me: PropTypes.object.isRequired, }; -function CallButton(props) { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const onVoiceCallClick = (ev) => { - dis.dispatch({ - action: 'place_call', - type: "voice", - room_id: props.roomId, - }); - }; - - return (); -} - -CallButton.propTypes = { - roomId: PropTypes.string.isRequired, -}; - -function VideoCallButton(props) { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const onCallClick = (ev) => { - dis.dispatch({ - action: 'place_call', - type: ev.shiftKey ? "screensharing" : "video", - room_id: props.roomId, - }); - }; - - return ; -} - -VideoCallButton.propTypes = { - roomId: PropTypes.string.isRequired, -}; - -function HangupButton(props) { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const onHangupClick = () => { - const call = CallHandler.getCallForRoom(props.roomId); - if (!call) { - return; - } - dis.dispatch({ - action: 'hangup', - // hangup the call for this room, which may not be the room in props - // (e.g. conferences which will hangup the 1:1 room instead) - room_id: call.roomId, - }); - }; - return (); -} - -HangupButton.propTypes = { - roomId: PropTypes.string.isRequired, -}; - -const EmojiButton = ({addEmoji}) => { - const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); - - let contextMenu; - if (menuDisplayed) { - const buttonRect = button.current.getBoundingClientRect(); - const EmojiPicker = sdk.getComponent('emojipicker.EmojiPicker'); - contextMenu = - - ; - } - - return - - - - - { contextMenu } - ; -}; - -class UploadButton extends React.Component { - static propTypes = { - roomId: PropTypes.string.isRequired, - } - - constructor(props) { - super(props); - this.onUploadClick = this.onUploadClick.bind(this); - this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this); - - this._uploadInput = createRef(); - this._dispatcherRef = dis.register(this.onAction); - } - - componentWillUnmount() { - dis.unregister(this._dispatcherRef); - } - - onAction = payload => { - if (payload.action === "upload_file") { - this.onUploadClick(); - } - }; - - onUploadClick(ev) { - if (MatrixClientPeg.get().isGuest()) { - dis.dispatch({action: 'require_registration'}); - return; - } - this._uploadInput.current.click(); - } - - onUploadFileInputChange(ev) { - if (ev.target.files.length === 0) return; - - // take a copy so we can safely reset the value of the form control - // (Note it is a FileList: we can't use slice or sensible iteration). - const tfiles = []; - for (let i = 0; i < ev.target.files.length; ++i) { - tfiles.push(ev.target.files[i]); - } - - ContentMessages.sharedInstance().sendContentListToRoom( - tfiles, this.props.roomId, MatrixClientPeg.get(), - ); - - // This is the onChange handler for a file form control, but we're - // not keeping any state, so reset the value of the form control - // to empty. - // NB. we need to set 'value': the 'files' property is immutable. - ev.target.value = ''; - } - - render() { - const uploadInputStyle = {display: 'none'}; - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - return ( - - - - ); - } -} - export default class MessageComposer extends React.Component { constructor(props) { super(props); @@ -323,13 +157,6 @@ export default class MessageComposer extends React.Component { } } - addEmoji(emoji) { - dis.dispatch({ - action: "insert_emoji", - emoji, - }); - } - render() { const controls = [ this.state.me ? : null, @@ -344,7 +171,6 @@ export default class MessageComposer extends React.Component { // complex because of conference calls. const SendMessageComposer = sdk.getComponent("rooms.SendMessageComposer"); - const callInProgress = this.props.callState && this.props.callState !== 'ended'; controls.push( , - , - , - , + , ); - - if (this.state.showCallButtons) { - if (callInProgress) { - controls.push( - , - ); - } else { - controls.push( - , - , - ); - } - } } else if (this.state.tombstone) { const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; From b7b4e13aa46bd1539f37269e0127863dc5eade85 Mon Sep 17 00:00:00 2001 From: Mike Pennisi Date: Fri, 19 Jun 2020 18:43:03 -0400 Subject: [PATCH 2/4] Refactor MessageActions using ARIA Toolbar pattern Signed-off-by: Mike Pennisi --- res/css/views/rooms/_MessageActions.scss | 6 +- src/components/views/rooms/MessageActions.js | 92 +++++++++++++++++--- src/components/views/rooms/Stickerpicker.js | 2 + src/i18n/strings/en_EN.json | 1 + src/i18n/strings/en_US.json | 1 + 5 files changed, 90 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_MessageActions.scss b/res/css/views/rooms/_MessageActions.scss index 5131ecb438f..1086db9318c 100644 --- a/res/css/views/rooms/_MessageActions.scss +++ b/res/css/views/rooms/_MessageActions.scss @@ -15,6 +15,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_MessageActions_wrapper { + display: flex; +} + .mx_MessageComposer_button { position: relative; margin-right: 12px; @@ -58,5 +62,3 @@ limitations under the License. .mx_MessageComposer_stickers::before { mask-image: url('$(res)/img/feather-customised/sticker.custom.svg'); } - - diff --git a/src/components/views/rooms/MessageActions.js b/src/components/views/rooms/MessageActions.js index 365c009663d..ef36b90225e 100644 --- a/src/components/views/rooms/MessageActions.js +++ b/src/components/views/rooms/MessageActions.js @@ -14,10 +14,11 @@ 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, {createRef} from 'react'; +import React, {createRef, useCallback, useEffect, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import CallHandler from '../../../CallHandler'; +import {Key} from '../../../Keyboard'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; import * as sdk from '../../../index'; @@ -36,6 +37,7 @@ function CallButton(props) { }; return (); @@ -43,9 +45,10 @@ function CallButton(props) { CallButton.propTypes = { roomId: PropTypes.string.isRequired, + tabIndex: PropTypes.number, }; -const EmojiButton = ({addEmoji}) => { +const EmojiButton = ({addEmoji, tabIndex}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); let contextMenu; @@ -63,6 +66,7 @@ const EmojiButton = ({addEmoji}) => { isExpanded={menuDisplayed} label={_t('Emoji picker')} inputRef={button} + tabIndex={tabIndex} > @@ -93,6 +97,7 @@ function HangupButton(props) { }); }; return (); @@ -100,11 +105,13 @@ function HangupButton(props) { HangupButton.propTypes = { roomId: PropTypes.string.isRequired, + tabIndex: PropTypes.tabIndex, }; class UploadButton extends React.Component { static propTypes = { roomId: PropTypes.string.isRequired, + tabIndex: PropTypes.number, } constructor(props) { @@ -160,6 +167,7 @@ class UploadButton extends React.Component { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return ( @@ -186,6 +194,7 @@ function VideoCallButton(props) { }; return ; @@ -193,32 +202,95 @@ function VideoCallButton(props) { VideoCallButton.propTypes = { roomId: PropTypes.string.isRequired, + tabIndex: PropTypes.number, }; +/** + * This component implements the Toolbar design pattern from the WAI-ARIA + * Authoring Practices guidelines. + * + * https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar + */ export default function MessageActions(props) { const callInProgress = props.callState && props.callState !== 'ended'; + const elRef = useRef(null); + const [focused, setFocused] = useState('upload'); + const tabIndex = (target) => target === focused ? 0 : -1; const controls = [ - , - , - , + , + , + , ]; if (props.showCallButtons) { if (callInProgress) { controls.push( - , + , ); } else { controls.push( - , - , + , + , ); } } + // Serialize keys for use as a callback dependency. The serialized form + // (rather than the original data structure) must be used within the + // callback in order to satisfy the project's linter. + const keysString = controls.map(({key}) => key).join(','); + const handleKeyDown = useCallback( + (event) => { + const keys = keysString.split(','); + const current = keys.indexOf(focused); + let newIndex; + + if (event.key === Key.ARROW_RIGHT) { + newIndex = (current + 1) % keys.length; + } else if (event.key === Key.ARROW_LEFT) { + newIndex = current ? current - 1 : keys.length - 1; + } else if (event.key === Key.HOME) { + newIndex = 0; + } else if (event.key === Key.END) { + newIndex = keys.length - 1; + } else { + return; + } + + setFocused(keys[newIndex]); + elRef.current.children[newIndex].focus(); + event.stopPropagation(); + }, + [keysString, focused], + ); + + useEffect(() => { + const el = elRef.current; + el.addEventListener('keydown', handleKeyDown, true); + + return () => { + el.removeEventListener('keydown', handleKeyDown, true); + }; + }, [handleKeyDown]); + return ( - +
{controls} - +
); } diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index fc6e80fc616..4e98fcc7db6 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -386,6 +386,7 @@ export default class Stickerpicker extends React.Component { key="controls_hide_stickers" className="mx_MessageComposer_button mx_MessageComposer_stickers mx_Stickers_hideStickers" onClick={this._onHideStickersClick} + tabIndex={this.props.tabIndex} title={_t("Hide Stickers")} >
; @@ -414,6 +415,7 @@ export default class Stickerpicker extends React.Component { key="controls_show_stickers" className="mx_MessageComposer_button mx_MessageComposer_stickers" onClick={this._onShowStickersClick} + tabIndex={this.props.tabIndex} title={_t("Show Stickers")} >
; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ba20cf1a295..c55b62bda7f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1033,6 +1033,7 @@ "Invited": "Invited", "Filter room members": "Filter room members", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", + "Message actions": "Message actions", "Voice call": "Voice call", "Video call": "Video call", "Hangup": "Hangup", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 05f29c3887d..5567987dc35 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -272,6 +272,7 @@ "Verification": "Verification", "verified": "verified", "Verified key": "Verified key", + "Message actions": "Message actions", "Video call": "Video call", "Voice call": "Voice call", "VoIP conference finished.": "VoIP conference finished.", From 16b58f5737267d43766b6cb49546937042002964 Mon Sep 17 00:00:00 2001 From: Mike Pennisi Date: Fri, 19 Jun 2020 19:54:39 -0400 Subject: [PATCH 3/4] fixup! Refactor MessageActions using ARIA Toolbar pattern --- src/components/views/rooms/MessageActions.js | 10 ++++------ src/i18n/strings/en_EN.json | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/MessageActions.js b/src/components/views/rooms/MessageActions.js index ef36b90225e..685f9c0c401 100644 --- a/src/components/views/rooms/MessageActions.js +++ b/src/components/views/rooms/MessageActions.js @@ -205,12 +205,10 @@ VideoCallButton.propTypes = { tabIndex: PropTypes.number, }; -/** - * This component implements the Toolbar design pattern from the WAI-ARIA - * Authoring Practices guidelines. - * - * https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar - */ +// This component implements the Toolbar design pattern from the WAI-ARIA +// Authoring Practices guidelines. +// +// https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar export default function MessageActions(props) { const callInProgress = props.callState && props.callState !== 'ended'; const elRef = useRef(null); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c55b62bda7f..b2324324949 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1033,12 +1033,12 @@ "Invited": "Invited", "Filter room members": "Filter room members", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", - "Message actions": "Message actions", "Voice call": "Voice call", - "Video call": "Video call", - "Hangup": "Hangup", "Emoji picker": "Emoji picker", + "Hangup": "Hangup", "Upload file": "Upload file", + "Video call": "Video call", + "Message actions": "Message actions", "Send an encrypted reply…": "Send an encrypted reply…", "Send a reply…": "Send a reply…", "Send an encrypted message…": "Send an encrypted message…", From 273f2d505558a7c7d751c10ec0f6aecc01952184 Mon Sep 17 00:00:00 2001 From: Mike Pennisi Date: Mon, 6 Jul 2020 23:18:58 -0400 Subject: [PATCH 4/4] fixup! Refactor MessageActions using ARIA Toolbar pattern --- src/components/views/rooms/MessageActions.js | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/views/rooms/MessageActions.js b/src/components/views/rooms/MessageActions.js index 685f9c0c401..78753265ac3 100644 --- a/src/components/views/rooms/MessageActions.js +++ b/src/components/views/rooms/MessageActions.js @@ -216,31 +216,31 @@ export default function MessageActions(props) { const tabIndex = (target) => target === focused ? 0 : -1; const controls = [ , + tabIndex={tabIndex('upload')} + roomId={props.room.roomId} />, , + tabIndex={tabIndex('emoji')} + addEmoji={addEmoji} />, , + tabIndex={tabIndex('sticker')} + room={props.room} />, ]; if (props.showCallButtons) { if (callInProgress) { controls.push( , + tabIndex={tabIndex('hangup')} + roomId={props.room.roomId} />, ); } else { controls.push( , + tabIndex={tabIndex('call')} + roomId={props.room.roomId} />, , + tabIndex={tabIndex('videocall')} + roomId={props.room.roomId} />, ); } }