feat(docs-infra): change navigation in resources page (#34756)
https://angular.io/resources needs to be sturctured to be able to navigate to all resources with improved user experience. A lone scroll bar in this page will not help the reader a great deal in exploring the resources Fixes #33526 PR Close #34756
This commit is contained in:
parent
7431206247
commit
e672b1f2ac
|
@ -1,28 +1,28 @@
|
|||
<div class="resources-container">
|
||||
<div class="l-flex--column">
|
||||
<div class="showcase" *ngFor="let category of categories">
|
||||
<header class="c-resource-header">
|
||||
<a class="h-anchor-offset" id="{{category.id}}"></a>
|
||||
<h2>{{category.title}}</h2>
|
||||
</header>
|
||||
<div class="flex-center group-buttons">
|
||||
<a *ngFor="let category of categories"
|
||||
[class.selected]="category.id == selectedCategory.id"
|
||||
class="button mat-button filter-button"
|
||||
(click)="selectCategory(category.id)"
|
||||
(keyup.enter)="selectCategory(category.id)">{{category.title}}</a>
|
||||
</div>
|
||||
<div class="l-flex--column align-items-center">
|
||||
<div class="shadow-1 showcase">
|
||||
<div *ngFor="let subCategory of selectedCategory?.subCategories">
|
||||
<a class="h-anchor-offset" id="{{subCategory.id}}"></a>
|
||||
<h3 class="subcategory-title">{{subCategory.title}}</h3>
|
||||
|
||||
<div class="shadow-1">
|
||||
<div *ngFor="let subCategory of category.subCategories">
|
||||
<a class="h-anchor-offset" id="{{subCategory.id}}"></a>
|
||||
<h3 class="subcategory-title">{{subCategory.title}}</h3>
|
||||
|
||||
<div *ngFor="let resource of subCategory.resources">
|
||||
<div class="c-resource" *ngIf="resource.rev">
|
||||
<a class="l-flex--column resource-row-link" target="_blank" [href]="resource.url">
|
||||
<div>
|
||||
<h4>{{resource.title}}</h4>
|
||||
<p class="resource-description">{{resource.desc || 'No Description'}}</p>
|
||||
</div>
|
||||
</a>
|
||||
<div *ngFor="let resource of subCategory.resources">
|
||||
<div class="c-resource" *ngIf="resource.rev">
|
||||
<a class="l-flex--column resource-row-link" target="_blank" [href]="resource.url">
|
||||
<div>
|
||||
<h4>{{resource.title}}</h4>
|
||||
<p class="resource-description">{{resource.desc || 'No Description'}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,78 +1,115 @@
|
|||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { ResourceListComponent } from './resource-list.component';
|
||||
import { ResourceService } from './resource.service';
|
||||
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { Category } from './resource.model';
|
||||
|
||||
// Testing the component class behaviors, independent of its template
|
||||
// Let e2e tests verify how it displays.
|
||||
describe('ResourceListComponent', () => {
|
||||
|
||||
let component: ResourceListComponent;
|
||||
let injector: ReflectiveInjector;
|
||||
let location: TestPlatformLocation;
|
||||
let resourceService: TestResourceService;
|
||||
let locationService: TestLocationService;
|
||||
let categories: Category[];
|
||||
|
||||
beforeEach(() => {
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
ResourceListComponent,
|
||||
{provide: PlatformLocation, useClass: TestPlatformLocation },
|
||||
{provide: ResourceService, useClass: TestResourceService }
|
||||
{provide: ResourceService, useClass: TestResourceService },
|
||||
{provide: LocationService, useClass: TestLocationService }
|
||||
]);
|
||||
|
||||
location = injector.get(PlatformLocation);
|
||||
locationService = injector.get(LocationService);
|
||||
resourceService = injector.get(ResourceService);
|
||||
categories = resourceService.testCategories;
|
||||
});
|
||||
|
||||
it('should set the location w/o leading slashes', () => {
|
||||
location.pathname = '////resources';
|
||||
const component = getComponent();
|
||||
expect(component.location).toBe('resources');
|
||||
it('should select the first category when no query string', () => {
|
||||
component = getComponent();
|
||||
expect(component.selectedCategory).toBe(categories[0]);
|
||||
});
|
||||
|
||||
it('href(id) should return the expected href', () => {
|
||||
location.pathname = '////resources';
|
||||
const component = getComponent();
|
||||
expect(component.href({id: 'foo'})).toBe('resources#foo');
|
||||
it('should select the first category when query string w/o "category" property', () => {
|
||||
locationService.searchResult = { foo: 'development' };
|
||||
component = getComponent();
|
||||
expect(component.selectedCategory).toBe(categories[0]);
|
||||
});
|
||||
|
||||
it('should set scroll position to zero when no target element', () => {
|
||||
const component = getComponent();
|
||||
component.onScroll(undefined);
|
||||
expect(component.scrollPos).toBe(0);
|
||||
it('should select the first category when query category not found', () => {
|
||||
locationService.searchResult = { category: 'foo' };
|
||||
component = getComponent();
|
||||
expect(component.selectedCategory).toBe(categories[0]);
|
||||
});
|
||||
|
||||
it('should set scroll position to element.scrollTop when that is defined', () => {
|
||||
const component = getComponent();
|
||||
component.onScroll({scrollTop: 42});
|
||||
expect(component.scrollPos).toBe(42);
|
||||
it('should select the education category when query category is "education"', () => {
|
||||
locationService.searchResult = { category: 'education' };
|
||||
component = getComponent();
|
||||
expect(component.selectedCategory).toBe(categories[1]);
|
||||
});
|
||||
|
||||
it('should set scroll position to element.body.scrollTop when that is defined', () => {
|
||||
const component = getComponent();
|
||||
component.onScroll({body: {scrollTop: 42}});
|
||||
expect(component.scrollPos).toBe(42);
|
||||
it('should select the education category when query category is "EDUCATION" (case insensitive)', () => {
|
||||
locationService.searchResult = { category: 'EDUCATION' };
|
||||
component = getComponent();
|
||||
expect(component.selectedCategory).toBe(categories[1]);
|
||||
});
|
||||
|
||||
it('should set scroll position to 0 when no target.body.scrollTop defined', () => {
|
||||
const component = getComponent();
|
||||
component.onScroll({body: {}});
|
||||
expect(component.scrollPos).toBe(0);
|
||||
it('should set the query to the "education" category when user selects "education"', () => {
|
||||
component = getComponent();
|
||||
component.selectCategory('education');
|
||||
expect(locationService.searchResult['category']).toBe('education');
|
||||
});
|
||||
|
||||
it('should set the query to the first category when user selects unknown name', () => {
|
||||
component = getComponent();
|
||||
component.selectCategory('education'); // a legit group that isn't the first
|
||||
|
||||
component.selectCategory('foo'); // not a legit group name
|
||||
expect(locationService.searchResult['category']).toBe('development');
|
||||
});
|
||||
|
||||
//// Test Helpers ////
|
||||
function getComponent(): ResourceListComponent { return injector.get(ResourceListComponent); }
|
||||
|
||||
class TestPlatformLocation {
|
||||
pathname = 'resources';
|
||||
function getComponent(): ResourceListComponent {
|
||||
const comp = injector.get(ResourceListComponent);
|
||||
comp.ngOnInit();
|
||||
return comp;
|
||||
}
|
||||
|
||||
class TestResourceService {
|
||||
categories = of(getTestData);
|
||||
testCategories = getTestData();
|
||||
categories = of(this.testCategories);
|
||||
}
|
||||
|
||||
interface SearchResult { [index: string]: string; }
|
||||
|
||||
class TestLocationService {
|
||||
searchResult: SearchResult = {};
|
||||
search = jasmine.createSpy('search').and.callFake(() => this.searchResult);
|
||||
setSearch = jasmine.createSpy('setSearch')
|
||||
.and.callFake((_label: string, result: SearchResult) => {
|
||||
this.searchResult = result;
|
||||
});
|
||||
}
|
||||
|
||||
function getTestData(): Category[] {
|
||||
return []; // Not interested in the data in these tests
|
||||
return [
|
||||
// Not interested in the sub-categories data in these tests
|
||||
{
|
||||
id: 'development',
|
||||
title: 'Development',
|
||||
order: 0,
|
||||
subCategories: []
|
||||
},
|
||||
{
|
||||
id: 'education',
|
||||
title: 'Education',
|
||||
order: 1,
|
||||
subCategories: []
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
|
||||
import { Category } from './resource.model';
|
||||
import { ResourceService } from './resource.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
|
||||
/* tslint:disable:template-accessibility-elements-content */
|
||||
@Component({
|
||||
|
@ -11,16 +12,27 @@ import { ResourceService } from './resource.service';
|
|||
export class ResourceListComponent implements OnInit {
|
||||
|
||||
categories: Category[];
|
||||
location: string;
|
||||
selectedCategory: Category;
|
||||
|
||||
constructor(
|
||||
private resourceService: ResourceService) {
|
||||
this.location = location.pathname.replace(/^\/+/, '');
|
||||
private resourceService: ResourceService,
|
||||
private locationService: LocationService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const category = this.locationService.search()['category'] || '';
|
||||
// Not using async pipe because cats appear twice in template
|
||||
// No need to unsubscribe because categories observable completes.
|
||||
this.resourceService.categories.subscribe(cats => this.categories = cats);
|
||||
this.resourceService.categories.subscribe(cats => {
|
||||
this.categories = cats;
|
||||
this.selectCategory(category);
|
||||
});
|
||||
}
|
||||
|
||||
selectCategory(id: string) {
|
||||
id = id.toLowerCase();
|
||||
this.selectedCategory =
|
||||
this.categories.find(category => category.id.toLowerCase() === id) || this.categories[0];
|
||||
this.locationService.setSearch('', {category: this.selectedCategory.id});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,6 +169,10 @@ aio-resource-list {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.align-items-center{
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c-resource-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue