summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-09-17 18:04:02 +0200
committerOskar Nyberg <oskar@mullvad.net>2020-09-24 15:02:20 +0200
commit6a8ce1507a99b6cfaf6b722f4881350a8ebc9b05 (patch)
tree755bfadcb90693eefb386e2efc9c94f11b46ae9d
parentce533488629f9815fa6696e97a2b6598546c56a6 (diff)
downloadmullvadvpn-6a8ce1507a99b6cfaf6b722f4881350a8ebc9b05.tar.xz
mullvadvpn-6a8ce1507a99b6cfaf6b722f4881350a8ebc9b05.zip
Add labels to extLink icons and use them as descriptions for buttons
-rw-r--r--gui/src/renderer/components/Account.tsx20
-rw-r--r--gui/src/renderer/components/AriaGroup.tsx80
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorView.tsx20
-rw-r--r--gui/src/renderer/components/Settings.tsx55
-rw-r--r--gui/src/renderer/components/Support.tsx48
-rw-r--r--gui/src/renderer/components/WireguardKeys.tsx24
6 files changed, 184 insertions, 63 deletions
diff --git a/gui/src/renderer/components/Account.tsx b/gui/src/renderer/components/Account.tsx
index 6dae04387e..7905431f1d 100644
--- a/gui/src/renderer/components/Account.tsx
+++ b/gui/src/renderer/components/Account.tsx
@@ -14,6 +14,7 @@ import {
} from './AccountStyles';
import AccountTokenLabel from './AccountTokenLabel';
import * as AppButton from './AppButton';
+import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import { Layout } from './Layout';
import { ModalContainer } from './Modal';
import { BackBarItem, NavigationBar, NavigationItems, TitleBarItem } from './NavigationBar';
@@ -81,10 +82,21 @@ export default class Account extends React.Component<IProps> {
<AppButton.BlockingButton
disabled={this.props.isOffline}
onClick={this.props.onBuyMore}>
- <StyledBuyCreditButton>
- <AppButton.Label>{messages.gettext('Buy more credit')}</AppButton.Label>
- <AppButton.Icon source="icon-extLink" height={16} width={16} />
- </StyledBuyCreditButton>
+ <AriaDescriptionGroup>
+ <AriaDescribed>
+ <StyledBuyCreditButton>
+ <AppButton.Label>{messages.gettext('Buy more credit')}</AppButton.Label>
+ <AriaDescription>
+ <AppButton.Icon
+ source="icon-extLink"
+ height={16}
+ width={16}
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
+ </StyledBuyCreditButton>
+ </AriaDescribed>
+ </AriaDescriptionGroup>
</AppButton.BlockingButton>
<StyledRedeemVoucherButton />
diff --git a/gui/src/renderer/components/AriaGroup.tsx b/gui/src/renderer/components/AriaGroup.tsx
index 19913171ac..bf5b2d7439 100644
--- a/gui/src/renderer/components/AriaGroup.tsx
+++ b/gui/src/renderer/components/AriaGroup.tsx
@@ -28,12 +28,40 @@ export function AriaControlGroup(props: IAriaGroupProps) {
);
}
+interface IAriaDescriptionContext {
+ descriptionId?: string;
+ setHasDescription: (value: boolean) => void;
+}
+
+const AriaDescriptionContext = React.createContext<IAriaDescriptionContext>({
+ setHasDescription(_value) {
+ throw new Error('Missing AriaDescriptionContext.Provider');
+ },
+});
+
+export function AriaDescriptionGroup(props: IAriaGroupProps) {
+ const id = useMemo(getNewId, []);
+ const [hasDescription, setHasDescription] = useState(false);
+
+ const contextValue = useMemo(
+ () => ({
+ descriptionId: hasDescription ? `${id}-description` : undefined,
+ setHasDescription,
+ }),
+ [hasDescription],
+ );
+
+ return (
+ <AriaDescriptionContext.Provider value={contextValue}>
+ {props.children}
+ </AriaDescriptionContext.Provider>
+ );
+}
+
interface IAriaInputContext {
inputId: string;
labelId?: string;
- descriptionId?: string;
setHasLabel: (value: boolean) => void;
- setHasDescription: (value: boolean) => void;
}
const missingAriaInputContextError = new Error('Missing AriaInputContext.Provider');
@@ -44,27 +72,26 @@ const AriaInputContext = React.createContext<IAriaInputContext>({
setHasLabel() {
throw missingAriaInputContextError;
},
- setHasDescription() {
- throw missingAriaInputContextError;
- },
});
export function AriaInputGroup(props: IAriaGroupProps) {
const id = useMemo(getNewId, []);
const [hasLabel, setHasLabel] = useState(false);
- const [hasDescription, setHasDescription] = useState(false);
- const contextValue = {
- inputId: `${id}-input`,
- labelId: hasLabel ? `${id}-label` : undefined,
- descriptionId: hasDescription ? `${id}-description` : undefined,
- setHasLabel,
- setHasDescription,
- };
+ const contextValue = useMemo(
+ () => ({
+ inputId: `${id}-input`,
+ labelId: hasLabel ? `${id}-label` : undefined,
+ setHasLabel,
+ }),
+ [hasLabel],
+ );
return (
- <AriaInputContext.Provider value={contextValue}>{props.children}</AriaInputContext.Provider>
+ <AriaDescriptionGroup>
+ <AriaInputContext.Provider value={contextValue}>{props.children}</AriaInputContext.Provider>
+ </AriaDescriptionGroup>
);
}
@@ -83,13 +110,16 @@ export function AriaControls(props: IAriaElementProps) {
}
export function AriaInput(props: IAriaElementProps) {
- const { inputId, labelId, descriptionId } = useContext(AriaInputContext);
+ const { inputId, labelId } = useContext(AriaInputContext);
- return React.cloneElement(props.children, {
- id: inputId,
- 'aria-labelledby': labelId,
- 'aria-describedby': descriptionId,
- });
+ return (
+ <AriaDescribed>
+ {React.cloneElement(props.children, {
+ id: inputId,
+ 'aria-labelledby': labelId,
+ })}
+ </AriaDescribed>
+ );
}
export function AriaLabel(props: IAriaElementProps) {
@@ -106,8 +136,16 @@ export function AriaLabel(props: IAriaElementProps) {
});
}
+export function AriaDescribed(props: IAriaElementProps) {
+ const { descriptionId } = useContext(AriaDescriptionContext);
+
+ return React.cloneElement(props.children, {
+ 'aria-describedby': descriptionId,
+ });
+}
+
export function AriaDescription(props: IAriaElementProps) {
- const { descriptionId, setHasDescription } = useContext(AriaInputContext);
+ const { descriptionId, setHasDescription } = useContext(AriaDescriptionContext);
useEffect(() => {
setHasDescription(true);
diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
index d64c780808..2a2af26e54 100644
--- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx
+++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
@@ -6,6 +6,7 @@ import { AccountToken } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
import { LoginState } from '../redux/account/reducers';
import * as AppButton from './AppButton';
+import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import * as Cell from './Cell';
import {
StyledAccountTokenContainer,
@@ -168,10 +169,21 @@ export default class ExpiredAccountErrorView extends React.Component<
<AppButton.BlockingButton
disabled={this.getRecoveryAction() === RecoveryAction.disconnect}
onClick={this.onOpenExternalPayment}>
- <StyledBuyCreditButton>
- <AppButton.Label>{buttonText}</AppButton.Label>
- <AppButton.Icon source="icon-extLink" height={16} width={16} />
- </StyledBuyCreditButton>
+ <AriaDescriptionGroup>
+ <AriaDescribed>
+ <StyledBuyCreditButton>
+ <AppButton.Label>{buttonText}</AppButton.Label>
+ <AriaDescription>
+ <AppButton.Icon
+ source="icon-extLink"
+ height={16}
+ width={16}
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
+ </StyledBuyCreditButton>
+ </AriaDescribed>
+ </AriaDescriptionGroup>
</AppButton.BlockingButton>
);
}
diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx
index a852d084ea..3cfdefebd5 100644
--- a/gui/src/renderer/components/Settings.tsx
+++ b/gui/src/renderer/components/Settings.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import { colors, links } from '../../config.json';
import { hasExpired, formatRemainingTime } from '../../shared/account-expiry';
import { messages } from '../../shared/gettext';
+import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import * as Cell from './Cell';
import { Layout } from './Layout';
import {
@@ -176,15 +177,24 @@ export default class Settings extends React.Component<IProps> {
}
return (
- <>
- <Cell.CellButton disabled={this.props.isOffline} onClick={this.openDownloadLink}>
- {icon}
- <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label>
- <Cell.SubText>{this.props.appVersion}</Cell.SubText>
- <Cell.Icon height={16} width={16} source="icon-extLink" />
- </Cell.CellButton>
+ <AriaDescriptionGroup>
+ <AriaDescribed>
+ <Cell.CellButton disabled={this.props.isOffline} onClick={this.openDownloadLink}>
+ {icon}
+ <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label>
+ <Cell.SubText>{this.props.appVersion}</Cell.SubText>
+ <AriaDescription>
+ <Cell.Icon
+ height={16}
+ width={16}
+ source="icon-extLink"
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
+ </Cell.CellButton>
+ </AriaDescribed>
{footer}
- </>
+ </AriaDescriptionGroup>
);
}
@@ -201,15 +211,26 @@ export default class Settings extends React.Component<IProps> {
<Cell.Icon height={12} width={7} source="icon-chevron" />
</Cell.CellButton>
- <Cell.CellButton disabled={this.props.isOffline} onClick={this.openFaqLink}>
- <Cell.Label>
- {
- // TRANSLATORS: Link to the webpage
- messages.pgettext('settings-view', 'FAQs & Guides')
- }
- </Cell.Label>
- <Cell.Icon height={16} width={16} source="icon-extLink" />
- </Cell.CellButton>
+ <AriaDescriptionGroup>
+ <AriaDescribed>
+ <Cell.CellButton disabled={this.props.isOffline} onClick={this.openFaqLink}>
+ <Cell.Label>
+ {
+ // TRANSLATORS: Link to the webpage
+ messages.pgettext('settings-view', 'FAQs & Guides')
+ }
+ </Cell.Label>
+ <AriaDescription>
+ <Cell.Icon
+ height={16}
+ width={16}
+ source="icon-extLink"
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
+ </Cell.CellButton>
+ </AriaDescribed>
+ </AriaDescriptionGroup>
<Cell.CellButton onClick={this.props.onViewSelectLanguage}>
<StyledCellIcon width={24} height={24} source="icon-language" />
diff --git a/gui/src/renderer/components/Support.tsx b/gui/src/renderer/components/Support.tsx
index d1aee5b0d4..5cb9adcba6 100644
--- a/gui/src/renderer/components/Support.tsx
+++ b/gui/src/renderer/components/Support.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import { links } from '../../config.json';
import { messages } from '../../shared/gettext';
import * as AppButton from './AppButton';
+import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import ImageView from './ImageView';
import { Layout } from './Layout';
import { ModalAlert, ModalAlertType, ModalContainer } from './Modal';
@@ -283,13 +284,25 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
type={ModalAlertType.Warning}
message={message}
buttons={[
- <AppButton.GreenButton
- key="upgrade"
- disabled={this.props.isOffline}
- onClick={this.openDownloadLink}>
- <AppButton.Label>{messages.pgettext('support-view', 'Upgrade app')}</AppButton.Label>
- <AppButton.Icon height={16} width={16} source="icon-extLink" />
- </AppButton.GreenButton>,
+ <AriaDescriptionGroup key="upgrade">
+ <AriaDescribed>
+ <AppButton.GreenButton
+ disabled={this.props.isOffline}
+ onClick={this.openDownloadLink}>
+ <AppButton.Label>
+ {messages.pgettext('support-view', 'Upgrade app')}
+ </AppButton.Label>
+ <AriaDescription>
+ <AppButton.Icon
+ height={16}
+ width={16}
+ source="icon-extLink"
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
+ </AppButton.GreenButton>
+ </AriaDescribed>
+ </AriaDescriptionGroup>,
<AppButton.RedButton key="proceed" onClick={this.acknowledgeOutdateVersion}>
{messages.pgettext('support-view', 'Continue anyway')}
</AppButton.RedButton>,
@@ -322,10 +335,23 @@ export default class Support extends React.Component<ISupportProps, ISupportStat
</StyledFormMessageRow>
</StyledForm>
<StyledFooter>
- <StyledBlueButton onClick={this.onViewLog} disabled={this.state.disableActions}>
- <AppButton.Label>{messages.pgettext('support-view', 'View app logs')}</AppButton.Label>
- <AppButton.Icon source="icon-extLink" height={16} width={16} />
- </StyledBlueButton>
+ <AriaDescriptionGroup>
+ <AriaDescribed>
+ <StyledBlueButton onClick={this.onViewLog} disabled={this.state.disableActions}>
+ <AppButton.Label>
+ {messages.pgettext('support-view', 'View app logs')}
+ </AppButton.Label>
+ <AriaDescription>
+ <AppButton.Icon
+ source="icon-extLink"
+ height={16}
+ width={16}
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
+ </StyledBlueButton>
+ </AriaDescribed>
+ </AriaDescriptionGroup>
<AppButton.GreenButton
disabled={!this.validate() || this.state.disableActions}
onClick={this.onSend}>
diff --git a/gui/src/renderer/components/WireguardKeys.tsx b/gui/src/renderer/components/WireguardKeys.tsx
index a8cc7f517e..3f3e8521d7 100644
--- a/gui/src/renderer/components/WireguardKeys.tsx
+++ b/gui/src/renderer/components/WireguardKeys.tsx
@@ -6,6 +6,7 @@ import { TunnelState } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
import { IWgKey, WgKeyState } from '../redux/settings/reducers';
import * as AppButton from './AppButton';
+import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import ClipboardLabel from './ClipboardLabel';
import ImageView from './ImageView';
import { Layout } from './Layout';
@@ -145,12 +146,23 @@ export default class WireguardKeys extends React.Component<IProps, IState> {
<AppButton.BlockingButton
disabled={this.props.isOffline}
onClick={this.props.onVisitWebsiteKey}>
- <AppButton.BlueButton>
- <AppButton.Label>
- {messages.pgettext('wireguard-key-view', 'Manage keys')}
- </AppButton.Label>
- <AppButton.Icon source="icon-extLink" height={16} width={16} />
- </AppButton.BlueButton>
+ <AriaDescriptionGroup>
+ <AriaDescribed>
+ <AppButton.BlueButton>
+ <AppButton.Label>
+ {messages.pgettext('wireguard-key-view', 'Manage keys')}
+ </AppButton.Label>
+ <AriaDescription>
+ <AppButton.Icon
+ source="icon-extLink"
+ height={16}
+ width={16}
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
+ </AppButton.BlueButton>
+ </AriaDescribed>
+ </AriaDescriptionGroup>
</AppButton.BlockingButton>
</StyledLastButtonRow>
</StyledContent>