<!DOCTYPE html>
<html>
<head>
<base href="." />
<link href="https://fonts.googleapis.com/css?family=Lobster|Open+Sans:400,600" rel="stylesheet">
<script type="text/javascript" charset="utf-8">
window.window.AngularVersionForThisPlunker = 'latest'
</script>
<title>angular playground</title>
<link rel="stylesheet" href="app.css" />
<script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js/dist/zone.js"></script>
<script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
<script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import('app')
.catch(console.error.bind(console));
</script>
</head>
<body>
<dashboard>loading...</dashboard>
</body>
</html>
body {
font-family: 'Open Sans', sans-serif;
}
var angularVersion;
if(window.AngularVersionForThisPlunker === 'latest'){
angularVersion = ''; //picks up latest
}
else {
angularVersion = '@' + window.AngularVersionForThisPlunker;
}
System.config({
//use typescript for compilation
transpiler: 'typescript',
//typescript compiler options
typescriptOptions: {
emitDecoratorMetadata: true
},
paths: {
'npm:': 'https://unpkg.com/'
},
//map tells the System loader where to look for things
map: {
'app': './src',
'@angular/core': 'npm:@angular/core'+ angularVersion + '/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common' + angularVersion + '/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler' + angularVersion + '/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser' + angularVersion + '/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic' + angularVersion + '/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http' + angularVersion + '/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router' + angularVersion +'/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms' + angularVersion + '/bundles/forms.umd.js',
'@angular/animations': 'npm:@angular/animations' + angularVersion + '/bundles/animations.umd.js',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser' + angularVersion + '/bundles/platform-browser-animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations' + angularVersion + '/bundles/animations-browser.umd.js',
'@angular/core/testing': 'npm:@angular/core' + angularVersion + '/bundles/core-testing.umd.js',
'@angular/common/testing': 'npm:@angular/common' + angularVersion + '/bundles/common-testing.umd.js',
'@angular/compiler/testing': 'npm:@angular/compiler' + angularVersion + '/bundles/compiler-testing.umd.js',
'@angular/platform-browser/testing': 'npm:@angular/platform-browser' + angularVersion + '/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic' + angularVersion + '/bundles/platform-browser-dynamic-testing.umd.js',
'@angular/http/testing': 'npm:@angular/http' + angularVersion + '/bundles/http-testing.umd.js',
'@angular/router/testing': 'npm:@angular/router' + angularVersion + '/bundles/router-testing.umd.js',
'tslib': 'npm:tslib@1.6.1',
'rxjs': 'npm:rxjs',
'typescript': 'npm:typescript@2.2.1/lib/typescript.js'
},
//packages defines our app package
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
}
}
});
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {DashboardModule} from './dashboard';
platformBrowserDynamic().bootstrapModule(DashboardModule)
//our root app component
import {Component, NgModule, VERSION} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import { style, animate, group, animateChild, transition, state, trigger, query, stagger } from '@angular/animations';
import {ProfileDetailsComponent} from './profile-details';
import {ProfileStatsComponent} from './profile-stats';
export interface User {
name: string;
}
@Component({
selector: 'dashboard',
styleUrls: ['src/dashboard.css'],
template: `
<div>
<header>
<span class="primary-font title justify-start">Dashboard</span>
<div class="image-container" (click)="toggleProfileDetails()" data-tooltip="Profile" >
<img class="profile-button" src="https://api.adorable.io/avatars/60/me@you.com.png" />
</div>
</header>
<profile-details [user]="user" *ngIf="showProfileDetails"></profile-details>
</div>
`
})
export class DashboardComponent {
showProfileDetails: boolean = false;
user: User;
constructor() {
this.user = {
name: 'Dominic Elm',
title: 'Frontend Developer',
location: 'Germany',
hearts: 235,
views: 23500
};
}
toggleProfileDetails() {
this.showProfileDetails = !this.showProfileDetails;
}
}
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule
],
declarations: [ DashboardComponent, ProfileDetailsComponent, ProfileStatsComponent ],
bootstrap: [ DashboardComponent ]
})
export class DashboardModule {}
.title {
color: white;
font-weight: bold;
margin-right: auto;
}
header {
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
background-color: #272727;
padding: 0.5em 1em;
}
.profile-button {
width: 40px;
cursor: pointer;
border-radius: 50%;
}
.image-container {
display: flex;
align-items: center;
}
.image-container:hover:before {
opacity: 1;
transform: translateX(-10px);
}
.image-container:before {
display: inline-block;
content: attr(data-tooltip);
color: white;
opacity: 0;
transform: translateX(-30px);
transition: opacity .2s ease, transform .2s ease;
}
import { Component, Input, HostBinding } from '@angular/core';
import { style, animate, animation, animateChild, useAnimation, group, sequence, transition, state, trigger, query, stagger } from '@angular/animations';
import { User } from './dashboard.ts';
import { fadeAnimation } from './animations.ts';
@Component({
selector: 'profile-details',
styleUrls: ['src/profile-details.css'],
animations: [
trigger('profileAnimation', [
transition(':enter', group([
query('.wrapper', style({ opacity: 0, transform: 'rotateX(-90deg) translateY(150px) translateZ(50px)' })),
query('.profile-image-border, .profile-image', style({ transform: 'scale(0)' })),
query('.username, .username-title', style({ opacity: 0, transform: 'translateX(50px)' })),
query('main', style({ transform: 'translateY(100%)' })),
query('.wrapper', group([
useAnimation(fadeAnimation, {
params: {
duration: '150ms',
to: 1
}
}),
animate('300ms cubic-bezier(0.68, 0, 0.68, 0.19)', style({ transform: 'matrix(1, 0, 0, 1, 0, 0)' }))
])),
query('.profile-image-border', [
animate('200ms 250ms ease-out', style('*'))
]),
query('.profile-image', [
animate('200ms 300ms ease-out', style('*'))
]),
query('.username, .username-title', stagger('100ms', [
animate('200ms 250ms ease-out', style('*'))
])),
query('main', [
animate('200ms 250ms ease-out', style('*'))
])
query('profile-stats', animateChild())
]))
])
],
template: `
<div class="wrapper">
<header>
<div class="profile-image-wrapper">
<div class="profile-image-border"></div>
<img class="profile-image" src="https://api.adorable.io/avatars/90/me@you.com.png" />
</div>
<div class="profile-header-content">
<span class="username">{{ user.name }}</span>
<span class="username-title">{{ user.title }}</span>
</div>
</header>
<main>
<profile-stats [user]="user"></profile-stats>
</main>
</div>
`,
})
export class ProfileDetailsComponent {
@Input() user: User;
@HostBinding('@profileAnimation')
public animateProfile = true;
constructor() {}
}
:host {
position: fixed;
perspective: 500px;
top: calc(50% + 30px);
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 400px;
}
.wrapper {
box-shadow: 0px 0 20px 0px rgba(0,0,0,0.2);
transform-origin: top center;
background: white;
height: 320px;
display: flex;
flex-direction: column;
overflow: hidden;
}
header {
padding: 2em;
display: flex;
justify-content: center;
z-index: 0;
}
main {
background: #f8f8f8;
flex-grow: 1;
}
.profile-image {
border-radius: 50%;
}
.profile-image-wrapper {
position: relative;
display: inline-block;
height: 90px;
width: auto;
}
.profile-image-border {
background: #e3e3e3;
position: absolute;
top: -4px;
right: -4px;
bottom: -4px;
left: -4px;
border-radius: 50%;
z-index: -1;
}
.profile-header-content {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 1.5em;
color: #c8c8c8;
}
.profile-header-content > span {
position: relative;
}
.username {
font-weight: 600;
font-size: 1.3em;
}
import { Component, Input, HostBinding } from '@angular/core';
import { style, animate, animation, animateChild, useAnimation, group, sequence, transition, state, trigger, query, stagger } from '@angular/animations';
import { User } from './dashboard.ts';
@Component({
selector: 'profile-stats',
styleUrls: ['src/profile-stats.css'],
animations: [
trigger('statsAnimation', [
transition('* => *', group([
query('.stats-icon', style({ opacity: 0, transform: 'scale(0.8) translateY(10px)' })),
query('.stats-text', style({ opacity: 0 })),
query('.stats-icon', stagger('100ms', [
animate('200ms 250ms ease-out', style('*'))
])),
query('.stats-text', stagger('100ms', [
animate('200ms 250ms ease-out', style('*'))
])),
])
])
],
template: `
<ul class="stats">
<li class="stats-item">
<span class="stats-icon icon-eye"></span>
<span class="stats-text">{{ user.views }}</span>
</li>
<li class="stats-item">
<span class="stats-icon icon-location"></span>
<span class="stats-text">{{ user.location }}</span>
</li>
<li class="stats-item">
<span class="stats-icon icon-heart"></span>
<span class="stats-text">{{ user.hearts }}</span>
</li>
</ul>
`,
})
export class ProfileStatsComponent {
@Input() user: User;
@HostBinding('@statsAnimation')
public animateProfile = true;
constructor() {}
}
.stats {
list-style: none;
display: flex;
justify-content: space-around;
padding: 2em;
}
.stats-item {
display: flex;
flex-direction: column;
align-items: center;
color: #ababab;
}
.stats-icon {
position: relative;
margin-bottom: 1em;
height: 32px;
width: 32px;
}
.icon-eye {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACyElEQVRYR+1WwXUaMRCdUQN2KohdgenAuANcgdGs78EVhFRg585KUEFwBbE7IBXEriCmAU3eJ9I+7bK74HDwhb3wQMvMn/9n/ojpgx/+4Px0BHBk4N0MeO9PVfWCmQdEdBqb+E1VV8z8y1r79p7G3gsAkhLRjaqOiGi4I8ETMy+JaLEPmF4ASBxCuGdmJN5Uq6prZl6p6lMOhJmHqjpg5pOMlaUx5q4PSCcA7/0XVZ1mNC9CCMvb21tU1/nMZrORMQaAbxIQIpqIyKLtT1sAvPcDVb3PqH5k5om19gUB2lghIvRArVrv/VksIAGBNDbFSWBqAGLyn6haVV+NMWNrbUV1fp7kwGdOOzNfWWtXKYH3fhhCmDPzZwBtnlcAyrIcM7OPf1yIyDinLHb/7yjJMzMDXMUKGCCiy5jkvKm7c24eZQFbd0VR4Ps/K86Tx8OHpl5ZgGcRaZ0E5xzYAojvIjJpxijLcsLMkBfNbAGCG8k3P7Y1S1mWoO+EmVHdpvLmE3UHSy8ict4Rp2IaINg59ydq3pkcgZxzihEsiiKZT+skJKAi0jlhWdFvYGBTWaKka74AAPqKyKe+MXwPADQ6x7n9ketyqAQIXBTF2S4JQgjXbU3YKkXWhE8ictUWfFcTRnPbNHjVhClQoxnnTQuNBvQSZ75mKrH5MMKw47Ux5iwfw8y8NqOdy71lRCEEBIefIxmcq2ZE2Tlipc1X7QljzLBpRKoKcHBGgKudt1kxXDCZCtCCjW8NK8aMY5zgbnjnlYjw3kOqHKyEEL7CsCLLMK9R06D6RgVJpslmAQRrVkQe+6bAOXepqgCX6F4jTlEUW+ZWOWFXwKgdgMDBqjVLRLh81NaxqsIdq0tKXNsAPf2vddzcAyEEVITKLvoYIKLnuBnnB19IOuwWDTcIIdSuZMYYbMDVPknzuHtdyXZUfNDxEcCRgQ9n4C9KZe8Lt5Mt/QAAAABJRU5ErkJggg==");
background-repeat: no-repeat;
}
.icon-location {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADJElEQVRYR61XwVEcMRDUiACACAwRGCIAIuAcAUjizxEBEAH4j6QjAkMEPiLgHIFxBOAA2HbNlnSl25K0OvD+dksatXp6emZJrPF477cAHAOYENGOEGIvbF8AeCGiByJ6VEq9tYal1oXW2isiOhdCbIU9T4O9B+H9jYhulVLXLbFHAYRb/+TbAvgDYLqxsTEf3pLXvb+/H/LhRPRFCLEgoqMxNqoABodfG2OuWm4V2LpsAVEF4JybCyEOAChjzGx4uPf+EAC01sN0CGvtKRF5IcRca31UAl4EEAMAWLm5936n67pLIjpNgwKYSSmvlVIv8XtkonQBXlcD0AcyxrDa+8d7zzpgPbAQHwEs+iBEXA3HQggWIOe9/86PtZarg1nazbGQBXB3dzeRUv4AcGGMuQ2Hcwn+BkBSyolSitOzfDgdXddxGYKIdqP4rLVTIrrpuu7b2dnZwxBEFoBzjvN9EgL1TDjnGMh5KRCvSYAv08YpY+BCiO9a62krgDmAPWNMrPmeymFKcpTmKLfWcmoWWuvDZgC8MN3gnAPnXWs9qZVirByt9ZLd8G0lXoxRSgHnd1NrvR8XBgBPuVukgHIAAisv6zDAYjnO3OJrKrAhE8G4XodM1djLMhA9IBVc4gszY4zKpcE51wNP6z4RZtbMsgAS5d5rrZeGkxzwIKW8iKYTzOmGiFgfKzpJKmo71xeKRhRyuUJ5oJjL8SQwENturJZ7IprGg8J6LsFfJe2sbcXBlLgHMDPRJdntZkNzilZc845qMxqz0Vo5Rhse844xAL2N1ppJCUQi2qz4qj6QBv0oC63OOToRldpyjf6WNtzMQNJSN2smFANG5QP4m7byEuBRBkpdrpJ7Hl4va8pP9zYBCO24H8/SFp2x4th6R3vGWikItc/T0HNtxouNiIj206moppdmBgILxaEken5p8PiUBlKBdV3HrvcabtlbcRDeM4BtKeXO2L/AhzQQNyXj9nLEahnX/gsDyXASBckDCw+rPCk3C+9TDATK+d+Af7240wkAu1LKvfSfYKxPrF0Fw4Bx3A4AluN768GfBpB4Q3bYbAXyD+fiQj/I0Pj/AAAAAElFTkSuQmCC");
background-repeat: no-repeat;
}
.icon-heart {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACuUlEQVRYR8VX7XETMRDdtQuIqYC4AkwFmApIB7GkAogrIFQAKUCS04GpgKQCnAowHTgF+JZ5N9KN7nKf9kE8kx+Jtbtvn1bvbZhe+cN19Z1zn0TkipkXRISfnYjsmXmrtb5vw+ycuw6xl0nsLsT+qMaWAHjvFyLyjYiWLUWQTCmldumZEOtD0abwB2Zep7EFgJDgJxHNQvR9lmXb6XR6OB6Ps8lkchU6uyCiAzN/jInSWBF5RrfVWCK6DnlLsTkA7/1MRH6juIg8oZhSal9tw3t/icTM/C6CwBkRyYF3xC6yLNsksXOl1CEH4JxDgqWI/JlMJgt80cQhwGZZBipzEOFcLL7sEYsrfEtE91rrFaOr0D0FWh+6HkYFBBgAa63FY07vPRpFw6g3Z+fcdyL6TESPWuu24Svhwr2DCfwxFC8NZcdLQdwHIroDgPwXEVkbYwDmn3+stTfMjNf2CAAS6MBUd9I/BrrSNbw6AGst3uVF3wEck4FcM5IZ+GqMuR2jQFcOa+0tM3/JZyD5Za+1nncFj/G9cw6ih+e/LunA/3gJyQuADryJShi1oKTTY3RbY1jRb+601jeFGVlrIZGFxlfd7lwwFcN6MsbA5il1w5LGQybbdH0IoCD3vxLDKmT7xT4QjAaWC0YgTo3G1AdEcFrQjl3jOZhd4bQvNqKo8dCGc0HUFEfnJc+oXcnGANGneGkGapaPwjZFZGOMUX0oj2estZ6ZV8Fn3jcNdS0DSZIVM2PPg+f3BpEWFxFljNk0gW8FgCBr7SAQQ4q3XkGKuAKi0TMSWQdjrZ3H/J0MxIPOOdCYb7Z1yVOQ2HSgcn1mpjcAJGsCUSmeL5t9ive+gjRZFUSY8nxQ46bbt/hJAAITcaks1vKhS+3gGUi7Omctr7IzaAaqIERkG64A/0md5BknAxhyz21n/wLMOAqHi8kHLQAAAABJRU5ErkJggg==');
background-repeat: no-repeat;
transform: scale(0.8);
}
import { style, animate, animation, animateChild, useAnimation, group, sequence, transition, state, trigger, query, stagger } from '@angular/animations';
export const fadeAnimation = animation([
animate('{{ duration }}', style({ opacity: '{{ to }}' }))
], { params: { duration: '1s', to: 1 }});