mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(expansion-panel): introduce expansion panel"
This commit is contained in:
committed by
Andrew Seguin
parent
37df5d3c15
commit
cac7610460
@@ -41,6 +41,7 @@ import {DataTableDemo} from './data-table/data-table-demo';
|
||||
import {PeopleDatabase} from './data-table/people-database';
|
||||
import {DatepickerDemo} from './datepicker/datepicker-demo';
|
||||
import {TypographyDemo} from './typography/typography-demo';
|
||||
import {ExpansionDemo} from './expansion/expansion-demo';
|
||||
import {
|
||||
CdkDataTableModule,
|
||||
FullscreenOverlayContainer,
|
||||
@@ -53,6 +54,7 @@ import {
|
||||
MdCoreModule,
|
||||
MdDatepickerModule,
|
||||
MdDialogModule,
|
||||
MdExpansionModule,
|
||||
MdGridListModule,
|
||||
MdIconModule,
|
||||
MdInputModule,
|
||||
@@ -87,6 +89,7 @@ import {
|
||||
MdChipsModule,
|
||||
MdDatepickerModule,
|
||||
MdDialogModule,
|
||||
MdExpansionModule,
|
||||
MdGridListModule,
|
||||
MdIconModule,
|
||||
MdInputModule,
|
||||
@@ -170,6 +173,7 @@ export class DemoMaterialModule {}
|
||||
FoggyTabContent,
|
||||
PlatformDemo,
|
||||
TypographyDemo,
|
||||
ExpansionDemo,
|
||||
],
|
||||
providers: [
|
||||
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
|
||||
|
||||
@@ -43,6 +43,7 @@ export class DemoApp {
|
||||
{name: 'Data Table', route: 'data-table'},
|
||||
{name: 'Datepicker', route: 'datepicker'},
|
||||
{name: 'Dialog', route: 'dialog'},
|
||||
{name: 'Expansion Panel', route: 'expansion'},
|
||||
{name: 'Gestures', route: 'gestures'},
|
||||
{name: 'Grid List', route: 'grid-list'},
|
||||
{name: 'Icon', route: 'icon'},
|
||||
|
||||
@@ -35,6 +35,7 @@ import {StyleDemo} from '../style/style-demo';
|
||||
import {DatepickerDemo} from '../datepicker/datepicker-demo';
|
||||
import {DataTableDemo} from '../data-table/data-table-demo';
|
||||
import {TypographyDemo} from '../typography/typography-demo';
|
||||
import {ExpansionDemo} from '../expansion/expansion-demo';
|
||||
|
||||
export const DEMO_APP_ROUTES: Routes = [
|
||||
{path: '', component: Home},
|
||||
@@ -71,5 +72,6 @@ export const DEMO_APP_ROUTES: Routes = [
|
||||
{path: 'snack-bar', component: SnackBarDemo},
|
||||
{path: 'platform', component: PlatformDemo},
|
||||
{path: 'style', component: StyleDemo},
|
||||
{path: 'typography', component: TypographyDemo}
|
||||
{path: 'typography', component: TypographyDemo},
|
||||
{path: 'expansion', component: ExpansionDemo},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<h1>Single Expansion Panel</h1>
|
||||
<md-expansion-panel class="md-expansion-demo-width" #myPanel>
|
||||
<md-expansion-panel-header>
|
||||
<mat-panel-description>This is a panel description.</mat-panel-description>
|
||||
<mat-panel-title>Panel Title</mat-panel-title>
|
||||
</md-expansion-panel-header>
|
||||
This is the content text that makes sense here.
|
||||
<md-action-row>
|
||||
<button md-button (click)="myPanel.expanded = false">CANCEL</button>
|
||||
<button md-button>SAVE</button>
|
||||
</md-action-row>
|
||||
</md-expansion-panel>
|
||||
<br>
|
||||
<h1>Accordion</h1>
|
||||
<div>
|
||||
<p>Accordion Options</p>
|
||||
<div>
|
||||
<md-slide-toggle [(ngModel)]="multi">Allow Multi Expansion</md-slide-toggle>
|
||||
<md-slide-toggle [(ngModel)]="hideToggle">Hide Indicators</md-slide-toggle>
|
||||
<md-slide-toggle [(ngModel)]="showPanel3">Show Panel 3</md-slide-toggle>
|
||||
</div>
|
||||
<p>Accordion Style</p>
|
||||
<md-radio-group [(ngModel)]="displayMode">
|
||||
<md-radio-button value="default">Default</md-radio-button>
|
||||
<md-radio-button value="flat">Flat</md-radio-button>
|
||||
</md-radio-group>
|
||||
<p>Accordion Panel(s)</p>
|
||||
<div>
|
||||
<md-checkbox [(ngModel)]="panel1.expanded">Panel 1</md-checkbox>
|
||||
<md-checkbox [(ngModel)]="panel2.expanded">Panel 2</md-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<md-accordion [displayMode]="displayMode" [multi]="multi"
|
||||
class="md-expansion-demo-width">
|
||||
<md-expansion-panel #panel1 [hideToggle]="hideToggle">
|
||||
<md-expansion-panel-header>Section 1</md-expansion-panel-header>
|
||||
<p>This is the content text that makes sense here.</p>
|
||||
</md-expansion-panel>
|
||||
<md-expansion-panel #panel2 [hideToggle]="hideToggle">
|
||||
<md-expansion-panel-header>Section 2</md-expansion-panel-header>
|
||||
<p>This is the content text that makes sense here.</p>
|
||||
</md-expansion-panel>
|
||||
<md-expansion-panel #panel3 *ngIf="showPanel3" [hideToggle]="hideToggle">
|
||||
<md-expansion-panel-header>Section 3</md-expansion-panel-header>
|
||||
<md-checkbox #showButtons>Reveal Buttons Below</md-checkbox>
|
||||
<md-action-row *ngIf="showButtons.checked">
|
||||
<button md-button (click)="panel2.expanded = true">OPEN SECTION 2</button>
|
||||
<button md-button (click)="panel3.expanded = false">CLOSE</button>
|
||||
</md-action-row>
|
||||
</md-expansion-panel>
|
||||
</md-accordion>
|
||||
@@ -0,0 +1,4 @@
|
||||
.md-expansion-demo-width {
|
||||
width: 600px;
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import {Component, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'expansion-demo',
|
||||
styleUrls: ['expansion-demo.css'],
|
||||
templateUrl: 'expansion-demo.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class ExpansionDemo {
|
||||
displayMode: string = 'default';
|
||||
multi: boolean = false;
|
||||
hideToggle: boolean = false;
|
||||
showPanel3 = true;
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
@import '../../chips/chips-theme';
|
||||
@import '../../datepicker/datepicker-theme';
|
||||
@import '../../dialog/dialog-theme';
|
||||
@import '../../expansion/expansion-theme';
|
||||
@import '../../grid-list/grid-list-theme';
|
||||
@import '../../icon/icon-theme';
|
||||
@import '../../input/input-theme';
|
||||
@@ -37,6 +38,7 @@
|
||||
@include mat-chips-theme($theme);
|
||||
@include mat-datepicker-theme($theme);
|
||||
@include mat-dialog-theme($theme);
|
||||
@include mat-expansion-panel-theme($theme);
|
||||
@include mat-grid-list-theme($theme);
|
||||
@include mat-icon-theme($theme);
|
||||
@include mat-input-theme($theme);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
@import '../core/theming/palette';
|
||||
@import '../core/theming/theming';
|
||||
|
||||
@mixin mat-expansion-panel-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.mat-expansion-panel {
|
||||
background: mat-color($background, card);
|
||||
color: mat-color($foreground, base);
|
||||
}
|
||||
|
||||
.mat-action-row {
|
||||
border-top-color: mat-color($foreground, divider);
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header:focus,
|
||||
.mat-expansion-panel-header:hover {
|
||||
background: mat-color($background, hover);
|
||||
}
|
||||
.mat-expansion-panel-header-title {
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-description {
|
||||
color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
|
||||
.mat-expansion-indicator::after {
|
||||
color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import {Output, EventEmitter, Input, Injectable, OnDestroy, Optional} from '@angular/core';
|
||||
import {UniqueSelectionDispatcher} from '../core';
|
||||
import {CdkAccordion} from './accordion';
|
||||
|
||||
/** Used to generate unique ID for each expansion panel. */
|
||||
let nextId = 0;
|
||||
|
||||
/**
|
||||
* An abstract class to be extended and decorated as a component. Sets up all
|
||||
* events and attributes needed to be managed by a CdkAccordion parent.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AccordionItem implements OnDestroy {
|
||||
/** Event emitted every time the MdAccordianChild is closed. */
|
||||
@Output() closed = new EventEmitter<void>();
|
||||
/** Event emitted every time the MdAccordianChild is opened. */
|
||||
@Output() opened = new EventEmitter<void>();
|
||||
/** Event emitted when the MdAccordianChild is destroyed. */
|
||||
@Output() destroyed = new EventEmitter<void>();
|
||||
/** The unique MdAccordianChild id. */
|
||||
readonly id = `cdk-accordion-child-${nextId++}`;
|
||||
/** Whether the MdAccordianChild is expanded. */
|
||||
@Input() get expanded(): boolean { return this._expanded; }
|
||||
set expanded(expanded: boolean) {
|
||||
// Only emit events and update the internal value if the value changes.
|
||||
if (this._expanded !== expanded) {
|
||||
this._expanded = expanded;
|
||||
if (expanded) {
|
||||
this.opened.emit();
|
||||
/**
|
||||
* In the unique selection dispatcher, the id parameter is the id of the CdkAccordonItem,
|
||||
* the name value is the id of the accordion.
|
||||
*/
|
||||
let accordionId = this.accordion ? this.accordion.id : this.id;
|
||||
this._expansionDispatcher.notify(this.id, accordionId);
|
||||
} else {
|
||||
this.closed.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
private _expanded: boolean;
|
||||
|
||||
constructor(@Optional() public accordion: CdkAccordion,
|
||||
protected _expansionDispatcher: UniqueSelectionDispatcher) {
|
||||
_expansionDispatcher.listen((id: string, accordionId: string) => {
|
||||
if (this.accordion && !this.accordion.multi &&
|
||||
this.accordion.id === accordionId && this.id !== id) {
|
||||
this.expanded = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Emits an event for the accordion item being destroyed. */
|
||||
ngOnDestroy() {
|
||||
this.destroyed.emit();
|
||||
}
|
||||
|
||||
/** Toggles the expanded state of the accordion item. */
|
||||
toggle(): void {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
|
||||
/** Sets the expanded state of the accordion item to false. */
|
||||
close(): void {
|
||||
this.expanded = false;
|
||||
}
|
||||
|
||||
/** Sets the expanded state of the accordion item to true. */
|
||||
open(): void {
|
||||
this.expanded = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {async, TestBed} from '@angular/core/testing';
|
||||
import {Component} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MdExpansionModule} from './index';
|
||||
|
||||
|
||||
describe('CdkAccordion', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
MdExpansionModule
|
||||
],
|
||||
declarations: [
|
||||
SetOfItems
|
||||
],
|
||||
});
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
it('should ensure only one item is expanded at a time', () => {
|
||||
let fixture = TestBed.createComponent(SetOfItems);
|
||||
let items = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
|
||||
|
||||
fixture.componentInstance.firstPanelExpanded = true;
|
||||
fixture.detectChanges();
|
||||
expect(items[0].classes['mat-expanded']).toBeTruthy();
|
||||
expect(items[1].classes['mat-expanded']).toBeFalsy();
|
||||
|
||||
fixture.componentInstance.secondPanelExpanded = true;
|
||||
fixture.detectChanges();
|
||||
expect(items[0].classes['mat-expanded']).toBeFalsy();
|
||||
expect(items[1].classes['mat-expanded']).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should allow multiple items to be expanded simultaneously', () => {
|
||||
let fixture = TestBed.createComponent(SetOfItems);
|
||||
let panels = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
|
||||
|
||||
fixture.componentInstance.multi = true;
|
||||
fixture.componentInstance.firstPanelExpanded = true;
|
||||
fixture.componentInstance.secondPanelExpanded = true;
|
||||
fixture.detectChanges();
|
||||
expect(panels[0].classes['mat-expanded']).toBeTruthy();
|
||||
expect(panels[1].classes['mat-expanded']).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@Component({template: `
|
||||
<md-accordion [multi]="multi">
|
||||
<md-expansion-panel [expanded]="firstPanelExpanded">
|
||||
<md-expansion-panel-header>Summary</md-expansion-panel-header>
|
||||
<p>Content</p>
|
||||
</md-expansion-panel>
|
||||
<md-expansion-panel [expanded]="secondPanelExpanded">
|
||||
<md-expansion-panel-header>Summary</md-expansion-panel-header>
|
||||
<p>Content</p>
|
||||
</md-expansion-panel>
|
||||
</md-accordion>`})
|
||||
class SetOfItems {
|
||||
multi: boolean = false;
|
||||
firstPanelExpanded: boolean = false;
|
||||
secondPanelExpanded: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {Directive, Input} from '@angular/core';
|
||||
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
|
||||
|
||||
/** MdAccordion's display modes. */
|
||||
export type MdAccordionDisplayMode = 'default' | 'flat';
|
||||
|
||||
/** Unique ID counter */
|
||||
let nextId = 0;
|
||||
|
||||
/**
|
||||
* Directive whose purpose is to manage the expanded state of CdkAccordionItem children.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[cdk-accordion]',
|
||||
})
|
||||
export class CdkAccordion {
|
||||
/** A readonly id value to use for unique selection coordination. */
|
||||
readonly id = `cdk-accordion-${nextId++}`;
|
||||
|
||||
/** Whether the accordion should allow multiple expanded accordion items simulateously. */
|
||||
@Input() get multi(): boolean { return this._multi; }
|
||||
set multi(multi: boolean) { this._multi = coerceBooleanProperty(multi); }
|
||||
private _multi: boolean = false;
|
||||
|
||||
/** Whether the expansion indicator should be hidden. */
|
||||
@Input() get hideToggle(): boolean { return this._hideToggle; }
|
||||
set hideToggle(show: boolean) { this._hideToggle = coerceBooleanProperty(show); }
|
||||
private _hideToggle: boolean = false;
|
||||
|
||||
/**
|
||||
* The display mode used for all expansion panels in the accordion. Currently two display
|
||||
* modes exist:
|
||||
* default - a gutter-like spacing is placed around any expanded panel, placing the expanded
|
||||
* panel at a different elevation from the reset of the accordion.
|
||||
* flat - no spacing is placed around expanded panels, showing all panels at the same
|
||||
* elevation.
|
||||
*/
|
||||
@Input() displayMode: MdAccordionDisplayMode = 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Directive for a Material Design Accordion.
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'mat-accordion, md-accordion',
|
||||
host: {
|
||||
class: 'mat-accordion'
|
||||
}
|
||||
})
|
||||
export class MdAccordion extends CdkAccordion {}
|
||||
@@ -0,0 +1,7 @@
|
||||
<span class="mat-content">
|
||||
<ng-content select="md-panel-title, mat-panel-title"></ng-content>
|
||||
<ng-content select="md-panel-description, mat-panel-description"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
</span>
|
||||
<span [@indicatorRotate]="_getExpandedState()" *ngIf="!_getHideToggle()"
|
||||
class="mat-expansion-indicator"></span>
|
||||
@@ -0,0 +1,61 @@
|
||||
$mat-expansion-panel-header-height: 48px;
|
||||
$mat-expansion-panel-header-height-expanded: 64px;
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: $mat-expansion-panel-header-height;
|
||||
line-height: $mat-expansion-panel-header-height;
|
||||
padding: 0 24px;
|
||||
|
||||
&.mat-expanded {
|
||||
height: $mat-expansion-panel-header-height-expanded;
|
||||
line-height: $mat-expansion-panel-header-height-expanded;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.mat-expanded:focus,
|
||||
&.mat-expanded:hover, {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-title {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
font-size: 15px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-description {
|
||||
display: flex;
|
||||
flex-grow: 2;
|
||||
font-size: 15px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the expansion indicator arrow. Done using ::after rather than having
|
||||
* additional nodes in the template.
|
||||
*/
|
||||
.mat-expansion-indicator::after {
|
||||
border-style: solid;
|
||||
border-width: 0 2px 2px 0;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
transform: rotate(45deg);
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
Component,
|
||||
Directive,
|
||||
Host,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
transition,
|
||||
animate,
|
||||
} from '@angular/animations';
|
||||
import {SPACE, ENTER} from '../core/keyboard/keycodes';
|
||||
import {MdExpansionPanel, EXPANSION_PANEL_ANIMATION_TIMING} from './expansion-panel';
|
||||
|
||||
|
||||
/**
|
||||
* <md-expansion-panel-header> component.
|
||||
*
|
||||
* This component corresponds to the header element of an <md-expansion-panel>.
|
||||
*
|
||||
* Please refer to README.md for examples on how to use it.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'md-expansion-panel-header, mat-expansion-panel-header',
|
||||
styleUrls: ['./expansion-panel-header.css'],
|
||||
templateUrl: './expansion-panel-header.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {
|
||||
'class': 'mat-expansion-panel-header',
|
||||
'role': 'button',
|
||||
'tabindex': '0',
|
||||
'[attr.aria-controls]': '_getPanelId()',
|
||||
'[attr.aria-expanded]': '_isExpanded()',
|
||||
'[class.mat-expanded]': '_isExpanded()',
|
||||
'(click)': '_toggle()',
|
||||
'(keyup)': '_keyup($event)',
|
||||
'[@expansionHeight]': '_getExpandedState()',
|
||||
},
|
||||
animations: [
|
||||
trigger('indicatorRotate', [
|
||||
state('collapsed', style({transform: 'rotate(0deg)'})),
|
||||
state('expanded', style({transform: 'rotate(180deg)'})),
|
||||
transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
|
||||
]),
|
||||
trigger('expansionHeight', [
|
||||
state('collapsed', style({height: '48px', 'line-height': '48px'})),
|
||||
state('expanded', style({height: '64px', 'line-height': '68px'})),
|
||||
transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class MdExpansionPanelHeader {
|
||||
constructor(@Host() public panel: MdExpansionPanel) {}
|
||||
|
||||
/** Toggles the expanded state of the panel. */
|
||||
_toggle(event?: KeyboardEvent): void {
|
||||
this.panel.toggle();
|
||||
}
|
||||
|
||||
/** Gets whether the panel is expanded. */
|
||||
_isExpanded(): boolean {
|
||||
return this.panel.expanded;
|
||||
}
|
||||
|
||||
/** Gets the expanded state string of the panel. */
|
||||
_getExpandedState(): string {
|
||||
return this.panel._getExpandedState();
|
||||
}
|
||||
|
||||
/** Gets the panel id. */
|
||||
_getPanelId(): string {
|
||||
return this.panel.id;
|
||||
}
|
||||
|
||||
/** Gets whether the expand indicator is hidden. */
|
||||
_getHideToggle(): boolean {
|
||||
return this.panel.hideToggle;
|
||||
}
|
||||
|
||||
/** Handle keyup event calling to toggle() if appropriate. */
|
||||
_keyup(event: KeyboardEvent) {
|
||||
switch (event.keyCode) {
|
||||
// Toggle for space and enter keys.
|
||||
case SPACE:
|
||||
case ENTER:
|
||||
event.preventDefault();
|
||||
this._toggle();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <md-panel-description> directive.
|
||||
*
|
||||
* This direction is to be used inside of the MdExpansionPanelHeader component.
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'md-panel-description, mat-panel-description',
|
||||
host : {
|
||||
class: 'mat-expansion-panel-header-description'
|
||||
}
|
||||
})
|
||||
export class MdExpansionPanelDescription {}
|
||||
|
||||
/**
|
||||
* <md-panel-title> directive.
|
||||
*
|
||||
* This direction is to be used inside of the MdExpansionPanelHeader component.
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'md-panel-title, mat-panel-title',
|
||||
host : {
|
||||
class: 'mat-expansion-panel-header-title'
|
||||
}
|
||||
})
|
||||
export class MdExpansionPanelTitle {}
|
||||
@@ -0,0 +1,8 @@
|
||||
<ng-content select="mat-expansion-panel-header, md-expansion-panel-header"></ng-content>
|
||||
<div [class.mat-expanded]="expanded" class="mat-expansion-panel-content"
|
||||
[@bodyExpansion]="_getExpandedState()" [id]="id">
|
||||
<div class="mat-expansion-panel-body">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<ng-content select="mat-action-row, md-action-row"></ng-content>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
@import '../core/style/variables';
|
||||
@import '../core/style/elevation';
|
||||
|
||||
.mat-expansion-panel {
|
||||
@include mat-elevation-transition;
|
||||
box-sizing: content-box;
|
||||
display: block;
|
||||
|
||||
&:not([class*='mat-elevation-z']) {
|
||||
@include mat-elevation(2);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-expansion-panel-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-body {
|
||||
padding: 0 24px 16px;
|
||||
}
|
||||
|
||||
.mat-action-row {
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 8px 16px 24px;
|
||||
|
||||
button.mat-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
Component,
|
||||
Directive,
|
||||
Host,
|
||||
Input,
|
||||
ViewEncapsulation,
|
||||
Optional,
|
||||
forwardRef,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
trigger,
|
||||
state,
|
||||
style,
|
||||
transition,
|
||||
animate,
|
||||
} from '@angular/animations';
|
||||
import {MdAccordion, MdAccordionDisplayMode} from './accordion';
|
||||
import {AccordionItem} from './accordion-item';
|
||||
import {UniqueSelectionDispatcher} from '../core';
|
||||
|
||||
|
||||
/** MdExpansionPanel's states. */
|
||||
export type MdExpansionPanelState = 'expanded' | 'collapsed';
|
||||
|
||||
/** Time and timing curve for expansion panel animations. */
|
||||
export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2,1)';
|
||||
|
||||
/**
|
||||
* <md-expansion-panel> component.
|
||||
*
|
||||
* This component can be used as a single element to show expandable content, or as one of
|
||||
* multiple children of an element with the CdkAccordion directive attached.
|
||||
*
|
||||
* Please refer to README.md for examples on how to use it.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
styleUrls: ['./expansion-panel.css'],
|
||||
selector: 'md-expansion-panel, mat-expansion-panel',
|
||||
templateUrl: './expansion-panel.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: {
|
||||
'class': 'mat-expansion-panel',
|
||||
'[class.mat-expanded]': 'expanded',
|
||||
'[@displayMode]': '_getDisplayMode()',
|
||||
},
|
||||
providers: [
|
||||
{provide: AccordionItem, useExisting: forwardRef(() => MdExpansionPanel)}
|
||||
],
|
||||
animations: [
|
||||
trigger('bodyExpansion', [
|
||||
state('collapsed', style({height: '0px'})),
|
||||
state('expanded', style({height: '*'})),
|
||||
transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
|
||||
]),
|
||||
trigger('displayMode', [
|
||||
state('collapsed', style({margin: '0'})),
|
||||
state('default', style({margin: '16px 0'})),
|
||||
state('flat', style({margin: '0'})),
|
||||
transition('flat <=> collapsed, default <=> collapsed, flat <=> default',
|
||||
animate(EXPANSION_PANEL_ANIMATION_TIMING)),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class MdExpansionPanel extends AccordionItem {
|
||||
/** Whether the toggle indicator should be hidden. */
|
||||
@Input() hideToggle: boolean = false;
|
||||
|
||||
constructor(@Optional() @Host() accordion: MdAccordion,
|
||||
_uniqueSelectionDispatcher: UniqueSelectionDispatcher) {
|
||||
super(accordion, _uniqueSelectionDispatcher);
|
||||
this.accordion = accordion;
|
||||
}
|
||||
|
||||
/** Whether the expansion indicator should be hidden. */
|
||||
_getHideToggle(): boolean {
|
||||
if (this.accordion) {
|
||||
return this.accordion.hideToggle;
|
||||
}
|
||||
return this.hideToggle;
|
||||
}
|
||||
|
||||
/** Gets the panel's display mode. */
|
||||
_getDisplayMode(): MdAccordionDisplayMode | MdExpansionPanelState {
|
||||
if (!this.expanded) {
|
||||
return this._getExpandedState();
|
||||
}
|
||||
if (this.accordion) {
|
||||
return this.accordion.displayMode;
|
||||
}
|
||||
return this._getExpandedState();
|
||||
}
|
||||
|
||||
/** Gets the expanded state string. */
|
||||
_getExpandedState(): MdExpansionPanelState {
|
||||
return this.expanded ? 'expanded' : 'collapsed';
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: 'mat-action-row, md-action-row',
|
||||
host: {
|
||||
class: 'mat-action-row'
|
||||
}
|
||||
})
|
||||
export class MdExpansionPanelActionRow {}
|
||||
@@ -0,0 +1,73 @@
|
||||
import {async, TestBed} from '@angular/core/testing';
|
||||
import {Component} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MdExpansionModule} from './index';
|
||||
|
||||
|
||||
describe('MdExpansionPanel', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
MdExpansionModule
|
||||
],
|
||||
declarations: [
|
||||
PanelWithContent
|
||||
],
|
||||
});
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
it('should expanded and collapse the panel', () => {
|
||||
let fixture = TestBed.createComponent(PanelWithContent);
|
||||
let contentEl = fixture.debugElement.query(By.css('.mat-expansion-panel-content'));
|
||||
let headerEl = fixture.debugElement.query(By.css('.mat-expansion-panel-header'));
|
||||
fixture.detectChanges();
|
||||
expect(headerEl.classes['mat-expanded']).toBeFalsy();
|
||||
expect(contentEl.classes['mat-expanded']).toBeFalsy();
|
||||
|
||||
fixture.componentInstance.expanded = true;
|
||||
fixture.detectChanges();
|
||||
expect(headerEl.classes['mat-expanded']).toBeTruthy();
|
||||
expect(contentEl.classes['mat-expanded']).toBeTruthy();
|
||||
});
|
||||
|
||||
it('emit correct events for change in panel expanded state', () => {
|
||||
let fixture = TestBed.createComponent(PanelWithContent);
|
||||
fixture.componentInstance.expanded = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.openCallback).toHaveBeenCalled();
|
||||
|
||||
fixture.componentInstance.expanded = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.closeCallback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a unique panel id for each panel', () => {
|
||||
let fixtureOne = TestBed.createComponent(PanelWithContent);
|
||||
let headerElOne = fixtureOne.nativeElement.querySelector('.mat-expansion-panel-header');
|
||||
let fixtureTwo = TestBed.createComponent(PanelWithContent);
|
||||
let headerElTwo = fixtureTwo.nativeElement.querySelector('.mat-expansion-panel-header');
|
||||
fixtureOne.detectChanges();
|
||||
fixtureTwo.detectChanges();
|
||||
|
||||
let panelIdOne = headerElOne.getAttribute('aria-controls');
|
||||
let panelIdTwo = headerElTwo.getAttribute('aria-controls');
|
||||
expect(panelIdOne).not.toBe(panelIdTwo);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@Component({template: `
|
||||
<md-expansion-panel [expanded]="expanded"
|
||||
(opened)="openCallback()"
|
||||
(closed)="closeCallback()">
|
||||
<md-expansion-panel-header>Panel Title</md-expansion-panel-header>
|
||||
<p>Some content</p>
|
||||
</md-expansion-panel>`})
|
||||
class PanelWithContent {
|
||||
expanded: boolean = false;
|
||||
openCallback = jasmine.createSpy('openCallback');
|
||||
closeCallback = jasmine.createSpy('closeCallback');
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {NgModule, ModuleWithProviders} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {CompatibilityModule, UNIQUE_SELECTION_DISPATCHER_PROVIDER} from '../core';
|
||||
import {
|
||||
MdExpansionPanelHeader,
|
||||
MdExpansionPanelDescription,
|
||||
MdExpansionPanelTitle
|
||||
} from './expansion-panel-header';
|
||||
import {
|
||||
MdExpansionPanel,
|
||||
MdExpansionPanelActionRow,
|
||||
} from './expansion-panel';
|
||||
import {
|
||||
CdkAccordion,
|
||||
MdAccordion,
|
||||
} from './accordion';
|
||||
|
||||
@NgModule({
|
||||
imports: [CompatibilityModule, CommonModule],
|
||||
exports: [
|
||||
CdkAccordion,
|
||||
MdAccordion,
|
||||
MdExpansionPanel,
|
||||
MdExpansionPanelActionRow,
|
||||
MdExpansionPanelHeader,
|
||||
MdExpansionPanelTitle,
|
||||
MdExpansionPanelDescription
|
||||
],
|
||||
declarations: [
|
||||
CdkAccordion,
|
||||
MdAccordion,
|
||||
MdExpansionPanel,
|
||||
MdExpansionPanelActionRow,
|
||||
MdExpansionPanelHeader,
|
||||
MdExpansionPanelTitle,
|
||||
MdExpansionPanelDescription
|
||||
],
|
||||
providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER]
|
||||
})
|
||||
export class MdExpansionModule {}
|
||||
|
||||
export {
|
||||
CdkAccordion,
|
||||
MdAccordion,
|
||||
MdAccordionDisplayMode
|
||||
} from './accordion';
|
||||
export {AccordionItem} from './accordion-item';
|
||||
export {
|
||||
MdExpansionPanel,
|
||||
MdExpansionPanelState,
|
||||
MdExpansionPanelActionRow
|
||||
} from './expansion-panel';
|
||||
export {
|
||||
MdExpansionPanelHeader,
|
||||
MdExpansionPanelDescription,
|
||||
MdExpansionPanelTitle
|
||||
} from './expansion-panel-header';
|
||||
@@ -37,6 +37,7 @@ import {MdAutocompleteModule} from './autocomplete/index';
|
||||
import {StyleModule} from './core/style/index';
|
||||
import {MdDatepickerModule} from './datepicker/index';
|
||||
import {CdkDataTableModule} from './core/data-table/index';
|
||||
import {MdExpansionModule} from './expansion/index';
|
||||
|
||||
const MATERIAL_MODULES = [
|
||||
MdAutocompleteModule,
|
||||
@@ -47,6 +48,7 @@ const MATERIAL_MODULES = [
|
||||
MdCheckboxModule,
|
||||
MdDatepickerModule,
|
||||
MdDialogModule,
|
||||
MdExpansionModule,
|
||||
MdGridListModule,
|
||||
MdIconModule,
|
||||
MdInputModule,
|
||||
|
||||
@@ -16,6 +16,7 @@ export * from './checkbox/index';
|
||||
export * from './core/data-table/index';
|
||||
export * from './datepicker/index';
|
||||
export * from './dialog/index';
|
||||
export * from './expansion/index';
|
||||
export * from './grid-list/index';
|
||||
export * from './icon/index';
|
||||
export * from './input/index';
|
||||
|
||||
Reference in New Issue
Block a user