react-cm-ui
Version:
React UI for Healthy Church
431 lines (377 loc) • 14.3 kB
JSX
import _ from 'lodash';
import ClassNames from 'classnames';
import moment from 'moment-timezone';
import PropTypes from 'prop-types';
import React from 'react';
import DatePickerDropdownMonth from './datePickerDropdownMonth';
import DatePickerDropdownYear from './datePickerDropdownYear';
import DatePickerMonth from './datePickerMonth';
import DatePickerUtils from '../../utils/datePickerUtils';
import Icon from '../../dataDisplay/icon';
class DatePickerCalendar extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
date: _.cloneDeep(props.date),
dateFrom: _.cloneDeep(props.dateFrom),
dateTo: _.cloneDeep(props.dateTo),
dateInView: this._localizeMoment(
this._getDateInView(
props.date,
props.dateFrom,
props.maxDate,
props.minDate,
),
),
showDropdownMonth: false,
showDropdownYear: false,
};
this._onMonthChange = this._onMonthChange.bind(this);
this._onYearChange = this._onYearChange.bind(this);
this._onDayClick = this._onDayClick.bind(this);
this._onDropdownMonthClick = this._onDropdownMonthClick.bind(this);
this._onDropdownYearClick = this._onDropdownYearClick.bind(this);
this._onPreviousMonthChange = this._onPreviousMonthChange.bind(this);
this._onNextMonthChange = this._onNextMonthChange.bind(this);
}
componentDidUpdate(prevProps, prevState) {
const {
date,
dateFrom,
dateTo,
maxDate,
minDate,
} = this.props;
if (!DatePickerUtils.isSameDay(prevProps.date, date) ||
!DatePickerUtils.isSameDay(prevProps.dateFrom, dateFrom) ||
!DatePickerUtils.isSameDay(prevProps.dateTo, dateTo)
) {
this.setState({
date,
dateFrom,
dateTo,
dateInView: this._localizeMoment(
this._getDateInView(
date,
dateFrom,
maxDate,
minDate,
),
),
});
}
}
render() {
const {
events,
excludeDates,
filterDates,
id,
includeDates,
maxDate,
minDate,
mode,
range,
rangeFrom,
rangeTo,
style,
} = this.props;
const { date, dateFrom, dateTo, dateInView } = this.state;
const containerClasses = ClassNames('ui', 'date-picker-calendar', {
'date-picker-calendar-mode-input': mode === 'input',
'date-picker-calendar-mode-calendar': mode === 'calendar',
'date-picker-calendar-date-range': !_.isEmpty(dateFrom) || !_.isEmpty(dateTo),
'date-picker-calendar-single-date': _.isEmpty(dateFrom) && _.isEmpty(dateTo),
});
return (
<div className={containerClasses} id={id} style={style}>
{mode === 'input' ? (
<div className="date-picker-pointer" />
) : null}
{this._renderControls()}
<div className="date-picker-month-container">
{this._renderHeaderDays()}
<DatePickerMonth
date={date}
dateFrom={dateFrom}
dateInView={dateInView}
dateTo={dateTo}
events={events}
excludeDates={excludeDates}
filterDates={filterDates}
includeDates={includeDates}
maxDate={maxDate}
minDate={minDate}
onDayClick={this._onDayClick}
mode={mode}
range={range}
rangeFrom={rangeFrom}
rangeTo={rangeTo}
/>
</div>
</div>
);
}
_getDateInView(date, dateFrom, maxDate, minDate) {
const today = moment();
if (moment.isMoment(date)) {
return date;
} else if (moment.isMoment(dateFrom)) {
return dateFrom;
}
if (moment.isMoment(minDate) && minDate.isAfter(today)) {
return minDate;
} else if (moment.isMoment(maxDate) && maxDate.isBefore(today)) {
return maxDate;
} else {
return today;
}
}
_localizeMoment(value) {
const { locale } = this.props;
return moment.isMoment(value) && value.clone().locale(locale || moment.locale());
}
_onMonthChange(month) {
const { onMonthChange } = this.props;
const { dateInView } = this.state;
if (!_.isUndefined(onMonthChange)) {
onMonthChange(month, dateInView.year());
}
this.setState({
dateInView: dateInView.clone().set('month', month),
showDropdownMonth: false,
});
}
_onYearChange(year) {
const { dateInView } = this.state;
this.setState({
dateInView: dateInView.clone().set('year', year),
showDropdownYear: false,
});
}
_onDayClick(date) {
const { onChange, range, rangeFrom, rangeTo } = this.props;
const { dateFrom, dateTo } = this.state;
const isDateSame = DatePickerUtils.isSameDay(this.state.date, date);
const isDateBeforeFrom = !_.isUndefined(dateFrom) && date.isBefore(dateFrom);
const isDateBetweenToAndFrom = date.isAfter(dateFrom) && date.isBefore(dateTo);
const isDateAfterTo = !_.isUndefined(dateTo) && date.isAfter(dateTo);
const isDateSameAsFrom = DatePickerUtils.isSameDay(date, dateFrom);
const isDateSameAsTo = DatePickerUtils.isSameDay(date, dateTo);
const isDateSameAsFromAndTo = isDateSameAsFrom && isDateSameAsTo;
if (range) {
if (isDateBeforeFrom) {
if (!_.isUndefined(onChange)) {
onChange({ date: undefined, dateFrom: date, dateTo: dateTo });
} else {
this.setState({ dateFrom: date });
}
}
if (isDateSameAsTo || isDateSameAsFrom || _.isUndefined(dateFrom) && _.isUndefined(dateTo)) {
if (!_.isUndefined(onChange)) {
onChange({ date: undefined, dateFrom: date, dateTo: date });
} else {
this.setState({
dateFrom: date,
dateTo: date,
});
}
}
if (isDateAfterTo || isDateBetweenToAndFrom) {
if (!_.isUndefined(onChange)) {
onChange({ date: undefined, dateFrom: dateFrom, dateTo: date });
} else {
this.setState({ dateTo: date });
}
}
if (isDateSameAsFromAndTo) {
if (!_.isUndefined(onChange)) {
onChange({ date: undefined, dateFrom: null, dateTo: null });
} else {
this.setState({
dateFrom: null,
dateTo: null,
});
}
}
} else if (rangeFrom || rangeTo) {
let newDateFrom, newDateTo;
if (rangeFrom && isDateSameAsTo || isDateAfterTo) {
newDateFrom = !isDateSameAsFromAndTo ? date : null;
newDateTo = !isDateSameAsFromAndTo ? date : null;
} else if (rangeFrom && !isDateSameAsTo && !isDateAfterTo) {
newDateFrom = date;
newDateTo = dateTo;
}
if (rangeTo && isDateSameAsFrom || isDateBeforeFrom) {
newDateFrom = !isDateSameAsFromAndTo ? date : null;
newDateTo = !isDateSameAsFromAndTo ? date : null;
} else if (rangeTo && !isDateSameAsFrom && !isDateBeforeFrom) {
newDateFrom = dateFrom;
newDateTo = date;
}
onChange({ date: undefined, dateFrom: newDateFrom, dateTo: newDateTo });
} else {
if (!_.isUndefined(onChange)) {
onChange({
date: !isDateSame ? date : null,
dateFrom: undefined,
dateTo: undefined,
});
} else {
this.setState({ date: !isDateSame ? date : null });
}
}
}
_onDropdownMonthClick() {
const { showDropdownMonth } = this.state;
this.setState({ showDropdownMonth: !showDropdownMonth });
}
_onDropdownYearClick() {
const { showDropdownYear } = this.state;
this.setState({ showDropdownYear: !showDropdownYear });
}
_onPreviousMonthChange() {
this.setState((prev) => {
const { onMonthChange } = this.props;
const dateInView = prev.dateInView.clone().subtract(1, 'months');
if (!_.isUndefined(onMonthChange)) {
onMonthChange(dateInView.month(), dateInView.year());
}
return {
dateInView,
showDropdownMonth: false,
};
});
}
_onNextMonthChange() {
this.setState((prev) => {
const { onMonthChange } = this.props;
const dateInView = prev.dateInView.clone().add(1, 'months');
if (!_.isUndefined(onMonthChange)) {
onMonthChange(dateInView.month(), dateInView.year());
}
return {
dateInView,
showDropdownMonth: false,
};
});
}
_renderControls() {
const { controls } = this.props;
const {
dateInView,
showDropdownMonth,
showDropdownYear,
} = this.state;
if (controls === 'arrows') {
return (
<div className="date-picker-selected-month-year-arrows">
<Icon
align="left"
onClick={this._onPreviousMonthChange}
size="medium"
type="chevron-left"
/>
<div className="title">
{dateInView.format('MMMM YYYY')}
</div>
<Icon
align="right"
onClick={this._onNextMonthChange}
size="medium"
type="chevron-right"
/>
</div>
);
} else {
return (
<div className="date-picker-selected-month-year-dropdowns">
<div
className="date-picker-selected-month"
onClick={this._onDropdownMonthClick}
>
<span>
{dateInView.format('MMMM')}
<Icon align="right" size="xxsmall" type="caret-down" />
</span>
{showDropdownMonth ? (
<DatePickerDropdownMonth
month={dateInView.month()}
onChange={this._onMonthChange}
onClose={this._onDropdownMonthClick}
/>
) : null}
</div>
<div
className="date-picker-selected-year"
onClick={this._onDropdownYearClick}
>
<span>
{dateInView.format('YYYY')}
<Icon align="right" size="xxsmall" type="caret-down" />
</span>
{showDropdownYear ? (
<DatePickerDropdownYear
onChange={this._onYearChange}
onClose={this._onDropdownYearClick}
year={dateInView.year()}
/>
) : null}
</div>
</div>
);
}
}
_renderHeaderDays() {
const { dateInView } = this.state;
const startOfWeek = dateInView.clone().startOf('week');
const days = [ 0, 1, 2, 3, 4, 5, 6 ].map(offset => {
const day = startOfWeek.clone().add(offset, 'days');
return (
<div className="ui date-picker-day" key={offset} >
{day.localeData().weekdaysMin(day)}
</div>
);
});
return (
<div className="ui date-picker-header-days">
{days}
</div>
);
}
}
DatePickerCalendar.defaultProps = {
className: '',
controls: 'arrows',
locale: 'en-US',
mode: 'calendar',
rangeFrom: false,
rangeTo: false,
};
DatePickerCalendar.propTypes = {
className: PropTypes.string,
controls: PropTypes.oneOf([ 'dropdowns', 'arrows' ]),
date: PropTypes.shape({}),
dateFrom: PropTypes.shape({}),
dateTo: PropTypes.shape({}),
events: PropTypes.array,
excludeDates: PropTypes.array,
filterDates: PropTypes.func,
id: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]),
includeDates: PropTypes.array,
locale: PropTypes.string,
maxDate: PropTypes.shape({}),
minDate: PropTypes.shape({}),
mode: PropTypes.oneOf([ 'calendar', 'input' ]),
onChange: PropTypes.func,
onMonthChange: PropTypes.func,
range: PropTypes.bool,
rangeFrom: PropTypes.bool,
rangeTo: PropTypes.bool,
style: PropTypes.shape({}),
};
export default DatePickerCalendar;