From 7c30ed56300b6c0c643c848c109d3326bdd3706e Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Tue, 24 Oct 2017 16:53:34 +0300 Subject: [PATCH 01/11] Adding basic month selection --- src/components/date-picker/calendar.tsx | 60 +++++++++++++++---- src/components/date-picker/date-picker.st.css | 27 +++++++++ test-kit/components/date-picker-driver.ts | 12 ++++ test/components/date-picker.spec.tsx | 14 +++++ 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index 070755456..96c0a8b03 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -24,11 +24,19 @@ export interface CalendarProps { updateDropdownDate(date: Date): void; } +export interface CalendarState { + showMonthView: boolean; +} + const monthNames = getMonthNames(); @stylable(styles) @observer -export class Calendar extends React.Component { +export class Calendar extends React.Component { + public componentWillMount() { + this.setState({showMonthView: false}); + } + public render() { return (
@@ -42,10 +50,18 @@ export class Calendar extends React.Component { > - - - {this.monthName} - + + {this.state.showMonthView ? + null + : + + {this.monthName} + + }   {this.year} @@ -57,12 +73,18 @@ export class Calendar extends React.Component {
-
- {this.dayNames} - {this.previousDays} - {this.days} - {this.followingDays} -
+ {this.state.showMonthView ? +
+ {this.monthArray} +
+ : +
+ {this.dayNames} + {this.previousDays} + {this.days} + {this.followingDays} +
+ } ); @@ -160,6 +182,17 @@ export class Calendar extends React.Component { return followingDays; } + @computed + get monthArray(): JSX.Element[] { + const monthArray: JSX.Element[] = []; + + monthNames.forEach(month => { + monthArray.push({month}); + }); + + return monthArray; + } + private isCurrentDay(day: number): boolean { const currentDate = new Date(); return (this.props.value.getFullYear() === currentDate.getFullYear() @@ -199,4 +232,9 @@ export class Calendar extends React.Component { getMonthFromOffset(new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), -1); this.props.updateDropdownDate(previousMonth); } + + private toggleMonthView: React.EventHandler> = event => { + event.preventDefault(); + this.setState({showMonthView: !this.state.showMonthView}); + } } diff --git a/src/components/date-picker/date-picker.st.css b/src/components/date-picker/date-picker.st.css index 9b12dc9ae..b827a2df4 100644 --- a/src/components/date-picker/date-picker.st.css +++ b/src/components/date-picker/date-picker.st.css @@ -85,6 +85,7 @@ /* Contains current month and year */ .headerDate { display: inline; + cursor: pointer; width: 150px; height: 24px; font-family: value(fontFamily); @@ -94,6 +95,10 @@ color: value(color_MainText_Background); } +.headerDate:hover { + background-color: rgba(255, 255, 255, 0.18); +} + /* General styling for the month buttons */ .arrowWrapper { height: 100%; @@ -146,6 +151,10 @@ flex: 0 0 14.2857%; } +.month-view { + -st-extends: calendar; +} + /* Styling for the days of the week */ .dayName { font-family: value(fontFamily); @@ -159,6 +168,24 @@ cursor: default; } +.monthName { + /* 100/3 = 33.3333... show 3 months per row */ + flex: 0 0 33.3333%; + line-height: 54px; + font-family: value(fontFamily); + font-size: 14px; + font-weight: bold; + text-align: center; + color: value(color_MainText); + cursor: pointer; +} + +/* Hover styles for month selection */ +.monthName:hover { + color: value(color_MainText); + background-color: value(color_Keyboard_Focused); +} + /* Styling for the days of the month */ .day { -st-states: focused, selected, current, inactive; diff --git a/test-kit/components/date-picker-driver.ts b/test-kit/components/date-picker-driver.ts index aec0f4c5a..688bb5da7 100644 --- a/test-kit/components/date-picker-driver.ts +++ b/test-kit/components/date-picker-driver.ts @@ -41,6 +41,10 @@ export class DatePickerTestDriver extends DriverBase { simulate.mouseDown(this.prevMonthLabel); } + public clickOnHeader(): void { + simulate.mouseDown(this.calendarHeader); + } + public openCalender(): void { simulate.click(this.select('CALENDAR_ICON')); } @@ -69,6 +73,14 @@ export class DatePickerTestDriver extends DriverBase { return bodySelect(datePickerDropdown); } + public get calendarHeader(): HTMLSpanElement | null { + return bodySelect('CALENDAR_HEADER'); + } + + public get monthView(): HTMLDivElement | null { + return bodySelect('MONTH_VIEW'); + } + public getDay(day: number | string): HTMLSpanElement | null { return bodySelect(datePickerDropdown, `DAY_${day}`); } diff --git a/test/components/date-picker.spec.tsx b/test/components/date-picker.spec.tsx index 8575d2ad3..0c6069ae0 100644 --- a/test/components/date-picker.spec.tsx +++ b/test/components/date-picker.spec.tsx @@ -481,6 +481,20 @@ describe('The DatePicker Component', () => { await waitForDom(() => expect(datePicker.dropDown).to.be.present()); }); + + it('should show a list of the months when the year/month header is clicked, and then the header should' + + ' display only the year', async () => { + const {driver: datePicker, waitForDom} = clientRenderer.render( + + ).withDriver(DatePickerTestDriver); + + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.monthView).to.be.present(); + expect(datePicker.monthLabel).to.be.absent(); + }); + }); }); describe('The Helper Functions', () => { From 92b56a7dfc7ff74c1c1d0456bb83e542392566ce Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Tue, 24 Oct 2017 19:30:15 +0300 Subject: [PATCH 02/11] Added tests for date selection, month selection is finished --- src/components/date-picker/calendar.tsx | 23 ++++++++++++++++++- test-kit/components/date-picker-driver.ts | 8 +++++++ test/components/date-picker.spec.tsx | 27 +++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index 96c0a8b03..7d0964238 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -187,7 +187,16 @@ export class Calendar extends React.Component { const monthArray: JSX.Element[] = []; monthNames.forEach(month => { - monthArray.push({month}); + monthArray.push( + + {month} + + ); }); return monthArray; @@ -219,6 +228,18 @@ export class Calendar extends React.Component { } } + private selectMonth: React.EventHandler> = event => { + event.preventDefault(); + const eventTarget = event.target as HTMLSpanElement; + this.setState({showMonthView: !this.state.showMonthView}); + + const date = new Date(this.props.value.getFullYear(), + monthNames.indexOf(eventTarget.textContent!), + this.props.value.getDate()); + + this.props.updateDropdownDate(date); + } + private goToNextMonth: React.EventHandler> = event => { event.preventDefault(); const nextMonth: Date = diff --git a/test-kit/components/date-picker-driver.ts b/test-kit/components/date-picker-driver.ts index 688bb5da7..38257daea 100644 --- a/test-kit/components/date-picker-driver.ts +++ b/test-kit/components/date-picker-driver.ts @@ -45,6 +45,10 @@ export class DatePickerTestDriver extends DriverBase { simulate.mouseDown(this.calendarHeader); } + public clickOnMonth(month: string): void { + simulate.mouseDown(this.getMonth(month)); + } + public openCalender(): void { simulate.click(this.select('CALENDAR_ICON')); } @@ -108,4 +112,8 @@ export class DatePickerTestDriver extends DriverBase { public get monthLabel(): HTMLSpanElement | null { return bodySelect(datePickerDropdown, 'MONTH_NAME'); } + + public getMonth(month: string): HTMLSpanElement | null { + return bodySelect(datePickerDropdown, `MONTH_${month.toUpperCase()}`); + } } diff --git a/test/components/date-picker.spec.tsx b/test/components/date-picker.spec.tsx index 0c6069ae0..42edd3bb9 100644 --- a/test/components/date-picker.spec.tsx +++ b/test/components/date-picker.spec.tsx @@ -7,6 +7,7 @@ import { getDayNames, getDaysInMonth, getMonthFromOffset, + getMonthNames, getNumOfFollowingDays, getNumOfPreviousDays } from '../../src/utils'; @@ -484,6 +485,7 @@ describe('The DatePicker Component', () => { it('should show a list of the months when the year/month header is clicked, and then the header should' + ' display only the year', async () => { + const monthNames = getMonthNames(); const {driver: datePicker, waitForDom} = clientRenderer.render( ).withDriver(DatePickerTestDriver); @@ -493,6 +495,31 @@ describe('The DatePicker Component', () => { await waitForDom(() => { expect(datePicker.monthView).to.be.present(); expect(datePicker.monthLabel).to.be.absent(); + monthNames.forEach(month => expect(datePicker.getMonth(month)).to.be.present()); + }); + }); + + it('when in month-select view, clicking on a month should change the month' + + ' and hide the list of months', async () => { + const monthToClick = 'March'; + const {driver: datePicker, waitForDom} = clientRenderer.render( + + ).withDriver(DatePickerTestDriver); + + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.monthView).to.be.present(); + expect(datePicker.monthLabel).to.be.absent(); + }); + + datePicker.clickOnMonth(monthToClick); + + await waitForDom(() => { + expect(datePicker.monthView).to.be.absent(); + expect(datePicker.monthLabel).to.be.present(); + expect(datePicker.isOpen()).to.equal(true); + expect(datePicker.monthLabel).to.have.text(monthToClick); }); }); }); From e2ae1de8f92b1ed8c4b30fb9970d539edaecd15d Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Thu, 26 Oct 2017 15:07:07 +0300 Subject: [PATCH 03/11] Better wording --- test/components/date-picker.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/components/date-picker.spec.tsx b/test/components/date-picker.spec.tsx index 42edd3bb9..7853687e7 100644 --- a/test/components/date-picker.spec.tsx +++ b/test/components/date-picker.spec.tsx @@ -499,8 +499,8 @@ describe('The DatePicker Component', () => { }); }); - it('when in month-select view, clicking on a month should change the month' - + ' and hide the list of months', async () => { + it('when in the month-view, clicking on a month should change the current month' + + ' to the selected month, and then hide the month-view', async () => { const monthToClick = 'March'; const {driver: datePicker, waitForDom} = clientRenderer.render( From 4f17f56f845e623ef2c04e5da0e0feabb75e2574 Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Sun, 29 Oct 2017 12:01:01 +0200 Subject: [PATCH 04/11] First commits --- src/components/date-picker/calendar.tsx | 6 +++++- test-kit/components/date-picker-driver.ts | 4 ++++ test/components/date-picker.spec.tsx | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index 7d0964238..f6900c114 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -26,6 +26,7 @@ export interface CalendarProps { export interface CalendarState { showMonthView: boolean; + showYearView: boolean; } const monthNames = getMonthNames(); @@ -34,7 +35,10 @@ const monthNames = getMonthNames(); @observer export class Calendar extends React.Component { public componentWillMount() { - this.setState({showMonthView: false}); + this.setState({ + showMonthView: false, + showYearView: false + }); } public render() { diff --git a/test-kit/components/date-picker-driver.ts b/test-kit/components/date-picker-driver.ts index 38257daea..9b3ebc585 100644 --- a/test-kit/components/date-picker-driver.ts +++ b/test-kit/components/date-picker-driver.ts @@ -85,6 +85,10 @@ export class DatePickerTestDriver extends DriverBase { return bodySelect('MONTH_VIEW'); } + public get yearView(): HTMLDivElement | null { + return bodySelect('YEAR_VIEW'); + } + public getDay(day: number | string): HTMLSpanElement | null { return bodySelect(datePickerDropdown, `DAY_${day}`); } diff --git a/test/components/date-picker.spec.tsx b/test/components/date-picker.spec.tsx index 7853687e7..8465298b1 100644 --- a/test/components/date-picker.spec.tsx +++ b/test/components/date-picker.spec.tsx @@ -522,6 +522,28 @@ describe('The DatePicker Component', () => { expect(datePicker.monthLabel).to.have.text(monthToClick); }); }); + + it('when in the month-view, clicking on the year should bring up the year-view', async () => { + const {driver: datePicker, waitForDom} = clientRenderer.render( + + ).withDriver(DatePickerTestDriver); + + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.monthView).to.be.present(); + expect(datePicker.monthLabel).to.be.absent(); + }); + + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.monthView).to.be.absent(); + expect(datePicker.yearView).to.be.present(); + expect(datePicker.monthLabel).to.be.absent(); + expect(datePicker.yearLabel).to.be.absent(); + }); + }); }); describe('The Helper Functions', () => { From 001f28fef33a2c00a3ad1529e9e7c65981a265bf Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Tue, 7 Nov 2017 14:29:04 +0200 Subject: [PATCH 05/11] Tests are passing --- src/components/date-picker/calendar.tsx | 166 +++++++++++++----- src/components/date-picker/date-picker.st.css | 22 +++ test-kit/components/date-picker-driver.ts | 12 +- test/components/date-picker.spec.tsx | 116 ++++++++++-- 4 files changed, 261 insertions(+), 55 deletions(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index dfa10d62b..e8003fd6e 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -1,4 +1,4 @@ -import {computed} from 'mobx'; +import {computed, observable} from 'mobx'; import {observer} from 'mobx-react'; import * as React from 'react'; import {stylable} from 'wix-react-tools'; @@ -31,16 +31,18 @@ export interface CalendarProps { export interface CalendarState { showMonthView: boolean; showYearView: boolean; + viewDate: Date; } const monthNames = getMonthNames(); @stylable(styles) @observer -export class Calendar extends React.Component { - public state: CalendarState = { +export class Calendar extends React.Component { + @observable public state: CalendarState = { showMonthView: false, - showYearView: false + showYearView: false, + viewDate: this.props.value }; public render() { @@ -59,7 +61,7 @@ export class Calendar extends React.Component { {this.getHeader()} @@ -71,28 +73,23 @@ export class Calendar extends React.Component { - {this.state.showMonthView ? -
- {this.monthArray} -
- : -
- {this.dayNames} - {this.previousDays} - {this.days} - {this.followingDays} -
- } + {this.getCalendarView()} ); } private getHeader = () => { - if (this.state.showMonthView) { + if (this.state.showYearView) { + const decade = `${this.state.viewDate.getFullYear() - 5}-${this.state.viewDate.getFullYear() + 5}` return ( - {this.year} + {decade} + ); + } else if (this.state.showMonthView) { + return ( + + {this.state.viewDate.getFullYear()} ); } else { return ( @@ -102,6 +99,31 @@ export class Calendar extends React.Component { } } + private getCalendarView = () => { + if (this.state.showYearView) { + return ( +
+ {this.yearArray} +
+ ); + } else if (this.state.showMonthView) { + return ( +
+ {this.monthArray} +
+ ); + } else { + return ( +
+ {this.dayNames} + {this.previousDays} + {this.days} + {this.followingDays} +
+ ); + } + } + @computed private get monthName(): string { return monthNames[this.props.value.getMonth()]; @@ -205,19 +227,41 @@ export class Calendar extends React.Component { monthNames.forEach(month => { monthArray.push( {month} - ); + ); }); return monthArray; } + @computed + private get yearArray(): JSX.Element[] { + const yearArray: JSX.Element[] = []; + + for (let year = this.state.viewDate.getFullYear() - 5; year <= this.state.viewDate.getFullYear() + 5; year ++) { + if (year === this.props.value.getFullYear()) { continue; } + + yearArray.push( + + {year} + + ); + }; + + return yearArray; + } + private isCurrentDay(day: number): boolean { const currentDate = new Date(); return (this.props.value.getFullYear() === currentDate.getFullYear() @@ -244,46 +288,88 @@ export class Calendar extends React.Component { } } - private toggleMonthView = () => { - this.setState({showMonthView: !this.state.showMonthView}); + private toggleDateSelectView = () => { + if (this.state.showYearView && this.state.showMonthView) { + this.state.showYearView = false; + this.state.showMonthView = false; + this.resetViewDate(); + } else if (this.state.showYearView) { + this.state.showYearView = false; + this.state.showMonthView = true; + } else if (this.state.showMonthView) { + this.state.showYearView = true; + this.state.showMonthView = true; + } else { + this.state.showMonthView = true; + } + } + + private closeDateSelectView = () => { + if (this.state.showYearView) { + this.state.showYearView = false; + this.state.showMonthView = true; + } else if (this.state.showMonthView) { + this.state.showYearView = false; + this.state.showMonthView = false; + } + } + + private resetViewDate = () => { + this.state.viewDate = this.props.value; } private onSelectMonth: React.EventHandler> = event => { event.preventDefault(); - this.toggleMonthView(); - const date = new Date(this.props.value.getFullYear(), + const date = new Date(this.state.viewDate.getFullYear(), monthNames.indexOf((event.target as HTMLSpanElement).textContent!), - this.props.value.getDate()); + this.state.viewDate.getDate()); this.props.updateDropdownDate(date); + this.state.viewDate = date; + this.closeDateSelectView(); } - private goToNextMonth: React.EventHandler> = event => { + private onSelectYear: React.EventHandler> = event => { event.preventDefault(); - const nextDate: Date = this.state.showMonthView - ? new Date(this.props.value.getFullYear() + 1, this.props.value.getMonth(), 1) - : getMonthFromOffset(new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), 1); - this.props.updateDropdownDate(nextDate); + const date = new Date(parseInt((event.target as HTMLSpanElement).textContent!), + this.state.viewDate.getMonth(), + this.state.viewDate.getDate()); + + this.props.updateDropdownDate(date); + this.state.viewDate = date; + this.closeDateSelectView(); } - private goToPrevMonth: React.EventHandler> = event => { + private goToNextMonth: React.EventHandler> = event => { event.preventDefault(); - const nextDate: Date = this.state.showMonthView - ? new Date(this.props.value.getFullYear() - 1, this.props.value.getMonth(), 1) - : getMonthFromOffset(new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), -1); - this.props.updateDropdownDate(nextDate); + if (this.state.showMonthView || this.state.showYearView) { + this.state.viewDate = this.state.showYearView + ? new Date(this.state.viewDate.getFullYear() + 10, this.state.viewDate.getMonth(), 1) + : new Date(this.state.viewDate.getFullYear() + 1, this.state.viewDate.getMonth(), 1); + } else { + const nextDate: Date = getMonthFromOffset(new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), 1); + this.props.updateDropdownDate(nextDate); + } } - private headerClicked: React.EventHandler> = event => { + private goToPrevMonth: React.EventHandler> = event => { event.preventDefault(); - this.toggleMonthView(); + + if (this.state.showMonthView || this.state.showYearView) { + this.state.viewDate = this.state.showYearView + ? new Date(this.state.viewDate.getFullYear() - 10, this.state.viewDate.getMonth(), 1) + : new Date(this.state.viewDate.getFullYear() - 1, this.state.viewDate.getMonth(), 1); + } else { + const nextDate: Date = getMonthFromOffset(new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), -1); + this.props.updateDropdownDate(nextDate); + } } - private toggleMonthView: React.EventHandler> = event => { + private onHeaderClick: React.EventHandler> = event => { event.preventDefault(); - this.setState({showMonthView: !this.state.showMonthView}); + this.toggleDateSelectView(); } } diff --git a/src/components/date-picker/date-picker.st.css b/src/components/date-picker/date-picker.st.css index cdda2a891..da54df45c 100644 --- a/src/components/date-picker/date-picker.st.css +++ b/src/components/date-picker/date-picker.st.css @@ -165,6 +165,10 @@ -st-extends: calendar; } +.year-view { + -st-extends: calendar; +} + /* Styling for the days of the week */ .dayName { font-family: value(fontFamily); @@ -196,6 +200,24 @@ background-color: value(color_Keyboard_Focused); } +.year { + /* 100/2 = 50... show 2 years per row */ + flex: 0 0 50%; + line-height: 54px; + font-family: value(fontFamily); + font-size: 14px; + font-weight: bold; + text-align: center; + color: value(color_MainText); + cursor: pointer; +} + +/* Hover styles for year selection */ +.year:hover { + color: value(color_MainText); + background-color: value(color_Keyboard_Focused); +} + /* Styling for the days of the month */ .day { -st-states: focused, selected, current, inactive, disabled; diff --git a/test-kit/components/date-picker-driver.ts b/test-kit/components/date-picker-driver.ts index 56c05ce02..024cc8570 100644 --- a/test-kit/components/date-picker-driver.ts +++ b/test-kit/components/date-picker-driver.ts @@ -51,6 +51,10 @@ export class DatePickerTestDriver extends DriverBase { simulate.mouseDown(this.getMonth(month)); } + public clickOnYear(year: string): void { + simulate.mouseDown(this.getYear(year)); + } + public openCalender(): void { simulate.click(this.select('CALENDAR_ICON')); } @@ -119,11 +123,11 @@ export class DatePickerTestDriver extends DriverBase { return bodySelect(datePickerDropdown, `MONTH_${month.toUpperCase()}`); } - public elementHasStylableState(element: Element, stateName: string): boolean { - return elementHasStylableState(element, baseStyle, stateName); + public getYear(year: string): HTMLSpanElement | null { + return bodySelect(datePickerDropdown, `YEAR_${year}`); } - public getMonth(month: string): HTMLSpanElement | null { - return bodySelect(datePickerDropdown, `MONTH_${month.toUpperCase()}`); + public hasStylableState(element: Element, stateName: string): boolean { + return elementHasStylableState(element, baseStyle, stateName); } } diff --git a/test/components/date-picker.spec.tsx b/test/components/date-picker.spec.tsx index 070ad4b88..e9004b193 100644 --- a/test/components/date-picker.spec.tsx +++ b/test/components/date-picker.spec.tsx @@ -83,7 +83,7 @@ describe('The DatePicker Component', () => { datePickerDemo.datePicker.clickOnMonth(monthToClick); await waitForDom(() => { - expect(datePickerDemo.datePicker.monthView).to.be.absent(); + expect(datePickerDemo.datePicker.monthView, 'expected month view to be absent').to.be.absent(); expect(datePickerDemo.datePicker.isOpen()).to.equal(true); expect(datePickerDemo.datePicker.headerDate).to.have.text(`${monthToClick} 2017`); }); @@ -231,7 +231,7 @@ describe('The DatePicker Component', () => { datePicker.changeDate('2sgsdfsdfw223'); - await waitForDom(() => expect(datePicker.elementHasStylableState(datePicker.root, 'error')).to.equal(true)); + await waitForDom(() => expect(datePicker.hasStylableState(datePicker.root, 'error')).to.equal(true)); }); it('should remove error state when a date is chosen from the calendar', async () => { @@ -240,13 +240,13 @@ describe('The DatePicker Component', () => { datePicker.changeDate('2sgsdfsdfw223'); - await waitForDom(() => expect(datePicker.elementHasStylableState(datePicker.root, 'error')).to.equal(true)); + await waitForDom(() => expect(datePicker.hasStylableState(datePicker.root, 'error')).to.equal(true)); datePicker.openCalender(); await waitForDom(() => datePicker.clickOnDay(2)); - await waitForDom(() => expect(datePicker.elementHasStylableState(datePicker.root, 'error')) + await waitForDom(() => expect(datePicker.hasStylableState(datePicker.root, 'error')) .to.equal(false)); }); }); @@ -302,7 +302,7 @@ describe('The DatePicker Component', () => { const {driver: datePicker, waitForDom} = clientRenderer.render() .withDriver(DatePickerTestDriver); - await waitForDom(() => expect(datePicker.elementHasStylableState(datePicker.root, 'disabled')) + await waitForDom(() => expect(datePicker.hasStylableState(datePicker.root, 'disabled')) .to.equal(true)); }); }); @@ -358,7 +358,7 @@ describe('The DatePicker Component', () => { const {driver: datePicker, waitForDom} = clientRenderer.render() .withDriver(DatePickerTestDriver); - await waitForDom(() => expect(datePicker.elementHasStylableState(datePicker.root, 'readOnly')) + await waitForDom(() => expect(datePicker.hasStylableState(datePicker.root, 'readOnly')) .to.equal(true)); }); }); @@ -613,7 +613,8 @@ describe('The DatePicker Component', () => { }); }); - it('when in the month-view, clicking on the year should bring up the year-view', async () => { + it('when in the month-view, clicking on the year should bring up the year-view' + + ' and the header date should show a decade', async () => { const {driver: datePicker, waitForDom} = clientRenderer.render( ).withDriver(DatePickerTestDriver); @@ -622,7 +623,7 @@ describe('The DatePicker Component', () => { await waitForDom(() => { expect(datePicker.monthView).to.be.present(); - expect(datePicker.monthLabel).to.be.absent(); + expect(datePicker.headerDate).to.have.text('2017'); }); datePicker.clickOnHeader(); @@ -630,11 +631,104 @@ describe('The DatePicker Component', () => { await waitForDom(() => { expect(datePicker.monthView).to.be.absent(); expect(datePicker.yearView).to.be.present(); - expect(datePicker.monthLabel).to.be.absent(); - expect(datePicker.yearLabel).to.be.absent(); + expect(datePicker.headerDate).to.have.text('2012-2022'); + }); + }); + + it('when in the month-view, clicking on the arrows should not change the actual date', async () => { + const {driver: datePicker, waitForDom} = clientRenderer.render( + + ).withDriver(DatePickerTestDriver); + + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.monthView).to.be.present(); + expect(datePicker.headerDate).to.have.text('2017'); + }); + + datePicker.clickOnPrevMonth(); + datePicker.clickOnPrevMonth(); + datePicker.clickOnHeader(); + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.monthView).to.be.absent(); + expect(datePicker.yearView).to.be.absent(); + expect(datePicker.headerDate).to.have.text('January 2017'); + }); + }); + + it('when in the year-view, clicking on the arrows should change the decade', async () => { + const {driver: datePicker, waitForDom} = clientRenderer.render( + + ).withDriver(DatePickerTestDriver); + + datePicker.clickOnHeader(); + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.yearView).to.be.present(); + expect(datePicker.headerDate).to.have.text('2012-2022'); + }); + + datePicker.clickOnNextMonth(); + + await waitForDom(() => { + expect(datePicker.yearView).to.be.present(); + expect(datePicker.headerDate).to.have.text('2022-2032'); + }); + + datePicker.clickOnPrevMonth(); + datePicker.clickOnPrevMonth(); + + await waitForDom(() => { + expect(datePicker.yearView).to.be.present(); + expect(datePicker.headerDate).to.have.text('2002-2012'); }); }); + it('when in the year-view, clicking on a year should change the current year' + + ' to the selected year, and then hide the year-view, showing the month-view', async () => { + const yearToClick = '2019'; + const {driver: datePicker, waitForDom} = clientRenderer.render( + + ).withDriver(DatePickerTestDriver); + + datePicker.clickOnHeader(); + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.yearView).to.be.present(); + expect(datePicker.headerDate).to.have.text('2012-2022'); + }); + + datePicker.clickOnYear(yearToClick); + + await waitForDom(() => { + expect(datePicker.yearView).to.be.absent(); + expect(datePicker.monthView).to.be.present(); + expect(datePicker.isOpen()).to.equal(true); + expect(datePicker.headerDate).to.have.text(`${yearToClick}`); + }); + }); + + it('when in the year-view, it should not show the current year', async () => { + const currentYear = '2017'; + const {driver: datePicker, waitForDom} = clientRenderer.render( + + ).withDriver(DatePickerTestDriver); + + datePicker.clickOnHeader(); + datePicker.clickOnHeader(); + + await waitForDom(() => { + expect(datePicker.getYear(currentYear)).to.be.absent(); + }); + }); + + + it('should allow disabling weekends', async () => { const {driver: datePicker, waitForDom} = clientRenderer.render( { await waitForDom(() => { // Check a weekend to ensure the days are disabled - expect(datePicker.elementHasStylableState(datePicker.getDay(4) as Element, 'disabled')).to.be.true; + expect(datePicker.hasStylableState(datePicker.getDay(4) as Element, 'disabled')).to.be.true; }); datePicker.clickOnDay(4); From 3f2842995968ecb5ab95ee024a34d3702689190d Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Tue, 7 Nov 2017 14:47:26 +0200 Subject: [PATCH 06/11] Refactored --- src/components/date-picker/calendar.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index e8003fd6e..8a2b8bda3 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -81,7 +81,7 @@ export class Calendar extends React.Component { private getHeader = () => { if (this.state.showYearView) { - const decade = `${this.state.viewDate.getFullYear() - 5}-${this.state.viewDate.getFullYear() + 5}` + const decade = `${this.state.viewDate.getFullYear() - 5}-${this.state.viewDate.getFullYear() + 5}`; return ( {decade} @@ -257,7 +257,7 @@ export class Calendar extends React.Component { {year} ); - }; + } return yearArray; } @@ -333,7 +333,7 @@ export class Calendar extends React.Component { private onSelectYear: React.EventHandler> = event => { event.preventDefault(); - const date = new Date(parseInt((event.target as HTMLSpanElement).textContent!), + const date = new Date(parseInt((event.target as HTMLSpanElement).textContent!, 10), this.state.viewDate.getMonth(), this.state.viewDate.getDate()); @@ -350,7 +350,8 @@ export class Calendar extends React.Component { ? new Date(this.state.viewDate.getFullYear() + 10, this.state.viewDate.getMonth(), 1) : new Date(this.state.viewDate.getFullYear() + 1, this.state.viewDate.getMonth(), 1); } else { - const nextDate: Date = getMonthFromOffset(new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), 1); + const nextDate: Date = getMonthFromOffset( + new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), 1); this.props.updateDropdownDate(nextDate); } } @@ -363,7 +364,8 @@ export class Calendar extends React.Component { ? new Date(this.state.viewDate.getFullYear() - 10, this.state.viewDate.getMonth(), 1) : new Date(this.state.viewDate.getFullYear() - 1, this.state.viewDate.getMonth(), 1); } else { - const nextDate: Date = getMonthFromOffset(new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), -1); + const nextDate: Date = getMonthFromOffset( + new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), -1); this.props.updateDropdownDate(nextDate); } } From bb9260e3bec1179fac4537c2200a3c504743a450 Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Tue, 7 Nov 2017 14:50:31 +0200 Subject: [PATCH 07/11] Removed blank lines --- test/components/date-picker.spec.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/components/date-picker.spec.tsx b/test/components/date-picker.spec.tsx index e9004b193..9a7f35b7b 100644 --- a/test/components/date-picker.spec.tsx +++ b/test/components/date-picker.spec.tsx @@ -727,8 +727,6 @@ describe('The DatePicker Component', () => { }); }); - - it('should allow disabling weekends', async () => { const {driver: datePicker, waitForDom} = clientRenderer.render( Date: Mon, 13 Nov 2017 15:02:47 +0200 Subject: [PATCH 08/11] Added comment --- src/components/date-picker/calendar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index 8a2b8bda3..2d30bc78e 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -245,6 +245,7 @@ export class Calendar extends React.Component { const yearArray: JSX.Element[] = []; for (let year = this.state.viewDate.getFullYear() - 5; year <= this.state.viewDate.getFullYear() + 5; year ++) { + // Don't show the current year as an option to select from if (year === this.props.value.getFullYear()) { continue; } yearArray.push( From f972863bf50f4afedcb86aae5f82941e1988420a Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Mon, 13 Nov 2017 15:16:44 +0200 Subject: [PATCH 09/11] Changed behaviour --- src/components/date-picker/calendar.tsx | 7 ++----- test/components/date-picker.spec.tsx | 24 +++++------------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index 2d30bc78e..e336ff207 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -81,7 +81,7 @@ export class Calendar extends React.Component { private getHeader = () => { if (this.state.showYearView) { - const decade = `${this.state.viewDate.getFullYear() - 5}-${this.state.viewDate.getFullYear() + 5}`; + const decade = `${this.state.viewDate.getFullYear() - 5}-${this.state.viewDate.getFullYear() + 4}`; return ( {decade} @@ -244,10 +244,7 @@ export class Calendar extends React.Component { private get yearArray(): JSX.Element[] { const yearArray: JSX.Element[] = []; - for (let year = this.state.viewDate.getFullYear() - 5; year <= this.state.viewDate.getFullYear() + 5; year ++) { - // Don't show the current year as an option to select from - if (year === this.props.value.getFullYear()) { continue; } - + for (let year = this.state.viewDate.getFullYear() - 5; year <= this.state.viewDate.getFullYear() + 4; year ++) { yearArray.push( { await waitForDom(() => { expect(datePicker.monthView).to.be.absent(); expect(datePicker.yearView).to.be.present(); - expect(datePicker.headerDate).to.have.text('2012-2022'); + expect(datePicker.headerDate).to.have.text('2012-2021'); }); }); @@ -669,14 +669,14 @@ describe('The DatePicker Component', () => { await waitForDom(() => { expect(datePicker.yearView).to.be.present(); - expect(datePicker.headerDate).to.have.text('2012-2022'); + expect(datePicker.headerDate).to.have.text('2012-2021'); }); datePicker.clickOnNextMonth(); await waitForDom(() => { expect(datePicker.yearView).to.be.present(); - expect(datePicker.headerDate).to.have.text('2022-2032'); + expect(datePicker.headerDate).to.have.text('2022-2031'); }); datePicker.clickOnPrevMonth(); @@ -684,7 +684,7 @@ describe('The DatePicker Component', () => { await waitForDom(() => { expect(datePicker.yearView).to.be.present(); - expect(datePicker.headerDate).to.have.text('2002-2012'); + expect(datePicker.headerDate).to.have.text('2002-2011'); }); }); @@ -700,7 +700,7 @@ describe('The DatePicker Component', () => { await waitForDom(() => { expect(datePicker.yearView).to.be.present(); - expect(datePicker.headerDate).to.have.text('2012-2022'); + expect(datePicker.headerDate).to.have.text('2012-2021'); }); datePicker.clickOnYear(yearToClick); @@ -713,20 +713,6 @@ describe('The DatePicker Component', () => { }); }); - it('when in the year-view, it should not show the current year', async () => { - const currentYear = '2017'; - const {driver: datePicker, waitForDom} = clientRenderer.render( - - ).withDriver(DatePickerTestDriver); - - datePicker.clickOnHeader(); - datePicker.clickOnHeader(); - - await waitForDom(() => { - expect(datePicker.getYear(currentYear)).to.be.absent(); - }); - }); - it('should allow disabling weekends', async () => { const {driver: datePicker, waitForDom} = clientRenderer.render( Date: Tue, 14 Nov 2017 15:20:15 +0200 Subject: [PATCH 10/11] Renamed state --- src/components/date-picker/calendar.tsx | 89 +++++++++++-------------- 1 file changed, 39 insertions(+), 50 deletions(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index e336ff207..eaf425963 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -29,8 +29,7 @@ export interface CalendarProps { } export interface CalendarState { - showMonthView: boolean; - showYearView: boolean; + view: 'year' | 'month' | 'default'; viewDate: Date; } @@ -39,9 +38,8 @@ const monthNames = getMonthNames(); @stylable(styles) @observer export class Calendar extends React.Component { - @observable public state: CalendarState = { - showMonthView: false, - showYearView: false, + @observable public calendarState: CalendarState = { + view: 'default', viewDate: this.props.value }; @@ -80,16 +78,17 @@ export class Calendar extends React.Component { } private getHeader = () => { - if (this.state.showYearView) { - const decade = `${this.state.viewDate.getFullYear() - 5}-${this.state.viewDate.getFullYear() + 4}`; + if (this.calendarState.view === 'year') { + const decade = `${this.calendarState.viewDate.getFullYear() - 5} + -${this.calendarState.viewDate.getFullYear() + 4}`; return ( {decade} ); - } else if (this.state.showMonthView) { + } else if (this.calendarState.view === 'month') { return ( - {this.state.viewDate.getFullYear()} + {this.calendarState.viewDate.getFullYear()} ); } else { return ( @@ -100,15 +99,15 @@ export class Calendar extends React.Component { } private getCalendarView = () => { - if (this.state.showYearView) { + if (this.calendarState.view === 'year') { return ( -
+
{this.yearArray}
); - } else if (this.state.showMonthView) { + } else if (this.calendarState.view === 'month') { return ( -
+
{this.monthArray}
); @@ -244,7 +243,8 @@ export class Calendar extends React.Component { private get yearArray(): JSX.Element[] { const yearArray: JSX.Element[] = []; - for (let year = this.state.viewDate.getFullYear() - 5; year <= this.state.viewDate.getFullYear() + 4; year ++) { + for (let year = this.calendarState.viewDate.getFullYear() - 5; + year <= this.calendarState.viewDate.getFullYear() + 4; year ++) { yearArray.push( { } private toggleDateSelectView = () => { - if (this.state.showYearView && this.state.showMonthView) { - this.state.showYearView = false; - this.state.showMonthView = false; + if (this.calendarState.view === 'year') { + this.calendarState.view = 'default'; this.resetViewDate(); - } else if (this.state.showYearView) { - this.state.showYearView = false; - this.state.showMonthView = true; - } else if (this.state.showMonthView) { - this.state.showYearView = true; - this.state.showMonthView = true; - } else { - this.state.showMonthView = true; + } else if (this.calendarState.view === 'default') { + this.calendarState.view = 'month'; + } else if (this.calendarState.view === 'month') { + this.calendarState.view = 'year'; } } private closeDateSelectView = () => { - if (this.state.showYearView) { - this.state.showYearView = false; - this.state.showMonthView = true; - } else if (this.state.showMonthView) { - this.state.showYearView = false; - this.state.showMonthView = false; + if (this.calendarState.view === 'year') { + this.calendarState.view = 'month'; + } else if (this.calendarState.view === 'month') { + this.calendarState.view = 'default'; } } private resetViewDate = () => { - this.state.viewDate = this.props.value; + this.calendarState.viewDate = this.props.value; } private onSelectMonth: React.EventHandler> = event => { - event.preventDefault(); - - const date = new Date(this.state.viewDate.getFullYear(), + const date = new Date(this.calendarState.viewDate.getFullYear(), monthNames.indexOf((event.target as HTMLSpanElement).textContent!), - this.state.viewDate.getDate()); + this.calendarState.viewDate.getDate()); this.props.updateDropdownDate(date); - this.state.viewDate = date; + this.calendarState.viewDate = date; this.closeDateSelectView(); } private onSelectYear: React.EventHandler> = event => { - event.preventDefault(); - const date = new Date(parseInt((event.target as HTMLSpanElement).textContent!, 10), - this.state.viewDate.getMonth(), - this.state.viewDate.getDate()); + this.calendarState.viewDate.getMonth(), + this.calendarState.viewDate.getDate()); this.props.updateDropdownDate(date); - this.state.viewDate = date; + this.calendarState.viewDate = date; this.closeDateSelectView(); } private goToNextMonth: React.EventHandler> = event => { event.preventDefault(); - if (this.state.showMonthView || this.state.showYearView) { - this.state.viewDate = this.state.showYearView - ? new Date(this.state.viewDate.getFullYear() + 10, this.state.viewDate.getMonth(), 1) - : new Date(this.state.viewDate.getFullYear() + 1, this.state.viewDate.getMonth(), 1); + if (this.calendarState.view !== 'default') { + this.calendarState.viewDate = this.calendarState.view === 'year' + ? new Date(this.calendarState.viewDate.getFullYear() + 10, this.calendarState.viewDate.getMonth(), 1) + : new Date(this.calendarState.viewDate.getFullYear() + 1, this.calendarState.viewDate.getMonth(), 1); } else { const nextDate: Date = getMonthFromOffset( new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), 1); @@ -357,10 +346,10 @@ export class Calendar extends React.Component { private goToPrevMonth: React.EventHandler> = event => { event.preventDefault(); - if (this.state.showMonthView || this.state.showYearView) { - this.state.viewDate = this.state.showYearView - ? new Date(this.state.viewDate.getFullYear() - 10, this.state.viewDate.getMonth(), 1) - : new Date(this.state.viewDate.getFullYear() - 1, this.state.viewDate.getMonth(), 1); + if (this.calendarState.view !== 'default') { + this.calendarState.viewDate = this.calendarState.view === 'year' + ? new Date(this.calendarState.viewDate.getFullYear() - 10, this.calendarState.viewDate.getMonth(), 1) + : new Date(this.calendarState.viewDate.getFullYear() - 1, this.calendarState.viewDate.getMonth(), 1); } else { const nextDate: Date = getMonthFromOffset( new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), -1); From ea1c953fcaaa77a612e22a7f6dabc7641168284f Mon Sep 17 00:00:00 2001 From: Kieran Williams Date: Wed, 15 Nov 2017 15:19:58 +0200 Subject: [PATCH 11/11] Made some changes --- src/components/date-picker/calendar.tsx | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/date-picker/calendar.tsx b/src/components/date-picker/calendar.tsx index eaf425963..35cddd306 100644 --- a/src/components/date-picker/calendar.tsx +++ b/src/components/date-picker/calendar.tsx @@ -38,7 +38,7 @@ const monthNames = getMonthNames(); @stylable(styles) @observer export class Calendar extends React.Component { - @observable public calendarState: CalendarState = { + @observable private calendarState: CalendarState = { view: 'default', viewDate: this.props.value }; @@ -79,8 +79,8 @@ export class Calendar extends React.Component { private getHeader = () => { if (this.calendarState.view === 'year') { - const decade = `${this.calendarState.viewDate.getFullYear() - 5} - -${this.calendarState.viewDate.getFullYear() + 4}`; + const decade = `${this.calendarState.viewDate.getFullYear() - 5}` + + `-${this.calendarState.viewDate.getFullYear() + 4}`; return ( {decade} @@ -332,28 +332,28 @@ export class Calendar extends React.Component { private goToNextMonth: React.EventHandler> = event => { event.preventDefault(); - if (this.calendarState.view !== 'default') { - this.calendarState.viewDate = this.calendarState.view === 'year' - ? new Date(this.calendarState.viewDate.getFullYear() + 10, this.calendarState.viewDate.getMonth(), 1) - : new Date(this.calendarState.viewDate.getFullYear() + 1, this.calendarState.viewDate.getMonth(), 1); - } else { + if (this.calendarState.view === 'default') { const nextDate: Date = getMonthFromOffset( new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), 1); this.props.updateDropdownDate(nextDate); + } else { + this.calendarState.viewDate = this.calendarState.view === 'year' + ? new Date(this.calendarState.viewDate.getFullYear() + 10, this.calendarState.viewDate.getMonth(), 1) + : new Date(this.calendarState.viewDate.getFullYear() + 1, this.calendarState.viewDate.getMonth(), 1); } } private goToPrevMonth: React.EventHandler> = event => { event.preventDefault(); - if (this.calendarState.view !== 'default') { - this.calendarState.viewDate = this.calendarState.view === 'year' - ? new Date(this.calendarState.viewDate.getFullYear() - 10, this.calendarState.viewDate.getMonth(), 1) - : new Date(this.calendarState.viewDate.getFullYear() - 1, this.calendarState.viewDate.getMonth(), 1); - } else { + if (this.calendarState.view === 'default') { const nextDate: Date = getMonthFromOffset( new Date(this.props.value.getFullYear(), this.props.value.getMonth(), 1), -1); this.props.updateDropdownDate(nextDate); + } else { + this.calendarState.viewDate = this.calendarState.view === 'year' + ? new Date(this.calendarState.viewDate.getFullYear() - 10, this.calendarState.viewDate.getMonth(), 1) + : new Date(this.calendarState.viewDate.getFullYear() - 1, this.calendarState.viewDate.getMonth(), 1); } }