fix(docs-infra): highlight the currently active node in top-bar (#33351)
Related to #33239 and #33255. PR Close #33351
This commit is contained in:
parent
34b84f61e0
commit
2aa940f55c
|
@ -22,7 +22,7 @@
|
||||||
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
|
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
|
||||||
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
|
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
|
||||||
</a>
|
</a>
|
||||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes" [currentNode]="currentNodes?.TopBar"></aio-top-menu>
|
||||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
||||||
<div class="toolbar-external-icons-container">
|
<div class="toolbar-external-icons-container">
|
||||||
<a href="https://twitter.com/angular" title="Twitter" aria-label="Angular on twitter">
|
<a href="https://twitter.com/angular" title="Twitter" aria-label="Angular on twitter">
|
||||||
|
|
|
@ -10,6 +10,8 @@ describe('TopMenuComponent', () => {
|
||||||
const list: HTMLUListElement = fixture.debugElement.nativeElement.querySelector('ul');
|
const list: HTMLUListElement = fixture.debugElement.nativeElement.querySelector('ul');
|
||||||
return Array.from(list.querySelectorAll('li'));
|
return Array.from(list.querySelectorAll('li'));
|
||||||
};
|
};
|
||||||
|
const getSelected = (items: HTMLLIElement[]) =>
|
||||||
|
items.filter(item => item.classList.contains('selected'));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
@ -38,4 +40,47 @@ describe('TopMenuComponent', () => {
|
||||||
expect(links.map(link => link.textContent)).toEqual(['API', 'Features']);
|
expect(links.map(link => link.textContent)).toEqual(['API', 'Features']);
|
||||||
expect(links.map(link => link.title)).toEqual(['API docs', 'Angular features overview']);
|
expect(links.map(link => link.title)).toEqual(['API docs', 'Angular features overview']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should mark the currently selected node with `.selected`', () => {
|
||||||
|
const items = getListItems();
|
||||||
|
expect(getSelected(items)).toEqual([]);
|
||||||
|
|
||||||
|
component.currentNode = {url: 'api', view: 'foo', nodes: []};
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSelected(items)).toEqual([items[0]]);
|
||||||
|
|
||||||
|
component.currentNode = {url: 'features', view: 'foo', nodes: []};
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSelected(items)).toEqual([items[1]]);
|
||||||
|
|
||||||
|
component.currentNode = {url: 'something/else', view: 'foo', nodes: []};
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSelected(items)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not mark any node with `.selected` if the current URL is undefined', () => {
|
||||||
|
component.nodes = [
|
||||||
|
{url: '', title: 'API', tooltip: 'API docs'},
|
||||||
|
{url: undefined, title: 'Features', tooltip: 'Angular features overview'},
|
||||||
|
];
|
||||||
|
fixture.detectChanges();
|
||||||
|
const items = getListItems();
|
||||||
|
|
||||||
|
component.currentNode = undefined;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSelected(items)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly mark a node with `.selected` even if its URL is empty', () => {
|
||||||
|
component.nodes = [
|
||||||
|
{url: '', title: 'API', tooltip: 'API docs'},
|
||||||
|
{url: undefined, title: 'Features', tooltip: 'Angular features overview'},
|
||||||
|
];
|
||||||
|
fixture.detectChanges();
|
||||||
|
const items = getListItems();
|
||||||
|
|
||||||
|
component.currentNode = {url: '', view: 'Empty url', nodes: []};
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getSelected(items)).toEqual([items[0]]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { NavigationNode } from 'app/navigation/navigation.service';
|
import { CurrentNode, NavigationNode } from 'app/navigation/navigation.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'aio-top-menu',
|
selector: 'aio-top-menu',
|
||||||
template: `
|
template: `
|
||||||
<ul role="navigation">
|
<ul role="navigation">
|
||||||
<li *ngFor="let node of nodes">
|
<li *ngFor="let node of nodes" [ngClass]="{selected: node.url === currentUrl}">
|
||||||
<a class="nav-link" [href]="node.url" [title]="node.tooltip">
|
<a class="nav-link" [href]="node.url" [title]="node.tooltip">
|
||||||
<span class="nav-link-inner">{{ node.title }}</span>
|
<span class="nav-link-inner">{{ node.title }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -14,5 +14,7 @@ import { NavigationNode } from 'app/navigation/navigation.service';
|
||||||
})
|
})
|
||||||
export class TopMenuComponent {
|
export class TopMenuComponent {
|
||||||
@Input() nodes: NavigationNode[];
|
@Input() nodes: NavigationNode[];
|
||||||
|
@Input() currentNode: CurrentNode | undefined;
|
||||||
|
|
||||||
|
get currentUrl(): string | null { return this.currentNode ? this.currentNode.url : null; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,41 +149,47 @@ aio-top-menu {
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
a.nav-link {
|
a.nav-link {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.nav-link-inner {
|
.nav-link-inner {
|
||||||
padding: 8px 16px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba($white, 0.15);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba($white, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
.nav-link-inner {
|
||||||
|
background: rgba($white, 0.15);
|
||||||
|
border-radius: 1px;
|
||||||
|
box-shadow: 0 0 1px 2px $focus-outline-ondark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
.nav-link-inner {
|
||||||
|
background: rgba($white, 0.15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&.selected {
|
||||||
outline: none;
|
a.nav-link {
|
||||||
|
.nav-link-inner {
|
||||||
.nav-link-inner {
|
background: rgba($white, 0.15);
|
||||||
background: rgba($white, 0.15);
|
}
|
||||||
border-radius: 1px;
|
|
||||||
box-shadow: 0 0 1px 2px $focus-outline-ondark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.nav-link-inner {
|
|
||||||
background: rgba($white, 0.15);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SEARCH BOX
|
// SEARCH BOX
|
||||||
|
|
Loading…
Reference in New Issue