mirror of
https://github.com/suitenumerique/docs.git
synced 2026-04-25 17:15:01 +02:00
♿️(frontend) fix Copy link toast accessibility for screen readers
Add aria-live announcements so screen readers announce the toast feedback.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<User[]>([]);
|
||||
const [userQuery, setUserQuery] = useState('');
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [liveAnnouncement, setLiveAnnouncement] = useState('');
|
||||
|
||||
const [listHeight, setListHeight] = useState<string>('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
|
||||
>
|
||||
<ShareModalStyle />
|
||||
{/* Screen reader announcements */}
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
className="sr-only"
|
||||
>
|
||||
{liveAnnouncement}
|
||||
</div>
|
||||
<Box
|
||||
$height="auto"
|
||||
$maxHeight={canViewAccesses ? modalContentHeight : 'none'}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
VariantType,
|
||||
useToastProvider,
|
||||
} from '@gouvfr-lasuite/cunningham-react';
|
||||
import { announce } from '@react-aria/live-announcer';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -14,22 +15,18 @@ export const useClipboard = () => {
|
||||
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],
|
||||
|
||||
Reference in New Issue
Block a user