Skip to content

Commit f0a76cc

Browse files
committed
✨(frontend) improve screen reader support in DocShare modal with aria-labels
adds relevant aria-labels to enhance accessibility for assistive technologies Signed-off-by: Cyril <[email protected]>
1 parent 8dad9ea commit f0a76cc

File tree

6 files changed

+57
-2
lines changed

6 files changed

+57
-2
lines changed

src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const QuickSearch = ({
6969
label={label}
7070
shouldFilter={false}
7171
ref={ref}
72-
tabIndex={0}
72+
tabIndex={-1}
7373
value={selectedValue}
7474
onValueChange={handleValueChange}
7575
>

src/frontend/apps/impress/src/features/docs/doc-share/components/DocRoleDropdown.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type DocRoleDropdownProps = {
1818
onSelectRole: (role: Role) => void;
1919
rolesAllowed?: Role[];
2020
isLastOwner?: boolean;
21+
ariaLabel: string;
2122
};
2223

2324
export const DocRoleDropdown = ({
@@ -29,6 +30,7 @@ export const DocRoleDropdown = ({
2930
rolesAllowed,
3031
access,
3132
isLastOwner = false,
33+
ariaLabel,
3234
}: DocRoleDropdownProps) => {
3335
const { t } = useTranslation();
3436
const { transRole, translatedRoles } = useTrans();
@@ -113,7 +115,10 @@ export const DocRoleDropdown = ({
113115
return (
114116
<DropdownMenu
115117
topMessage={topMessage}
116-
label="doc-role-dropdown"
118+
label={t('{{action}}, current role: {{role}}', {
119+
action: ariaLabel,
120+
role: transRole(currentRole),
121+
})}
117122
showArrow={true}
118123
arrowCss={css`
119124
color: var(--c--theme--colors--primary-800) !important;

src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAccessRequest.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ const DocShareAccessRequestItem = ({ doc, accessRequest }: Props) => {
7878
onSelectRole={setRole}
7979
canUpdate={doc.abilities.accesses_manage}
8080
rolesAllowed={accessRequest.abilities.set_role_to}
81+
ariaLabel={t('Change role for {{name}}', {
82+
name: accessRequest.user.full_name || accessRequest.user.email,
83+
})}
8184
/>
8285
<Button
8386
color="tertiary"

src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitation.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ export const DocShareInvitationItem = ({
112112
canUpdate={canUpdate}
113113
doc={doc}
114114
access={invitation}
115+
ariaLabel={t('Change role for {{email}}', {
116+
email: invitation.email,
117+
})}
115118
/>
116119

117120
{canUpdate && (

src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareMember.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ export const DocShareMemberItem = ({
7777
rolesAllowed={access.abilities.set_role_to}
7878
access={access}
7979
doc={doc}
80+
ariaLabel={t('Change role for {{name}}', {
81+
name: access.user.full_name || access.user.email,
82+
})}
8083
/>
8184
</Box>
8285
}

src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareModal.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
7676
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
7777
const [userQuery, setUserQuery] = useState('');
7878
const [inputValue, setInputValue] = useState('');
79+
const [liveAnnouncement, setLiveAnnouncement] = useState('');
7980

8081
const [listHeight, setListHeight] = useState<string>('400px');
8182
const canShare = doc.abilities.accesses_manage && isRootDoc;
@@ -88,6 +89,19 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
8889
setSelectedUsers((prev) => [...prev, user]);
8990
setUserQuery('');
9091
setInputValue('');
92+
93+
// Announce to screen readers
94+
const userName = user.full_name || user.email;
95+
setLiveAnnouncement(
96+
t(
97+
'{{name}} added to invite list. Add more members or press Tab to select role and invite.',
98+
{
99+
name: userName,
100+
},
101+
),
102+
);
103+
// Clear announcement after it's been read
104+
setTimeout(() => setLiveAnnouncement(''), 100);
91105
};
92106

93107
const { data: membersQuery } = useDocAccesses({
@@ -114,6 +128,16 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
114128
}
115129
const newArray = [...prevState];
116130
newArray.splice(index, 1);
131+
132+
// Announce to screen readers
133+
const userName = row.full_name || row.email;
134+
setLiveAnnouncement(
135+
t('{{name}} removed from invite list', {
136+
name: userName,
137+
}),
138+
);
139+
setTimeout(() => setLiveAnnouncement(''), 100);
140+
117141
return newArray;
118142
});
119143
};
@@ -175,12 +199,29 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
175199
<ButtonCloseModal
176200
aria-label={t('Close the share modal')}
177201
onClick={onClose}
202+
tabIndex={-1}
178203
/>
179204
</Box>
180205
}
181206
hideCloseButton
182207
>
183208
<ShareModalStyle />
209+
{/* Screen reader announcements */}
210+
<div
211+
role="status"
212+
aria-live="polite"
213+
aria-atomic="true"
214+
className="sr-only"
215+
style={{
216+
position: 'absolute',
217+
left: '-10000px',
218+
width: '1px',
219+
height: '1px',
220+
overflow: 'hidden',
221+
}}
222+
>
223+
{liveAnnouncement}
224+
</div>
184225
<Box
185226
$height="auto"
186227
$maxHeight={canViewAccesses ? modalContentHeight : 'none'}

0 commit comments

Comments
 (0)