diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-09-21 17:30:41 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-09-29 11:45:50 +0200 |
| commit | 7da2ffb4218b07a622429d039b37607344bcda34 (patch) | |
| tree | 333a128b67bae834a40d4fdac2c84a39b6193fa0 /gui/src/renderer/components | |
| parent | 09e0229b42515c44bca27c103bafdfd53bacf5ee (diff) | |
| download | mullvadvpn-7da2ffb4218b07a622429d039b37607344bcda34.tar.xz mullvadvpn-7da2ffb4218b07a622429d039b37607344bcda34.zip | |
Reset focus on navigation
Diffstat (limited to 'gui/src/renderer/components')
| -rw-r--r-- | gui/src/renderer/components/Focus.tsx | 63 | ||||
| -rw-r--r-- | gui/src/renderer/components/TransitionContainer.tsx | 2 |
2 files changed, 65 insertions, 0 deletions
diff --git a/gui/src/renderer/components/Focus.tsx b/gui/src/renderer/components/Focus.tsx new file mode 100644 index 0000000000..a844497b86 --- /dev/null +++ b/gui/src/renderer/components/Focus.tsx @@ -0,0 +1,63 @@ +import path from 'path'; +import React, { useImperativeHandle, useState } from 'react'; +import { useLocation } from 'react-router'; +import { sprintf } from 'sprintf-js'; +import styled from 'styled-components'; +import { messages } from '../../shared/gettext'; + +const PageChangeAnnouncer = styled.div({ + width: 0, + height: 0, + overflow: 'hidden', +}); + +export interface IFocusHandle { + resetFocus(): void; +} + +interface IFocusProps { + children?: React.ReactElement; +} + +function Focus(props: IFocusProps, ref: React.Ref<IFocusHandle>) { + const location = useLocation(); + const [title, setTitle] = useState<string>(); + + useImperativeHandle( + ref, + () => ({ + resetFocus: () => { + const pageName = path.basename(location.pathname); + const titleElement = document.getElementsByTagName('h1')[0]; + const titleContent = titleElement?.textContent ?? pageName; + setTitle(titleContent); + + const focusElement = titleElement ?? document.getElementsByTagName('header')[0]; + if (focusElement) { + focusElement.setAttribute('tabindex', '-1'); + focusElement.focus(); + } + }, + }), + [location.pathname], + ); + + return ( + <> + {title && ( + <PageChangeAnnouncer aria-live="polite"> + { + // TRANSLATORS: This string is used to notify users of screenreaders that the view has + // TRANSLATORS: changed, usually as a result of pressing a navigation button. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(title)s - page title + sprintf(messages.pgettext('accessibility', '%(title)s, View loaded'), { title }) + } + </PageChangeAnnouncer> + )} + {props.children} + </> + ); +} + +export default React.memo(React.forwardRef(Focus)); diff --git a/gui/src/renderer/components/TransitionContainer.tsx b/gui/src/renderer/components/TransitionContainer.tsx index 9b4c2520e3..cdb2211c51 100644 --- a/gui/src/renderer/components/TransitionContainer.tsx +++ b/gui/src/renderer/components/TransitionContainer.tsx @@ -16,6 +16,7 @@ interface ITransitionQueueItem { interface IProps extends ITransitionGroupProps { children: TransitioningView; + onTransitionEnd: () => void; } interface IItemStyle { @@ -192,6 +193,7 @@ export default class TransitionContainer extends React.Component<IProps, IState> private finishCycling() { this.isCycling = false; + this.props.onTransitionEnd(); } private continueCycling = () => { |
