-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
feat(datepicker): add month & year view #2904
Changes from 17 commits
97c212f
304f4e4
35ec1a5
2e78102
bcdb462
102d483
1b3ff7b
6f2217c
85f0464
35b8a9d
1aabacc
81f587a
7b5e4ba
52cee53
07b3080
3914d1d
2d6b58f
38c5a9a
e3c0dae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<h1>Work in progress, not ready for use.</h1> | ||
|
||
<md-month-view [date]="date" [(selected)]="selected"></md-month-view> | ||
<md-year-view [date]="date" [(selected)]="selected"></md-year-view> | ||
|
||
<br> | ||
<div>{{selected?.toNativeDate()}}</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import {Component} from '@angular/core'; | ||
import {SimpleDate} from '@angular/material'; | ||
|
||
|
||
@Component({ | ||
moduleId: module.id, | ||
selector: 'datepicker-demo', | ||
templateUrl: 'datepicker-demo.html' | ||
}) | ||
export class DatepickerDemo { | ||
date = SimpleDate.today(); | ||
selected: SimpleDate; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,63 @@ | |
* details and the time component of the native Date. | ||
*/ | ||
export class SimpleDate { | ||
/** | ||
* Create a SimpleDate from a native JS Date object. | ||
* @param nativeDate The native JS Date object to convert. | ||
*/ | ||
static fromNativeDate(nativeDate: Date) { | ||
return new SimpleDate(nativeDate.getFullYear(), nativeDate.getMonth(), nativeDate.getDate()); | ||
} | ||
|
||
constructor(public year: number, public month: number, public date: number) {} | ||
/** Creates a SimpleDate object representing today. */ | ||
static today() { | ||
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. Return type? Same for methods below. |
||
return SimpleDate.fromNativeDate(new Date()); | ||
} | ||
|
||
/** The native JS Date. */ | ||
private _date: Date; | ||
|
||
constructor(year: number, month: number, date: number) { | ||
this._date = new Date(year, month, date); | ||
// We need to correct for the fact that JS native Date treats years in range [0, 99] as | ||
// abbreviations for 19xx. | ||
if (year >= 0 && year < 100) { | ||
this._date = new Date(this._date.setFullYear(this.year - 1900)); | ||
} | ||
} | ||
|
||
/** The year component of this date. */ | ||
get year() { | ||
return this._date.getFullYear(); | ||
} | ||
|
||
/** The month component of this date. */ | ||
get month() { | ||
return this._date.getMonth(); | ||
} | ||
|
||
/** The date component of this date. */ | ||
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: Also add doc about date index? |
||
get date() { | ||
return this._date.getDate(); | ||
} | ||
|
||
/** The day component of this date. */ | ||
get day() { | ||
return this._date.getDay(); | ||
} | ||
|
||
/** | ||
* Adds an amount of time (in days, months, and years) to the date. | ||
* @param amount The amount of time to add. | ||
*/ | ||
add(amount: {days: number, months: number, years: number}) { | ||
return new SimpleDate( | ||
this.year + amount.years || 0, | ||
this.month + amount.months || 0, | ||
this.date + amount.days || 0); | ||
} | ||
|
||
/** Converts the SimpleDate to a native JS Date object. */ | ||
toNativeDate() { | ||
return new Date(this.year, this.month, this.date); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# md-datepicker | ||
|
||
Work in progress, not ready for use. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
@import '../core/theming/palette'; | ||
@import '../core/theming/theming'; | ||
|
||
|
||
@mixin mat-datepicker-theme($theme) { | ||
$primary: map-get($theme, primary); | ||
$foreground: map-get($theme, foreground); | ||
$background: map-get($theme, background); | ||
|
||
.mat-calendar-table-label { | ||
color: md-color($foreground, secondary-text); | ||
} | ||
|
||
.mat-calendar-table-cell-content { | ||
color: md-color($foreground, text); | ||
|
||
.mat-calendar-table-cell:hover & { | ||
background: md-color($background, hover); | ||
} | ||
|
||
.mat-calendar-table-cell &.mat-calendar-table-selected { | ||
background: md-color($primary); | ||
color: md-color($primary, default-contrast); | ||
} | ||
|
||
&.mat-calendar-table-today { | ||
border-color: md-color($foreground, divider); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<table class="mat-calendar-table-table"> | ||
<!-- If there's not enough space in the first row, create a separate label row. --> | ||
<tr *ngIf="_firstRowOffset < labelMinRequiredCells"> | ||
<td class="mat-calendar-table-label" [attr.colspan]="numCols" >{{label}}</td> | ||
</tr> | ||
|
||
<!-- Create the first row separately so we can include a special spacer cell. --> | ||
<tr *ngFor="let row of rows; let i = index"> | ||
<td *ngIf="i === 0 && _firstRowOffset" | ||
class="mat-calendar-table-label" | ||
[attr.colspan]="_firstRowOffset"> | ||
{{_firstRowOffset >= labelMinRequiredCells ? label : ''}} | ||
</td> | ||
<td *ngFor="let item of row" | ||
class="mat-calendar-table-cell" | ||
(click)="_cellClicked(item.value)"> | ||
<div class="mat-calendar-table-cell-content" | ||
[class.mat-calendar-table-selected]="selectedValue === item.value" | ||
[class.mat-calendar-table-today]="todayValue === item.value"> | ||
{{item.displayValue}} | ||
</div> | ||
</td> | ||
</tr> | ||
</table> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
$mat-calendar-table-font-size: 12px !default; | ||
$mat-calendar-table-cell-padding: 1px !default; | ||
$mat-calendar-table-cell-content-size: 32px !default; | ||
$mat-calendar-table-cell-content-border-width: 1px !default; | ||
|
||
|
||
.mat-calendar-table-table { | ||
border-spacing: 0; | ||
font-size: $mat-calendar-table-font-size; | ||
} | ||
|
||
.mat-calendar-table-label { | ||
height: $mat-calendar-table-cell-content-size; | ||
padding: 0 0 0 10px; | ||
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: Sass variable for 10px? Seems like the only one missing. |
||
text-align: left; | ||
font-weight: normal; | ||
} | ||
|
||
.mat-calendar-table-cell { | ||
padding: $mat-calendar-table-cell-padding; | ||
} | ||
|
||
.mat-calendar-table-cell-content { | ||
display: table-cell; | ||
box-sizing: border-box; | ||
width: $mat-calendar-table-cell-content-size; | ||
height: $mat-calendar-table-cell-content-size; | ||
border: $mat-calendar-table-cell-content-border-width solid transparent; | ||
border-radius: 50%; | ||
text-align: center; | ||
vertical-align: middle; | ||
} | ||
|
||
[dir='rtl'] { | ||
.mat-calendar-table-label { | ||
padding: 0 10px 0 0; | ||
text-align: right; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import {async, TestBed, ComponentFixture} from '@angular/core/testing'; | ||
import {MdDatepickerModule} from './index'; | ||
import {Component} from '@angular/core'; | ||
import {MdCalendarTable, MdCalendarCell} from './calendar-table'; | ||
import {By} from '@angular/platform-browser'; | ||
|
||
|
||
describe('MdCalendarTable', () => { | ||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [MdDatepickerModule], | ||
declarations: [ | ||
StandardCalendarTable, | ||
], | ||
}); | ||
|
||
TestBed.compileComponents(); | ||
})); | ||
|
||
describe('standard calendar table', () => { | ||
let fixture: ComponentFixture<StandardCalendarTable>; | ||
let testComponent: StandardCalendarTable; | ||
let calendarTableNativeElement: Element; | ||
let rowEls: NodeListOf<Element>; | ||
let labelEls: NodeListOf<Element>; | ||
let cellEls: NodeListOf<Element>; | ||
|
||
let refreshElementLists = () => { | ||
rowEls = calendarTableNativeElement.querySelectorAll('tr'); | ||
labelEls = calendarTableNativeElement.querySelectorAll('.mat-calendar-table-label'); | ||
cellEls = calendarTableNativeElement.querySelectorAll('.mat-calendar-table-cell'); | ||
}; | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(StandardCalendarTable); | ||
fixture.detectChanges(); | ||
|
||
let calendarTableDebugElement = fixture.debugElement.query(By.directive(MdCalendarTable)); | ||
calendarTableNativeElement = calendarTableDebugElement.nativeElement; | ||
testComponent = fixture.componentInstance; | ||
|
||
refreshElementLists(); | ||
}); | ||
|
||
it('creates table', () => { | ||
expect(rowEls.length).toBe(3); | ||
expect(labelEls.length).toBe(1); | ||
expect(cellEls.length).toBe(14); | ||
}); | ||
|
||
it('highlights today', () => { | ||
let todayCell = calendarTableNativeElement.querySelector('.mat-calendar-table-today'); | ||
expect(todayCell).not.toBeNull(); | ||
expect(todayCell.innerHTML.trim()).toBe('3'); | ||
}); | ||
|
||
it('highlights selected', () => { | ||
let todayCell = calendarTableNativeElement.querySelector('.mat-calendar-table-selected'); | ||
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. very nit: selectedCell? |
||
expect(todayCell).not.toBeNull(); | ||
expect(todayCell.innerHTML.trim()).toBe('4'); | ||
}); | ||
|
||
it('places label in first row if space is available', () => { | ||
testComponent.rows[0] = testComponent.rows[0].slice(3); | ||
testComponent.rows = testComponent.rows.slice(); | ||
fixture.detectChanges(); | ||
refreshElementLists(); | ||
|
||
expect(rowEls.length).toBe(2); | ||
expect(labelEls.length).toBe(1); | ||
expect(cellEls.length).toBe(11); | ||
expect(rowEls[0].firstElementChild.classList.contains('mat-calendar-table-label')).toBe( | ||
true, 'first cell should be the label'); | ||
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. This might be a bit more readable if you use expect(rowEls[0].firstElementChild.classList)
.toContain('mat-calendar-table-label', 'First cell should be the label.'); |
||
expect(labelEls[0].getAttribute('colspan')).toBe('3'); | ||
}); | ||
|
||
it('cell should be selected on click', () => { | ||
let todayElement = | ||
calendarTableNativeElement.querySelector('.mat-calendar-table-today') as HTMLElement; | ||
todayElement.click(); | ||
fixture.detectChanges(); | ||
|
||
expect(todayElement.classList.contains('mat-calendar-table-selected')).toBe( | ||
true, 'today should be selected'); | ||
}); | ||
}); | ||
}); | ||
|
||
|
||
@Component({ | ||
template: `<md-calendar-table [label]="label" | ||
[rows]="rows" | ||
[todayValue]="todayValue" | ||
[selectedValue]="selectedValue" | ||
[labelMinRequiredCells]="labelMinRequiredCells" | ||
[numCols]="numCols" | ||
(selectedValueChange)="onSelect($event)"> | ||
</md-calendar-table>`, | ||
}) | ||
class StandardCalendarTable { | ||
label = 'Jan 2017'; | ||
rows = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14]].map(r => r.map(createCell)); | ||
todayValue = 3; | ||
selectedValue = 4; | ||
labelMinRequiredCells = 3; | ||
numCols = 7; | ||
|
||
onSelect(value: number) { | ||
this.selectedValue = value; | ||
} | ||
} | ||
|
||
|
||
function createCell(value: number) { | ||
return new MdCalendarCell(value, `${value}`); | ||
} |
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.
Is it (2016, 10, 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.
The months are 0-indexed to match native JS Dates, so 10 = November. I considered using 1-indexed because it make more sense, but I thought it was better to just stay consistent with what people already know. I'll add a note about the 0-indexing to the SimpleDate docs.