Skip to content

Commit

Permalink
CXSPA-7971: Enable arrow-key navigation for sidebar-tabs (SAP#19082)
Browse files Browse the repository at this point in the history
  • Loading branch information
steinsebastian authored Jul 30, 2024
1 parent 7144693 commit dd451f1
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@
>
<div class="cx-menu-bar" role="tablist">
<button
#menuTab
[tabindex]="getTabIndexForMenuTab()"
role="tab"
[attr.aria-selected]="!showFilter"
[class.active]="!showFilter"
(keydown)="switchTabOnArrowPress($event, '#menuTab')"
(keydown.enter)="onMenu()"
(keydown.space)="onMenu()"
(click)="onMenu()"
>
{{ 'configurator.overviewSidebar.menu' | cxTranslate }}
</button>
<button
#filterTab
[tabindex]="getTabIndexForFilterTab()"
role="tab"
[attr.aria-selected]="showFilter"
[class.active]="showFilter"
(keydown)="switchTabOnArrowPress($event, '#filterTab')"
(keydown.enter)="onFilter()"
(keydown.space)="onFilter()"
(click)="onFilter()"
>
{{ 'configurator.overviewSidebar.filter' | cxTranslate }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,34 @@ describe('ConfiguratorOverviewSidebarComponent', () => {
);
});

it('should render overview filter component when filter tab is selected by enter-key', () => {
initTestComponent();
// keypress on filter button
fixture.debugElement
.queryAll(By.css('.cx-menu-bar button'))[1]
.triggerEventHandler('keydown.enter');
fixture.detectChanges();
CommonConfiguratorTestUtilsService.expectElementPresent(
expect,
htmlElem,
'cx-configurator-overview-filter'
);
});

it('should render overview filter component when filter tab is selected by space-key', () => {
initTestComponent();
// keypress on filter button
fixture.debugElement
.queryAll(By.css('.cx-menu-bar button'))[1]
.triggerEventHandler('keydown.space');
fixture.detectChanges();
CommonConfiguratorTestUtilsService.expectElementPresent(
expect,
htmlElem,
'cx-configurator-overview-filter'
);
});

it('should render overview menu component when menu tab is selected', () => {
initTestComponent();
component.onFilter();
Expand All @@ -187,6 +215,38 @@ describe('ConfiguratorOverviewSidebarComponent', () => {
);
});

it('should render overview menu component when menu tab is selected by enter-key', () => {
initTestComponent();
component.onFilter();
fixture.detectChanges();
// keypress on menu button
fixture.debugElement
.queryAll(By.css('.cx-menu-bar button'))[0]
.triggerEventHandler('keydown.enter');
fixture.detectChanges();
CommonConfiguratorTestUtilsService.expectElementPresent(
expect,
htmlElem,
'cx-configurator-overview-menu'
);
});

it('should render overview menu component when menu tab is selected by space-key', () => {
initTestComponent();
component.onFilter();
fixture.detectChanges();
// keypress on menu button
fixture.debugElement
.queryAll(By.css('.cx-menu-bar button'))[0]
.triggerEventHandler('keydown.space');
fixture.detectChanges();
CommonConfiguratorTestUtilsService.expectElementPresent(
expect,
htmlElem,
'cx-configurator-overview-menu'
);
});

it('should set showFilters to true by calling onFilter', () => {
component.onFilter();
expect(component.showFilter).toBe(true);
Expand All @@ -197,4 +257,118 @@ describe('ConfiguratorOverviewSidebarComponent', () => {
component.onMenu();
expect(component.showFilter).toBe(false);
});

describe('getTabIndexForMenuTab', () => {
it('should return tabindex 0 if menu tab content is displayed', () => {
component.showFilter = false;
expect(component.getTabIndexForMenuTab()).toBe(0);
});

it('should return tabindex -1 if filter content is displayed', () => {
component.showFilter = true;
expect(component.getTabIndexForMenuTab()).toBe(-1);
});
});

describe('getTabIndexForFilterTab', () => {
it('should return tabindex 0 if filter tab content is displayed', () => {
component.showFilter = true;
expect(component.getTabIndexForFilterTab()).toBe(0);
});

it('should return tabindex -1 if menu tab content is displayed', () => {
component.showFilter = false;
expect(component.getTabIndexForFilterTab()).toBe(-1);
});
});

describe('switchTabOnArrowPress', () => {
it('should focus filter tab if right arrow pressed and if current tab is menu tab', () => {
fixture.detectChanges();
const event = new KeyboardEvent('keydown', {
code: 'ArrowRight',
});
component.switchTabOnArrowPress(event, '#menuTab');
let focusedElement = document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.filter '
);
});

it('should focus filter tab if left arrow pressed and if current tab is menu tab', () => {
fixture.detectChanges();
const event = new KeyboardEvent('keydown', {
code: 'ArrowLeft',
});
component.switchTabOnArrowPress(event, '#menuTab');
let focusedElement = document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.filter '
);
});

it('should not change focus if up arrow pressed', () => {
fixture.detectChanges();
const leftEvent = new KeyboardEvent('keydown', {
code: 'ArrowLeft',
});
component.switchTabOnArrowPress(leftEvent, '#menuTab');
let focusedElement = document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.filter '
);
const upEvent = new KeyboardEvent('keydown', {
code: 'ArrowUp',
});
component.switchTabOnArrowPress(upEvent, '#menuTab');
document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.filter '
);
});

it('should not change focus if down arrow pressed', () => {
fixture.detectChanges();
const leftEvent = new KeyboardEvent('keydown', {
code: 'ArrowLeft',
});
component.switchTabOnArrowPress(leftEvent, '#menuTab');
let focusedElement = document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.filter '
);
const downEvent = new KeyboardEvent('keydown', {
code: 'ArrowDown',
});
component.switchTabOnArrowPress(downEvent, '#menuTab');
document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.filter '
);
});

it('should focus menu tab if right arrow pressed and if current tab is filter tab', () => {
fixture.detectChanges();
const event = new KeyboardEvent('keydown', {
code: 'ArrowRight',
});
component.switchTabOnArrowPress(event, '#filterTab');
let focusedElement = document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.menu '
);
});

it('should focus menu tab if left arrow pressed and if current tab is filter tab', () => {
fixture.detectChanges();
const event = new KeyboardEvent('keydown', {
code: 'ArrowLeft',
});
component.switchTabOnArrowPress(event, '#filterTab');
let focusedElement = document.activeElement;
expect(focusedElement?.innerHTML).toBe(
' configurator.overviewSidebar.menu '
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Component, HostBinding } from '@angular/core';
import { Component, HostBinding, ViewChild, ElementRef } from '@angular/core';
import { ConfiguratorRouterExtractorService } from '@spartacus/product-configurator/common';
import { Observable, OperatorFunction } from 'rxjs';
import { filter, switchMap, tap } from 'rxjs/operators';
Expand All @@ -18,6 +18,8 @@ import { ConfiguratorStorefrontUtilsService } from '../service/configurator-stor
})
export class ConfiguratorOverviewSidebarComponent {
@HostBinding('class.ghost') ghostStyle = true;
@ViewChild('menuTab') menuTab: ElementRef<HTMLElement>;
@ViewChild('filterTab') filterTab: ElementRef<HTMLElement>;
showFilter: boolean = false;

constructor(
Expand Down Expand Up @@ -58,4 +60,39 @@ export class ConfiguratorOverviewSidebarComponent {
onMenu() {
this.showFilter = false;
}

/**
* Returns the tabindex for the menu tab.
*
* The menu tab is excluded from the tab chain if currently the filter tab content is displayed.
* @returns tabindex of the menu tab
*/
getTabIndexForMenuTab(): number {
return this.showFilter ? -1 : 0;
}

/**
* Returns the tabindex for the filter tab.
* The filter tab is excluded from the tab chain if currently the menu tab content is displayed.
* @returns tabindex of the fitler tab
*/
getTabIndexForFilterTab(): number {
return this.showFilter ? 0 : -1;
}

/**
* Switches the focus of the tabs on pressing left or right arrow key.
* @param {KeyboardEvent} event - Keyboard event
* @param {string} currentTab - Current tab
*/
switchTabOnArrowPress(event: KeyboardEvent, currentTab: string): void {
if (event.code === 'ArrowLeft' || event.code === 'ArrowRight') {
event.preventDefault();
if (currentTab === '#menuTab') {
this.filterTab.nativeElement?.focus();
} else {
this.menuTab.nativeElement?.focus();
}
}
}
}

0 comments on commit dd451f1

Please sign in to comment.