From d9692cc4fa0459727e324f1ce05a1d6e49e80077 Mon Sep 17 00:00:00 2001 From: Emmanuel Pelletier Date: Thu, 20 Oct 2022 11:49:19 +0200 Subject: [PATCH 1/2] feat(a11y) buttons can now have toggled-aware a11y labels We can now set toggledAccessibilityLabel and toggleTooltip attributes on an AbstractButton, allowing us to change its label depending on toggled state. This is important so that all users, whether they use a screen reader or not, understand clearly the actual action that the button will do when they'll press it. --- .../base/toolbox/components/AbstractButton.js | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/react/features/base/toolbox/components/AbstractButton.js b/react/features/base/toolbox/components/AbstractButton.js index b82618490..8d67f0344 100644 --- a/react/features/base/toolbox/components/AbstractButton.js +++ b/react/features/base/toolbox/components/AbstractButton.js @@ -108,10 +108,21 @@ export default class AbstractButton extends Component { * A succinct description of what the button does. Used by accessibility * tools and torture tests. * + * If `toggledAccessibilityLabel` is defined, this is used only when the + * button is not toggled on. + * * @abstract */ accessibilityLabel: string; + /** + * This is the same as `accessibilityLabel`, replacing it when the button + * is toggled on. + * + * @abstract + */ + toggledAccessibilityLabel: string; + labelProps: Object; /** @@ -144,10 +155,22 @@ export default class AbstractButton extends Component { /** * The text to display in the tooltip. Used only on web. * + * If `toggleTooltip` is defined, this is used only when the button is not + * toggled on. + * * @abstract */ tooltip: ?string; + /** + * The text to display in the tooltip when the button is toggled on. + * + * Used only on web. + * + * @abstract + */ + toggledTooltip: ?string; + /** * Initializes a new {@code AbstractButton} instance. * @@ -221,6 +244,24 @@ export default class AbstractButton extends Component { || this.label; } + /** + * Gets the current accessibility label, taking the toggled state into + * account. If no toggled label is provided, the regular accessibility label + * will also be used in the toggled state. + * + * The accessibility label is not visible in the UI, it is meant to be + * used by assistive technologies, mainly screen readers. + * + * @private + * @returns {string} + */ + _getAccessibilityLabel() { + return (this._isToggled() + ? this.toggledAccessibilityLabel + : this.accessibilityLabel + ) || this.accessibilityLabel; + } + /** * Gets the current styles, taking the toggled state into account. If no * toggled styles are provided, the regular styles will also be used in the @@ -257,7 +298,9 @@ export default class AbstractButton extends Component { * @returns {string} */ _getTooltip() { - return this.tooltip || ''; + return (this._isToggled() ? this.toggledTooltip : this.tooltip) + || this.tooltip + || ''; } /** @@ -324,7 +367,7 @@ export default class AbstractButton extends Component { render(): React$Node { const props = { ...this.props, - accessibilityLabel: this.accessibilityLabel, + accessibilityLabel: this._getAccessibilityLabel(), elementAfter: this._getElementAfter(), icon: this._getIcon(), label: this._getLabel(), From 75ba0d45f7c4f73d9530e833de2f64bd95883062 Mon Sep 17 00:00:00 2001 From: Emmanuel Pelletier Date: Thu, 20 Oct 2022 11:51:36 +0200 Subject: [PATCH 2/2] fix(a11y) fix ambiguous label on toggle buttons --- lang/main.json | 41 ++++++++++++++----- .../chat/components/web/ChatButton.js | 25 ++--------- .../components/SharedDocumentButton.web.js | 25 ++--------- .../components/web/ParticipantsPaneButton.js | 2 + .../components/web/RaiseHandButton.js | 21 ++-------- .../components/web/SharedVideoButton.js | 23 ++--------- .../toolbox/components/AudioMuteButton.js | 2 + .../toolbox/components/VideoMuteButton.js | 2 + .../components/web/FullscreenButton.js | 25 ++--------- .../components/web/HangupToggleButton.tsx | 17 +------- .../components/web/OverflowToggleButton.js | 18 +------- .../components/web/ShareDesktopButton.js | 2 +- .../video-layout/components/TileViewButton.js | 3 +- .../components/web/WhiteboardButton.tsx | 3 +- 14 files changed, 63 insertions(+), 146 deletions(-) diff --git a/lang/main.json b/lang/main.json index 72d8533f7..5e180545b 100644 --- a/lang/main.json +++ b/lang/main.json @@ -1097,11 +1097,20 @@ "cc": "Toggle subtitles", "chat": "Open / Close chat", "clap": "Clap", + "closeChat": "Close chat", + "closeMoreActions": "Close more actions menu", + "closeParticipantsPane": "Close participants pane", "collapse": "Collapse", "document": "Toggle shared document", + "documentClose": "Close shared document", + "documentOpen": "Open shared document", "download": "Download our apps", "embedMeeting": "Embed meeting", "endConference": "End meeting for all", + "enterFullScreen": "View full screen", + "enterTileView": "Enter tile view", + "exitFullScreen": "Exit full screen", + "exitTileView": "Exit tile view", "expand": "Expand", "feedback": "Leave feedback", "fullScreen": "Toggle full screen", @@ -1110,6 +1119,7 @@ "hangup": "Leave the meeting", "heading": "Toolbar", "help": "Help", + "hideWhiteboard": "Hide whiteboard", "invite": "Invite people", "kick": "Kick participant", "laugh": "Laugh", @@ -1119,21 +1129,23 @@ "lobbyButton": "Enable/disable lobby mode", "localRecording": "Toggle local recording controls", "lockRoom": "Toggle meeting password", + "lowerHand": "Lower your hand", "moreActions": "More actions", "moreActionsMenu": "More actions menu", "moreOptions": "Show more options", - "mute": "Mute / Unmute", + "mute": "Mute", "muteEveryone": "Mute everyone", "muteEveryoneElse": "Mute everyone else", "muteEveryoneElsesVideoStream": "Stop everyone else's video", "muteEveryonesVideoStream": "Stop everyone's video", "noiseSuppression": "Noise suppression", - "participants": "Participants", + "openChat": "Open chat", + "participants": "Open participants pane", "pip": "Toggle Picture-in-Picture mode", "privateMessage": "Send private message", "profile": "Edit your profile", - "raiseHand": "Raise / Lower your hand", - "reactionsMenu": "Open / Close reactions menu", + "raiseHand": "Raise your hand", + "reactionsMenu": "Reactions menu", "recording": "Toggle recording", "remoteMute": "Mute participant", "remoteVideoMute": "Disable camera of participant", @@ -1141,20 +1153,24 @@ "selectBackground": "Select Background", "selfView": "Toggle self view", "shareRoom": "Invite someone", - "shareYourScreen": "Start / Stop sharing your screen", + "shareYourScreen": "Start sharing your screen", "shareaudio": "Share audio", - "sharedvideo": "Toggle video sharing", + "sharedvideo": "Share video", "shortcuts": "Toggle shortcuts", "show": "Show on stage", + "showWhiteboard": "Show whiteboard", "silence": "Silence", "speakerStats": "Toggle participants statistics", + "stopScreenSharing": "Stop sharing your screen", + "stopSharedVideo": "Stop video", "surprised": "Surprised", "tileView": "Toggle tile view", "toggleCamera": "Toggle camera", "toggleFilmstrip": "Toggle filmstrip", + "unmute": "Unmute", "videoblur": "Toggle video blur", - "videomute": "Start / Stop camera", - "whiteboard": "Show / Hide whiteboard" + "videomute": "Stop camera", + "videounmute": "Start camera" }, "addPeople": "Add people to your call", "audioOnlyOff": "Disable low bandwidth mode", @@ -1167,6 +1183,7 @@ "chat": "Open / Close chat", "clap": "Clap", "closeChat": "Close chat", + "closeParticipantsPane": "Close participants pane", "closeReactionsMenu": "Close reactions menu", "disableNoiseSuppression": "Disable noise suppression", "disableReactionSounds": "You can disable reaction sounds for this meeting", @@ -1200,7 +1217,7 @@ "lowerYourHand": "Lower your hand", "moreActions": "More actions", "moreOptions": "More options", - "mute": "Mute / Unmute", + "mute": "Mute", "muteEveryone": "Mute everyone", "muteEveryonesVideo": "Disable everyone's camera", "noAudioSignalDesc": "If you did not purposely mute it from system settings or hardware, consider switching the device.", @@ -1217,7 +1234,7 @@ "pip": "Enter Picture-in-Picture mode", "privateMessage": "Send private message", "profile": "Edit your profile", - "raiseHand": "Raise / Lower your hand", + "raiseHand": "Raise your hand", "raiseYourHand": "Raise your hand", "reactionBoo": "Send boo reaction", "reactionClap": "Send clap reaction", @@ -1244,8 +1261,10 @@ "talkWhileMutedPopup": "Trying to speak? You are muted.", "tileViewToggle": "Toggle tile view", "toggleCamera": "Toggle camera", + "unmute": "Unmute", "videoSettings": "Video settings", - "videomute": "Start / Stop camera" + "videomute": "Stop camera", + "videounmute": "Start camera" }, "transcribing": { "ccButtonTooltip": "Start / Stop subtitles", diff --git a/react/features/chat/components/web/ChatButton.js b/react/features/chat/components/web/ChatButton.js index e28156178..2a1485272 100644 --- a/react/features/chat/components/web/ChatButton.js +++ b/react/features/chat/components/web/ChatButton.js @@ -24,30 +24,13 @@ type Props = AbstractButtonProps & { * Implementation of a button for accessing chat pane. */ class ChatButton extends AbstractButton { - accessibilityLabel = 'toolbar.accessibilityLabel.chat'; + accessibilityLabel = 'toolbar.accessibilityLabel.openChat'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.closeChat'; icon = IconMessage; label = 'toolbar.openChat'; toggledLabel = 'toolbar.closeChat'; - - /** - * Retrieves tooltip dynamically. - */ - get tooltip() { - if (this._isToggled()) { - return 'toolbar.closeChat'; - } - - return 'toolbar.openChat'; - } - - /** - * Required by linter due to AbstractButton overwritten prop being writable. - * - * @param {string} _value - The value. - */ - set tooltip(_value) { - // Unused. - } + tooltip = 'toolbar.openChat'; + toggledTooltip = 'toolbar.closeChat'; /** * Indicates whether this button is in toggled state or not. diff --git a/react/features/etherpad/components/SharedDocumentButton.web.js b/react/features/etherpad/components/SharedDocumentButton.web.js index a9c2e1a70..e9871327c 100644 --- a/react/features/etherpad/components/SharedDocumentButton.web.js +++ b/react/features/etherpad/components/SharedDocumentButton.web.js @@ -27,30 +27,13 @@ type Props = AbstractButtonProps & { * Implements an {@link AbstractButton} to open the chat screen on mobile. */ class SharedDocumentButton extends AbstractButton { - accessibilityLabel = 'toolbar.accessibilityLabel.document'; + accessibilityLabel = 'toolbar.accessibilityLabel.documentOpen'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.documentClose'; icon = IconShareDoc; label = 'toolbar.documentOpen'; toggledLabel = 'toolbar.documentClose'; - - /** - * Dynamically retrieves tooltip based on sharing state. - */ - get tooltip() { - if (this._isToggled()) { - return 'toolbar.documentClose'; - } - - return 'toolbar.documentOpen'; - } - - /** - * Required by linter due to AbstractButton overwritten prop being writable. - * - * @param {string} _value - The value. - */ - set tooltip(_value) { - // Unused. - } + tooltip = 'toolbar.documentOpen'; + toggledTooltip = 'toolbar.documentClose'; /** * Handles clicking / pressing the button, and opens / closes the appropriate dialog. diff --git a/react/features/participants-pane/components/web/ParticipantsPaneButton.js b/react/features/participants-pane/components/web/ParticipantsPaneButton.js index abd05de09..47963a817 100644 --- a/react/features/participants-pane/components/web/ParticipantsPaneButton.js +++ b/react/features/participants-pane/components/web/ParticipantsPaneButton.js @@ -24,9 +24,11 @@ type Props = AbstractButtonProps & { */ class ParticipantsPaneButton extends AbstractButton { accessibilityLabel = 'toolbar.accessibilityLabel.participants'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.closeParticipantsPane'; icon = IconUsers; label = 'toolbar.participants'; tooltip = 'toolbar.participants'; + toggledTooltip = 'toolbar.closeParticipantsPane'; /** * Indicates whether this button is in toggled state or not. diff --git a/react/features/reactions/components/web/RaiseHandButton.js b/react/features/reactions/components/web/RaiseHandButton.js index 3621c2899..a7c546749 100644 --- a/react/features/reactions/components/web/RaiseHandButton.js +++ b/react/features/reactions/components/web/RaiseHandButton.js @@ -21,25 +21,12 @@ type Props = AbstractButtonProps & { */ class RaiseHandButton extends AbstractButton { accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.lowerHand'; icon = IconRaiseHand; label = 'toolbar.raiseHand'; - toggledLabel = 'toolbar.raiseHand'; - - /** - * Retrieves tooltip dynamically. - */ - get tooltip() { - return 'toolbar.raiseHand'; - } - - /** - * Required by linter due to AbstractButton overwritten prop being writable. - * - * @param {string} _value - The value. - */ - set tooltip(_value) { - // Unused. - } + toggledLabel = 'toolbar.lowerYourHand'; + tooltip = 'toolbar.raiseHand'; + toggledTooltip = 'toolbar.lowerYourHand'; /** * Indicates whether this button is in toggled state or not. diff --git a/react/features/shared-video/components/web/SharedVideoButton.js b/react/features/shared-video/components/web/SharedVideoButton.js index 35dd53bfa..c59d3c2aa 100644 --- a/react/features/shared-video/components/web/SharedVideoButton.js +++ b/react/features/shared-video/components/web/SharedVideoButton.js @@ -35,29 +35,12 @@ type Props = AbstractButtonProps & { */ class SharedVideoButton extends AbstractButton { accessibilityLabel = 'toolbar.accessibilityLabel.sharedvideo'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.stopSharedVideo'; icon = IconPlay; label = 'toolbar.sharedvideo'; toggledLabel = 'toolbar.stopSharedVideo'; - - /** - * Dynamically retrieves tooltip based on sharing state. - */ - get tooltip() { - if (this._isToggled()) { - return 'toolbar.stopSharedVideo'; - } - - return 'toolbar.sharedvideo'; - } - - /** - * Required by linter due to AbstractButton overwritten prop being writable. - * - * @param {string} _value - The icon value. - */ - set tooltip(_value) { - // Unused. - } + tooltip = 'toolbar.sharedvideo'; + toggledTooltip = 'toolbar.stopSharedVideo'; /** * Handles clicking / pressing the button, and opens a new dialog. diff --git a/react/features/toolbox/components/AudioMuteButton.js b/react/features/toolbox/components/AudioMuteButton.js index 34e1dd66d..ca39e8a94 100644 --- a/react/features/toolbox/components/AudioMuteButton.js +++ b/react/features/toolbox/components/AudioMuteButton.js @@ -46,8 +46,10 @@ type Props = AbstractButtonProps & { */ class AudioMuteButton extends AbstractAudioMuteButton { accessibilityLabel = 'toolbar.accessibilityLabel.mute'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.unmute'; label = 'toolbar.mute'; tooltip = 'toolbar.mute'; + toggledTooltip = 'toolbar.unmute'; /** * Initializes a new {@code AudioMuteButton} instance. diff --git a/react/features/toolbox/components/VideoMuteButton.js b/react/features/toolbox/components/VideoMuteButton.js index 839a6270f..7b5dfb662 100644 --- a/react/features/toolbox/components/VideoMuteButton.js +++ b/react/features/toolbox/components/VideoMuteButton.js @@ -47,8 +47,10 @@ type Props = AbstractButtonProps & { */ class VideoMuteButton extends AbstractVideoMuteButton { accessibilityLabel = 'toolbar.accessibilityLabel.videomute'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.videounmute'; label = 'toolbar.videomute'; tooltip = 'toolbar.videomute'; + toggledTooltip = 'toolbar.videounmute'; /** * Initializes a new {@code VideoMuteButton} instance. diff --git a/react/features/toolbox/components/web/FullscreenButton.js b/react/features/toolbox/components/web/FullscreenButton.js index e4fad0b4e..a6c31bb89 100644 --- a/react/features/toolbox/components/web/FullscreenButton.js +++ b/react/features/toolbox/components/web/FullscreenButton.js @@ -17,9 +17,12 @@ type Props = AbstractButtonProps & { * Implementation of a button for toggling fullscreen state. */ class FullscreenButton extends AbstractButton { - accessibilityLabel = 'toolbar.accessibilityLabel.fullScreen'; + accessibilityLabel = 'toolbar.accessibilityLabel.enterFullScreen'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.exitFullScreen'; label = 'toolbar.enterFullScreen'; toggledLabel = 'toolbar.exitFullScreen'; + tooltip = 'toolbar.enterFullScreen'; + toggledTooltip = 'toolbar.exitFullScreen'; /** * Retrieves icon dynamically. @@ -41,26 +44,6 @@ class FullscreenButton extends AbstractButton { // Unused. } - /** - * Retrieves icon dynamically. - */ - get tooltip() { - if (this._isToggled()) { - return 'toolbar.exitFullScreen'; - } - - return 'toolbar.enterFullScreen'; - } - - /** - * Required by linter due to AbstractButton overwritten prop being writable. - * - * @param {string} _value - The value. - */ - set tooltip(_value) { - // Unused. - } - /** * Indicates whether this button is in toggled state or not. * diff --git a/react/features/toolbox/components/web/HangupToggleButton.tsx b/react/features/toolbox/components/web/HangupToggleButton.tsx index 07384224b..194fd2534 100644 --- a/react/features/toolbox/components/web/HangupToggleButton.tsx +++ b/react/features/toolbox/components/web/HangupToggleButton.tsx @@ -30,24 +30,9 @@ class HangupToggleButton extends AbstractButton { label = 'toolbar.hangup'; toggledIcon = IconCloseLarge; toggledLabel = 'toolbar.hangup'; + tooltip = 'toolbar.hangup'; props: Props; - /** - * Retrieves tooltip dynamically. - */ - get tooltip() { - return 'toolbar.hangup'; - } - - /** - * Required by linter due to AbstractButton overwritten prop being writable. - * - * @param {string} _value - The value. - */ - set tooltip(_value) { - // Unused. - } - /** * Indicates whether this button is in toggled state or not. * diff --git a/react/features/toolbox/components/web/OverflowToggleButton.js b/react/features/toolbox/components/web/OverflowToggleButton.js index 8a294c404..427f95e7a 100644 --- a/react/features/toolbox/components/web/OverflowToggleButton.js +++ b/react/features/toolbox/components/web/OverflowToggleButton.js @@ -26,25 +26,11 @@ type Props = AbstractButtonProps & { */ class OverflowToggleButton extends AbstractButton { accessibilityLabel = 'toolbar.accessibilityLabel.moreActions'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.closeMoreActions'; icon = IconDotsHorizontal; label = 'toolbar.moreActions'; toggledLabel = 'toolbar.moreActions'; - - /** - * Retrieves tooltip dynamically. - */ - get tooltip() { - return 'toolbar.moreActions'; - } - - /** - * Required by linter due to AbstractButton overwritten prop being writable. - * - * @param {string} _value - The value. - */ - set tooltip(_value) { - // Unused. - } + tooltip = 'toolbar.moreActions'; /** * Indicates whether this button is in toggled state or not. diff --git a/react/features/toolbox/components/web/ShareDesktopButton.js b/react/features/toolbox/components/web/ShareDesktopButton.js index b4b091607..6705a6c58 100644 --- a/react/features/toolbox/components/web/ShareDesktopButton.js +++ b/react/features/toolbox/components/web/ShareDesktopButton.js @@ -31,11 +31,11 @@ type Props = AbstractButtonProps & { */ class ShareDesktopButton extends AbstractButton { accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.stopScreenSharing'; label = 'toolbar.startScreenSharing'; icon = IconScreenshare; toggledIcon = IconStopScreenshare; toggledLabel = 'toolbar.stopScreenSharing'; - tooltip = 'toolbar.accessibilityLabel.shareYourScreen'; /** * Retrieves tooltip dynamically. diff --git a/react/features/video-layout/components/TileViewButton.js b/react/features/video-layout/components/TileViewButton.js index 76153b7cd..0832795ef 100644 --- a/react/features/video-layout/components/TileViewButton.js +++ b/react/features/video-layout/components/TileViewButton.js @@ -38,7 +38,8 @@ type Props = AbstractButtonProps & { * @augments AbstractButton */ class TileViewButton extends AbstractButton { - accessibilityLabel = 'toolbar.accessibilityLabel.tileView'; + accessibilityLabel = 'toolbar.accessibilityLabel.enterTileView'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.exitTileView'; icon = IconTileView; label = 'toolbar.enterTileView'; toggledLabel = 'toolbar.exitTileView'; diff --git a/react/features/whiteboard/components/web/WhiteboardButton.tsx b/react/features/whiteboard/components/web/WhiteboardButton.tsx index 97a1738dd..5eb5adad5 100644 --- a/react/features/whiteboard/components/web/WhiteboardButton.tsx +++ b/react/features/whiteboard/components/web/WhiteboardButton.tsx @@ -27,7 +27,8 @@ type Props = AbstractButtonProps & { * Component that renders a toolbar button for the whiteboard. */ class WhiteboardButton extends AbstractButton { - accessibilityLabel = 'toolbar.accessibilityLabel.whiteboard'; + accessibilityLabel = 'toolbar.accessibilityLabel.showWhiteboard'; + toggledAccessibilityLabel = 'toolbar.accessibilityLabel.hideWhiteboard'; icon = IconWhiteboard; label = 'toolbar.showWhiteboard'; toggledIcon = IconWhiteboardHide;