Merge pull request #13177 from KDSBrowne/bbb-wcag-01
WCAG2.1 - Chat updates
This commit is contained in:
commit
114649238b
@ -74,6 +74,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
[hidden]:not([hidden="false"]) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
textarea::-webkit-input-placeholder,
|
||||
input::-webkit-input-placeholder {
|
||||
color: var(--palette-placeholder-text);
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('gesturestart', function (e) {
|
||||
|
@ -47,6 +47,7 @@ import ConnectionStatusService from '/imports/ui/components/connection-status/se
|
||||
import { NAVBAR_HEIGHT, LARGE_NAVBAR_HEIGHT } from '/imports/ui/components/layout/defaultValues';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import LayoutService from '/imports/ui/components/layout/service';
|
||||
import { registerTitleView } from '/imports/utils/dom-utils';
|
||||
|
||||
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
@ -99,6 +100,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.whiteboard.annotations.poll',
|
||||
description: 'message displayed when a poll is published',
|
||||
},
|
||||
defaultViewLabel: {
|
||||
id: 'app.title.defaultViewLabel',
|
||||
description: 'view name apended to document title',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
@ -153,6 +158,8 @@ class App extends Component {
|
||||
const { browserName } = browserInfo;
|
||||
const { osName } = deviceInfo;
|
||||
|
||||
registerTitleView(intl.formatMessage(intlMessages.defaultViewLabel));
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_IS_RTL,
|
||||
value: isRTL,
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactModal from 'react-modal';
|
||||
import { styles } from './styles.scss';
|
||||
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
|
||||
|
||||
const propTypes = {
|
||||
overlayClassName: PropTypes.string.isRequired,
|
||||
@ -19,6 +20,15 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
export default class ModalBase extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
registerTitleView(this.props.contentLabel);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
unregisterTitleView();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.isOpen) return null;
|
||||
|
||||
@ -27,8 +37,8 @@ export default class ModalBase extends Component {
|
||||
{...this.props}
|
||||
parentSelector={() => {
|
||||
if (document.fullscreenElement &&
|
||||
document.fullscreenElement.nodeName &&
|
||||
document.fullscreenElement.nodeName.toLowerCase() === 'div')
|
||||
document.fullscreenElement.nodeName &&
|
||||
document.fullscreenElement.nodeName.toLowerCase() === 'div')
|
||||
return document.fullscreenElement;
|
||||
else return document.body;
|
||||
}}
|
||||
@ -55,12 +65,12 @@ export const withModalState = ComponentToWrap =>
|
||||
this.show = this.show.bind(this);
|
||||
}
|
||||
|
||||
hide(cb = () => {}) {
|
||||
hide(cb = () => { }) {
|
||||
Promise.resolve(cb())
|
||||
.then(() => this.setState({ isOpen: false }));
|
||||
}
|
||||
|
||||
show(cb = () => {}) {
|
||||
show(cb = () => { }) {
|
||||
Promise.resolve(cb())
|
||||
.then(() => this.setState({ isOpen: true }));
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import logger from '/imports/startup/client/logger';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import { toast } from 'react-toastify';
|
||||
import _ from 'lodash';
|
||||
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
|
||||
import { styles } from './styles';
|
||||
|
||||
const { isMobile } = deviceInfo;
|
||||
@ -214,6 +215,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploder.clearErrorsDesc',
|
||||
description: 'aria description for button clearing upload error',
|
||||
},
|
||||
uploadViewTitle: {
|
||||
id: 'app.presentationUploder.uploadViewTitle',
|
||||
description: 'view name apended to document title',
|
||||
}
|
||||
});
|
||||
|
||||
class PresentationUploader extends Component {
|
||||
@ -251,10 +256,16 @@ class PresentationUploader extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isOpen, presentations: propPresentations } = this.props;
|
||||
const { isOpen, presentations: propPresentations, intl } = this.props;
|
||||
const { presentations } = this.state;
|
||||
|
||||
if (!isOpen && prevProps.isOpen) {
|
||||
unregisterTitleView();
|
||||
}
|
||||
|
||||
// Updates presentation list when chat modal opens to avoid missing presentations
|
||||
if (isOpen && !prevProps.isOpen) {
|
||||
registerTitleView(intl.formatMessage(intlMessages.uploadViewTitle));
|
||||
const focusableElements =
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
||||
const modal = document.getElementById('upload-modal');
|
||||
|
@ -22,6 +22,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.guest-policy.description',
|
||||
description: 'Guest policy description',
|
||||
},
|
||||
policyBtnDesc: {
|
||||
id: 'app.guest-policy.policyBtnDesc',
|
||||
description: 'aria description for guest policy button',
|
||||
},
|
||||
askModerator: {
|
||||
id: 'app.guest-policy.button.askModerator',
|
||||
description: 'Ask moderator button label',
|
||||
@ -84,9 +88,11 @@ class GuestPolicyComponent extends PureComponent {
|
||||
<div className={styles.content}>
|
||||
<Button
|
||||
color="primary"
|
||||
className={styles.button}
|
||||
className={[styles.button, guestPolicy === ASK_MODERATOR && styles.active].join(' ')}
|
||||
disabled={guestPolicy === ASK_MODERATOR}
|
||||
label={intl.formatMessage(intlMessages.askModerator)}
|
||||
aria-describedby={guestPolicy === ASK_MODERATOR ? 'selected-btn-desc' : 'policy-btn-desc'}
|
||||
aria-pressed={guestPolicy === ASK_MODERATOR}
|
||||
data-test="askModerator"
|
||||
onClick={() => {
|
||||
changeGuestPolicy(ASK_MODERATOR);
|
||||
@ -95,9 +101,11 @@ class GuestPolicyComponent extends PureComponent {
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
className={styles.button}
|
||||
className={[styles.button, guestPolicy === ALWAYS_ACCEPT && styles.active].join(' ')}
|
||||
disabled={guestPolicy === ALWAYS_ACCEPT}
|
||||
label={intl.formatMessage(intlMessages.alwaysAccept)}
|
||||
aria-describedby={guestPolicy === ALWAYS_ACCEPT ? 'selected-btn-desc' : 'policy-btn-desc'}
|
||||
aria-pressed={guestPolicy === ALWAYS_ACCEPT}
|
||||
data-test="alwaysAccept"
|
||||
onClick={() => {
|
||||
changeGuestPolicy(ALWAYS_ACCEPT);
|
||||
@ -106,9 +114,11 @@ class GuestPolicyComponent extends PureComponent {
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
className={styles.button}
|
||||
className={[styles.button, guestPolicy === ALWAYS_DENY && styles.active].join(' ')}
|
||||
disabled={guestPolicy === ALWAYS_DENY}
|
||||
label={intl.formatMessage(intlMessages.alwaysDeny)}
|
||||
aria-describedby={guestPolicy === ALWAYS_DENY ? 'selected-btn-desc' : 'policy-btn-desc'}
|
||||
aria-pressed={guestPolicy === ALWAYS_DENY}
|
||||
data-test="alwaysDeny"
|
||||
onClick={() => {
|
||||
changeGuestPolicy(ALWAYS_DENY);
|
||||
@ -116,6 +126,9 @@ class GuestPolicyComponent extends PureComponent {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div id="policy-btn-desc" aria-hidden className="sr-only">
|
||||
{intl.formatMessage(intlMessages.policyBtnDesc)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
@ -66,3 +66,9 @@
|
||||
box-sizing: border-box;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.active {
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,27 @@
|
||||
|
||||
const TITLE_WITH_VIEW = 3;
|
||||
const ARIA_ALERT_TIMEOUT = 3000;
|
||||
|
||||
const getTitleData = () => {
|
||||
const title = document.getElementsByTagName('title')[0];
|
||||
return { title, data: title?.text?.split(' - ') };
|
||||
}
|
||||
|
||||
export const registerTitleView = (v) => {
|
||||
const { title, data } = getTitleData();
|
||||
if (data.length < TITLE_WITH_VIEW) data.push(`${v}`);
|
||||
else data.splice(TITLE_WITH_VIEW - 1, TITLE_WITH_VIEW, v);
|
||||
title.text = data.join(' - ');
|
||||
};
|
||||
|
||||
export const unregisterTitleView = () => {
|
||||
const { title, data } = getTitleData();
|
||||
if (data.length === TITLE_WITH_VIEW) {
|
||||
data.splice(TITLE_WITH_VIEW - 1, TITLE_WITH_VIEW, 'Default');
|
||||
}
|
||||
title.text = data.join(' - ');
|
||||
};
|
||||
|
||||
export const alertScreenReader = (s = '') => {
|
||||
const app = document.getElementById('app');
|
||||
const ariaAlert = document.createElement("div");
|
||||
@ -15,4 +37,4 @@ export const alertScreenReader = (s = '') => {
|
||||
}, ARIA_ALERT_TIMEOUT);
|
||||
};
|
||||
|
||||
export default { alertScreenReader };
|
||||
export default { registerTitleView, unregisterTitleView, alertScreenReader };
|
||||
|
@ -53,6 +53,7 @@
|
||||
"app.captions.pad.dictationOffDesc": "Turns speech recognition off",
|
||||
"app.captions.pad.speechRecognitionStop": "Speech recognition stopped due to the browser incompatibility or some time of silence",
|
||||
"app.textInput.sendLabel": "Send",
|
||||
"app.title.defaultViewLabel": "Default presentation view",
|
||||
"app.note.title": "Shared Notes",
|
||||
"app.note.label": "Note",
|
||||
"app.note.hideNoteLabel": "Hide note",
|
||||
@ -232,6 +233,7 @@
|
||||
"app.presentationUploder.itemPlural" : "items",
|
||||
"app.presentationUploder.clearErrors": "Clear errors",
|
||||
"app.presentationUploder.clearErrorsDesc": "Clears failed presentation uploads",
|
||||
"app.presentationUploder.uploadViewTitle": "Upload Presentation",
|
||||
"app.poll.pollPaneTitle": "Polling",
|
||||
"app.poll.quickPollTitle": "Quick Poll",
|
||||
"app.poll.hidePollDesc": "Hides the poll menu pane",
|
||||
@ -664,6 +666,7 @@
|
||||
"app.guest-policy.button.askModerator": "Ask moderator",
|
||||
"app.guest-policy.button.alwaysAccept": "Always accept",
|
||||
"app.guest-policy.button.alwaysDeny": "Always deny",
|
||||
"app.guest-policy.policyBtnDesc": "Sets meeting guest policy",
|
||||
"app.connection-status.ariaTitle": "Connection status modal",
|
||||
"app.connection-status.title": "Connection status",
|
||||
"app.connection-status.description": "View users' connection status",
|
||||
|
Loading…
Reference in New Issue
Block a user