Skip to content
This repository has been archived by the owner on Aug 31, 2022. It is now read-only.

Commit

Permalink
[DateRangePicker] Shortcuts component and renderCalendars method (pal…
Browse files Browse the repository at this point in the history
…antir#2859)

* Shortcuts component and renderCalendars method

to greatly simplify render()

* bind handleNextState
  • Loading branch information
giladgray authored Aug 21, 2018
1 parent c3e3337 commit 358ec60
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 156 deletions.
241 changes: 85 additions & 156 deletions packages/datetime/src/dateRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,10 @@
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import {
AbstractPureComponent,
Boundary,
Classes,
DISPLAYNAME_PREFIX,
Divider,
IProps,
Menu,
MenuItem,
Utils,
} from "@blueprintjs/core";
import { AbstractPureComponent, Boundary, DISPLAYNAME_PREFIX, Divider, IProps, Utils } from "@blueprintjs/core";
import classNames from "classnames";
import * as React from "react";
import ReactDayPicker from "react-day-picker";
import DayPicker from "react-day-picker";
import { DayModifiers } from "react-day-picker/types/common";
import { CaptionElementProps, DayPickerProps, NavbarElementProps } from "react-day-picker/types/props";

Expand All @@ -40,6 +30,7 @@ import {
} from "./datePickerCore";
import { DatePickerNavbar } from "./datePickerNavbar";
import { DateRangeSelectionStrategy } from "./dateRangeSelectionStrategy";
import { Shortcuts } from "./shortcuts";

export interface IDateRangeShortcut {
label: string;
Expand Down Expand Up @@ -200,85 +191,21 @@ export class DateRangePicker extends AbstractPureComponent<IDateRangePickerProps
}

public render() {
const modifiers = combineModifiers(this.modifiers, this.props.modifiers);
const {
className,
contiguousCalendarMonths,
dayPickerProps,
locale,
localeUtils,
maxDate,
minDate,
} = this.props;
const { className, contiguousCalendarMonths } = this.props;
const isShowingOneMonth = DateUtils.areSameMonth(this.props.minDate, this.props.maxDate);

const { leftView, rightView } = this.state;
const disabledDays = this.getDisabledDaysModifier();

const dayPickerBaseProps: DayPickerProps = {
locale,
localeUtils,
modifiers,
showOutsideDays: true,
...dayPickerProps,
disabledDays,
onDayClick: this.handleDayClick,
onDayMouseEnter: this.handleDayMouseEnter,
onDayMouseLeave: this.handleDayMouseLeave,
selectedDays: this.state.value,
};

const shortcuts = this.maybeRenderShortcuts();

if (contiguousCalendarMonths || isShowingOneMonth) {
const classes = classNames(DateClasses.DATEPICKER, DateClasses.DATERANGEPICKER, className, {
[DateClasses.DATERANGEPICKER_CONTIGUOUS]: contiguousCalendarMonths,
[DateClasses.DATERANGEPICKER_SINGLE_MONTH]: isShowingOneMonth,
});

// use the left DayPicker when we only need one
return (
<div className={classes}>
{shortcuts}
<ReactDayPicker
{...dayPickerBaseProps}
captionElement={this.renderSingleCaption}
navbarElement={this.renderNavbar}
fromMonth={minDate}
month={leftView.getFullDate()}
numberOfMonths={isShowingOneMonth ? 1 : 2}
onMonthChange={this.handleLeftMonthChange}
toMonth={maxDate}
/>
</div>
);
} else {
return (
<div className={classNames(DateClasses.DATEPICKER, DateClasses.DATERANGEPICKER, className)}>
{shortcuts}
<ReactDayPicker
{...dayPickerBaseProps}
canChangeMonth={true}
captionElement={this.renderLeftCaption}
navbarElement={this.renderNavbar}
fromMonth={minDate}
month={leftView.getFullDate()}
onMonthChange={this.handleLeftMonthChange}
toMonth={DateUtils.getDatePreviousMonth(maxDate)}
/>
<ReactDayPicker
{...dayPickerBaseProps}
canChangeMonth={true}
captionElement={this.renderRightCaption}
navbarElement={this.renderNavbar}
fromMonth={DateUtils.getDateNextMonth(minDate)}
month={rightView.getFullDate()}
onMonthChange={this.handleRightMonthChange}
toMonth={maxDate}
/>
</div>
);
}
const classes = classNames(DateClasses.DATEPICKER, DateClasses.DATERANGEPICKER, className, {
[DateClasses.DATERANGEPICKER_CONTIGUOUS]: contiguousCalendarMonths,
[DateClasses.DATERANGEPICKER_SINGLE_MONTH]: isShowingOneMonth,
});

// use the left DayPicker when we only need one
return (
<div className={classes}>
{this.maybeRenderShortcuts()}
{this.renderCalendars(isShowingOneMonth)}
</div>
);
}

public componentWillReceiveProps(nextProps: IDateRangePickerProps) {
Expand Down Expand Up @@ -329,34 +256,80 @@ export class DateRangePicker extends AbstractPureComponent<IDateRangePickerProps
};

private maybeRenderShortcuts() {
const propsShortcuts = this.props.shortcuts;
if (propsShortcuts == null || propsShortcuts === false) {
return undefined;
const { shortcuts } = this.props;
if (shortcuts == null || shortcuts === false) {
return null;
}

const shortcuts =
typeof propsShortcuts === "boolean"
? createDefaultShortcuts(this.props.allowSingleDayRange)
: propsShortcuts;

const shortcutElements = shortcuts.map((s, i) => (
<MenuItem
className={Classes.POPOVER_DISMISS_OVERRIDE}
disabled={!this.isShortcutInRange(s.dateRange)}
key={i}
onClick={this.getShorcutClickHandler(s.dateRange)}
text={s.label}
/>
));

const { allowSingleDayRange, maxDate, minDate } = this.props;
return [
<Menu key="shortcuts" className={DateClasses.DATERANGEPICKER_SHORTCUTS}>
{shortcutElements}
</Menu>,
<Shortcuts
key="shortcuts"
{...{ allowSingleDayRange, maxDate, minDate, shortcuts }}
onShortcutClick={this.handleNextState}
/>,
<Divider key="div" />,
];
}

private renderCalendars(isShowingOneMonth: boolean) {
const { contiguousCalendarMonths, dayPickerProps, locale, localeUtils, maxDate, minDate } = this.props;
const dayPickerBaseProps: DayPickerProps = {
locale,
localeUtils,
modifiers: combineModifiers(this.modifiers, this.props.modifiers),
showOutsideDays: true,
...dayPickerProps,
disabledDays: this.getDisabledDaysModifier(),
onDayClick: this.handleDayClick,
onDayMouseEnter: this.handleDayMouseEnter,
onDayMouseLeave: this.handleDayMouseLeave,
selectedDays: this.state.value,
};

if (contiguousCalendarMonths || isShowingOneMonth) {
return (
<DayPicker
{...dayPickerBaseProps}
captionElement={this.renderSingleCaption}
navbarElement={this.renderNavbar}
fromMonth={minDate}
month={this.state.leftView.getFullDate()}
numberOfMonths={isShowingOneMonth ? 1 : 2}
onMonthChange={this.handleLeftMonthChange}
toMonth={maxDate}
/>
);
} else {
return [
<DayPicker
key="left"
{...dayPickerBaseProps}
canChangeMonth={true}
captionElement={this.renderLeftCaption}
navbarElement={this.renderNavbar}
fromMonth={minDate}
month={this.state.leftView.getFullDate()}
numberOfMonths={1}
onMonthChange={this.handleLeftMonthChange}
toMonth={DateUtils.getDatePreviousMonth(maxDate)}
/>,
<DayPicker
key="right"
{...dayPickerBaseProps}
canChangeMonth={true}
captionElement={this.renderRightCaption}
navbarElement={this.renderNavbar}
fromMonth={DateUtils.getDateNextMonth(minDate)}
month={this.state.rightView.getFullDate()}
numberOfMonths={1}
onMonthChange={this.handleRightMonthChange}
toMonth={maxDate}
/>,
];
}
}

private renderNavbar = (navbarProps: NavbarElementProps) => (
<DatePickerNavbar {...navbarProps} maxDate={this.props.maxDate} minDate={this.props.minDate} />
);
Expand Down Expand Up @@ -442,11 +415,7 @@ export class DateRangePicker extends AbstractPureComponent<IDateRangePickerProps
this.handleNextState(nextValue);
};

private getShorcutClickHandler(nextValue: DateRange) {
return () => this.handleNextState(nextValue);
}

private handleNextState(nextValue: DateRange) {
private handleNextState = (nextValue: DateRange) => {
const { value } = this.state;
const nextState = getStateChange(value, nextValue, this.state, this.props.contiguousCalendarMonths);

Expand All @@ -455,7 +424,7 @@ export class DateRangePicker extends AbstractPureComponent<IDateRangePickerProps
}

Utils.safeInvoke(this.props.onChange, nextValue);
}
};

private handleLeftMonthChange = (newDate: Date) => {
const leftView = MonthAndYear.fromDate(newDate);
Expand Down Expand Up @@ -553,10 +522,6 @@ export class DateRangePicker extends AbstractPureComponent<IDateRangePickerProps
private setViews(leftView: MonthAndYear, rightView: MonthAndYear) {
this.setState({ leftView, rightView });
}

private isShortcutInRange(shortcutDateRange: DateRange) {
return DateUtils.isDayRangeInRange(shortcutDateRange, [this.props.minDate, this.props.maxDate]);
}
}

function getStateChange(
Expand Down Expand Up @@ -630,42 +595,6 @@ function getStateChange(
return {};
}

function createShortcut(label: string, dateRange: DateRange): IDateRangeShortcut {
return { dateRange, label };
}

function createDefaultShortcuts(allowSingleDayRange: boolean) {
const today = new Date();
const makeDate = (action: (d: Date) => void) => {
const returnVal = DateUtils.clone(today);
action(returnVal);
returnVal.setDate(returnVal.getDate() + 1);
return returnVal;
};

const yesterday = makeDate(d => d.setDate(d.getDate() - 2));
const oneWeekAgo = makeDate(d => d.setDate(d.getDate() - 7));
const oneMonthAgo = makeDate(d => d.setMonth(d.getMonth() - 1));
const threeMonthsAgo = makeDate(d => d.setMonth(d.getMonth() - 3));
const sixMonthsAgo = makeDate(d => d.setMonth(d.getMonth() - 6));
const oneYearAgo = makeDate(d => d.setFullYear(d.getFullYear() - 1));
const twoYearsAgo = makeDate(d => d.setFullYear(d.getFullYear() - 2));

const singleDayShortcuts = allowSingleDayRange
? [createShortcut("Today", [today, today]), createShortcut("Yesterday", [yesterday, yesterday])]
: [];

return [
...singleDayShortcuts,
createShortcut("Past week", [oneWeekAgo, today]),
createShortcut("Past month", [oneMonthAgo, today]),
createShortcut("Past 3 months", [threeMonthsAgo, today]),
createShortcut("Past 6 months", [sixMonthsAgo, today]),
createShortcut("Past year", [oneYearAgo, today]),
createShortcut("Past 2 years", [twoYearsAgo, today]),
];
}

function getInitialValue(props: IDateRangePickerProps): DateRange | null {
if (props.value != null) {
return props.value;
Expand Down
88 changes: 88 additions & 0 deletions packages/datetime/src/shortcuts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2018 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import { Classes, Menu, MenuItem } from "@blueprintjs/core";
import React from "react";
import { DATERANGEPICKER_SHORTCUTS } from "./common/classes";
import { clone, DateRange, isDayRangeInRange } from "./common/dateUtils";

export interface IDateRangeShortcut {
label: string;
dateRange: DateRange;
}

export interface IShortcutsProps {
allowSingleDayRange: boolean;
minDate: Date;
maxDate: Date;
shortcuts: IDateRangeShortcut[] | true;
onShortcutClick: (shortcut: DateRange) => void;
}

export class Shortcuts extends React.PureComponent<IShortcutsProps> {
public render() {
const shortcuts =
this.props.shortcuts === true
? createDefaultShortcuts(this.props.allowSingleDayRange)
: this.props.shortcuts;

const shortcutElements = shortcuts.map((s, i) => (
<MenuItem
className={Classes.POPOVER_DISMISS_OVERRIDE}
disabled={!this.isShortcutInRange(s.dateRange)}
key={i}
onClick={this.getShorcutClickHandler(s.dateRange)}
text={s.label}
/>
));

return <Menu className={DATERANGEPICKER_SHORTCUTS}>{shortcutElements}</Menu>;
}

private getShorcutClickHandler(nextValue: DateRange) {
return () => this.props.onShortcutClick(nextValue);
}

private isShortcutInRange(shortcutDateRange: DateRange) {
return isDayRangeInRange(shortcutDateRange, [this.props.minDate, this.props.maxDate]);
}
}

function createShortcut(label: string, dateRange: DateRange): IDateRangeShortcut {
return { dateRange, label };
}

function createDefaultShortcuts(allowSingleDayRange: boolean) {
const today = new Date();
const makeDate = (action: (d: Date) => void) => {
const returnVal = clone(today);
action(returnVal);
returnVal.setDate(returnVal.getDate() + 1);
return returnVal;
};

const yesterday = makeDate(d => d.setDate(d.getDate() - 2));
const oneWeekAgo = makeDate(d => d.setDate(d.getDate() - 7));
const oneMonthAgo = makeDate(d => d.setMonth(d.getMonth() - 1));
const threeMonthsAgo = makeDate(d => d.setMonth(d.getMonth() - 3));
const sixMonthsAgo = makeDate(d => d.setMonth(d.getMonth() - 6));
const oneYearAgo = makeDate(d => d.setFullYear(d.getFullYear() - 1));
const twoYearsAgo = makeDate(d => d.setFullYear(d.getFullYear() - 2));

const singleDayShortcuts = allowSingleDayRange
? [createShortcut("Today", [today, today]), createShortcut("Yesterday", [yesterday, yesterday])]
: [];

return [
...singleDayShortcuts,
createShortcut("Past week", [oneWeekAgo, today]),
createShortcut("Past month", [oneMonthAgo, today]),
createShortcut("Past 3 months", [threeMonthsAgo, today]),
createShortcut("Past 6 months", [sixMonthsAgo, today]),
createShortcut("Past year", [oneYearAgo, today]),
createShortcut("Past 2 years", [twoYearsAgo, today]),
];
}

0 comments on commit 358ec60

Please sign in to comment.