Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.

Commit 7f6cd6c

Browse files
mmalerbariavalon
authored andcommitted
feat(navbar): Add themepicker component with lazy loaded themes
1 parent e704d54 commit 7f6cd6c

17 files changed

+267
-4
lines changed

angular-cli.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
"mobile": false,
2020
"styles": [
2121
"main.scss",
22-
"highlightjs/solarized-light.css"
22+
"highlightjs/solarized-light.css",
23+
{"input": "assets/pink-bluegrey.css", "lazy": true},
24+
{"input": "assets/deeppurple-amber.css", "lazy": true},
25+
{"input": "assets/indigo-pink.css", "lazy": true},
26+
{"input": "assets/purple-green.css", "lazy": true}
2327
],
2428
"scripts": [],
2529
"environmentSource": "environments/environment.ts",

src/app/app-module.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
12
import {BrowserModule} from '@angular/platform-browser';
23
import {NgModule} from '@angular/core';
34
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
@@ -18,6 +19,7 @@ import {ComponentSidenav} from './pages/component-sidenav/component-sidenav';
1819
import {Footer} from './shared/footer/footer';
1920
import {ComponentPageTitle} from './pages/page-title/page-title';
2021
import {ComponentPageHeader} from './pages/component-page-header/component-page-header';
22+
import {StyleManager} from './shared/style-manager/style-manager';
2123

2224

2325
@NgModule({
@@ -31,7 +33,14 @@ import {ComponentPageHeader} from './pages/component-page-header/component-page-
3133
GuideList,
3234
GuideViewer,
3335
Homepage,
34-
Footer
36+
Footer,
37+
],
38+
schemas: [
39+
CUSTOM_ELEMENTS_SCHEMA,
40+
],
41+
exports: [
42+
MaterialDocsApp,
43+
Homepage,
3544
],
3645
imports: [
3746
BrowserModule,
@@ -45,6 +54,7 @@ import {ComponentPageHeader} from './pages/component-page-header/component-page-
4554
providers: [
4655
Location,
4756
ComponentPageTitle,
57+
StyleManager,
4858
{provide: LocationStrategy, useClass: PathLocationStrategy},
4959
],
5060
bootstrap: [MaterialDocsApp],

src/app/shared/navbar/navbar.html

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
</a>
99
<a md-button class="docs-button" routerLink="components">Components</a>
1010
<a md-button class="docs-button" routerLink="guides">Guides</a>
11+
<div class="flex-spacer"></div>
12+
<theme-chooser></theme-chooser>
1113
<a md-button class="docs-button" href="https://github.com/angular/material2" aria-label="GitHub Repository">
1214
<img class="docs-github-logo"
1315
src="../../../assets/img/homepage/github-circle-white-transparent.svg"

src/app/shared/navbar/navbar.scss

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.docs-navbar {
22
display: flex;
33
flex-wrap: wrap;
4+
align-items: center;
45
padding: 8px 16px;
56

67
> .mat-button {
@@ -21,3 +22,7 @@
2122
margin: 0 7px 2px 0;
2223
vertical-align: middle;
2324
}
25+
26+
.flex-spacer {
27+
flex-grow: 1;
28+
}

src/app/shared/shared-module.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {BrowserModule} from '@angular/platform-browser';
99
import {RouterModule} from '@angular/router';
1010
import {PlunkerButton} from './plunker';
1111
import {GuideItems} from './guide-items/guide-items';
12+
import {ThemeChooser} from './theme-chooser/theme-chooser';
1213

1314

1415
@NgModule({
@@ -18,8 +19,8 @@ import {GuideItems} from './guide-items/guide-items';
1819
BrowserModule,
1920
MaterialModule,
2021
],
21-
declarations: [DocViewer, ExampleViewer, NavBar, PlunkerButton],
22-
exports: [DocViewer, ExampleViewer, NavBar, PlunkerButton],
22+
declarations: [DocViewer, ExampleViewer, NavBar, PlunkerButton, ThemeChooser],
23+
exports: [DocViewer, ExampleViewer, NavBar, PlunkerButton, ThemeChooser],
2324
providers: [DocumentationItems, GuideItems],
2425
entryComponents: [
2526
ExampleViewer,

src/app/shared/style-manager/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './style-manager';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {TestBed, inject} from '@angular/core/testing';
2+
import {StyleManager} from './style-manager';
3+
4+
5+
describe('StyleManager', () => {
6+
let styleManager: StyleManager;
7+
8+
beforeEach(() => TestBed.configureTestingModule({
9+
providers: [StyleManager]
10+
}));
11+
12+
beforeEach(inject([StyleManager], (sm: StyleManager) => {
13+
styleManager = sm;
14+
}));
15+
16+
afterEach(() => {
17+
let links = document.head.querySelectorAll('link');
18+
for (let link of Array.prototype.slice.call(links)) {
19+
if (link.className.includes('style-manager-')) {
20+
document.head.removeChild(link);
21+
}
22+
}
23+
});
24+
25+
it('should add stylesheet to head', () => {
26+
styleManager.setStyle('test', 'test.css');
27+
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
28+
expect(styleEl).not.toBeNull();
29+
expect(styleEl.href.endsWith('test.css')).toBe(true);
30+
});
31+
32+
it('should change existing stylesheet', () => {
33+
styleManager.setStyle('test', 'test.css');
34+
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
35+
expect(styleEl).not.toBeNull();
36+
expect(styleEl.href.endsWith('test.css')).toBe(true);
37+
38+
styleManager.setStyle('test', 'new.css');
39+
expect(styleEl.href.endsWith('new.css')).toBe(true);
40+
});
41+
42+
it('should remove existing stylesheet', () => {
43+
styleManager.setStyle('test', 'test.css');
44+
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
45+
expect(styleEl).not.toBeNull();
46+
expect(styleEl.href.endsWith('test.css')).toBe(true);
47+
48+
styleManager.removeStyle('test');
49+
styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
50+
expect(styleEl).toBeNull();
51+
});
52+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {Injectable} from '@angular/core';
2+
3+
4+
/**
5+
* Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be
6+
* removed or changed later.
7+
*/
8+
@Injectable()
9+
export class StyleManager {
10+
/**
11+
* Set the stylesheet with the specified key.
12+
* @param key The key for the slot to load the stylesheet into.
13+
* @param href The url for the stylesheet.
14+
*/
15+
setStyle(key: string, href: string) {
16+
this._getLinkElementForKey(key, true).setAttribute('href', href);
17+
}
18+
19+
/**
20+
* Remove the stylesheet with the specified key.
21+
* @param key The key for the slot to clear.
22+
*/
23+
removeStyle(key: string) {
24+
let el = this._getLinkElementForKey(key);
25+
document.head.removeChild(el);
26+
}
27+
28+
/**
29+
* Gets the `<link>` element for the specified key.
30+
* @param key The key for the slot whose element we want.
31+
* @param create Whether to create the element if it doesn't exist.
32+
* @returns {HTMLLinkElement} The `<link.` element.
33+
* @private
34+
*/
35+
private _getLinkElementForKey(key: string, create: boolean = false): HTMLLinkElement {
36+
let className = `style-manager-${key}`;
37+
let linkEl = document.head.querySelector(`link[rel="stylesheet"].${className}`);
38+
if (!linkEl && create) {
39+
linkEl = document.createElement('link');
40+
linkEl.setAttribute('rel', 'stylesheet');
41+
linkEl.classList.add(className);
42+
document.head.appendChild(linkEl);
43+
}
44+
return linkEl as HTMLLinkElement;
45+
}
46+
}

src/app/shared/theme-chooser/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './theme-chooser';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<button md-icon-button [md-menu-trigger-for]="themeMenu">
2+
<md-icon>format_color_fill</md-icon>
3+
</button>
4+
5+
<md-menu class="theme-chooser-menu" #themeMenu="mdMenu" x-position="before">
6+
<md-grid-list cols="2">
7+
<md-grid-tile *ngFor="let theme of themes">
8+
<div md-menu-item (click)="installTheme(theme.href)">
9+
<div class="theme-chooser-swatch">
10+
<div class="theme-chooser-primary" [style.background]="theme.primary"></div>
11+
<div class="theme-chooser-accent" [style.background]="theme.accent"></div>
12+
</div>
13+
</div>
14+
</md-grid-tile>
15+
</md-grid-list>
16+
</md-menu>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
$theme-chooser-menu-padding: 8px;
2+
$theme-chooser-grid-cell-size: 48px;
3+
$theme-chooser-grid-cells-per-row: 2;
4+
$theme-chooser-swatch-size: 36px;
5+
$theme-chooser-accent-stripe-size: 6px;
6+
7+
8+
.theme-chooser-menu {
9+
.md-menu-content {
10+
padding: $theme-chooser-menu-padding;
11+
}
12+
13+
[md-menu-item] {
14+
flex: 0 0 auto;
15+
padding: 0;
16+
overflow: hidden;
17+
}
18+
19+
.theme-chooser-swatch {
20+
position: relative;
21+
width: $theme-chooser-swatch-size;
22+
height: $theme-chooser-swatch-size;
23+
margin: ($theme-chooser-grid-cell-size - $theme-chooser-swatch-size) / 2;
24+
border-radius: 50%;
25+
overflow: hidden;
26+
transform: rotate(-45deg);
27+
28+
&::after {
29+
content: '';
30+
position: absolute;
31+
top: 0;
32+
left: 0;
33+
width: 100%;
34+
height: 100%;
35+
box-sizing: border-box;
36+
border: 1px solid rgba(0,0,0,.2);
37+
border-radius: 50%;
38+
}
39+
}
40+
41+
.theme-chooser-primary {
42+
width: 100%;
43+
height: 100%;
44+
}
45+
46+
.theme-chooser-accent {
47+
position: absolute;
48+
bottom: $theme-chooser-accent-stripe-size;
49+
width: 100%;
50+
height: $theme-chooser-accent-stripe-size;
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {MaterialModule} from '@angular/material';
2+
import {async, TestBed} from '@angular/core/testing';
3+
4+
import {ThemeChooser} from './theme-chooser';
5+
import {StyleManager} from '../style-manager';
6+
7+
8+
describe('ThemeChooser', () => {
9+
beforeEach(async(() => {
10+
TestBed.configureTestingModule({
11+
imports: [MaterialModule],
12+
declarations: [ThemeChooser],
13+
providers: [StyleManager]
14+
});
15+
16+
TestBed.compileComponents();
17+
}));
18+
19+
it('should install theme based on href', () => {
20+
const fixture = TestBed.createComponent(ThemeChooser);
21+
const component = fixture.componentInstance;
22+
const href = 'assets/pink-bluegrey.css';
23+
spyOn(component._styleManager, 'setStyle');
24+
component.installTheme(href);
25+
expect(component._styleManager.setStyle).toHaveBeenCalled();
26+
expect(component._styleManager.setStyle).toHaveBeenCalledWith('theme', href);
27+
});
28+
});
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core';
2+
import {StyleManager} from '../style-manager/style-manager';
3+
4+
@Component({
5+
selector: 'theme-chooser',
6+
templateUrl: 'theme-chooser.html',
7+
styleUrls: ['theme-chooser.scss'],
8+
changeDetection: ChangeDetectionStrategy.OnPush,
9+
encapsulation: ViewEncapsulation.None,
10+
})
11+
export class ThemeChooser {
12+
themes = [
13+
{
14+
primary: '#673AB7',
15+
accent: '#FFC107',
16+
href: 'assets/deeppurple-amber.css'
17+
},
18+
{
19+
primary: '#3F51B5',
20+
accent: '#E91E63',
21+
href: 'assets/indigo-pink.css'
22+
},
23+
{
24+
primary: '#E91E63',
25+
accent: '#607D8B',
26+
href: 'assets/pink-bluegrey.css'
27+
},
28+
{
29+
primary: '#9C27B0',
30+
accent: '#4CAF50',
31+
href: 'assets/purple-green.css'
32+
},
33+
];
34+
35+
constructor(private _styleManager : StyleManager) {}
36+
37+
installTheme(href: string) {
38+
this._styleManager.setStyle('theme', href);
39+
}
40+
}

src/assets/deeppurple-amber.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/indigo-pink.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/pink-bluegrey.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/purple-green.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)