feat(list): add list component

This commit is contained in:
Kara Erickson
2016-03-21 13:27:36 -07:00
parent a61e2e90eb
commit fb71eb12e6
13 changed files with 553 additions and 1 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+2
View File
@@ -5,6 +5,8 @@ export LOGS_DIR=/tmp/angular-material2-build/logs
export SAUCE_USERNAME=angular-ci
export SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
export TRAVIS_JOB_NUMBER=12345
export BROWSER_PROVIDER_READY_FILE=/tmp/angular-material2-build/readyfile
mkdir -p $LOGS_DIR
rm -f $BROWSER_PROVIDER_READY_FILE
+5
View File
@@ -0,0 +1,5 @@
<div class="md-list-item">
<ng-content select="[md-list-avatar]"></ng-content>
<div class="md-list-text"><ng-content select="[md-line]"></ng-content></div>
<ng-content></ng-content>
</div>
+138
View File
@@ -0,0 +1,138 @@
@import "variables";
$md-list-side-padding: 16px;
$md-list-avatar-size: 40px;
/* Normal list variables */
$md-list-top-padding: 8px;
$md-list-font-size: 16px;
$md-list-secondary-font: 14px;
// height for single-line lists
$md-list-base-height: 48px;
// height for single-line lists with avatars
$md-list-avatar-height: 56px;
// spec requires two- and three-line lists be taller
$md-list-two-line-height: 72px;
$md-list-three-line-height: 88px;
/* Dense list variables */
$md-dense-top-padding: 4px;
$md-dense-font-size: 13px;
$md-dense-base-height: 40px;
$md-dense-avatar-height: 48px;
$md-dense-two-line-height: 60px;
$md-dense-three-line-height: 76px;
/*
This mixin provides all list-item styles, changing font size and height
based on whether the list is in "dense" mode.
*/
@mixin md-list-item-base($font-size, $base-height, $avatar-height,
$two-line-height, $three-line-height) {
.md-list-item {
display: flex;
flex-direction: row;
align-items: center;
font-family: $md-font-family;
box-sizing: border-box;
font-size: $font-size;
height: $base-height;
padding: 0 $md-list-side-padding;
}
&.md-list-avatar .md-list-item {
height: $avatar-height;
}
&.md-2-line .md-list-item {
height: $two-line-height;
}
&.md-3-line .md-list-item {
height: $three-line-height;
}
.md-list-text {
display: flex;
flex-direction: column;
width: 100%;
padding: 0 $md-list-side-padding;
&:first-child {
padding: 0;
}
&:empty {
display: none;
}
& > * {
margin: 0;
padding: 0;
font-weight: normal;
font-size: inherit;
}
}
[md-list-avatar] {
width: $md-list-avatar-size;
height: $md-list-avatar-size;
}
}
/*
This mixin provides all md-line styles, changing secondary font size
based on whether the list is in "dense" mode.
*/
@mixin md-line-base($secondary-font-size) {
[md-line] {
display: block;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
// all lines but the top line should have smaller text
&:nth-child(n+2) {
font-size: $secondary-font-size;
}
}
}
md-list {
padding-top: $md-list-top-padding;
display: block;
md-list-item {
@include md-list-item-base(
$md-list-font-size,
$md-list-base-height,
$md-list-avatar-height,
$md-list-two-line-height,
$md-list-three-line-height
);
@include md-line-base($md-list-secondary-font);
}
}
md-list[dense] {
padding-top: $md-dense-top-padding;
display: block;
md-list-item {
@include md-list-item-base(
$md-dense-font-size,
$md-dense-base-height,
$md-dense-avatar-height,
$md-dense-two-line-height,
$md-dense-three-line-height
);
@include md-line-base($md-dense-font-size);
}
}
+180
View File
@@ -0,0 +1,180 @@
import {
inject,
TestComponentBuilder
} from 'angular2/testing';
import {
it,
describe,
expect,
beforeEach,
} from '../../core/facade/testing';
import {Component} from 'angular2/core';
import {By} from 'angular2/platform/browser';
import {MD_LIST_DIRECTIVES} from './list';
export function main() {
describe('MdList', () => {
let builder: TestComponentBuilder;
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
builder = tcb;
}));
it('should not apply any class to a list without lines', (done: () => void) => {
var template = `
<md-list>
<md-list-item>
Paprika
</md-list-item>
</md-list>
`;
return builder.overrideTemplate(TestList, template)
.createAsync(TestList).then((fixture) => {
let listItem = fixture.debugElement.query(By.css('md-list-item'));
fixture.detectChanges();
expect(listItem.nativeElement.className).toBe('');
done();
});
});
it('should apply md-2-line class to lists with two lines', (done: () => void) => {
var template = `
<md-list>
<md-list-item *ngFor="#item of items">
<img src="">
<h3 md-line>{{item.name}}</h3>
<p md-line>{{item.description}}</p>
</md-list-item>
</md-list>
`;
return builder.overrideTemplate(TestList, template)
.createAsync(TestList).then((fixture) => {
fixture.detectChanges();
let listItems = fixture.debugElement.children[0].queryAll(By.css('md-list-item'));
expect(listItems[0].nativeElement.className).toBe('md-2-line');
expect(listItems[1].nativeElement.className).toBe('md-2-line');
done();
});
});
it('should apply md-3-line class to lists with three lines', (done: () => void) => {
var template = `
<md-list>
<md-list-item *ngFor="#item of items">
<h3 md-line>{{item.name}}</h3>
<p md-line>{{item.description}}</p>
<p md-line>Some other text</p>
</md-list-item>
</md-list>
`;
return builder.overrideTemplate(TestList, template)
.createAsync(TestList).then((fixture) => {
fixture.detectChanges();
let listItems = fixture.debugElement.children[0].queryAll(By.css('md-list-item'));
expect(listItems[0].nativeElement.className).toBe('md-3-line');
expect(listItems[1].nativeElement.className).toBe('md-3-line');
done();
});
});
it('should apply md-list-avatar class to list items with avatars', (done: () => void) => {
var template = `
<md-list>
<md-list-item>
<img src="" md-list-avatar>
Paprika
</md-list-item>
<md-list-item>
Pepper
</md-list-item>
</md-list>
`;
return builder.overrideTemplate(TestList, template)
.createAsync(TestList).then((fixture) => {
fixture.detectChanges();
let listItems = fixture.debugElement.children[0].queryAll(By.css('md-list-item'));
expect(listItems[0].nativeElement.className).toBe('md-list-avatar');
expect(listItems[1].nativeElement.className).toBe('');
done();
});
});
it('should not clear custom classes provided by user', (done: () => void) => {
var template = `
<md-list>
<md-list-item class="test-class" *ngFor="#item of items">
<h3 md-line>{{item.name}}</h3>
<p md-line>{{item.description}}</p>
</md-list-item>
</md-list>
`;
return builder.overrideTemplate(TestList, template)
.createAsync(TestList).then((fixture) => {
fixture.detectChanges();
let listItems = fixture.debugElement.children[0].queryAll(By.css('md-list-item'));
expect(listItems[0].nativeElement.classList.contains('test-class')).toBe(true);
done();
});
});
it('should update classes if number of lines change', (done: () => void) => {
var template = `
<md-list>
<md-list-item *ngFor="#item of items">
<h3 md-line>{{item.name}}</h3>
<p md-line>{{item.description}}</p>
<p md-line *ngIf="showThirdLine">Some other text</p>
</md-list-item>
</md-list>
`;
return builder.overrideTemplate(TestList, template)
.createAsync(TestList).then((fixture) => {
fixture.debugElement.componentInstance.showThirdLine = false;
fixture.detectChanges();
let listItem = fixture.debugElement.children[0].query(By.css('md-list-item'));
expect(listItem.nativeElement.className).toBe('md-2-line');
fixture.debugElement.componentInstance.showThirdLine = true;
fixture.detectChanges();
setTimeout(() => {
expect(listItem.nativeElement.className).toBe('md-3-line');
done();
});
});
});
it('should add aria roles properly', (done: () => void) => {
var template = `
<md-list>
<md-list-item *ngFor="#item of items">
{{item.name}}
</md-list-item>
</md-list>
`;
return builder.overrideTemplate(TestList, template)
.createAsync(TestList).then((fixture) => {
fixture.detectChanges();
let list = fixture.debugElement.children[0];
let listItem = fixture.debugElement.children[0].query(By.css('md-list-item'));
expect(list.nativeElement.getAttribute('role')).toBe('list');
expect(listItem.nativeElement.getAttribute('role')).toBe('listitem');
done();
});
});
});
}
@Component({
selector: 'test-list',
template: ``,
directives: [MD_LIST_DIRECTIVES]
})
class TestList {
items: any[] = [
{'name': 'Paprika', 'description': 'A seasoning'},
{'name': 'Pepper', 'description': 'Another seasoning'}
];
showThirdLine: boolean = false;
}
+63
View File
@@ -0,0 +1,63 @@
import {Component, ViewEncapsulation, ContentChildren, ContentChild, QueryList, Directive,
ElementRef, Renderer, AfterContentInit} from 'angular2/core';
import {CONST_EXPR} from 'angular2/src/facade/lang';
@Component({
selector: 'md-list',
host: {'role': 'list'},
template: '<ng-content></ng-content>',
styleUrls: ['./components/list/list.css'],
encapsulation: ViewEncapsulation.None
})
export class MdList {}
/* Need directive for a ContentChildren query in list-item */
@Directive({ selector: '[md-line]' })
export class MdLine {}
/* Need directive for a ContentChild query in list-item */
@Directive({ selector: '[md-list-avatar]' })
export class MdListAvatar {}
@Component({
selector: 'md-list-item',
host: {'role': 'listitem'},
templateUrl: './components/list/list-item.html',
encapsulation: ViewEncapsulation.None
})
export class MdListItem implements AfterContentInit {
@ContentChildren(MdLine) _lines: QueryList<MdLine>;
ngAfterContentInit() {
this._setLineClass(this._lines.length);
this._lines.changes.subscribe(() => {
this._setLineClass(this._lines.length);
});
}
@ContentChild(MdListAvatar)
set _hasAvatar(avatar: MdListAvatar) {
this._setClass('md-list-avatar', avatar != null);
}
constructor(private _renderer: Renderer, private _element: ElementRef) {}
_setLineClass(count: number): void {
this._resetClasses();
if (count === 2 || count === 3) {
this._setClass(`md-${count}-line`, true);
}
}
_resetClasses(): void {
this._setClass('md-2-line', false);
this._setClass('md-3-line', false);
}
_setClass(className: string, bool: boolean): void {
this._renderer.setElementClass(this._element.nativeElement, className, bool);
}
}
export const MD_LIST_DIRECTIVES: any[] = CONST_EXPR([MdList, MdListItem, MdLine, MdListAvatar]);
+27
View File
@@ -0,0 +1,27 @@
{
"name": "@angular2-material/list",
"version": "2.0.0-alpha.1",
"description": "Angular 2 Material list",
"main": "./list.js",
"typings": "./list.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/angular/material2.git"
},
"keywords": [
"angular",
"material",
"material design",
"components",
"list"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/angular/material2/issues"
},
"homepage": "https://github.com/angular/material2#readme",
"peerDependencies": {
"angular2": "^2.0.0-beta.8",
"@angular2-material/core": "2.0.0-alpha.1"
}
}
+1
View File
@@ -9,6 +9,7 @@
<li><a [routerLink]="['CheckboxDemo']">Checkbox demo</a></li>
<li><a [routerLink]="['ToolbarDemo']">Toolbar demo</a></li>
<li><a [routerLink]="['RadioDemo']">Radio demo</a></li>
<li><a [routerLink]="['ListDemo']">List demo</a></li>
</ul>
<button md-raised-button (click)="root.dir = (root.dir == 'rtl' ? 'ltr' : 'rtl')">
{{root.dir.toUpperCase()}}
+2 -1
View File
@@ -10,7 +10,7 @@ import {Dir} from '../core/rtl/dir';
import {MdButton} from '../components/button/button';
import {PortalDemo} from './portal/portal-demo';
import {ToolbarDemo} from './toolbar/toolbar-demo';
import {ListDemo} from './list/list-demo';
@Component({
selector: 'home',
@@ -36,5 +36,6 @@ export class Home {}
new Route({path: '/portal', name: 'PortalDemo', component: PortalDemo}),
new Route({path: '/checkbox', name: 'CheckboxDemo', component: CheckboxDemo}),
new Route({path: '/toolbar', name: 'ToolbarDemo', component: ToolbarDemo}),
new Route({path: '/list', name: 'ListDemo', component: ListDemo})
])
export class DemoApp { }
+68
View File
@@ -0,0 +1,68 @@
<h1>md-list demo</h1>
<button md-raised-button (click)="thirdLine=!thirdLine" class="demo-button">Show third line</button>
<div class="demo">
<div>
<h2>Normal lists</h2>
<md-list>
<md-list-item *ngFor="#item of items">
{{item}}
</md-list-item>
</md-list>
<md-list>
<md-list-item *ngFor="#contact of contacts">
<img md-list-avatar src="" alt="">
<h3 md-line>{{contact.name}}</h3>
<p md-line *ngIf="thirdLine">extra line</p>
<p md-line class="demo-secondary-text">{{contact.headline}}</p>
</md-list-item>
</md-list>
<md-list>
<md-list-item *ngFor="#message of messages">
<h3 md-line>{{message.from}}</h3>
<p md-line>
<span>{{message.subject}} -- </span>
<span class="demo-secondary-text">{{message.message}}</span>
</p>
</md-list-item>
</md-list>
<md-list>
<md-list-item *ngFor="#message of messages">
<h3 md-line>{{message.from}}</h3>
<p md-line> {{message.subject}} </p>
<p md-line class="demo-secondary-text">{{message.message}} </p>
</md-list-item>
</md-list>
</div>
<div>
<h2>Dense lists</h2>
<md-list dense>
<md-list-item *ngFor="#item of items">
{{item}}
</md-list-item>
</md-list>
<md-list dense>
<md-list-item *ngFor="#contact of contacts">
<img md-list-avatar src="" alt="">
<h3 md-line>{{contact.name}}</h3>
<p md-line class="demo-secondary-text">{{contact.headline}}</p>
</md-list-item>
</md-list>
<md-list dense>
<md-list-item *ngFor="#message of messages">
<h3 md-line>{{message.from}}</h3>
<p md-line> {{message.subject}} </p>
<p md-line class="demo-secondary-text">{{message.message}} </p>
</md-list-item>
</md-list>
</div>
</div>
+23
View File
@@ -0,0 +1,23 @@
.demo {
width: 350px;
display: flex;
md-list {
border: 1px dotted gray;
width: 350px;
margin: 0 20px;
}
h2 {
margin: 0 20px;
}
}
.demo-button {
margin: 20px;
}
.demo-secondary-text {
color: rgba(0, 0, 0, 0.54);
}
+44
View File
@@ -0,0 +1,44 @@
import {Component} from 'angular2/core';
import {MdButton} from '../../components/button/button';
import {MD_LIST_DIRECTIVES} from '../../components/list/list';
@Component({
selector: 'list-demo',
templateUrl: 'demo-app/list/list-demo.html',
styleUrls: ['demo-app/list/list-demo.css'],
directives: [MD_LIST_DIRECTIVES, MdButton]
})
export class ListDemo {
items: string[] = [
'Pepper',
'Salt',
'Paprika'
];
contacts: any[] = [
{name: 'Nancy', headline: 'Software engineer'},
{name: 'Mary', headline: 'TPM'},
{name: 'Bobby', headline: 'UX designer'}
];
messages: any[] = [
{
from: 'Nancy',
subject: 'Brunch?',
message: 'Did you want to go on Sunday? I was thinking that might work.'
},
{
from: 'Mary',
subject: 'Summer BBQ',
message: 'Wish I could come, but I have some prior obligations.'
},
{
from: 'Bobby',
subject: 'Oui oui',
message: 'Do you have Paris reservations for the 15th? I just booked!'
}
];
thirdLine: boolean = false;
}