(frontend) hide decorative icons, label menus, avoid name duplicates

improves a11y by hiding decorative icons, labeling menus and deduping names

Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
Cyril
2025-09-10 14:02:58 +02:00
parent 7a903041f8
commit ef08ba3a00
15 changed files with 86 additions and 14 deletions

View File

@@ -15,6 +15,7 @@ and this project adheres to
- #1349 - #1349
- #1271 - #1271
- #1341 - #1341
- #1362
### Changed ### Changed

View File

@@ -201,7 +201,7 @@ test.describe('Document create member', () => {
await page.getByLabel('Reader').click(); await page.getByLabel('Reader').click();
const moreActions = userInvitation.getByRole('button', { const moreActions = userInvitation.getByRole('button', {
name: 'more_horiz', name: 'Open invitation actions menu',
}); });
await moreActions.click(); await moreActions.click();

View File

@@ -227,6 +227,7 @@ export const DropdownMenu = ({
$theme="greyscale" $theme="greyscale"
$variation={isDisabled ? '400' : '1000'} $variation={isDisabled ? '400' : '1000'}
iconName={option.icon} iconName={option.icon}
aria-hidden="true"
/> />
)} )}
<Text $variation={isDisabled ? '400' : '1000'}> <Text $variation={isDisabled ? '400' : '1000'}>
@@ -235,7 +236,12 @@ export const DropdownMenu = ({
</Box> </Box>
{(option.isSelected || {(option.isSelected ||
selectedValues?.includes(option.value ?? '')) && ( selectedValues?.includes(option.value ?? '')) && (
<Icon iconName="check" $size="20px" $theme="greyscale" /> <Icon
iconName="check"
$size="20px"
$theme="greyscale"
aria-hidden="true"
/>
)} )}
</BoxButton> </BoxButton>
{option.showSeparator && ( {option.showSeparator && (

View File

@@ -250,8 +250,9 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
aria-label={t('Export the document')} aria-label={t('Export the document')}
/> />
)} )}
<DropdownMenu options={options}> <DropdownMenu options={options} label={t('Open the document options')}>
<IconOptions <IconOptions
aria-hidden="true"
isHorizontal isHorizontal
$theme="primary" $theme="primary"
$padding={{ all: 'xs' }} $padding={{ all: 'xs' }}
@@ -267,7 +268,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
` `
: ''} : ''}
`} `}
aria-label={t('Open the document options')}
/> />
</DropdownMenu> </DropdownMenu>
</Box> </Box>

View File

@@ -118,8 +118,9 @@ export const DocShareInvitationItem = ({
<DropdownMenu <DropdownMenu
data-testid="doc-share-invitation-more-actions" data-testid="doc-share-invitation-more-actions"
options={moreActions} options={moreActions}
label={t('Open invitation actions menu')}
> >
<IconOptions isHorizontal $variation="600" /> <IconOptions isHorizontal $variation="600" aria-hidden="true" />
</DropdownMenu> </DropdownMenu>
)} )}
</Box> </Box>
@@ -158,7 +159,12 @@ export const DocShareModalInviteUserRow = ({
<Text $theme="primary" $variation="800"> <Text $theme="primary" $variation="800">
{t('Add')} {t('Add')}
</Text> </Text>
<Icon $theme="primary" $variation="800" iconName="add" /> <Icon
$theme="primary"
$variation="800"
iconName="add"
aria-hidden="true"
/>
</Box> </Box>
} }
/> />

View File

@@ -155,6 +155,7 @@ export const DocTreeItemActions = ({
options={options} options={options}
isOpen={isOpen} isOpen={isOpen}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
aria-label={t('Open document actions menu')}
> >
<Icon <Icon
onClick={(e) => { onClick={(e) => {
@@ -166,6 +167,7 @@ export const DocTreeItemActions = ({
variant="filled" variant="filled"
$theme="primary" $theme="primary"
$variation="600" $variation="600"
aria-hidden="true"
/> />
</DropdownMenu> </DropdownMenu>
{doc.abilities.children_create && ( {doc.abilities.children_create && (

View File

@@ -191,6 +191,7 @@ export const DraggableDocGridContentList = ({
data-testid="drag-doc-overlay" data-testid="drag-doc-overlay"
$height="auto" $height="auto"
role="alert" role="alert"
aria-label={t('Drag and drop status')}
> >
<Text $size="xs" $variation="000" $weight="500"> <Text $size="xs" $variation="000" $weight="500">
{overlayText} {overlayText}

View File

@@ -100,7 +100,7 @@ export const DocsGrid = ({
)} )}
{hasDocs && ( {hasDocs && (
<Box $gap="6px" $overflow="auto"> <Box $gap="6px" $overflow="auto">
<Box role="grid"> <Box role="grid" aria-label={t('Documents grid')}>
<Box role="rowgroup"> <Box role="rowgroup">
<Box <Box
$direction="row" $direction="row"

View File

@@ -83,19 +83,23 @@ export const DocsGridActions = ({
return ( return (
<> <>
<DropdownMenu options={options} label={menuLabel}> <DropdownMenu
options={options}
label={menuLabel}
aria-label={t('More options')}
>
<Icon <Icon
data-testid={`docs-grid-actions-button-${doc.id}`} data-testid={`docs-grid-actions-button-${doc.id}`}
iconName="more_horiz" iconName="more_horiz"
$theme="primary" $theme="primary"
$variation="600" $variation="600"
aria-label={t('More options')}
$css={css` $css={css`
cursor: pointer; cursor: pointer;
&:hover { &:hover {
opacity: 0.8; opacity: 0.8;
} }
`} `}
aria-hidden="true"
/> />
</DropdownMenu> </DropdownMenu>

View File

@@ -95,6 +95,11 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
$variation="600" $variation="600"
$size="14px" $size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'} iconName={isPublic ? 'public' : 'vpn_lock'}
aria-label={
isPublic
? t('Accessible to anyone')
: t('Accessible to authenticated users')
}
/> />
)} )}
{!dragMode && ( {!dragMode && (
@@ -114,6 +119,11 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
$variation="600" $variation="600"
$size="14px" $size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'} iconName={isPublic ? 'public' : 'vpn_lock'}
aria-label={
isPublic
? t('Accessible to anyone')
: t('Accessible to authenticated users')
}
/> />
</div> </div>
</Tooltip> </Tooltip>

View File

@@ -33,7 +33,11 @@ export function HomeContent() {
const isFrLanguage = i18n.resolvedLanguage === 'fr'; const isFrLanguage = i18n.resolvedLanguage === 'fr';
return ( return (
<Box as="main" className="--docs--home-content"> <Box
as="main"
className="--docs--home-content"
aria-label={t('Main content')}
>
<HomeHeader /> <HomeHeader />
{isSmallMobile && ( {isSmallMobile && (
<Box $css="& .--docs--left-panel-header{display: none;}"> <Box $css="& .--docs--left-panel-header{display: none;}">

View File

@@ -55,12 +55,16 @@ export const LanguagePicker = () => {
> >
<Text <Text
$theme="primary" $theme="primary"
aria-label={t('Language')}
$direction="row" $direction="row"
$gap="0.5rem" $gap="0.5rem"
className="--docs--language-picker-text" className="--docs--language-picker-text"
> >
<Icon iconName="translate" $color="inherit" $size="xl" /> <Icon
iconName="translate"
$color="inherit"
$size="xl"
aria-hidden="true"
/>
{currentLanguageLabel} {currentLanguageLabel}
</Text> </Text>
</DropdownMenu> </DropdownMenu>

View File

@@ -371,6 +371,11 @@
"Open Source": "Open Source", "Open Source": "Open Source",
"Open the document options": "Öffnen Sie die Dokumentoptionen", "Open the document options": "Öffnen Sie die Dokumentoptionen",
"Open the header menu": "Öffne das Kopfzeilen-Menü", "Open the header menu": "Öffne das Kopfzeilen-Menü",
"Open document actions menu": "Dokumentaktionsmenü öffnen",
"Main content": "Hauptinhalt",
"Home content": "Startseiten-Inhalt",
"Documents grid": "Dokumentenraster",
"Drag and drop status": "Drag-and-Drop-Status",
"Organize": "Organisieren", "Organize": "Organisieren",
"Others are editing. Your network prevent changes.": "Ihre Änderung konnte nicht übernommen werden, da andere Benutzer diesen Bereich zurzeit bearbeiten.", "Others are editing. Your network prevent changes.": "Ihre Änderung konnte nicht übernommen werden, da andere Benutzer diesen Bereich zurzeit bearbeiten.",
"Owner": "Besitzer", "Owner": "Besitzer",
@@ -458,6 +463,11 @@
"Document title": "Document title", "Document title": "Document title",
"Search docs": "Search docs", "Search docs": "Search docs",
"More options": "More options", "More options": "More options",
"Open document actions menu": "Open document actions menu",
"Main content": "Main content",
"Home content": "Home content",
"Documents grid": "Documents grid",
"Drag and drop status": "Drag and drop status",
"Pinned documents": "Pinned documents", "Pinned documents": "Pinned documents",
"Close the access request modal": "Close the access request modal", "Close the access request modal": "Close the access request modal",
"Close the delete modal": "Close the delete modal", "Close the delete modal": "Close the delete modal",
@@ -596,6 +606,11 @@
"Open Source": "Código abierto", "Open Source": "Código abierto",
"Open the document options": "Abrir las opciones del documento", "Open the document options": "Abrir las opciones del documento",
"Open the header menu": "Abrir el menú de encabezado", "Open the header menu": "Abrir el menú de encabezado",
"Open document actions menu": "Abrir menú de acciones del documento",
"Main content": "Contenido principal",
"Home content": "Contenido de inicio",
"Documents grid": "Lista de documentos",
"Drag and drop status": "Estado de arrastrar y soltar",
"Organize": "Organiza", "Organize": "Organiza",
"Owner": "Propietario", "Owner": "Propietario",
"PDF": "PDF", "PDF": "PDF",
@@ -823,7 +838,12 @@
"Open Source": "Open Source", "Open Source": "Open Source",
"Open the document options": "Ouvrir les options du document", "Open the document options": "Ouvrir les options du document",
"Open the header menu": "Ouvrir le menu d'en-tête", "Open the header menu": "Ouvrir le menu d'en-tête",
"Open document actions menu": "Ouvrir le menu d'actions du document",
"Open the menu of actions for the document: {{title}}": "Ouvrir le menu des actions du document : {{title}}", "Open the menu of actions for the document: {{title}}": "Ouvrir le menu des actions du document : {{title}}",
"Main content": "Contenu principal",
"Home content": "Contenu d'accueil",
"Documents grid": "Grille des documents",
"Drag and drop status": "État du glisser-déposer",
"Organize": "Organiser", "Organize": "Organiser",
"Others are editing this document. Unfortunately your network blocks WebSockets, the technology enabling real-time co-editing.": "D'autres sont en train de modifier ce document. Malheureusement, votre réseau bloque les web sockets, la technologie permettant la coédition en temps réel.", "Others are editing this document. Unfortunately your network blocks WebSockets, the technology enabling real-time co-editing.": "D'autres sont en train de modifier ce document. Malheureusement, votre réseau bloque les web sockets, la technologie permettant la coédition en temps réel.",
"Others are editing. Your network prevent changes.": "D'autres sont en cours d'édition. Votre réseau empêche les changements.", "Others are editing. Your network prevent changes.": "D'autres sont en cours d'édition. Votre réseau empêche les changements.",
@@ -1183,6 +1203,11 @@
"Open Source": "Open Source", "Open Source": "Open Source",
"Open the document options": "Open document opties", "Open the document options": "Open document opties",
"Open the header menu": "Open het hoofdmenu", "Open the header menu": "Open het hoofdmenu",
"Open document actions menu": "Open documentactiemenu",
"Main content": "Hoofdinhoud",
"Home content": "Startpagina-inhoud",
"Documents grid": "Documentenraster",
"Drag and drop status": "Slepen en neerzetten status",
"Organize": "Organiseer", "Organize": "Organiseer",
"Owner": "Eigenaar", "Owner": "Eigenaar",
"PDF": "PDF", "PDF": "PDF",

View File

@@ -1,4 +1,5 @@
import { PropsWithChildren } from 'react'; import { PropsWithChildren } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components'; import { css } from 'styled-components';
import { Box } from '@/components'; import { Box } from '@/components';
@@ -20,6 +21,7 @@ export function MainLayout({
const { isDesktop } = useResponsiveStore(); const { isDesktop } = useResponsiveStore();
const { colorsTokens } = useCunninghamTheme(); const { colorsTokens } = useCunninghamTheme();
const currentBackgroundColor = !isDesktop ? 'white' : backgroundColor; const currentBackgroundColor = !isDesktop ? 'white' : backgroundColor;
const { t } = useTranslation();
return ( return (
<Box className="--docs--main-layout"> <Box className="--docs--main-layout">
@@ -32,6 +34,7 @@ export function MainLayout({
<LeftPanel /> <LeftPanel />
<Box <Box
as="main" as="main"
aria-label={t('Main content')}
id={MAIN_LAYOUT_ID} id={MAIN_LAYOUT_ID}
$align="center" $align="center"
$flex={1} $flex={1}

View File

@@ -1,4 +1,5 @@
import { PropsWithChildren } from 'react'; import { PropsWithChildren } from 'react';
import { useTranslation } from 'react-i18next';
import { Box } from '@/components'; import { Box } from '@/components';
import { Footer } from '@/features/footer'; import { Footer } from '@/features/footer';
@@ -15,7 +16,7 @@ export function PageLayout({
withFooter = true, withFooter = true,
}: PropsWithChildren<PageLayoutProps>) { }: PropsWithChildren<PageLayoutProps>) {
const { isDesktop } = useResponsiveStore(); const { isDesktop } = useResponsiveStore();
const { t } = useTranslation();
return ( return (
<Box <Box
$minHeight={`calc(100vh - ${HEADER_HEIGHT}px)`} $minHeight={`calc(100vh - ${HEADER_HEIGHT}px)`}
@@ -23,7 +24,12 @@ export function PageLayout({
className="--docs--page-layout" className="--docs--page-layout"
> >
<Header /> <Header />
<Box as="main" $width="100%" $css="flex-grow:1;"> <Box
as="main"
$width="100%"
$css="flex-grow:1;"
aria-label={t('Main content')}
>
{!isDesktop && <LeftPanel />} {!isDesktop && <LeftPanel />}
{children} {children}
</Box> </Box>