@roo-ui/components
Version:
231 lines (201 loc) • 6.83 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import Dayzed from 'dayzed';
import { subDays, differenceInCalendarMonths, startOfDay, endOfDay, isSameDay } from 'date-fns';
import throttle from 'lodash/fp/throttle';
import { Flex, Box } from '../';
import isDateInRange from './lib/isDateInRange';
import CalendarNav from './components/CalendarNav';
import CalendarMonth from './components/CalendarMonth';
const NOOP = () => false;
const throttled = throttle(10);
class DateRangePicker extends React.Component {
constructor(props) {
super(props);
const { initialStartDate, initialEndDate, initialDisplayDate } = this.props;
const startDate = initialStartDate ? startOfDay(initialStartDate) : null;
const endDate = initialEndDate ? endOfDay(initialEndDate) : null;
const focusDate = startDate || endDate || initialDisplayDate;
this.state = {
hoveredDate: null,
offset: differenceInCalendarMonths(focusDate, initialDisplayDate),
isSettingStartDate: props.isSettingStartDate,
isSettingEndDate: props.isSettingEndDate,
startDate,
endDate,
};
}
componentDidUpdate(prevProps, prevState) {
const { startDate, endDate } = this.state;
const { startDate: prevStartDate, endDate: prevEndDate } = prevState;
const startDateDidUpdate = !isSameDay(startDate, prevStartDate);
const endDateDidUpdate = !isSameDay(endDate, prevEndDate);
if (startDateDidUpdate || endDateDidUpdate) {
this.props.onChangeDates({ startDate, endDate });
}
}
onOffsetChanged = offset => this.setState({ offset });
onMouseLeaveOfCalendar = () => this.setState({ hoveredDate: null });
onMouseEnterOfDay = throttled((hoveredDate) => {
if (this.state.startDate && !this.state.endDate) {
this.setState({ hoveredDate: hoveredDate.date });
}
})
onDateSelected = ({ selectable, date }) => {
if (!selectable) return;
const {
startDate, endDate, isSettingStartDate, isSettingEndDate,
} = this.state;
if (!startDate || isSettingStartDate) {
this.selectStartDate(date);
} else if (!endDate || isSettingEndDate) {
this.selectEndDate(date);
} else {
this.resetWithStartDate(date);
}
this.notifyRangeSelection();
};
notifyRangeSelection = () => {
const { startDate, endDate } = this.state;
const { onRangeSelected } = this.props;
if (startDate && endDate && onRangeSelected) {
onRangeSelected({ startDate, endDate });
}
}
selectStartDate = (startDate) => {
const endDate = startDate <= this.state.endDate ? this.state.endDate : null;
const endDateRequiresSelection = endDate === null;
this.setState({
startDate,
endDate,
isSettingStartDate: false,
isSettingEndDate: endDateRequiresSelection,
hoveredDate: null,
});
};
selectEndDate = (date) => {
if (date <= this.state.startDate) {
this.resetWithStartDate(date);
} else {
this.setState({
endDate: date,
isSettingEndDate: false,
hoveredDate: null,
});
}
};
resetWithStartDate = (startDate) => {
this.setState({
startDate,
endDate: null,
isSettingStartDate: false,
isSettingEndDate: true,
hoveredDate: null,
});
};
isInRange = (date) => {
const {
startDate, endDate, isSettingStartDate, hoveredDate,
} = this.state;
return isDateInRange({
startDate, endDate, isSettingStartDate, hoveredDate, date,
});
}
render() {
const {
monthNames,
weekdayNames,
monthsToDisplay,
stacked,
disabledDates,
interactiveDisabledDates,
initialDisplayDate,
...rest
} = this.props;
const { startDate, endDate, offset } = this.state;
const selectedDates = [startDate, endDate];
return (
<Dayzed
{...rest}
selected={selectedDates}
date={initialDisplayDate}
offset={offset}
monthsToDisplay={monthsToDisplay}
onDateSelected={this.onDateSelected}
onOffsetChanged={this.onOffsetChanged}
render={({
calendars,
getBackProps,
getForwardProps,
getDateProps,
}) => {
if (!calendars.length) return null;
return (
<Box onMouseLeave={this.onMouseLeaveOfCalendar} position="relative">
<CalendarNav
prevProps={getBackProps({ calendars })}
nextProps={getForwardProps({ calendars })}
/>
<Flex flexWrap="wrap">
{calendars.map(calendar => (
<CalendarMonth
key={`${calendar.month}${calendar.year}`}
monthsToDisplay={monthsToDisplay}
monthName={monthNames[calendar.month]}
month={calendar.month}
year={calendar.year}
stacked={stacked}
weekdayNames={weekdayNames}
weeks={calendar.weeks}
getDateProps={getDateProps}
disabledDates={disabledDates}
interactiveDisabledDates={interactiveDisabledDates}
onMouseEnterOfDay={this.onMouseEnterOfDay}
isInRange={this.isInRange}
/>
))}
</Flex>
</Box>
);
}}
/>
);
}
}
DateRangePicker.defaultProps = {
monthsToDisplay: 1,
firstDayOfWeek: 1,
stacked: false,
minDate: subDays(new Date(), 1),
disabledDates: [],
monthNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
weekdayNames: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
interactiveDisabledDates: false,
initialStartDate: null,
initialEndDate: null,
initialDisplayDate: new Date(),
onChangeStartDate: null,
onChangeEndDate: null,
isSettingStartDate: false,
isSettingEndDate: false,
onRangeSelected: NOOP,
onChangeDates: NOOP,
};
DateRangePicker.propTypes = {
monthsToDisplay: PropTypes.number,
firstDayOfWeek: PropTypes.number,
stacked: PropTypes.bool,
minDate: PropTypes.instanceOf(Date),
disabledDates: PropTypes.arrayOf(PropTypes.instanceOf(Date)),
interactiveDisabledDates: PropTypes.bool,
monthNames: PropTypes.arrayOf(PropTypes.string),
weekdayNames: PropTypes.arrayOf(PropTypes.string),
initialStartDate: PropTypes.instanceOf(Date),
initialEndDate: PropTypes.instanceOf(Date),
initialDisplayDate: PropTypes.instanceOf(Date),
isSettingStartDate: PropTypes.bool,
isSettingEndDate: PropTypes.bool,
onRangeSelected: PropTypes.func,
onChangeDates: PropTypes.func,
};
export default DateRangePicker;