From 8472e661f595d884c5193336e3f5c556dc70cb90 Mon Sep 17 00:00:00 2001 From: Cyril Date: Tue, 17 Mar 2026 09:48:59 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BF=EF=B8=8F(frontend)=20fix=20Copy=20lin?= =?UTF-8?q?k=20toast=20accessibility=20for=20screen=20readers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add aria-live announcements so screen readers announce the toast feedback. --- CHANGELOG.md | 1 + src/frontend/apps/impress/package.json | 1 + .../doc-share/components/DocShareModal.tsx | 22 ++++------------ .../apps/impress/src/hooks/useClipboard.tsx | 25 ++++++++----------- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b513208c..35c03fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to - ♿️(frontend) ensure doc title is h1 for accessibility #2006 - ♿️(frontend) fix share modal heading hierarchy #2007 +- ♿️(frontend) fix Copy link toast accessibility for screen readers #2029 ## [v4.8.1] - 2026-03-17 diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index 16509d62..0ab8290f 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -46,6 +46,7 @@ "@hocuspocus/provider": "3.4.4", "@mantine/core": "8.3.14", "@mantine/hooks": "8.3.14", + "@react-aria/live-announcer": "3.4.4", "@react-pdf/renderer": "4.3.1", "@sentry/nextjs": "10.38.0", "@tanstack/react-query": "5.90.21", diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx index 1121dc06..0e0c283a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx @@ -1,4 +1,5 @@ import { Modal, ModalSize } from '@gouvfr-lasuite/cunningham-react'; +import { announce } from '@react-aria/live-announcer'; import { useQueryClient } from '@tanstack/react-query'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -80,7 +81,6 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { const [selectedUsers, setSelectedUsers] = useState([]); const [userQuery, setUserQuery] = useState(''); const [inputValue, setInputValue] = useState(''); - const [liveAnnouncement, setLiveAnnouncement] = useState(''); const [listHeight, setListHeight] = useState('400px'); const canShare = doc.abilities.accesses_manage && isRootDoc; @@ -93,18 +93,16 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { setUserQuery(''); setInputValue(''); - // Announce to screen readers const userName = user.full_name || user.email; - setLiveAnnouncement( + announce( t( '{{name}} added to invite list. Add more members or press Tab to select role and invite.', { name: userName, }, ), + 'polite', ); - // Clear announcement after it's been read - setTimeout(() => setLiveAnnouncement(''), 100); }; const { data: membersQuery } = useDocAccesses({ @@ -132,14 +130,13 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { const newArray = [...prevState]; newArray.splice(index, 1); - // Announce to screen readers const userName = row.full_name || row.email; - setLiveAnnouncement( + announce( t('{{name}} removed from invite list', { name: userName, }), + 'polite', ); - setTimeout(() => setLiveAnnouncement(''), 100); return newArray; }); @@ -208,15 +205,6 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { hideCloseButton > - {/* Screen reader announcements */} -
- {liveAnnouncement} -
{ navigator.clipboard .writeText(text) .then(() => { - toast( - successMessage ?? t('Copied to clipboard'), - VariantType.SUCCESS, - { - duration: 3000, - }, - ); + const message = successMessage ?? t('Copied to clipboard'); + toast(message, VariantType.SUCCESS, { + duration: 3000, + }); + announce(message, 'polite'); }) .catch(() => { - toast( - errorMessage ?? t('Failed to copy to clipboard'), - VariantType.ERROR, - { - duration: 3000, - }, - ); + const message = errorMessage ?? t('Failed to copy to clipboard'); + toast(message, VariantType.ERROR, { + duration: 3000, + }); + announce(message, 'assertive'); }); }, [t, toast],