summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/components
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-09-21 17:30:41 +0200
committerOskar Nyberg <oskar@mullvad.net>2020-09-29 11:45:50 +0200
commit7da2ffb4218b07a622429d039b37607344bcda34 (patch)
tree333a128b67bae834a40d4fdac2c84a39b6193fa0 /gui/src/renderer/components
parent09e0229b42515c44bca27c103bafdfd53bacf5ee (diff)
downloadmullvadvpn-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.tsx63
-rw-r--r--gui/src/renderer/components/TransitionContainer.tsx2
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 = () => {