Skip to content

Commit aba3a94

Browse files
129964: Fixed the header role structure being invalid in the custom theme
- Replaced the menubar role from the parent of all the header buttons like lang switch, auth menu & help toggle with toolbar - Replaced the remaining `<a>` buttons in the header with `<button>` to make them expandable with space - Fixed accessibility issues flagged by axe DevTools in the user menu dropdown
1 parent be85947 commit aba3a94

File tree

11 files changed

+70
-61
lines changed

11 files changed

+70
-61
lines changed

cypress/e2e/header.cy.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,24 @@ describe('Header', () => {
1515
cy.visit('/');
1616

1717
// Click the language switcher (globe) in header
18-
cy.get('a[data-test="lang-switch"]').click();
18+
cy.get('button[data-test="lang-switch"]').click();
1919
// Click on the "Deusch" language in dropdown
20-
cy.get('#language-menu-list li').contains('Deutsch').click();
20+
cy.get('#language-menu-list div[role="option"]').contains('Deutsch').click();
2121

2222
// HTML "lang" attribute should switch to "de"
2323
cy.get('html').invoke('attr', 'lang').should('eq', 'de');
2424

2525
// Login menu should now be in German
26-
cy.get('a[data-test="login-menu"]').contains('Anmelden');
26+
cy.get('[data-test="login-menu"]').contains('Anmelden');
2727

2828
// Change back to English from language switcher
29-
cy.get('a[data-test="lang-switch"]').click();
30-
cy.get('#language-menu-list li').contains('English').click();
29+
cy.get('button[data-test="lang-switch"]').click();
30+
cy.get('#language-menu-list div[role="option"]').contains('English').click();
3131

3232
// HTML "lang" attribute should switch to "en"
3333
cy.get('html').invoke('attr', 'lang').should('eq', 'en');
3434

3535
// Login menu should now be in English
36-
cy.get('a[data-test="login-menu"]').contains('Log In');
36+
cy.get('[data-test="login-menu"]').contains('Log In');
3737
});
3838
});

src/app/header/header.component.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
<img src="assets/images/dspace-logo.svg" [attr.alt]="'menu.header.image.logo' | translate"/>
66
</a>
77

8-
<nav role="navigation" [attr.aria-label]="'nav.user.description' | translate" class="navbar navbar-light navbar-expand-md flex-shrink-0 px-0">
8+
<div class="navbar navbar-light navbar-expand-md flex-shrink-0 px-0">
99
<ds-search-navbar></ds-search-navbar>
10-
<ds-lang-switch></ds-lang-switch>
11-
<ds-context-help-toggle></ds-context-help-toggle>
12-
<ds-auth-nav-menu></ds-auth-nav-menu>
13-
<ds-impersonate-navbar></ds-impersonate-navbar>
10+
<div role="toolbar" [attr.aria-label]="'nav.user.description' | translate">
11+
<ds-lang-switch></ds-lang-switch>
12+
<ds-context-help-toggle></ds-context-help-toggle>
13+
<ds-auth-nav-menu></ds-auth-nav-menu>
14+
<ds-impersonate-navbar></ds-impersonate-navbar>
15+
</div>
1416
@if (isMobile$ | async) {
1517
<div class="ps-2">
1618
<button class="navbar-toggler px-0" type="button" (click)="toggleNavbar()"
@@ -20,7 +22,7 @@
2022
</button>
2123
</div>
2224
}
23-
</nav>
25+
</div>
2426
</div>
2527
</div>
2628
</header>

src/app/header/header.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
}
2424
}
2525

26-
.navbar {
26+
.navbar, div[role="toolbar"] {
2727
display: flex;
2828
gap: calc(var(--bs-spacer) / 3);
2929
align-items: center;

src/app/navbar/navbar.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
@if ((isMobile$ | async) && (isAuthenticated$ | async)) {
1010
<ds-user-menu [inExpandableNavbar]="true"></ds-user-menu>
1111
}
12-
<div class="navbar-nav align-items-md-center me-auto shadow-none gapx-3">
12+
<div class="navbar-nav align-items-md-center me-auto shadow-none gapx-3" role="menubar">
1313
@for (section of (sections | async); track section) {
1414
<ng-container
1515
*ngComponentOutlet="(sectionMap$ | async).get(section.id)?.component; injector: (sectionMap$ | async).get(section.id)?.injector;"></ng-container>
Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,66 @@
1+
@let isAuthenticated = (isAuthenticated$ | async);
12
@if ((isMobile$ | async) !== true) {
23
<div class="navbar-nav me-auto" data-test="auth-nav">
3-
@if ((isAuthenticated | async) !== true && (showAuth | async)) {
4+
@let showAuth = (showAuth$ | async);
5+
@if (isAuthenticated !== true && showAuth) {
46
<div
57
class="nav-item"
68
(click)="$event.stopPropagation();">
79
<div ngbDropdown #loginDrop="ngbDropdown" display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
8-
<a href="javascript:void(0);" class="dropdownLogin px-0.5" [attr.aria-label]="'nav.login' |translate"
9-
(click)="$event.preventDefault()" [attr.data-test]="'login-menu' | dsBrowserOnly"
10-
role="menuitem"
11-
tabindex="0"
12-
aria-haspopup="menu"
13-
aria-controls="loginDropdownMenu"
14-
[attr.aria-expanded]="loginDrop.isOpen()"
15-
ngbDropdownToggle>{{ 'nav.login' | translate }}</a>
10+
<button class="dropdownLogin btn btn-link px-0" [attr.aria-label]="'nav.login' |translate"
11+
(click)="$event.preventDefault()" [attr.data-test]="'login-menu' | dsBrowserOnly"
12+
role="button"
13+
tabindex="0"
14+
aria-haspopup="menu"
15+
aria-controls="loginDropdownMenu"
16+
[attr.aria-expanded]="loginDrop.isOpen()"
17+
ngbDropdownToggle>
18+
{{ 'nav.login' | translate }}
19+
</button>
1620
<div id="loginDropdownMenu" [ngClass]="{'ps-3 pe-3': (loading | async)}" ngbDropdownMenu
17-
role="menu"
21+
role="dialog"
22+
aria-modal="true"
1823
[attr.aria-label]="'nav.login' | translate">
1924
<ds-log-in
2025
[isStandalonePage]="false"></ds-log-in>
2126
</div>
2227
</div>
2328
</div>
2429
}
25-
@if ((isAuthenticated | async) && (showAuth | async)) {
30+
@if (isAuthenticated && showAuth) {
2631
<div class="nav-item">
2732
<div ngbDropdown #loggedInDrop="ngbDropdown" display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
28-
<a href="javascript:void(0);"
29-
role="menuitem"
33+
<button
34+
role="button"
3035
tabindex="0"
3136
[attr.aria-label]="'nav.user-profile-menu-and-logout' | translate"
3237
aria-controls="user-menu-dropdown"
3338
(click)="$event.preventDefault()" [title]="'nav.user-profile-menu-and-logout' | translate"
34-
class="dropdownLogout px-1"
39+
class="dropdownLogout btn btn-link px-0"
3540
[attr.data-test]="'user-menu' | dsBrowserOnly"
3641
ngbDropdownToggle>
37-
<i class="fas fa-user-circle fa-lg fa-fw"></i></a>
38-
<div id="logoutDropdownMenu" ngbDropdownMenu>
39-
<ds-user-menu [inExpandableNavbar]="false" (changedRoute)="loggedInDrop.close()"></ds-user-menu>
40-
</div>
42+
<i class="fas fa-user-circle fa-lg fa-fw"></i>
43+
</button>
44+
<div id="logoutDropdownMenu" ngbDropdownMenu>
45+
<ds-user-menu [inExpandableNavbar]="false" (changedRoute)="loggedInDrop.close()"></ds-user-menu>
4146
</div>
4247
</div>
43-
}
44-
</div>
45-
} @else {
46-
<div data-test="auth-nav">
47-
@if ((isAuthenticated | async) !== true) {
48-
<a routerLink="/login" routerLinkActive="active" class="loginLink px-0.5" role="button" tabindex="0">
49-
{{ 'nav.login' | translate }}<span class="sr-only">(current)</span>
50-
</a>
51-
}
52-
@if ((isAuthenticated | async)) {
53-
<a role="button" [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="logoutLink px-1" role="button" tabindex="0">
54-
<i class="fas fa-sign-out-alt fa-lg fa-fw"></i>
55-
<span class="sr-only">(current)</span>
56-
</a>
57-
}
58-
</div>
59-
}
60-
61-
48+
</div>
49+
}
50+
</div>
51+
} @else {
52+
<div data-test="auth-nav">
53+
@if (isAuthenticated === true) {
54+
<a [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="logoutLink px-0" role="link" tabindex="0">
55+
<i class="fas fa-sign-out-alt fa-lg fa-fw"></i>
56+
<span class="sr-only">(current)</span>
57+
</a>
58+
} @else {
59+
<a routerLink="/login" routerLinkActive="active" class="loginLink px-0" role="link" tabindex="0">
60+
{{ 'nav.login' | translate }}<span class="sr-only">(current)</span>
61+
</a>
62+
}
63+
</div>
64+
}
6265

63-
<!-- Do not use ul/li in this menu as it breaks e2e accessibility tests -->
66+
<!-- Do not use ul/li in this menu as it breaks e2e accessibility tests -->

src/app/shared/auth-nav-menu/auth-nav-menu.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@
2828
box-shadow: unset;
2929
}
3030
}
31+
32+
.dropdown-toggle::after {
33+
margin-left: 0;
34+
}

src/app/shared/auth-nav-menu/auth-nav-menu.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class AuthNavMenuComponent implements OnInit {
6464
* Whether user is authenticated.
6565
* @type {Observable<string>}
6666
*/
67-
public isAuthenticated: Observable<boolean>;
67+
public isAuthenticated$: Observable<boolean>;
6868

6969
/**
7070
* True if the authentication is loading.
@@ -74,7 +74,7 @@ export class AuthNavMenuComponent implements OnInit {
7474

7575
public isMobile$: Observable<boolean>;
7676

77-
public showAuth = observableOf(false);
77+
public showAuth$ = observableOf(false);
7878

7979
public user: Observable<EPerson>;
8080

@@ -89,14 +89,14 @@ export class AuthNavMenuComponent implements OnInit {
8989

9090
ngOnInit(): void {
9191
// set isAuthenticated
92-
this.isAuthenticated = this.store.pipe(select(isAuthenticated));
92+
this.isAuthenticated$ = this.store.pipe(select(isAuthenticated));
9393

9494
// set loading
9595
this.loading = this.store.pipe(select(isAuthenticationLoading));
9696

9797
this.user = this.authService.getAuthenticatedUserFromStore();
9898

99-
this.showAuth = this.store.pipe(
99+
this.showAuth$ = this.store.pipe(
100100
select(routerStateSelector),
101101
filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state)),
102102
map((router: RouterReducerState) => (!router.state.url.startsWith(LOGIN_ROUTE)

src/app/shared/lang-switch/lang-switch.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[attr.aria-label]="'nav.language' |translate"
55
aria-controls="language-menu-list"
66
aria-haspopup="menu"
7-
class="dropdown-toggle"
7+
class="dropdown-toggle btn btn-link px-0"
88
[title]="'nav.language' | translate"
99
(click)="$event.preventDefault()" data-toggle="dropdown" ngbDropdownToggle
1010
data-test="lang-switch"
@@ -16,6 +16,7 @@
1616
@for (lang of translate.getLangs(); track lang) {
1717
<div class="dropdown-item" tabindex="0"
1818
role="option"
19+
[lang]="lang"
1920
(keyup.enter)="useLang(lang)"
2021
(click)="useLang(lang)"
2122
[attr.aria-selected]="lang === translate.currentLang"

src/app/shared/lang-switch/lang-switch.component.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
}
44

55
.dropdown-toggle {
6-
all: unset;
76
color: var(--ds-header-icon-color);
87

98
&:hover, &:focus {

src/app/shared/log-in/methods/password/log-in-password.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@
3535
<div class="mt-2">
3636
@if (canRegister$ | async) {
3737
<a class="dropdown-item" [routerLink]="[getRegisterRoute()]"
38-
[attr.data-test]="'register' | dsBrowserOnly" role="menuitem" tabindex="0">{{"login.form.new-user" | translate}}</a>
38+
[attr.data-test]="'register' | dsBrowserOnly" tabindex="0">{{"login.form.new-user" | translate}}</a>
3939
}
4040
@if (canForgot$ | async) {
4141
<a class="dropdown-item" [routerLink]="[getForgotRoute()]"
42-
[attr.data-test]="'forgot' | dsBrowserOnly" role="menuitem" tabindex="0">{{"login.form.forgot-password" | translate}}</a>
42+
[attr.data-test]="'forgot' | dsBrowserOnly" tabindex="0">{{"login.form.forgot-password" | translate}}</a>
4343
}
4444
</div>
4545
}

0 commit comments

Comments
 (0)