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