-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(datepicker): Add Custom Header to DatePicker #9639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 62 commits
6251e1e
8d30140
81c89fa
737a563
998be7c
a14dbd4
585407f
6668ae6
f9628a8
050904b
a2db283
7f071ec
5c9f662
3467c0a
014777a
179c9af
b6938f2
0faefc4
aa8f837
2b4d4d2
b238107
46b2c9d
a45f3ee
f8c4c5b
b6b50cc
ca7a0db
2058afb
f6f19e8
702317d
528361e
e36dc71
a7975d2
02b5604
57bd12c
a88fecb
4c66467
258929a
81147f1
586d5da
a9d4c58
9bba08f
1ff7e45
5196ee7
4cee00c
da49938
70ff16f
533739e
c287495
6d0a4d2
a33b70c
c7f21bf
715e413
5f4b0fc
3057f9d
d81db60
94c825a
b6fc003
796cbf2
abf1c62
1fdb1c3
822dd56
ba4e839
1ee0f93
aff641d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<div class="custom-header"> | ||
<button mat-icon-button (click)="previousClicked('year')"><<</button> | ||
<button mat-icon-button (click)="previousClicked('month')"><</button> | ||
<span class="custom-header-label">{{periodLabel}}</span> | ||
<button mat-icon-button (click)="nextClicked('month')">></button> | ||
<button mat-icon-button (click)="nextClicked('year')">>></button> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.custom-header { | ||
padding: 1em 1.5em; | ||
display: flex; | ||
align-items: center; | ||
} | ||
|
||
.custom-header-label { | ||
flex: 1; | ||
text-align: center; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra new line. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,12 +6,13 @@ | |
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {ChangeDetectionStrategy, Component} from '@angular/core'; | ||
import {ChangeDetectionStrategy, Component, Host} from '@angular/core'; | ||
import {FormControl} from '@angular/forms'; | ||
import {MatDatepickerInputEvent} from '@angular/material/datepicker'; | ||
import {DateAdapter} from '@angular/material/core'; | ||
import {MatCalendar} from '@angular/material'; | ||
import {ThemePalette} from '@angular/material/core'; | ||
|
||
|
||
@Component({ | ||
moduleId: module.id, | ||
selector: 'datepicker-demo', | ||
|
@@ -40,4 +41,38 @@ export class DatepickerDemo { | |
|
||
onDateInput = (e: MatDatepickerInputEvent<Date>) => this.lastDateInput = e.value; | ||
onDateChange = (e: MatDatepickerInputEvent<Date>) => this.lastDateChange = e.value; | ||
|
||
// pass custom header component type as input | ||
customHeader = CustomHeader; | ||
} | ||
|
||
// Custom header component for datepicker | ||
@Component({ | ||
moduleId: module.id, | ||
selector: 'custom-header', | ||
templateUrl: 'custom-header.html', | ||
styleUrls: ['custom-header.css'], | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class CustomHeader<D> { | ||
constructor(@Host() private _calendar: MatCalendar<D>, | ||
private _dateAdapter: DateAdapter<D>) {} | ||
|
||
get periodLabel() { | ||
let year = this._dateAdapter.getYearName(this._calendar.activeDate); | ||
let month = (this._dateAdapter.getMonth(this._calendar.activeDate) + 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both variables could be |
||
return `${month}/${year}`; | ||
} | ||
|
||
previousClicked(mode: 'month' | 'year') { | ||
this._calendar.activeDate = mode == 'month' ? | ||
this._dateAdapter.addCalendarMonths(this._calendar.activeDate, -1) : | ||
this._dateAdapter.addCalendarYears(this._calendar.activeDate, -1); | ||
} | ||
|
||
nextClicked(mode: 'month' | 'year') { | ||
this._calendar.activeDate = mode == 'month' ? | ||
this._dateAdapter.addCalendarMonths(this._calendar.activeDate, 1) : | ||
this._dateAdapter.addCalendarYears(this._calendar.activeDate, 1); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<div class="mat-calendar-header"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: normal indentation = 2 spaces, continuation lines can be indented 4 or align the attributes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done in a7975d2 |
||
<div class="mat-calendar-controls"> | ||
<button mat-button class="mat-calendar-period-button" | ||
(click)="currentPeriodClicked()" [attr.aria-label]="periodButtonLabel"> | ||
{{periodButtonText}} | ||
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="calendar.currentView != 'month'"></div> | ||
</button> | ||
|
||
<div class="mat-calendar-spacer"></div> | ||
|
||
<button mat-icon-button class="mat-calendar-previous-button" | ||
[disabled]="!previousEnabled()" (click)="previousClicked()" | ||
[attr.aria-label]="prevButtonLabel"> | ||
</button> | ||
|
||
<button mat-icon-button class="mat-calendar-next-button" | ||
[disabled]="!nextEnabled()" (click)="nextClicked()" | ||
[attr.aria-label]="nextButtonLabel"> | ||
</button> | ||
</div> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import {Direction, Directionality} from '@angular/cdk/bidi'; | ||
import {MatDatepickerModule} from './datepicker-module'; | ||
import {async, ComponentFixture, TestBed} from '@angular/core/testing'; | ||
import {MatDatepickerIntl} from './datepicker-intl'; | ||
import {DEC, FEB, JAN, MatNativeDateModule} from '@angular/material/core'; | ||
import {Component} from '@angular/core'; | ||
import {MatCalendar} from './calendar'; | ||
import {By} from '@angular/platform-browser'; | ||
import {yearsPerPage} from './multi-year-view'; | ||
|
||
describe('MatCalendarHeader', () => { | ||
let dir: { value: Direction }; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [ | ||
MatNativeDateModule, | ||
MatDatepickerModule, | ||
], | ||
declarations: [ | ||
// Test components. | ||
StandardCalendar, | ||
], | ||
providers: [ | ||
MatDatepickerIntl, | ||
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}} | ||
], | ||
}); | ||
|
||
TestBed.compileComponents(); | ||
})); | ||
|
||
describe('standard calendar', () => { | ||
let fixture: ComponentFixture<StandardCalendar>; | ||
let testComponent: StandardCalendar; | ||
let calendarElement: HTMLElement; | ||
let periodButton: HTMLElement; | ||
let prevButton: HTMLElement; | ||
let nextButton: HTMLElement; | ||
let calendarInstance: MatCalendar<Date>; | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(StandardCalendar); | ||
fixture.detectChanges(); | ||
|
||
let calendarDebugElement = fixture.debugElement.query(By.directive(MatCalendar)); | ||
calendarElement = calendarDebugElement.nativeElement; | ||
periodButton = calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement; | ||
prevButton = calendarElement.querySelector('.mat-calendar-previous-button') as HTMLElement; | ||
nextButton = calendarElement.querySelector('.mat-calendar-next-button') as HTMLElement; | ||
|
||
calendarInstance = calendarDebugElement.componentInstance; | ||
testComponent = fixture.componentInstance; | ||
}); | ||
|
||
it('should be in month view with specified month active', () => { | ||
expect(calendarInstance.currentView).toBe('month'); | ||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); | ||
}); | ||
|
||
it('should toggle view when period clicked', () => { | ||
expect(calendarInstance.currentView).toBe('month'); | ||
|
||
periodButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('multi-year'); | ||
|
||
periodButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('month'); | ||
}); | ||
|
||
it('should go to next and previous month', () => { | ||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); | ||
|
||
nextButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.activeDate).toEqual(new Date(2017, FEB, 28)); | ||
|
||
prevButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 28)); | ||
}); | ||
|
||
it('should go to previous and next year', () => { | ||
periodButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('multi-year'); | ||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); | ||
|
||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('year'); | ||
|
||
nextButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.activeDate).toEqual(new Date(2018, JAN, 31)); | ||
|
||
prevButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); | ||
}); | ||
|
||
it('should go to previous and next multi-year range', () => { | ||
periodButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('multi-year'); | ||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); | ||
|
||
nextButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.activeDate).toEqual(new Date(2017 + yearsPerPage, JAN, 31)); | ||
|
||
prevButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); | ||
}); | ||
|
||
it('should go back to month view after selecting year and month', () => { | ||
periodButton.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('multi-year'); | ||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); | ||
|
||
let yearCells = calendarElement.querySelectorAll('.mat-calendar-body-cell'); | ||
(yearCells[0] as HTMLElement).click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('year'); | ||
expect(calendarInstance.activeDate).toEqual(new Date(2016, JAN, 31)); | ||
|
||
let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell'); | ||
(monthCells[monthCells.length - 1] as HTMLElement).click(); | ||
fixture.detectChanges(); | ||
|
||
expect(calendarInstance.currentView).toBe('month'); | ||
expect(calendarInstance.activeDate).toEqual(new Date(2016, DEC, 31)); | ||
expect(testComponent.selected).toBeFalsy('no date should be selected yet'); | ||
}); | ||
|
||
}); | ||
}); | ||
|
||
@Component({ | ||
template: ` | ||
<mat-calendar | ||
[startAt]="startDate" | ||
[(selected)]="selected" | ||
(yearSelected)="selectedYear=$event" | ||
(monthSelected)="selectedMonth=$event"> | ||
</mat-calendar>` | ||
}) | ||
class StandardCalendar { | ||
selected: Date; | ||
selectedYear: Date; | ||
selectedMonth: Date; | ||
startDate = new Date(2017, JAN, 31); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn’t be only 2 spaces?