diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-09-17 18:04:02 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-09-24 15:02:20 +0200 |
| commit | 6a8ce1507a99b6cfaf6b722f4881350a8ebc9b05 (patch) | |
| tree | 755bfadcb90693eefb386e2efc9c94f11b46ae9d /gui/src | |
| parent | ce533488629f9815fa6696e97a2b6598546c56a6 (diff) | |
| download | mullvadvpn-6a8ce1507a99b6cfaf6b722f4881350a8ebc9b05.tar.xz mullvadvpn-6a8ce1507a99b6cfaf6b722f4881350a8ebc9b05.zip | |
Add labels to extLink icons and use them as descriptions for buttons
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/components/Account.tsx | 20 | ||||
| -rw-r--r-- | gui/src/renderer/components/AriaGroup.tsx | 80 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountErrorView.tsx | 20 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 55 | ||||
| -rw-r--r-- | gui/src/renderer/components/Support.tsx | 48 | ||||
| -rw-r--r-- | gui/src/renderer/components/WireguardKeys.tsx | 24 |
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> |
