From ab54126414c37233b3299b62e1d147be5944c344 Mon Sep 17 00:00:00 2001
From: Pierre Ecarlat <pecarlat@grandlyon.com>
Date: Wed, 11 Sep 2024 08:24:03 +0000
Subject: [PATCH] feat(a11y): Add a quick access link

---
 src/components/App.tsx                | 32 +++++++++++++++------------
 src/components/SkipLink/SkipLink.scss | 18 +++++++++++++++
 src/components/SkipLink/SkipLink.tsx  | 23 +++++++++++++++++++
 src/components/Terms/CGUModal.tsx     |  2 +-
 src/locales/fr.json                   |  3 ++-
 src/styles/base/_z-index.scss         |  1 +
 6 files changed, 63 insertions(+), 16 deletions(-)
 create mode 100644 src/components/SkipLink/SkipLink.scss
 create mode 100644 src/components/SkipLink/SkipLink.tsx

diff --git a/src/components/App.tsx b/src/components/App.tsx
index d8bead7a2..21b8c485d 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -10,6 +10,7 @@ import { useLocation } from 'react-router-dom'
 import { useAppSelector } from 'store/hooks'
 import MatomoTracker from 'utils/matomoTracker'
 import usePageTitle from './Hooks/usePageTitle'
+import SkipLink from './SkipLink/SkipLink'
 
 interface AppProps {
   tracker: undefined | MatomoTracker
@@ -38,20 +39,23 @@ export const App = ({ tracker }: AppProps) => {
   }, [webviewIntent])
 
   return (
-    <Layout>
-      <SplashRoot>
-        {termsStatus.accepted && (
-          <>
-            <WelcomeModal open={!onboarding.isWelcomeSeen} />
-            <Navbar />
-          </>
-        )}
-        <main className="app-content">
-          <AppRoutes termsStatus={termsStatus} />
-        </main>
-      </SplashRoot>
-      {process.env.NODE_ENV !== 'production' ? <CozyDevtools /> : null}
-    </Layout>
+    <>
+      <SkipLink />
+      <Layout>
+        <SplashRoot>
+          {termsStatus.accepted && (
+            <>
+              <WelcomeModal open={!onboarding.isWelcomeSeen} />
+              <Navbar />
+            </>
+          )}
+          <main id="app-content" className="app-content" tabIndex={-1}>
+            <AppRoutes termsStatus={termsStatus} />
+          </main>
+        </SplashRoot>
+        {process.env.NODE_ENV !== 'production' ? <CozyDevtools /> : null}
+      </Layout>
+    </>
   )
 }
 
diff --git a/src/components/SkipLink/SkipLink.scss b/src/components/SkipLink/SkipLink.scss
new file mode 100644
index 000000000..c893a4a3a
--- /dev/null
+++ b/src/components/SkipLink/SkipLink.scss
@@ -0,0 +1,18 @@
+@import 'src/styles/base/color';
+@import 'src/styles/base/z-index';
+
+.skip-link {
+  position: absolute;
+  top: -40px;
+  left: 0;
+  background: $dark;
+  color: $white;
+  border: $white;
+  padding: 8px;
+  z-index: $skip-link;
+  text-decoration: 'none';
+  transition: top 0.3s;
+  &:focus {
+    top: 0;
+  }
+}
diff --git a/src/components/SkipLink/SkipLink.tsx b/src/components/SkipLink/SkipLink.tsx
new file mode 100644
index 000000000..b00fb54e6
--- /dev/null
+++ b/src/components/SkipLink/SkipLink.tsx
@@ -0,0 +1,23 @@
+import { useI18n } from 'cozy-ui/transpiled/react/I18n'
+import React from 'react'
+import './SkipLink.scss'
+
+const SkipLink = () => {
+  const { t } = useI18n()
+
+  const handleSkip = (event: React.MouseEvent<HTMLButtonElement>) => {
+    event.preventDefault()
+    const mainContent = document.getElementById('app-content')
+    if (mainContent) {
+      mainContent.focus()
+    }
+  }
+
+  return (
+    <button className="skip-link" onClick={handleSkip}>
+      {t('common.accessibility.skip_link')}
+    </button>
+  )
+}
+
+export default SkipLink
diff --git a/src/components/Terms/CGUModal.tsx b/src/components/Terms/CGUModal.tsx
index 5398cea05..8bd6c0cbb 100644
--- a/src/components/Terms/CGUModal.tsx
+++ b/src/components/Terms/CGUModal.tsx
@@ -1,11 +1,11 @@
 import { Button } from '@material-ui/core'
 import Dialog from '@material-ui/core/Dialog'
 import CloseIcon from 'assets/icons/ico/close.svg'
+import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton'
 import GCUContent from 'components/Options/GCU/GCUContent'
 import { useI18n } from 'cozy-ui/transpiled/react/I18n'
 import React from 'react'
 import './termsView.scss'
-import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton'
 
 interface CGUModalProps {
   open: boolean
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 389b6ee5d..727e662ac 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -22,7 +22,8 @@
     "title_sge_connect": "Connexion à l'électricité",
     "title_gas_connect": "Connexion au gaz",
     "accessibility": {
-      "loading": "Chargement"
+      "loading": "Chargement",
+      "skip_link": "Aller au contenu"
     },
     "funders_logo": "Logo des financeurs : Métropole de Lyon, Etat via la Banque des Territoires et son programme France 2030, Union Européenne"
   },
diff --git a/src/styles/base/_z-index.scss b/src/styles/base/_z-index.scss
index 17a5dea8e..4fe88ba94 100644
--- a/src/styles/base/_z-index.scss
+++ b/src/styles/base/_z-index.scss
@@ -3,3 +3,4 @@ $z-pieChart: 5;
 $z-dialog: 10;
 $z-header: 18;
 $z-splash: 1500;
+$skip-link: 1000001;
-- 
GitLab