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

Commit

Permalink
feat(navbar): Add themepicker component with lazy loaded themes
Browse files Browse the repository at this point in the history
  • Loading branch information
mmalerba authored and riavalon committed Mar 30, 2017
1 parent e704d54 commit 7f6cd6c
Show file tree
Hide file tree
Showing 17 changed files with 267 additions and 4 deletions.
6 changes: 5 additions & 1 deletion angular-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
"mobile": false,
"styles": [
"main.scss",
"highlightjs/solarized-light.css"
"highlightjs/solarized-light.css",
{"input": "assets/pink-bluegrey.css", "lazy": true},
{"input": "assets/deeppurple-amber.css", "lazy": true},
{"input": "assets/indigo-pink.css", "lazy": true},
{"input": "assets/purple-green.css", "lazy": true}
],
"scripts": [],
"environmentSource": "environments/environment.ts",
Expand Down
12 changes: 11 additions & 1 deletion src/app/app-module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
Expand All @@ -18,6 +19,7 @@ import {ComponentSidenav} from './pages/component-sidenav/component-sidenav';
import {Footer} from './shared/footer/footer';
import {ComponentPageTitle} from './pages/page-title/page-title';
import {ComponentPageHeader} from './pages/component-page-header/component-page-header';
import {StyleManager} from './shared/style-manager/style-manager';


@NgModule({
Expand All @@ -31,7 +33,14 @@ import {ComponentPageHeader} from './pages/component-page-header/component-page-
GuideList,
GuideViewer,
Homepage,
Footer
Footer,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
exports: [
MaterialDocsApp,
Homepage,
],
imports: [
BrowserModule,
Expand All @@ -45,6 +54,7 @@ import {ComponentPageHeader} from './pages/component-page-header/component-page-
providers: [
Location,
ComponentPageTitle,
StyleManager,
{provide: LocationStrategy, useClass: PathLocationStrategy},
],
bootstrap: [MaterialDocsApp],
Expand Down
2 changes: 2 additions & 0 deletions src/app/shared/navbar/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
</a>
<a md-button class="docs-button" routerLink="components">Components</a>
<a md-button class="docs-button" routerLink="guides">Guides</a>
<div class="flex-spacer"></div>
<theme-chooser></theme-chooser>
<a md-button class="docs-button" href="https://github.com/angular/material2" aria-label="GitHub Repository">
<img class="docs-github-logo"
src="../../../assets/img/homepage/github-circle-white-transparent.svg"
Expand Down
5 changes: 5 additions & 0 deletions src/app/shared/navbar/navbar.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.docs-navbar {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 8px 16px;

> .mat-button {
Expand All @@ -21,3 +22,7 @@
margin: 0 7px 2px 0;
vertical-align: middle;
}

.flex-spacer {
flex-grow: 1;
}
5 changes: 3 additions & 2 deletions src/app/shared/shared-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {BrowserModule} from '@angular/platform-browser';
import {RouterModule} from '@angular/router';
import {PlunkerButton} from './plunker';
import {GuideItems} from './guide-items/guide-items';
import {ThemeChooser} from './theme-chooser/theme-chooser';


@NgModule({
Expand All @@ -18,8 +19,8 @@ import {GuideItems} from './guide-items/guide-items';
BrowserModule,
MaterialModule,
],
declarations: [DocViewer, ExampleViewer, NavBar, PlunkerButton],
exports: [DocViewer, ExampleViewer, NavBar, PlunkerButton],
declarations: [DocViewer, ExampleViewer, NavBar, PlunkerButton, ThemeChooser],
exports: [DocViewer, ExampleViewer, NavBar, PlunkerButton, ThemeChooser],
providers: [DocumentationItems, GuideItems],
entryComponents: [
ExampleViewer,
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/style-manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './style-manager';
52 changes: 52 additions & 0 deletions src/app/shared/style-manager/style-manager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {TestBed, inject} from '@angular/core/testing';
import {StyleManager} from './style-manager';


describe('StyleManager', () => {
let styleManager: StyleManager;

beforeEach(() => TestBed.configureTestingModule({
providers: [StyleManager]
}));

beforeEach(inject([StyleManager], (sm: StyleManager) => {
styleManager = sm;
}));

afterEach(() => {
let links = document.head.querySelectorAll('link');
for (let link of Array.prototype.slice.call(links)) {
if (link.className.includes('style-manager-')) {
document.head.removeChild(link);
}
}
});

it('should add stylesheet to head', () => {
styleManager.setStyle('test', 'test.css');
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);
});

it('should change existing stylesheet', () => {
styleManager.setStyle('test', 'test.css');
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);

styleManager.setStyle('test', 'new.css');
expect(styleEl.href.endsWith('new.css')).toBe(true);
});

it('should remove existing stylesheet', () => {
styleManager.setStyle('test', 'test.css');
let styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).not.toBeNull();
expect(styleEl.href.endsWith('test.css')).toBe(true);

styleManager.removeStyle('test');
styleEl = document.head.querySelector('.style-manager-test') as HTMLLinkElement;
expect(styleEl).toBeNull();
});
});
46 changes: 46 additions & 0 deletions src/app/shared/style-manager/style-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {Injectable} from '@angular/core';


/**
* Class for managing stylesheets. Stylesheets are loaded into named slots so that they can be
* removed or changed later.
*/
@Injectable()
export class StyleManager {
/**
* Set the stylesheet with the specified key.
* @param key The key for the slot to load the stylesheet into.
* @param href The url for the stylesheet.
*/
setStyle(key: string, href: string) {
this._getLinkElementForKey(key, true).setAttribute('href', href);
}

/**
* Remove the stylesheet with the specified key.
* @param key The key for the slot to clear.
*/
removeStyle(key: string) {
let el = this._getLinkElementForKey(key);
document.head.removeChild(el);
}

/**
* Gets the `<link>` element for the specified key.
* @param key The key for the slot whose element we want.
* @param create Whether to create the element if it doesn't exist.
* @returns {HTMLLinkElement} The `<link.` element.
* @private
*/
private _getLinkElementForKey(key: string, create: boolean = false): HTMLLinkElement {
let className = `style-manager-${key}`;
let linkEl = document.head.querySelector(`link[rel="stylesheet"].${className}`);
if (!linkEl && create) {
linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'stylesheet');
linkEl.classList.add(className);
document.head.appendChild(linkEl);
}
return linkEl as HTMLLinkElement;
}
}
1 change: 1 addition & 0 deletions src/app/shared/theme-chooser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './theme-chooser';
16 changes: 16 additions & 0 deletions src/app/shared/theme-chooser/theme-chooser.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<button md-icon-button [md-menu-trigger-for]="themeMenu">
<md-icon>format_color_fill</md-icon>
</button>

<md-menu class="theme-chooser-menu" #themeMenu="mdMenu" x-position="before">
<md-grid-list cols="2">
<md-grid-tile *ngFor="let theme of themes">
<div md-menu-item (click)="installTheme(theme.href)">
<div class="theme-chooser-swatch">
<div class="theme-chooser-primary" [style.background]="theme.primary"></div>
<div class="theme-chooser-accent" [style.background]="theme.accent"></div>
</div>
</div>
</md-grid-tile>
</md-grid-list>
</md-menu>
52 changes: 52 additions & 0 deletions src/app/shared/theme-chooser/theme-chooser.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
$theme-chooser-menu-padding: 8px;
$theme-chooser-grid-cell-size: 48px;
$theme-chooser-grid-cells-per-row: 2;
$theme-chooser-swatch-size: 36px;
$theme-chooser-accent-stripe-size: 6px;


.theme-chooser-menu {
.md-menu-content {
padding: $theme-chooser-menu-padding;
}

[md-menu-item] {
flex: 0 0 auto;
padding: 0;
overflow: hidden;
}

.theme-chooser-swatch {
position: relative;
width: $theme-chooser-swatch-size;
height: $theme-chooser-swatch-size;
margin: ($theme-chooser-grid-cell-size - $theme-chooser-swatch-size) / 2;
border-radius: 50%;
overflow: hidden;
transform: rotate(-45deg);

&::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 50%;
}
}

.theme-chooser-primary {
width: 100%;
height: 100%;
}

.theme-chooser-accent {
position: absolute;
bottom: $theme-chooser-accent-stripe-size;
width: 100%;
height: $theme-chooser-accent-stripe-size;
}
}
29 changes: 29 additions & 0 deletions src/app/shared/theme-chooser/theme-chooser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {MaterialModule} from '@angular/material';
import {async, TestBed} from '@angular/core/testing';

import {ThemeChooser} from './theme-chooser';
import {StyleManager} from '../style-manager';


describe('ThemeChooser', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MaterialModule],
declarations: [ThemeChooser],
providers: [StyleManager]
});

TestBed.compileComponents();
}));

it('should install theme based on href', () => {
const fixture = TestBed.createComponent(ThemeChooser);
const component = fixture.componentInstance;
const href = 'assets/pink-bluegrey.css';
spyOn(component._styleManager, 'setStyle');
component.installTheme(href);
expect(component._styleManager.setStyle).toHaveBeenCalled();
expect(component._styleManager.setStyle).toHaveBeenCalledWith('theme', href);
});
});

40 changes: 40 additions & 0 deletions src/app/shared/theme-chooser/theme-chooser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core';
import {StyleManager} from '../style-manager/style-manager';

@Component({
selector: 'theme-chooser',
templateUrl: 'theme-chooser.html',
styleUrls: ['theme-chooser.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class ThemeChooser {
themes = [
{
primary: '#673AB7',
accent: '#FFC107',
href: 'assets/deeppurple-amber.css'
},
{
primary: '#3F51B5',
accent: '#E91E63',
href: 'assets/indigo-pink.css'
},
{
primary: '#E91E63',
accent: '#607D8B',
href: 'assets/pink-bluegrey.css'
},
{
primary: '#9C27B0',
accent: '#4CAF50',
href: 'assets/purple-green.css'
},
];

constructor(private _styleManager : StyleManager) {}

installTheme(href: string) {
this._styleManager.setStyle('theme', href);
}
}
1 change: 1 addition & 0 deletions src/assets/deeppurple-amber.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/assets/indigo-pink.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/assets/pink-bluegrey.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/assets/purple-green.css

Large diffs are not rendered by default.

0 comments on commit 7f6cd6c

Please sign in to comment.