Skip to content

Commit

Permalink
feat: add useRouter property to sidenav (#2812)
Browse files Browse the repository at this point in the history
Co-authored-by: Akshat Patel <[email protected]>
  • Loading branch information
klaascuvelier and Akshat55 authored Apr 3, 2024
1 parent 69eac85 commit dcd242a
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 69 deletions.
157 changes: 112 additions & 45 deletions src/ui-shell/sidenav/side-nav.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class FooComponent { }
<cds-sidenav-menu title="Example Title"></cds-sidenav-menu>
<cds-sidenav-item
[route]="route"
[useRouter]="useRouter"
(navigation)="onNavigation($event)">
</cds-sidenav-item>
</cds-sidenav>
Expand All @@ -31,6 +32,7 @@ class SideNavTest {
hidden = false;
allowExpansion = false;
statusPromise = null;
useRouter = false;
onNavigation(event) {
this.statusPromise = event;
}
Expand Down Expand Up @@ -70,54 +72,119 @@ describe("SideNav", () => {
expect(fixture.componentInstance instanceof SideNav).toBe(true);
});

it("should emit the navigation status promise when the link is activated and call onNavigation", async () => {
fixture = TestBed.createComponent(SideNavTest);
wrapper = fixture.componentInstance;
spyOn(wrapper, "onNavigation").and.callThrough();
fixture.detectChanges();
element = fixture.debugElement.query(By.css(".cds--side-nav__link"));
element.nativeElement.click();
fixture.detectChanges();
expect(wrapper.onNavigation).toHaveBeenCalled();
const status = await wrapper.statusPromise;
expect(status).toBe(true);
});
describe("when useRouter is false", () => {
let fixture;

it("should expand sidenav-menu on click", () => {
fixture = TestBed.createComponent(SideNavTest);
wrapper = fixture.componentInstance;
fixture.detectChanges();
element = fixture.debugElement.query(By.css(".cds--side-nav__submenu"));
element.nativeElement.click();
fixture.detectChanges();
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("true");
expect(element.componentInstance.expanded).toBe(true);
element.nativeElement.click();
fixture.detectChanges();
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("false");
expect(element.componentInstance.expanded).toBe(false);
});
beforeEach(() => {
fixture = TestBed.createComponent(SideNavTest);
fixture.componentInstance.useRouter = false;
fixture.detectChanges();
});

it("should emit the navigation status promise when the link is activated and call onNavigation", async () => {
wrapper = fixture.componentInstance;
spyOn(wrapper, "onNavigation").and.callThrough();
fixture.detectChanges();
element = fixture.debugElement.query(By.css(".cds--side-nav__link"));
element.nativeElement.click();
fixture.detectChanges();
expect(wrapper.onNavigation).toHaveBeenCalled();
const status = await wrapper.statusPromise;
expect(status).toBe(true);
});

it("should set the sidenav-menu title to Example Title", () => {
fixture = TestBed.createComponent(SideNavTest);
fixture.detectChanges();
element = fixture.debugElement.query(By.css("cds-sidenav-menu"));
expect(element.nativeElement.textContent).toEqual("Example Title");
it("should expand sidenav-menu on click", () => {
wrapper = fixture.componentInstance;
fixture.detectChanges();
element = fixture.debugElement.query(By.css(".cds--side-nav__submenu"));
element.nativeElement.click();
fixture.detectChanges();
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("true");
expect(element.componentInstance.expanded).toBe(true);
element.nativeElement.click();
fixture.detectChanges();
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("false");
expect(element.componentInstance.expanded).toBe(false);
});

it("should set the sidenav-menu title to Example Title", () => {
fixture.detectChanges();
element = fixture.debugElement.query(By.css("cds-sidenav-menu"));
expect(element.nativeElement.textContent).toEqual("Example Title");
});

it("should toggle expanded on click", () => {
wrapper = fixture.componentInstance;
wrapper.allowExpansion = true;
fixture.detectChanges();
element = fixture.debugElement.query(By.css("cds-sidenav"));
let sidenavButton = element.nativeElement.querySelector(
".cds--side-nav__toggle",
);
element.componentInstance.expanded = false;
sidenavButton.click();
fixture.detectChanges();
expect(element.componentInstance.expanded).toBe(true);
sidenavButton.click();
fixture.detectChanges();
expect(element.componentInstance.expanded).toBe(false);
});
});

it("should toggle expanded on click", () => {
fixture = TestBed.createComponent(SideNavTest);
wrapper = fixture.componentInstance;
wrapper.allowExpansion = true;
fixture.detectChanges();
element = fixture.debugElement.query(By.css("cds-sidenav"));
let sidenavButton = element.nativeElement.querySelector(".cds--side-nav__toggle");
element.componentInstance.expanded = false;
sidenavButton.click();
fixture.detectChanges();
expect(element.componentInstance.expanded).toBe(true);
sidenavButton.click();
fixture.detectChanges();
expect(element.componentInstance.expanded).toBe(false);
describe("when useRouter is true", () => {
let fixture;

beforeEach(() => {
fixture = TestBed.createComponent(SideNavTest);
fixture.componentInstance.useRouter = true;
fixture.detectChanges();
});

it("should not emit the navigation status promise when the link is activated and call onNavigation", async () => {
wrapper = fixture.componentInstance;
spyOn(wrapper, "onNavigation").and.callThrough();
fixture.detectChanges();
element = fixture.debugElement.query(By.css(".cds--side-nav__link"));
element.nativeElement.click();
fixture.detectChanges();
expect(wrapper.onNavigation).not.toHaveBeenCalled();
});

it("should expand sidenav-menu on click", () => {
wrapper = fixture.componentInstance;
fixture.detectChanges();
element = fixture.debugElement.query(By.css(".cds--side-nav__submenu"));
element.nativeElement.click();
fixture.detectChanges();
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("true");
expect(element.componentInstance.expanded).toBe(true);
element.nativeElement.click();
fixture.detectChanges();
expect(element.nativeElement.getAttribute("aria-expanded")).toBe("false");
expect(element.componentInstance.expanded).toBe(false);
});

it("should set the sidenav-menu title to Example Title", () => {
fixture.detectChanges();
element = fixture.debugElement.query(By.css("cds-sidenav-menu"));
expect(element.nativeElement.textContent).toEqual("Example Title");
});

it("should toggle expanded on click", () => {
wrapper = fixture.componentInstance;
wrapper.allowExpansion = true;
fixture.detectChanges();
element = fixture.debugElement.query(By.css("cds-sidenav"));
let sidenavButton = element.nativeElement.querySelector(
".cds--side-nav__toggle",
);
element.componentInstance.expanded = false;
sidenavButton.click();
fixture.detectChanges();
expect(element.componentInstance.expanded).toBe(true);
sidenavButton.click();
fixture.detectChanges();
expect(element.componentInstance.expanded).toBe(false);
});
});
});
43 changes: 32 additions & 11 deletions src/ui-shell/sidenav/sidenav-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
Output,
EventEmitter,
OnChanges,
HostBinding
HostBinding,
SimpleChanges
} from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { Router } from "@angular/router";
Expand All @@ -16,22 +17,37 @@ import { Router } from "@angular/router";
@Component({
selector: "cds-sidenav-item, ibm-sidenav-item",
template: `
<a
class="cds--side-nav__link"
[ngClass]="{
<a *ngIf="!useRouter; else sidenavItemRouterTpl"
class="cds--side-nav__link"
[ngClass]="{
'cds--side-nav__item--active': active
}"
[href]="href"
[attr.aria-current]="(active ? 'page' : null)"
[attr.title]="title ? title : null"
(click)="navigate($event)">
[href]="href"
[attr.aria-current]="(active ? 'page' : null)"
[attr.title]="title ? title : null"
(click)="navigate($event)">
<ng-template [ngTemplateOutlet]="sidenavItemContentTpl"></ng-template>
</a>
<ng-template #sidenavItemRouterTpl>
<a
[routerLink]="route"
routerLinkActive="cds--side-nav__item--active"
ariaCurrentWhenActive="page"
[attr.title]="title ? title : null"
class="cds--side-nav__link">
<ng-template [ngTemplateOutlet]="sidenavItemContentTpl"></ng-template>
</a>
</ng-template>
<ng-template #sidenavItemContentTpl>
<div *ngIf="!isSubMenu" class="cds--side-nav__icon">
<ng-content select="svg, [icon]"></ng-content>
</div>
<span class="cds--side-nav__link-text">
<ng-content></ng-content>
</span>
</a>
</ng-template>
`,
styles: [`
:host {
Expand All @@ -55,6 +71,11 @@ export class SideNavItem implements OnChanges {
return this.domSanitizer.bypassSecurityTrustUrl(this._href) as string;
}

/**
* Use the routerLink attribute on <a> tag for navigation instead of using event handlers
*/
@Input() useRouter = true;

@HostBinding("class.cds--side-nav__item") get sideNav() {
return !this.isSubMenu;
}
Expand Down Expand Up @@ -104,13 +125,13 @@ export class SideNavItem implements OnChanges {

constructor(protected domSanitizer: DomSanitizer, @Optional() protected router: Router) {}

ngOnChanges(changes) {
ngOnChanges(changes: SimpleChanges) {
if (changes.active) {
this.selected.emit(this.active);
}
}

navigate(event) {
navigate(event: MouseEvent) {
if (this.router && this.route) {
event.preventDefault();
const status = this.router.navigate(this.route, this.routeExtras);
Expand Down
8 changes: 7 additions & 1 deletion src/ui-shell/sidenav/sidenav-menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { SideNavItemInterface } from "./sidenav-item.interface";
[href]="menuItem.href"
[route]="menuItem.route"
[routeExtras]="menuItem.routeExtras"
[useRouter]="useRouter"
[isSubMenu]="true">
{{ menuItem.content }}
</cds-sidenav-item>
Expand All @@ -64,7 +65,12 @@ export class SideNavMenu implements AfterContentInit, OnDestroy {
@HostBinding("attr.role") role = "listitem";

/**
* Heading for the gorup
* Use the routerLink attribute on <a> tag for navigation instead of using event handlers
*/
@Input() useRouter = false;

/**
* Heading for the group
*/
@Input() title: string;
/**
Expand Down
7 changes: 7 additions & 0 deletions src/ui-shell/sidenav/sidenav.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ import { NavigationItem } from "../header/header-navigation-items.interface";
[href]="navigationItem.href"
[route]="navigationItem.route"
[routeExtras]="navigationItem.routeExtras"
[useRouter]="useRouter"
[title]="navigationItem.title">
{{ navigationItem.content }}
</cds-sidenav-item>
<cds-sidenav-menu
*ngIf="navigationItem.type === 'menu'"
[title]="navigationItem.title"
[useRouter]="useRouter"
[menuItems]="navigationItem.menuItems">
</cds-sidenav-menu>
</ng-container>
Expand Down Expand Up @@ -101,6 +103,11 @@ export class SideNav {
*/
@Input() navigationItems: NavigationItem[];

/**
* Use the routerLink attribute on <a> tag for navigation instead of using event handlers
*/
@Input() useRouter = false;

constructor(public i18n: I18n) { }

toggle() {
Expand Down
3 changes: 2 additions & 1 deletion src/ui-shell/sidenav/sidenav.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterModule } from "@angular/router";

import { I18nModule } from "carbon-components-angular/i18n";

Expand All @@ -21,7 +22,7 @@ export {
SideNavItem,
SideNavMenu
],
imports: [CommonModule, I18nModule],
imports: [CommonModule, I18nModule, RouterModule],
exports: [
SideNav,
SideNavItem,
Expand Down
Loading

0 comments on commit dcd242a

Please sign in to comment.