-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Add shortcuts to DatePicker #3818
Changes from all commits
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 |
---|---|---|
|
@@ -26,6 +26,7 @@ import * as Errors from "./common/errors"; | |
import { DatePickerCaption } from "./datePickerCaption"; | ||
import { getDefaultMaxDate, getDefaultMinDate, IDatePickerBaseProps } from "./datePickerCore"; | ||
import { DatePickerNavbar } from "./datePickerNavbar"; | ||
import { IDatePickerShortcut, IDateRangeShortcut, Shortcuts } from "./shortcuts"; | ||
import { TimePicker } from "./timePicker"; | ||
|
||
export interface IDatePickerProps extends IDatePickerBaseProps, IProps { | ||
|
@@ -66,12 +67,31 @@ export interface IDatePickerProps extends IDatePickerBaseProps, IProps { | |
*/ | ||
onChange?: (selectedDate: Date, isUserChange: boolean) => void; | ||
|
||
/** | ||
* Called when the `shortcuts` props is enabled and the user changes the shortcut. | ||
*/ | ||
onShortcutChange?: (shortcut: IDatePickerShortcut, index: number) => void; | ||
|
||
/** | ||
* Whether the bottom bar displaying "Today" and "Clear" buttons should be shown. | ||
* @default false | ||
*/ | ||
showActionsBar?: boolean; | ||
|
||
/** | ||
* Whether shortcuts to quickly select a date are displayed or not. | ||
* If `true`, preset shortcuts will be displayed. | ||
* If `false`, no shortcuts will be displayed. | ||
* If an array is provided, the custom shortcuts will be displayed. | ||
*/ | ||
shortcuts?: boolean | IDatePickerShortcut[]; | ||
|
||
/** | ||
* The currently selected shortcut. | ||
* If this prop is provided, the component acts in a controlled manner. | ||
*/ | ||
selectedShortcutIndex?: number; | ||
|
||
/** | ||
* Text for the today button in the action bar. | ||
* @default "Today" | ||
|
@@ -95,6 +115,7 @@ export interface IDatePickerState { | |
displayYear: number; | ||
selectedDay: number | null; | ||
value: Date | null; | ||
selectedShortcutIndex?: number; | ||
} | ||
|
||
@polyfill | ||
|
@@ -107,6 +128,7 @@ export class DatePicker extends AbstractPureComponent2<IDatePickerProps, IDatePi | |
maxDate: getDefaultMaxDate(), | ||
minDate: getDefaultMinDate(), | ||
reverseMonthAndYearMenus: false, | ||
shortcuts: false, | ||
showActionsBar: false, | ||
timePickerProps: {}, | ||
todayButtonText: "Today", | ||
|
@@ -124,6 +146,8 @@ export class DatePicker extends AbstractPureComponent2<IDatePickerProps, IDatePi | |
displayMonth: initialMonth.getMonth(), | ||
displayYear: initialMonth.getFullYear(), | ||
selectedDay: value == null ? null : value.getDate(), | ||
selectedShortcutIndex: | ||
this.props.selectedShortcutIndex !== undefined ? this.props.selectedShortcutIndex : -1, | ||
value, | ||
}; | ||
} | ||
|
@@ -134,26 +158,29 @@ export class DatePicker extends AbstractPureComponent2<IDatePickerProps, IDatePi | |
|
||
return ( | ||
<div className={classNames(Classes.DATEPICKER, className)}> | ||
<DayPicker | ||
showOutsideDays={true} | ||
locale={locale} | ||
localeUtils={localeUtils} | ||
modifiers={this.getDatePickerModifiers()} | ||
{...dayPickerProps} | ||
canChangeMonth={true} | ||
captionElement={this.renderCaption} | ||
navbarElement={this.renderNavbar} | ||
disabledDays={this.getDisabledDaysModifier()} | ||
fromMonth={minDate} | ||
month={new Date(displayYear, displayMonth)} | ||
onDayClick={this.handleDayClick} | ||
onMonthChange={this.handleMonthChange} | ||
selectedDays={this.state.value} | ||
toMonth={maxDate} | ||
renderDay={this.renderDay} | ||
/> | ||
{this.maybeRenderTimePicker()} | ||
{showActionsBar && this.renderOptionsBar()} | ||
{this.maybeRenderShortcuts()} | ||
<div> | ||
<DayPicker | ||
showOutsideDays={true} | ||
locale={locale} | ||
localeUtils={localeUtils} | ||
modifiers={this.getDatePickerModifiers()} | ||
{...dayPickerProps} | ||
canChangeMonth={true} | ||
captionElement={this.renderCaption} | ||
navbarElement={this.renderNavbar} | ||
disabledDays={this.getDisabledDaysModifier()} | ||
fromMonth={minDate} | ||
month={new Date(displayYear, displayMonth)} | ||
onDayClick={this.handleDayClick} | ||
onMonthChange={this.handleMonthChange} | ||
selectedDays={this.state.value} | ||
toMonth={maxDate} | ||
renderDay={this.renderDay} | ||
/> | ||
{this.maybeRenderTimePicker()} | ||
{showActionsBar && this.renderOptionsBar()} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
@@ -175,6 +202,10 @@ export class DatePicker extends AbstractPureComponent2<IDatePickerProps, IDatePi | |
value, | ||
}); | ||
} | ||
|
||
if (this.props.selectedShortcutIndex !== prevProps.selectedShortcutIndex) { | ||
this.setState({ selectedShortcutIndex: this.props.selectedShortcutIndex }); | ||
} | ||
} | ||
|
||
protected validateProps(props: IDatePickerProps) { | ||
|
@@ -269,11 +300,72 @@ export class DatePicker extends AbstractPureComponent2<IDatePickerProps, IDatePi | |
); | ||
} | ||
|
||
private maybeRenderShortcuts() { | ||
const { shortcuts } = this.props; | ||
if (shortcuts == null || shortcuts === false) { | ||
return null; | ||
} | ||
|
||
const { selectedShortcutIndex } = this.state; | ||
const { maxDate, minDate, timePrecision } = this.props; | ||
// Reuse the existing date range shortcuts and only care about start date | ||
const dateRangeShortcuts: IDateRangeShortcut[] | true = | ||
shortcuts === true | ||
? true | ||
: shortcuts.map(shortcut => ({ | ||
...shortcut, | ||
dateRange: [shortcut.date, undefined], | ||
})); | ||
return [ | ||
<Shortcuts | ||
key="shortcuts" | ||
{...{ | ||
allowSingleDayRange: true, | ||
maxDate, | ||
minDate, | ||
selectedShortcutIndex, | ||
shortcuts: dateRangeShortcuts, | ||
timePrecision, | ||
}} | ||
onShortcutClick={this.handleShortcutClick} | ||
useSingleDateShortcuts={true} | ||
/>, | ||
<Divider key="div" />, | ||
]; | ||
} | ||
|
||
private handleDayClick = (day: Date, modifiers: DayModifiers, e: React.MouseEvent<HTMLDivElement>) => { | ||
Utils.safeInvoke(this.props.dayPickerProps.onDayClick, day, modifiers, e); | ||
if (modifiers.disabled) { | ||
return; | ||
} | ||
|
||
this.updateDay(day); | ||
|
||
// allow toggling selected date by clicking it again (if prop enabled) | ||
const newValue = | ||
this.props.canClearSelection && modifiers.selected ? null : DateUtils.getDateTime(day, this.state.value); | ||
this.updateValue(newValue, true); | ||
}; | ||
|
||
private handleShortcutClick = (shortcut: IDateRangeShortcut, selectedShortcutIndex: number) => { | ||
const { onShortcutChange, selectedShortcutIndex: currentShortcutIndex } = this.props; | ||
const { dateRange, includeTime } = shortcut; | ||
const newDate = dateRange[0]; | ||
const newValue = includeTime ? newDate : DateUtils.getDateTime(newDate, this.state.value); | ||
|
||
this.updateDay(newDate); | ||
this.updateValue(newValue, true); | ||
|
||
if (currentShortcutIndex === undefined) { | ||
this.setState({ selectedShortcutIndex }); | ||
} | ||
|
||
const datePickerShortcut = { ...shortcut, date: shortcut.dateRange[0] }; | ||
Utils.safeInvoke(onShortcutChange, datePickerShortcut, selectedShortcutIndex); | ||
}; | ||
|
||
private updateDay = (day: 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. pulled out this common logic for when clicking a shortcut vs. a date on the calendar |
||
if (this.props.value === undefined) { | ||
// set now if uncontrolled, otherwise they'll be updated in `componentDidUpdate` | ||
this.setState({ | ||
|
@@ -285,11 +377,6 @@ export class DatePicker extends AbstractPureComponent2<IDatePickerProps, IDatePi | |
if (this.state.value != null && this.state.value.getMonth() !== day.getMonth()) { | ||
this.ignoreNextMonthChange = true; | ||
} | ||
|
||
// allow toggling selected date by clicking it again (if prop enabled) | ||
const newValue = | ||
this.props.canClearSelection && modifiers.selected ? null : DateUtils.getDateTime(day, this.state.value); | ||
this.updateValue(newValue, true); | ||
}; | ||
|
||
private computeValidDateInSpecifiedMonthYear(displayYear: number, displayMonth: number): Date { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,33 @@ A `DatePicker` shows a monthly calendar and allows the user to choose a single d | |
|
||
@reactExample DatePickerExample | ||
|
||
@## Shortcuts | ||
|
||
The menu on the left of the calendars provides "shortcuts" that allow users to | ||
quickly select common dates. The items in this menu are controlled through | ||
the `shortcuts` prop: `true` to show presets, `false` to hide (default), or an | ||
array of `IDatePickerShortcut` objects to define custom shortcuts. | ||
|
||
The **preset shortcuts** can be seen in the example above. They are as follows: | ||
|
||
- Today | ||
- Yesterday | ||
- 1 week ago | ||
- 1 month ago | ||
- 3 months ago | ||
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 is a pretty long list of presets. let's remove this one 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. Hesitant to remove 3 months ago as that is a default that users are already used to in our existing platform where we will be consuming this. 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. I'll remove 6 months ago instead |
||
- 1 year ago | ||
|
||
**Custom shortcuts** use the following interface: | ||
|
||
@interface IDatePickerShortcut | ||
|
||
@## Props | ||
|
||
Use the `onChange` prop to listen for changes to the set date range. You can | ||
control the selected date range by setting the `value` prop, or use the | ||
component in uncontrolled mode and specify an initial date range by setting | ||
`defaultValue`. | ||
|
||
@## Modifiers | ||
|
||
You can use the `modifiers` prop to conditionally apply styles to days. | ||
|
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.
will this div cause issues? Adding this div and the maybeRenderShortcuts is all that changed here
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.
yes, it's a very slight HTML break... but relying on
>
selectors is not good practice IMO... I'm fine with this change