mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(list): add list component
This commit is contained in:
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 |
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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]);
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()}}
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user