️(frontend) add correct attributes to decorative and interactive icons

Add aria-hidden and aria-label to improve screen reader accessibility

Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
Cyril
2025-08-01 10:12:16 +02:00
parent 1cdb6b62c8
commit 99d674c615
15 changed files with 51 additions and 38 deletions

View File

@@ -31,7 +31,9 @@ and this project adheres to
- ♻️(frontend) redirect to doc after duplicate #1175
- 🔧(project) change env.d system by using local files #1200
- ⚡️(frontend) improve tree stability #1207
- ⚡️(frontend) improve accessibility #1232
- ⚡️(frontend) improve accessibility
- #1232
- #1255
- 🛂(frontend) block drag n drop when not desktop #1239
### Fixed

View File

@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
await expect(
page.locator('header').first().locator('h2').getByText('Docs'),
page.locator('header').first().locator('h1').getByText('Docs'),
).toBeVisible();
await page.goto('unknown-page404');
});

View File

@@ -22,7 +22,7 @@ test.describe('Doc Create', () => {
);
const header = page.locator('header').first();
await header.locator('h2').getByText('Docs').click();
await header.locator('h1').getByText('Docs').click();
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();

View File

@@ -429,7 +429,7 @@ test.describe('Doc Export', () => {
await page.waitForLoadState('domcontentloaded');
const header = page.locator('header').first();
await header.locator('h2').getByText('Docs').click();
await header.locator('h1').getByText('Docs').click();
const randomDocFrench = randomName(
'doc-language-export-french',

View File

@@ -8,9 +8,9 @@ test.describe('Doc grid dnd', () => {
await page.goto('/');
const header = page.locator('header').first();
await createDoc(page, 'Draggable doc', browserName, 1);
await header.locator('h2').getByText('Docs').click();
await header.locator('h1').getByText('Docs').click();
await createDoc(page, 'Droppable doc', browserName, 1);
await header.locator('h2').getByText('Docs').click();
await header.locator('h1').getByText('Docs').click();
const response = await page.waitForResponse(
(response) =>

View File

@@ -244,9 +244,7 @@ test.describe('Document create member: Multiple login', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible();
await expect(page.getByTestId('header-logo-link')).toBeVisible();
await page.goto(urlDoc);
@@ -271,9 +269,7 @@ test.describe('Document create member: Multiple login', () => {
await page.goto('/');
await keyCloakSignIn(page, browserName);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible({
await expect(page.getByTestId('header-logo-link')).toBeVisible({
timeout: 10000,
});
@@ -334,9 +330,7 @@ test.describe('Document create member: Multiple login', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible({
await expect(page.getByTestId('header-logo-link')).toBeVisible({
timeout: 10000,
});

View File

@@ -122,9 +122,7 @@ test.describe('Doc Visibility: Restricted', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible({
await expect(page.getByTestId('header-logo-link')).toBeVisible({
timeout: 10000,
});
@@ -178,9 +176,7 @@ test.describe('Doc Visibility: Restricted', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible();
await expect(page.getByTestId('header-logo-link')).toBeVisible();
await page.goto(urlDoc);
@@ -455,9 +451,7 @@ test.describe('Doc Visibility: Authenticated', () => {
const otherBrowser = BROWSERS.find((b) => b !== browserName);
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible({
await expect(page.getByTestId('header-logo-link')).toBeVisible({
timeout: 10000,
});
@@ -545,9 +539,7 @@ test.describe('Doc Visibility: Authenticated', () => {
const otherBrowser = BROWSERS.find((b) => b !== browserName);
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible();
await expect(page.getByTestId('header-logo-link')).toBeVisible();
await page.goto(urlDoc);

View File

@@ -12,7 +12,7 @@ test.describe('Header', () => {
const header = page.locator('header').first();
await expect(header.getByLabel('Docs Logo')).toBeVisible();
await expect(header.getByLabel('Back to homepage')).toBeVisible();
await expect(header.locator('h2').getByText('Docs')).toHaveCSS(
'font-family',
/Roboto/i,

View File

@@ -154,7 +154,7 @@ export const goToGridDoc = async (
{ nthRow = 1, title }: GoToGridDocOptions = {},
) => {
const header = page.locator('header').first();
await header.locator('h2').getByText('Docs').click();
await header.locator('h1').getByText('Docs').click();
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();

View File

@@ -39,7 +39,7 @@ export const Header = () => {
className="--docs--header"
>
{!isDesktop && <ButtonTogglePanel />}
<StyledLink href="/">
<StyledLink href="/" data-testid="header-logo-link">
<Box
$align="center"
$gap={spacingsTokens['3xs']}
@@ -49,11 +49,11 @@ export const Header = () => {
$margin={{ top: 'auto' }}
>
<IconDocs
aria-label={t('Docs Logo')}
aria-label={t('Back to homepage')}
width={32}
color={colorsTokens['primary-text']}
/>
<Title />
<Title headingLevel="h1" />
</Box>
</StyledLink>
{!isDesktop ? (

View File

@@ -3,7 +3,11 @@ import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components/';
import { useCunninghamTheme } from '@/cunningham';
export const Title = () => {
type TitleSemanticsProps = {
headingLevel?: 'h1' | 'h2' | 'h3';
};
export const Title = ({ headingLevel = 'h2' }: TitleSemanticsProps) => {
const { t } = useTranslation();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
@@ -16,7 +20,7 @@ export const Title = () => {
>
<Text
$margin="none"
as="h2"
as={headingLevel}
$color={colorsTokens['primary-text']}
$zIndex={1}
$size="1.375rem"

View File

@@ -48,7 +48,7 @@ export default function HomeBanner() {
$gap={spacingsTokens['sm']}
>
<IconDocs
aria-label={t('Docs Logo')}
aria-label={t('Back to homepage')}
width={64}
color={colorsTokens['primary-text']}
/>

View File

@@ -1,4 +1,5 @@
import { Button } from '@openfun/cunningham-react';
import { t } from 'i18next';
import { useRouter } from 'next/router';
import { PropsWithChildren, useCallback, useState } from 'react';
@@ -54,10 +55,16 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
<Box $direction="row" $gap="2px">
<Button
onClick={goToHome}
aria-label={t('Back to homepage')}
size="medium"
color="tertiary-text"
icon={
<Icon $variation="800" $theme="primary" iconName="house" />
<Icon
$variation="800"
$theme="primary"
iconName="house"
aria-hidden="true"
/>
}
/>
{authenticated && (
@@ -65,8 +72,14 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
onClick={openSearchModal}
size="medium"
color="tertiary-text"
aria-label={t('Search docs')}
icon={
<Icon $variation="800" $theme="primary" iconName="search" />
<Icon
$variation="800"
$theme="primary"
iconName="search"
aria-hidden="true"
/>
}
/>
)}

View File

@@ -21,7 +21,7 @@ export const LeftPanelHeaderButton = () => {
<Button
color="primary"
onClick={() => createDoc()}
icon={<Icon $variation="000" iconName="add" />}
icon={<Icon $variation="000" iconName="add" aria-hidden="true" />}
disabled={isDocCreating}
>
{t('New doc')}

View File

@@ -207,6 +207,7 @@
"Doc visibility card": "Dokumenten-Sichtbarkeitskarte",
"Docs": "Docs",
"Docs Logo": "Docs-Logo",
"Back to homepage": "Zurück zur Startseite",
"Docs is already available, log in to use it now.": "Docs ist bereits verfügbar. Melden Sie sich an, um es jetzt zu nutzen.",
"Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.": "Docs macht die Zusammenarbeit in Echtzeit einfach. Laden Sie Mitarbeiter — Beamte oder externe Partner — mit einem Klick ein, um ihre Änderungen live zu sehen und dabei die genaue Zugangskontrolle zwecks Datensicherheit beizubehalten.",
"Docs offers an intuitive writing experience. Its minimalist interface favors content over layout, while offering the essentials: media import, offline mode and keyboard shortcuts for greater efficiency.": "Docs bietet ein intuitives Schreiberlebnis. Seine minimalistische Oberfläche bevorzugt Inhalte über Layout, bietet aber das Wesentliche: Medien-Import, Offline-Modus und Tastaturkürzel für mehr Effizienz.",
@@ -306,6 +307,7 @@
"Reset": "Zurücksetzen",
"Restore": "Wiederherstellen",
"Search": "Suchen",
"Search docs": "Dokumente durchsuchen",
"Search modal": "Suche Modal",
"Search user result": "Suchergebnis",
"Select a document": "Dokument auswählen",
@@ -364,6 +366,7 @@
},
"en": {
"translation": {
"Search docs": "Search docs",
"Share with {{count}} users_one": "Share with {{count}} user",
"Shared with {{count}} users_many": "Shared with {{count}} users",
"Shared with {{count}} users_one": "Shared with {{count}} user",
@@ -418,6 +421,7 @@
"Doc visibility card": "Accesos al documento",
"Docs": "Docs",
"Docs Logo": "Logo de Docs",
"Back to homepage": "Volver a la página de inicio",
"Docs is already available, log in to use it now.": "Docs ya está disponible, inicia sesión para empezar a usarlo.",
"Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.": "Docs simplifica la colaboración en tiempo real. Invitar colaboradores - funcionarios públicos o socios externos - con un solo clic para ver sus cambios en vivo, manteniendo un control de acceso preciso para la seguridad de los datos.",
"Docs offers an intuitive writing experience. Its minimalist interface favors content over layout, while offering the essentials: media import, offline mode and keyboard shortcuts for greater efficiency.": "Docs ofrece una experiencia de escritura intuitiva. Su interfaz minimalista favorece el contenido sobre el diseño, mientras ofrece lo esencial: importación de medios, modo sin conexión y atajos de teclado para una mayor eficiencia.",
@@ -502,6 +506,7 @@
"Request access": "Solicitar acceso",
"Restore": "Recuperar",
"Search": "Buscar",
"Search docs": "Buscar documentos",
"Search modal": "Modal de búsqueda",
"Search user result": "Resultado de la búsqueda de usuarios",
"Select a document": "Selecciona un documento",
@@ -608,6 +613,7 @@
"Doc visibility card": "Carte de visibilité du doc",
"Docs": "Docs",
"Docs Logo": "Logo Docs",
"Back to homepage": "Retour page d'accueil",
"Docs is already available, log in to use it now.": "Docs est déjà disponible, connectez-vous pour lutiliser dès maintenant.",
"Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.": "Docs simplifie la collaboration en temps réel. Invitez des collaborateurs - agents publics ou partenaires externes - d'un clic pour voir leurs modifications en direct, tout en gardant un contrôle précis des accès pour la sécurité des données.",
"Docs offers an intuitive writing experience. Its minimalist interface favors content over layout, while offering the essentials: media import, offline mode and keyboard shortcuts for greater efficiency.": "Docs propose une expérience d'écriture intuitive. Son interface minimaliste privilégie le contenu sur la mise en page, tout en offrant l'essentiel : import de médias, mode hors-ligne et raccourcis clavier pour plus d'efficacité.",
@@ -719,6 +725,7 @@
"Restore": "Restaurer",
"Search": "Rechercher",
"Search by title": "Recherchez par titre",
"Search docs": "Rechercher un document",
"Search modal": "Modale de partage",
"Search user result": "Résultat de la recherche utilisateur",
"Select a doc": "Sélectionnez un doc",
@@ -1055,6 +1062,7 @@
"Rephrase": "Herschrijf",
"Restore": "Herstel",
"Search": "Zoeken",
"Search docs": "Documenten zoeken",
"Search modal": "Zoek modal",
"Search user result": "Zoek resultaat",
"Select a document": "Selecteer een document",