Merge pull request #15930 from KDSBrowne/26-con-status-a11y

Connection Status Modal A11y Updates
This commit is contained in:
Ramón Souza 2022-11-24 10:21:33 -03:00 committed by GitHub
commit bdf8dad8ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 212 additions and 162 deletions

View File

@ -158,7 +158,7 @@ class ConnectionStatusComponent extends PureComponent {
this.help = Service.getHelp();
this.state = {
selectedTab: '1',
selectedTab: 0,
dataPage: '1',
dataSaving: props.dataSaving,
hasNetworkData: false,
@ -187,6 +187,7 @@ class ConnectionStatusComponent extends PureComponent {
this.audioDownloadLabel = intl.formatMessage(intlMessages.audioDownloadRate);
this.videoUploadLabel = intl.formatMessage(intlMessages.videoUploadRate);
this.videoDownloadLabel = intl.formatMessage(intlMessages.videoDownloadRate);
this.handleSelectTab = this.handleSelectTab.bind(this);
}
async componentDidMount() {
@ -197,12 +198,24 @@ class ConnectionStatusComponent extends PureComponent {
Meteor.clearInterval(this.rateInterval);
}
handleSelectTab(tab) {
this.setState({
selectedTab: tab,
});
}
handleDataSavingChange(key) {
const { dataSaving } = this.state;
dataSaving[key] = !dataSaving[key];
this.setState(dataSaving);
}
setButtonMessage(msg) {
this.setState({
copyButtonText: msg,
});
}
/**
* Start monitoring the network data.
* @return {Promise} A Promise that resolves when process started.
@ -262,6 +275,43 @@ class ConnectionStatusComponent extends PureComponent {
}, NETWORK_MONITORING_INTERVAL_MS);
}
displaySettingsStatus(status) {
const { intl } = this.props;
return (
<Styled.ToggleLabel>
{status ? intl.formatMessage(intlMessages.on)
: intl.formatMessage(intlMessages.off)}
</Styled.ToggleLabel>
);
}
/**
* Copy network data to clipboard
* @return {Promise} A Promise that is resolved after data is copied.
*
*
*/
async copyNetworkData() {
const { intl } = this.props;
const {
networkData,
hasNetworkData,
} = this.state;
if (!hasNetworkData) return;
this.setButtonMessage(intl.formatMessage(intlMessages.copied));
const data = JSON.stringify(networkData, null, 2);
await navigator.clipboard.writeText(data);
this.copyNetworkDataTimeout = setTimeout(() => {
this.setButtonMessage(intl.formatMessage(intlMessages.copy));
}, MIN_TIMEOUT);
}
renderEmpty() {
const { intl } = this.props;
@ -278,52 +328,6 @@ class ConnectionStatusComponent extends PureComponent {
);
}
displaySettingsStatus(status) {
const { intl } = this.props;
return (
<Styled.ToggleLabel>
{status ? intl.formatMessage(intlMessages.on)
: intl.formatMessage(intlMessages.off)}
</Styled.ToggleLabel>
);
}
setButtonMessage(msg) {
this.setState({
copyButtonText: msg,
});
}
/**
* Copy network data to clipboard
* @param {Object} e Event object from click event
* @return {Promise} A Promise that is resolved after data is copied.
*
*
*/
async copyNetworkData(e) {
const { intl } = this.props;
const {
networkData,
hasNetworkData,
} = this.state;
if (!hasNetworkData) return;
const { target: copyButton } = e;
this.setButtonMessage(intl.formatMessage(intlMessages.copied));
const data = JSON.stringify(networkData, null, 2);
await navigator.clipboard.writeText(data);
this.copyNetworkDataTimeout = setTimeout(() => {
this.setButtonMessage(intl.formatMessage(intlMessages.copy));
}, MIN_TIMEOUT);
}
renderConnections() {
const {
connectionStatus,
@ -345,7 +349,7 @@ class ConnectionStatusComponent extends PureComponent {
return (
<Styled.Item
key={index}
key={`${conn?.name}-${dateTime}`}
last={(index + 1) === connections.length}
data-test="connectionStatusItemUser"
>
@ -521,6 +525,7 @@ class ConnectionStatusComponent extends PureComponent {
<Styled.Prev>
<Styled.ButtonLeft
role="button"
tabIndex={0}
disabled={dataPage === '1'}
aria-label={`${intl.formatMessage(intlMessages.prev)} ${intl.formatMessage(intlMessages.ariaTitle)}`}
onClick={handlePaginationClick.bind(this, 'prev')}
@ -585,6 +590,7 @@ class ConnectionStatusComponent extends PureComponent {
<Styled.Next>
<Styled.ButtonRight
role="button"
tabIndex={0}
disabled={dataPage === '2'}
aria-label={`${intl.formatMessage(intlMessages.next)} ${intl.formatMessage(intlMessages.ariaTitle)}`}
onClick={handlePaginationClick.bind(this, 'next')}
@ -619,81 +625,23 @@ class ConnectionStatusComponent extends PureComponent {
return null;
}
const { intl } = this.props;
const { hasNetworkData } = this.state;
const { hasNetworkData, copyButtonText } = this.state;
return (
<Styled.CopyContainer aria-live="polite">
<Styled.Copy
disabled={!hasNetworkData}
role="button"
data-test="copyStats"
data-test="copyStats"
onClick={this.copyNetworkData.bind(this)}
onKeyPress={this.copyNetworkData.bind(this)}
tabIndex={0}
>
{this.state.copyButtonText}
{copyButtonText}
</Styled.Copy>
</Styled.CopyContainer>
);
}
/**
* The navigation bar.
* @returns {Object} The component to be renderized.
*/
renderNavigation() {
const { intl } = this.props;
const handleTabClick = (event) => {
const activeTabElement = document.querySelector('.activeConnectionStatusTab');
const { target } = event;
if (activeTabElement) {
activeTabElement.classList.remove('activeConnectionStatusTab');
}
target.classList.add('activeConnectionStatusTab');
this.setState({
selectedTab: target.dataset.tab,
});
}
return (
<Styled.Navigation>
<div
data-tab="1"
className="activeConnectionStatusTab"
onClick={handleTabClick}
onKeyDown={handleTabClick}
role="button"
>
{intl.formatMessage(intlMessages.connectionStats)}
</div>
<div
data-tab="2"
onClick={handleTabClick}
onKeyDown={handleTabClick}
role="button"
>
{intl.formatMessage(intlMessages.myLogs)}
</div>
{Service.isModerator()
&& (
<div
data-tab="3"
onClick={handleTabClick}
onKeyDown={handleTabClick}
role="button"
>
{intl.formatMessage(intlMessages.sessionLogs)}
</div>
)
}
</Styled.Navigation>
);
}
render() {
const {
closeModal,
@ -715,18 +663,43 @@ class ConnectionStatusComponent extends PureComponent {
{intl.formatMessage(intlMessages.title)}
</Styled.Title>
</Styled.Header>
{this.renderNavigation()}
<Styled.Main>
<Styled.Body>
{selectedTab === '1'
? this.renderNetworkData()
: this.renderConnections()
<Styled.ConnectionTabs
onSelect={this.handleSelectTab}
selectedIndex={selectedTab}
>
<Styled.ConnectionTabList>
<Styled.ConnectionTabSelector selectedClassName="is-selected">
<span id="connection-status-tab">{intl.formatMessage(intlMessages.title)}</span>
</Styled.ConnectionTabSelector>
<Styled.ConnectionTabSelector selectedClassName="is-selected">
<span id="my-logs-tab">{intl.formatMessage(intlMessages.myLogs)}</span>
</Styled.ConnectionTabSelector>
{Service.isModerator()
&& (
<Styled.ConnectionTabSelector selectedClassName="is-selected">
<span id="session-logs-tab">{intl.formatMessage(intlMessages.sessionLogs)}</span>
</Styled.ConnectionTabSelector>
)
}
</Styled.Body>
{selectedTab === '1' &&
this.renderCopyDataButton()
</Styled.ConnectionTabList>
<Styled.ConnectionTabPanel selectedClassName="is-selected">
<div>
{this.renderNetworkData()}
{this.renderCopyDataButton()}
</div>
</Styled.ConnectionTabPanel>
<Styled.ConnectionTabPanel selectedClassName="is-selected">
<div>{this.renderConnections()}</div>
</Styled.ConnectionTabPanel>
{Service.isModerator()
&& (
<Styled.ConnectionTabPanel selectedClassName="is-selected">
<div>{this.renderConnections()}</div>
</Styled.ConnectionTabPanel>
)
}
</Styled.Main>
</Styled.ConnectionTabs>
</Styled.Container>
</Styled.ConnectionStatusModal>
);

View File

@ -7,12 +7,13 @@ import {
colorGrayLabel,
colorGrayLightest,
colorPrimary,
colorWhite,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
smPaddingX,
smPaddingY,
mdPaddingY,
lgPaddingY,
lgPaddingX,
titlePositionLeft,
mdPaddingX,
borderSizeLarge,
@ -26,7 +27,11 @@ import {
hasPhoneDimentions,
mediumDown,
hasPhoneWidth,
smallOnly,
} from '/imports/ui/stylesheets/styled-components/breakpoints';
import {
Tab, Tabs, TabList, TabPanel,
} from 'react-tabs';
const Item = styled.div`
display: flex;
@ -173,6 +178,7 @@ const NetworkDataContainer = styled.div`
width: 100%;
height: 100%;
display: flex;
padding-bottom: 1.25rem;
@media ${mediumDown} {
justify-content: space-between;
@ -202,6 +208,8 @@ const CopyContainer = styled.div`
const ConnectionStatusModal = styled(Modal)`
padding: 1rem;
height: 28rem;
`;
const Container = styled.div`
@ -271,11 +279,12 @@ const Helper = styled.div`
flex-direction: column;
justify-content: center;
align-items: center;
padding: .5rem;
@media ${mediumDown} {
${({ page }) => page === '1'
${({ page }) => (page === '1'
? 'display: flex;'
: 'display: none;'}
: 'display: none;')}
}
`;
@ -286,9 +295,9 @@ const NetworkDataContent = styled.div`
flex-grow: 1;
@media ${mediumDown} {
${({ page }) => page === '2'
${({ page }) => (page === '2'
? 'display: flex;'
: 'display: none;'}
: 'display: none;')}
}
`;
@ -316,42 +325,6 @@ const Body = styled.div`
position: relative;
`;
const Navigation = styled.div`
display: flex;
border: none;
border-bottom: 1px solid ${colorOffWhite};
user-select: none;
overflow-y: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
& :not(:last-child) {
margin: 0;
margin-right: ${lgPaddingX};
}
.activeConnectionStatusTab {
border: none;
border-bottom: 2px solid ${colorPrimary};
color: ${colorPrimary};
}
& * {
cursor: pointer;
white-space: nowrap;
}
[dir="rtl"] & {
& :not(:last-child) {
margin: 0;
margin-left: ${lgPaddingX};
}
}
`;
const Prev = styled.div`
display: none;
margin: 0 .5rem 0 .25rem;
@ -394,8 +367,9 @@ const Button = styled.button`
opacity: .75;
}
&:hover,
&:focus {
outline: none;
outline: 2px solid ${colorPrimary};
}
@media ${hasPhoneWidth} {
@ -431,6 +405,106 @@ const Chevron = styled.svg`
}
`;
const ConnectionTabs = styled(Tabs)`
display: flex;
flex-flow: column;
justify-content: flex-start;
@media ${smallOnly} {
width: 100%;
flex-flow: column;
}
`;
const ConnectionTabList = styled(TabList)`
display: flex;
flex-flow: row;
margin: 0;
margin-bottom: .5rem;
border: none;
padding: 0;
width: calc(100% / 3);
@media ${smallOnly} {
width: 100%;
flex-flow: row;
flex-wrap: wrap;
justify-content: center;
}
`;
const ConnectionTabPanel = styled(TabPanel)`
display: none;
margin: 0 0 0 1rem;
height: 13rem;
[dir="rtl"] & {
margin: 0 1rem 0 0;
}
&.is-selected {
display: flex;
flex-flow: column;
}
@media ${smallOnly} {
width: 100%;
margin: 0;
padding-left: 1rem;
padding-right: 1rem;
}
`;
const ConnectionTabSelector = styled(Tab)`
display: flex;
flex-flow: row;
font-size: 0.9rem;
flex: 0 0 auto;
justify-content: flex-start;
border: none !important;
padding: ${mdPaddingY} ${mdPaddingX};
border-radius: .2rem;
cursor: pointer;
margin-bottom: ${smPaddingY};
align-items: center;
flex-grow: 0;
min-width: 0;
& > span {
min-width: 0;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media ${smallOnly} {
max-width: 100%;
margin: 0 ${smPaddingX} 0 0;
& > i {
display: none;
}
[dir="rtl"] & {
margin: 0 0 0 ${smPaddingX};
}
}
span {
border-bottom: 2px solid ${colorWhite};
}
&.is-selected {
border: none;
color: ${colorPrimary};
span {
border-bottom: 2px solid ${colorPrimary};
}
}
`;
export default {
Item,
Left,
@ -463,7 +537,6 @@ export default {
NetworkDataContent,
Main,
Body,
Navigation,
FullName,
DataColumn,
Prev,
@ -471,4 +544,8 @@ export default {
ButtonLeft,
ButtonRight,
Chevron,
ConnectionTabs,
ConnectionTabList,
ConnectionTabSelector,
ConnectionTabPanel,
};