Skip to content

Commit 475cf1e

Browse files
committed
improvement: a11y: aria-label for accounts list
1 parent ce856ff commit 475cf1e

File tree

3 files changed

+99
-76
lines changed

3 files changed

+99
-76
lines changed

packages/frontend/src/components/AccountListSidebar/AccountItem.tsx

Lines changed: 63 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -242,64 +242,72 @@ export default function AccountItem({
242242
// for a different account, and upon initial render.
243243

244244
return (
245-
<button
246-
className={classNames(styles.account, rovingTabindex.className, {
247-
[styles.active]: isSelected,
248-
[styles['context-menu-active']]: isContextMenuActive,
245+
<li
246+
className={classNames(styles.accountWrapper, {
249247
[styles.isSticky]: isSticky,
250-
'unconfigured-account': account?.kind !== 'Configured',
251248
})}
252-
aria-busy={!account}
253-
onClick={() => onSelectAccount(accountId)}
254-
onContextMenu={onContextMenu}
255-
onMouseEnter={() => account && updateAccountForHoverInfo(account, true)}
256-
onMouseLeave={() => account && updateAccountForHoverInfo(account, false)}
257-
x-account-sidebar-account-id={accountId}
258-
data-testid={`account-item-${accountId}`}
259-
ref={ref}
260-
tabIndex={rovingTabindex.tabIndex}
261-
onFocus={rovingTabindex.setAsActiveElement}
262-
onKeyDown={rovingTabindex.onKeydown}
263249
>
264-
{!account ? (
265-
<div className={styles.avatar}>
266-
<div className={styles.content}></div>
267-
</div>
268-
) : account.kind == 'Configured' ? (
269-
<div className={styles.avatar}>
270-
{' '}
271-
{account.profileImage ? (
272-
<img
273-
className={styles.content}
274-
src={runtime.transformBlobURL(account.profileImage)}
275-
/>
276-
) : (
277-
<div
278-
className={styles.content}
279-
style={{ backgroundColor: account.color }}
280-
>
281-
{avatarInitial(
282-
account.displayName || '',
283-
account.addr || undefined
284-
)}
285-
</div>
286-
)}
287-
</div>
288-
) : (
289-
<div className={styles.avatar}>
290-
<div className={styles.content}>?</div>
291-
</div>
292-
)}
293-
{muted && (
294-
<div
295-
aria-label='Account notifications muted'
296-
className={styles.accountMutedIconShadow}
297-
>
298-
<Icon className={styles.accountMutedIcon} icon='audio-muted' />
299-
</div>
300-
)}
301-
<div className={classNames(styles.accountBadge)}>{badgeContent}</div>
302-
</button>
250+
<button
251+
className={classNames(styles.account, rovingTabindex.className, {
252+
[styles.active]: isSelected,
253+
[styles['context-menu-active']]: isContextMenuActive,
254+
[styles.isSticky]: isSticky,
255+
'unconfigured-account': account?.kind !== 'Configured',
256+
})}
257+
aria-busy={!account}
258+
onClick={() => onSelectAccount(accountId)}
259+
onContextMenu={onContextMenu}
260+
onMouseEnter={() => account && updateAccountForHoverInfo(account, true)}
261+
onMouseLeave={() =>
262+
account && updateAccountForHoverInfo(account, false)
263+
}
264+
x-account-sidebar-account-id={accountId}
265+
data-testid={`account-item-${accountId}`}
266+
ref={ref}
267+
tabIndex={rovingTabindex.tabIndex}
268+
onFocus={rovingTabindex.setAsActiveElement}
269+
onKeyDown={rovingTabindex.onKeydown}
270+
>
271+
{!account ? (
272+
<div className={styles.avatar}>
273+
<div className={styles.content}></div>
274+
</div>
275+
) : account.kind == 'Configured' ? (
276+
<div className={styles.avatar}>
277+
{' '}
278+
{account.profileImage ? (
279+
<img
280+
className={styles.content}
281+
src={runtime.transformBlobURL(account.profileImage)}
282+
/>
283+
) : (
284+
<div
285+
className={styles.content}
286+
style={{ backgroundColor: account.color }}
287+
>
288+
{avatarInitial(
289+
account.displayName || '',
290+
account.addr || undefined
291+
)}
292+
</div>
293+
)}
294+
</div>
295+
) : (
296+
<div className={styles.avatar}>
297+
<div className={styles.content}>?</div>
298+
</div>
299+
)}
300+
{muted && (
301+
<div
302+
aria-label='Account notifications muted'
303+
className={styles.accountMutedIconShadow}
304+
>
305+
<Icon className={styles.accountMutedIcon} icon='audio-muted' />
306+
</div>
307+
)}
308+
<div className={classNames(styles.accountBadge)}>{badgeContent}</div>
309+
</button>
310+
</li>
303311
)
304312
}
305313

packages/frontend/src/components/AccountListSidebar/AccountListSidebar.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export default function AccountListSidebar({
4646
}: Props) {
4747
const tx = useTranslationFunction()
4848

49-
const accountsListRef = useRef<HTMLDivElement>(null)
49+
const accountsListRef = useRef<HTMLUListElement>(null)
5050
const { openDialog } = useDialog()
5151
const [accounts, setAccounts] = useState<number[]>([])
5252
const [{ accounts: noficationSettings }] = useAccountNotificationStore()
@@ -144,8 +144,12 @@ export default function AccountListSidebar({
144144
data-tauri-drag-region
145145
/>
146146
)}
147-
<div
147+
<ul
148148
ref={accountsListRef}
149+
// Perhaps just "Profiles" would be more appropriate,
150+
// because you can do other things with profiles in this list,
151+
// but we have the same on Android.
152+
aria-label={tx('switch_account')}
149153
className={styles.accountList}
150154
onScroll={updateHoverInfoPosition}
151155
>
@@ -162,9 +166,11 @@ export default function AccountListSidebar({
162166
muted={noficationSettings[id]?.muted || false}
163167
/>
164168
))}
165-
<AddAccountButton onClick={onAddAccount} />
169+
<li>
170+
<AddAccountButton onClick={onAddAccount} />
171+
</li>
166172
</RovingTabindexProvider>
167-
</div>
173+
</ul>
168174
{/* The condition is the same as in https://github.com/deltachat/deltachat-desktop/blob/63af023437ff1828a27de2da37bf94ab180ec528/src/renderer/contexts/KeybindingsContext.tsx#L26 */}
169175
{window.__screen === Screens.Main && (
170176
<div className={styles.buttonsContainer}>

packages/frontend/src/components/AccountListSidebar/styles.module.scss

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
--als-avatar-margin: 5px;
5151
--als-active-indicator-color: white;
5252

53+
margin: 0;
54+
padding: 0;
55+
list-style: none;
56+
5357
align-items: center;
5458
display: flex;
5559
flex-direction: column;
@@ -63,6 +67,28 @@
6367
}
6468
}
6569

70+
.accountWrapper {
71+
margin-bottom: var(--als-avatar-margin);
72+
margin-top: var(--als-avatar-margin);
73+
74+
&.isSticky {
75+
position: sticky;
76+
bottom: var(--als-avatar-margin);
77+
top: var(--als-avatar-margin);
78+
// Only needed when this account is scrolled _above_ the visible region.
79+
z-index: map-get($z-index, account-list-sidebar-scope-sticky-account);
80+
81+
.account {
82+
.avatar {
83+
opacity: 1;
84+
.content {
85+
box-shadow: 0px 0px 4px 2px #00000040;
86+
}
87+
}
88+
}
89+
}
90+
}
91+
6692
.account {
6793
border: none;
6894
background: none;
@@ -71,8 +97,6 @@
7197

7298
height: var(--als-avatar-size);
7399

74-
margin-bottom: var(--als-avatar-margin);
75-
margin-top: var(--als-avatar-margin);
76100
// Adding extra `var(--als-avatar-size) + 2 * var(--als-avatar-margin)`
77101
// to the margins so that
78102
// if there is another `.isSticky` account, then that account does not
@@ -107,21 +131,6 @@
107131
}
108132
}
109133

110-
&.isSticky {
111-
position: sticky;
112-
bottom: var(--als-avatar-margin);
113-
top: var(--als-avatar-margin);
114-
// Only needed when this account is scrolled _above_ the visible region.
115-
z-index: map-get($z-index, account-list-sidebar-scope-sticky-account);
116-
117-
.avatar {
118-
opacity: 1;
119-
.content {
120-
box-shadow: 0px 0px 4px 2px #00000040;
121-
}
122-
}
123-
}
124-
125134
&.active,
126135
&:hover,
127136
&.context-menu-active {

0 commit comments

Comments
 (0)