feat(grid-list): add header and footer support

This commit is contained in:
Kara Erickson
2016-06-01 10:00:21 -07:00
parent 55cc197cd4
commit 43806f6aa6
12 changed files with 265 additions and 94 deletions
+36 -14
View File
@@ -1,4 +1,15 @@
// TODO(kara): Review this to see if MD spec updates are needed
@import "list-shared";
/* height of tile header or footer if it has one line */
$md-grid-list-one-line-height: 48px;
/* height of tile header or footer if it has two lines */
$md-grid-list-two-line-height: 68px;
/* side padding for text in tile headers and footers */
$md-grid-list-text-padding: 16px;
/* font styles for text in tile headers and footers */
$md-grid-list-font-size: 16px;
$md-grid-list-secondary-font: 12px;
md-grid-list {
display: block;
@@ -29,32 +40,30 @@ md-grid-tile {
// Headers & footers
md-grid-tile-header,
md-grid-tile-footer {
@include md-line-base($md-grid-list-secondary-font);
@include md-normalize-text();
display: flex;
flex-direction: row;
align-items: center;
height: 48px;
height: $md-grid-list-one-line-height;
color: #fff;
background: rgba(0, 0, 0, 0.18);
overflow: hidden;
padding: 0 $md-grid-list-text-padding;
font-size: $md-grid-list-font-size;
// Positioning
position: absolute;
left: 0;
right: 0;
h3,
h4 {
font-weight: 400;
margin: 0 0 0 16px;
&.md-2-line {
height: $md-grid-list-two-line-height;
}
}
h3 {
font-size: 14px;
}
h4 {
font-size: 12px;
}
.md-grid-list-text {
@include md-line-wrapper-base();
}
md-grid-tile-header {
@@ -64,4 +73,17 @@ md-grid-tile {
md-grid-tile-footer {
bottom: 0;
}
[md-grid-avatar] {
padding-right: $md-grid-list-text-padding;
[dir='rtl'] & {
padding-right: 0;
padding-left: $md-grid-list-text-padding;
}
&:empty {
display:none;
}
}
}
+43 -1
View File
@@ -12,7 +12,7 @@ import {Component} from '@angular/core';
import {By} from '@angular/platform-browser';
import {MD_GRID_LIST_DIRECTIVES, MdGridList} from './grid-list';
import {MdGridTile} from './grid-tile';
import {MdGridTile, MdGridTileText} from './grid-tile';
describe('MdGridList', () => {
let builder: TestComponentBuilder;
@@ -374,6 +374,48 @@ describe('MdGridList', () => {
});
});
});
it('should add not add any classes to footers without lines', () => {
var template = `
<md-grid-list cols="1">
<md-grid-tile>
<md-grid-tile-footer>
I'm a footer!
</md-grid-tile-footer>
</md-grid-tile>
</md-grid-list>
`;
return builder.overrideTemplate(TestGridList, template)
.createAsync(TestGridList).then((fixture: ComponentFixture<TestGridList>) => {
fixture.detectChanges();
let footer = fixture.debugElement.query(By.directive(MdGridTileText));
expect(footer.nativeElement.classList.contains('md-2-line')).toBe(false);
});
});
it('should add class to footers with two lines', () => {
var template = `
<md-grid-list cols="1">
<md-grid-tile>
<md-grid-tile-footer>
<h3 md-line>First line</h3>
<span md-line>Second line</span>
</md-grid-tile-footer>
</md-grid-tile>
</md-grid-list>
`;
return builder.overrideTemplate(TestGridList, template)
.createAsync(TestGridList).then((fixture: ComponentFixture<TestGridList>) => {
fixture.detectChanges();
let footer = fixture.debugElement.query(By.directive(MdGridTileText));
expect(footer.nativeElement.classList.contains('md-2-line')).toBe(true);
});
});
});
@Component({
+3 -2
View File
@@ -10,7 +10,7 @@ import {
ElementRef,
Optional
} from '@angular/core';
import {MdGridTile} from './grid-tile';
import {MdGridTile, MdGridTileText} from './grid-tile';
import {TileCoordinator} from './tile-coordinator';
import {
TileStyler,
@@ -20,6 +20,7 @@ import {
} from './tile-styler';
import {MdGridListColsError} from './grid-list-errors';
import {Dir} from '@angular2-material/core/rtl/dir';
import {MdLine} from '@angular2-material/core/line/line';
// TODO(kara): Conditional (responsive) column count / row size.
// TODO(kara): Re-layout on window resize / media change (debounced).
@@ -167,4 +168,4 @@ export function coerceToNumber(value: string | number): number {
return typeof value === 'string' ? parseInt(value, 10) : value;
}
export const MD_GRID_LIST_DIRECTIVES: any[] = [MdGridList, MdGridTile];
export const MD_GRID_LIST_DIRECTIVES: any[] = [MdGridList, MdGridTile, MdLine, MdGridTileText];
@@ -0,0 +1,3 @@
<ng-content select="[md-grid-avatar]"></ng-content>
<div class="md-grid-list-text"><ng-content select="[md-line]"></ng-content></div>
<ng-content></ng-content>
+28 -7
View File
@@ -4,9 +4,12 @@ import {
Renderer,
ElementRef,
Input,
ContentChildren,
QueryList,
AfterContentInit
} from '@angular/core';
import {coerceToNumber} from './grid-list';
import { coerceToNumber } from './grid-list';
import { MdLine, MdLineSetter } from '@angular2-material/core/line/line';
@Component({
moduleId: module.id,
@@ -19,11 +22,8 @@ import {coerceToNumber} from './grid-list';
export class MdGridTile {
_rowspan: number = 1;
_colspan: number = 1;
_element: HTMLElement;
constructor(private _renderer: Renderer, element: ElementRef) {
this._element = element.nativeElement;
}
constructor(private _renderer: Renderer, private _element: ElementRef) {}
@Input()
get rowspan() {
@@ -48,7 +48,28 @@ export class MdGridTile {
* @internal
*/
setStyle(property: string, value: string): void {
this._renderer.setElementStyle(this._element, property, value);
this._renderer.setElementStyle(this._element.nativeElement, property, value);
}
}
@Component({
moduleId: module.id,
selector: 'md-grid-tile-header, md-grid-tile-footer',
templateUrl: 'grid-tile-text.html'
})
export class MdGridTileText implements AfterContentInit {
/**
* Helper that watches the number of lines in a text area and sets
* a class on the host element that matches the line count.
*/
_lineSetter: MdLineSetter;
@ContentChildren(MdLine) _lines: QueryList<MdLine>;
constructor(private _renderer: Renderer, private _element: ElementRef) {}
ngAfterContentInit() {
this._lineSetter = new MdLineSetter(this._lines, this._renderer, this._element);
}
}
+2 -36
View File
@@ -1,5 +1,6 @@
@import "variables";
@import "default-theme";
@import "list-shared";
$md-list-side-padding: 16px;
$md-list-avatar-size: 40px;
@@ -56,27 +57,12 @@ based on whether the list is in dense mode.
}
.md-list-text {
display: flex;
flex-direction: column;
width: 100%;
@include md-line-wrapper-base();
padding: 0 $md-list-side-padding;
box-sizing: border-box;
overflow: hidden;
&:first-child {
padding: 0;
}
&:empty {
display: none;
}
& > * {
margin: 0;
padding: 0;
font-weight: normal;
font-size: inherit;
}
}
[md-list-avatar] {
@@ -93,26 +79,6 @@ based on whether the list is in dense mode.
}
}
/*
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;
}
}
}
/*
This mixin provides all subheader styles, adjusting heights and padding
based on whether the list is in dense mode.
+9 -32
View File
@@ -9,8 +9,7 @@ import {
Renderer,
AfterContentInit,
} from '@angular/core';
import { MdLine, MdLineSetter } from '../../core/line/line';
@Component({
moduleId: module.id,
@@ -22,10 +21,6 @@ import {
})
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 {}
@@ -42,27 +37,25 @@ export class MdListAvatar {}
encapsulation: ViewEncapsulation.None
})
export class MdListItem implements AfterContentInit {
@ContentChildren(MdLine) _lines: QueryList<MdLine>;
/** @internal */
hasFocus: boolean = false;
/** TODO: internal */
ngAfterContentInit() {
this._setLineClass(this._lines.length);
_lineSetter: MdLineSetter;
this._lines.changes.subscribe(() => {
this._setLineClass(this._lines.length);
});
}
@ContentChildren(MdLine) _lines: QueryList<MdLine>;
@ContentChild(MdListAvatar)
private set _hasAvatar(avatar: MdListAvatar) {
this._setClass('md-list-avatar', avatar != null);
this._renderer.setElementClass(this._element.nativeElement, 'md-list-avatar', avatar != null);
}
constructor(private _renderer: Renderer, private _element: ElementRef) {}
/** TODO: internal */
ngAfterContentInit() {
this._lineSetter = new MdLineSetter(this._lines, this._renderer, this._element);
}
/** @internal */
handleFocus() {
this.hasFocus = true;
@@ -72,22 +65,6 @@ export class MdListItem implements AfterContentInit {
handleBlur() {
this.hasFocus = false;
}
private _setLineClass(count: number): void {
this._resetClasses();
if (count === 2 || count === 3) {
this._setClass(`md-${count}-line`, true);
}
}
private _resetClasses(): void {
this._setClass('md-2-line', false);
this._setClass('md-3-line', false);
}
private _setClass(className: string, bool: boolean): void {
this._renderer.setElementClass(this._element.nativeElement, className, bool);
}
}
export const MD_LIST_DIRECTIVES = [MdList, MdListItem, MdLine, MdListAvatar];
+44
View File
@@ -0,0 +1,44 @@
import {
Directive,
Renderer,
ElementRef,
QueryList
} from '@angular/core';
/**
* Shared directive to count lines inside a text area, such as a list item.
* Line elements can be extracted with a @ContentChildren(MdLine) query, then
* counted by checking the query list's length.
*/
@Directive({ selector: '[md-line]' })
export class MdLine {}
/* Helper that takes a query list of lines and sets the correct class on the host */
export class MdLineSetter {
constructor(private _lines: QueryList<MdLine>, private _renderer: Renderer,
private _element: ElementRef) {
this._setLineClass(this._lines.length);
this._lines.changes.subscribe(() => {
this._setLineClass(this._lines.length);
});
}
private _setLineClass(count: number): void {
this._resetClasses();
if (count === 2 || count === 3) {
this._setClass(`md-${count}-line`, true);
}
}
private _resetClasses(): void {
this._setClass('md-2-line', false);
this._setClass('md-3-line', false);
}
private _setClass(className: string, bool: boolean): void {
this._renderer.setElementClass(this._element.nativeElement, className, bool);
}
}
+50
View File
@@ -0,0 +1,50 @@
/**
* 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;
}
}
}
/**
* This mixin provides base styles for the wrapper around md-line
* elements in a list.
*/
@mixin md-line-wrapper-base() {
@include md-normalize-text();
display: flex;
flex-direction: column;
width: 100%;
box-sizing: border-box;
overflow: hidden;
// Must remove wrapper when lines are empty or it takes up horizontal
// space and pushes other elements to the right.
&:empty {
display: none;
}
}
/**
* This mixin normalizes default element styles, e.g. font weight for heading text.
*/
@mixin md-normalize-text() {
& > * {
margin: 0;
padding: 0;
font-weight: normal;
font-size: inherit;
}
}
@@ -57,5 +57,35 @@
<p>Change gutter: <input type="number" [(ngModel)]="ratioGutter"></p>
</md-card-actions>
</md-card>
<md-card>
<md-card-title>Grid list with header</md-card-title>
<md-card-content>
<md-grid-list cols="3" rowHeight="200px">
<md-grid-tile *ngFor="let dog of dogs" style="background:gray">
<md-grid-tile-header>
<md-icon md-grid-avatar>info_outline</md-icon>
{{dog.name}}
</md-grid-tile-header>
</md-grid-tile>
</md-grid-list>
</md-card-content>
</md-card>
<md-card>
<md-card-title>Grid list with footer</md-card-title>
<md-card-content>
<md-grid-list cols="3" rowHeight="200px">
<md-grid-tile *ngFor="let dog of dogs">
<img [alt]="dog.name" src="https://material.angularjs.org/material2_assets/ngconf/{{dog.name}}.png">
<md-grid-tile-footer>
<h3 md-line>{{dog.name}}</h3>
<span md-line>Human: {{dog.human}}</span>
<md-icon>star_border</md-icon>
</md-grid-tile-footer>
</md-grid-tile>
</md-grid-list>
</md-card-content>
</md-card>
</div>
+5 -1
View File
@@ -1,5 +1,5 @@
.demo-grid-list {
width: 800px;
width: 1100px;
md-card {
margin: 16px 0;
@@ -12,4 +12,8 @@
.demo-basic-list md-grid-tile {
background: rgba(0,0,0,0.32);
}
img {
width: 350px;
}
}
+12 -1
View File
@@ -2,13 +2,15 @@ import {Component} from '@angular/core';
import {MD_GRID_LIST_DIRECTIVES} from '@angular2-material/grid-list/grid-list';
import {MdButton} from '@angular2-material/button/button';
import {MD_CARD_DIRECTIVES} from '@angular2-material/card/card';
import {MdIcon, MdIconRegistry} from '@angular2-material/icon/icon';
@Component({
moduleId: module.id,
selector: 'grid-list-demo',
templateUrl: 'grid-list-demo.html',
styleUrls: ['grid-list-demo.css'],
directives: [MD_GRID_LIST_DIRECTIVES, MdButton, MD_CARD_DIRECTIVES]
directives: [MD_GRID_LIST_DIRECTIVES, MdButton, MD_CARD_DIRECTIVES, MdIcon],
providers: [MdIconRegistry]
})
export class GridListDemo {
tiles: any[] = [
@@ -18,6 +20,15 @@ export class GridListDemo {
{text: 'Four', cols: 2, rows: 1, color: '#DDBDF1'},
];
dogs: Object[] = [
{ name: 'Porter', human: 'Kara' },
{ name: 'Mal', human: 'Jeremy' },
{ name: 'Koby', human: 'Igor' },
{ name: 'Razzle', human: 'Ward' },
{ name: 'Molly', human: 'Rob' },
{ name: 'Husi', human: 'Matias' },
];
fixedCols: number = 4;
fixedRowHeight: number = 100;
ratioGutter: number = 1;