Skip to content
This repository has been archived by the owner on Nov 1, 2019. It is now read-only.

DatePicker - adding better year selection #580

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 122 additions & 38 deletions src/components/date-picker/calendar.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -29,15 +29,19 @@ export interface CalendarProps {
}

export interface CalendarState {
showMonthView: boolean;
view: 'year' | 'month' | 'default';
viewDate: Date;
}

const monthNames = getMonthNames();

@stylable(styles)
@observer
export class Calendar extends React.Component<CalendarProps, CalendarState> {
public state: CalendarState = {showMonthView: false};
export class Calendar extends React.Component<CalendarProps> {
@observable private calendarState: CalendarState = {
view: 'default',
viewDate: this.props.value
};

public render() {
return (
Expand All @@ -55,7 +59,7 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
<span
data-automation-id="CALENDAR_HEADER"
className="headerDate"
onMouseDown={this.headerClicked}
onMouseDown={this.onHeaderClick}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this span necessary to wrap the DOM you get back from getHeader()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, but the alternative is duplicating those attributes on all three of the possible return values for getHeader

>
{this.getHeader()}
</span>
Expand All @@ -67,28 +71,24 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
<i className="headerArrow headerArrowNext" />
</span>
</div>
{this.state.showMonthView ?
<div className="month-view" data-automation-id="MONTH_VIEW">
{this.monthArray}
</div>
:
<div className="calendar" data-automation-id="DAY_GRID">
{this.dayNames}
{this.previousDays}
{this.days}
{this.followingDays}
</div>
}
{this.getCalendarView()}
</div>
</div>
);
}

private getHeader = () => {
if (this.state.showMonthView) {
if (this.calendarState.view === 'year') {
const decade = `${this.calendarState.viewDate.getFullYear() - 5}`
+ `-${this.calendarState.viewDate.getFullYear() + 4}`;
return (
<span data-automation-id="HEADER_DATE">
{this.year}
{decade}
</span>);
} else if (this.calendarState.view === 'month') {
return (
<span data-automation-id="HEADER_DATE">
{this.calendarState.viewDate.getFullYear()}
</span>);
} else {
return (
Expand All @@ -98,6 +98,31 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
}
}

private getCalendarView = () => {
if (this.calendarState.view === 'year') {
return (
<div className="yearView" data-automation-id="YEAR_VIEW">
{this.yearArray}
</div>
);
} else if (this.calendarState.view === 'month') {
return (
<div className="monthView" data-automation-id="MONTH_VIEW">
{this.monthArray}
</div>
);
} else {
return (
<div className="calendar" data-automation-id="DAY_GRID">
{this.dayNames}
{this.previousDays}
{this.days}
{this.followingDays}
</div>
);
}
}

@computed
private get monthName(): string {
return monthNames[this.props.value.getMonth()];
Expand Down Expand Up @@ -201,19 +226,40 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
monthNames.forEach(month => {
monthArray.push(
<span
className="calendarItem monthName"
className="monthName"
onMouseDown={this.onSelectMonth}
key={`MONTH_${month.toUpperCase()}`}
data-automation-id={`MONTH_${month.toUpperCase()}`}
>
{month}
</span>
);
);
});

return monthArray;
}

@computed
private get yearArray(): JSX.Element[] {
const yearArray: JSX.Element[] = [];

for (let year = this.calendarState.viewDate.getFullYear() - 5;
year <= this.calendarState.viewDate.getFullYear() + 4; year ++) {
yearArray.push(
<span
className="year"
onMouseDown={this.onSelectYear}
key={`YEAR_${year}`}
data-automation-id={`YEAR_${year}`}
>
{year}
</span>
);
}

return yearArray;
}

private isCurrentDay(day: number): boolean {
const currentDate = new Date();
return (this.props.value.getFullYear() === currentDate.getFullYear()
Expand All @@ -240,41 +286,79 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
}
}

private toggleMonthView = () => {
this.setState({showMonthView: !this.state.showMonthView});
private toggleDateSelectView = () => {
if (this.calendarState.view === 'year') {
this.calendarState.view = 'default';
this.resetViewDate();
} else if (this.calendarState.view === 'default') {
this.calendarState.view = 'month';
} else if (this.calendarState.view === 'month') {
this.calendarState.view = 'year';
}
}

private onSelectMonth: React.EventHandler<React.SyntheticEvent<Element>> = event => {
event.preventDefault();
this.toggleMonthView();
private closeDateSelectView = () => {
if (this.calendarState.view === 'year') {
this.calendarState.view = 'month';
} else if (this.calendarState.view === 'month') {
this.calendarState.view = 'default';
}
}

const date = new Date(this.props.value.getFullYear(),
private resetViewDate = () => {
this.calendarState.viewDate = this.props.value;
}

private onSelectMonth: React.EventHandler<React.SyntheticEvent<Element>> = event => {
const date = new Date(this.calendarState.viewDate.getFullYear(),
monthNames.indexOf((event.target as HTMLSpanElement).textContent!),
this.props.value.getDate());
this.calendarState.viewDate.getDate());

this.props.updateDropdownDate(date);
this.calendarState.viewDate = date;
this.closeDateSelectView();
}

private onSelectYear: React.EventHandler<React.SyntheticEvent<Element>> = event => {
const date = new Date(parseInt((event.target as HTMLSpanElement).textContent!, 10),
this.calendarState.viewDate.getMonth(),
this.calendarState.viewDate.getDate());

this.props.updateDropdownDate(date);
this.calendarState.viewDate = date;
this.closeDateSelectView();
}

private goToNextMonth: React.EventHandler<React.SyntheticEvent<Element>> = 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.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<React.SyntheticEvent<Element>> = 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.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 headerClicked: React.EventHandler<React.SyntheticEvent<Element>> = event => {
private onHeaderClick: React.EventHandler<React.SyntheticEvent<Element>> = event => {
event.preventDefault();
this.toggleMonthView();
this.toggleDateSelectView();
}
}
22 changes: 22 additions & 0 deletions src/components/date-picker/date-picker.st.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@
-st-extends: calendar;
}

.year-view {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one last detail, this should be in camel case.

-st-extends: calendar;
}

/* Styling for the days of the week */
.dayName {
font-family: value(fontFamily);
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 13 additions & 1 deletion test-kit/components/date-picker-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
}
Expand Down Expand Up @@ -87,6 +91,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}`);
}
Expand Down Expand Up @@ -115,7 +123,11 @@ export class DatePickerTestDriver extends DriverBase {
return bodySelect(datePickerDropdown, `MONTH_${month.toUpperCase()}`);
}

public elementHasStylableState(element: Element, stateName: string): boolean {
public getYear(year: string): HTMLSpanElement | null {
return bodySelect(datePickerDropdown, `YEAR_${year}`);
}

public hasStylableState(element: Element, stateName: string): boolean {
return elementHasStylableState(element, baseStyle, stateName);
}
}
Loading