(frontend) improve screen reader support in DocShare modal

adds relevant aria-labels to enhance accessibility for assistive technologies

Signed-off-by: Cyril <c.gromoff@gmail.com>
This commit is contained in:
Cyril
2025-11-18 15:35:07 +01:00
parent f7baf238e3
commit 5f9968d81e
13 changed files with 69 additions and 23 deletions

View File

@@ -69,7 +69,7 @@ export const QuickSearch = ({
label={label}
shouldFilter={false}
ref={ref}
tabIndex={0}
tabIndex={-1}
value={selectedValue}
onValueChange={handleValueChange}
>

View File

@@ -18,6 +18,7 @@ type DocRoleDropdownProps = {
onSelectRole: (role: Role) => void;
rolesAllowed?: Role[];
isLastOwner?: boolean;
ariaLabel?: string;
};
export const DocRoleDropdown = ({
@@ -29,6 +30,7 @@ export const DocRoleDropdown = ({
rolesAllowed,
access,
isLastOwner = false,
ariaLabel,
}: DocRoleDropdownProps) => {
const { t } = useTranslation();
const { transRole, translatedRoles } = useTrans();
@@ -113,11 +115,15 @@ export const DocRoleDropdown = ({
return (
<DropdownMenu
topMessage={topMessage}
label="doc-role-dropdown"
label={t('{{action}}, current role: {{role}}', {
action: ariaLabel,
role: transRole(currentRole),
})}
showArrow={true}
arrowCss={css`
color: var(--c--theme--colors--primary-800) !important;
`}
testId="doc-role-dropdown"
options={[
...roles,
{

View File

@@ -78,6 +78,9 @@ const DocShareAccessRequestItem = ({ doc, accessRequest }: Props) => {
onSelectRole={setRole}
canUpdate={doc.abilities.accesses_manage}
rolesAllowed={accessRequest.abilities.set_role_to}
ariaLabel={t('Change role for {{name}}', {
name: accessRequest.user.full_name || accessRequest.user.email,
})}
/>
<Button
color="tertiary"

View File

@@ -153,6 +153,7 @@ export const DocShareAddMemberList = ({
onClick={() => void onInvite()}
disabled={isLoading}
aria-label={inviteLabel}
data-testid="doc-share-invite-button"
>
{t('Invite')}
</Button>

View File

@@ -112,6 +112,9 @@ export const DocShareInvitationItem = ({
canUpdate={canUpdate}
doc={doc}
access={invitation}
ariaLabel={t('Change role for {{email}}', {
email: invitation.email,
})}
/>
{canUpdate && (

View File

@@ -77,6 +77,9 @@ export const DocShareMemberItem = ({
rolesAllowed={access.abilities.set_role_to}
access={access}
doc={doc}
ariaLabel={t('Change role for {{name}}', {
name: access.user.full_name || access.user.email,
})}
/>
</Box>
}

View File

@@ -76,6 +76,7 @@ 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;
@@ -88,6 +89,19 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
setSelectedUsers((prev) => [...prev, user]);
setUserQuery('');
setInputValue('');
// Announce to screen readers
const userName = user.full_name || user.email;
setLiveAnnouncement(
t(
'{{name}} added to invite list. Add more members or press Tab to select role and invite.',
{
name: userName,
},
),
);
// Clear announcement after it's been read
setTimeout(() => setLiveAnnouncement(''), 100);
};
const { data: membersQuery } = useDocAccesses({
@@ -114,6 +128,16 @@ 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(
t('{{name}} removed from invite list', {
name: userName,
}),
);
setTimeout(() => setLiveAnnouncement(''), 100);
return newArray;
});
};
@@ -175,12 +199,22 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
<ButtonCloseModal
aria-label={t('Close the share modal')}
onClick={onClose}
tabIndex={-1}
/>
</Box>
}
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'}