react-datepicker-cy
Version:
react datepicker component. (include persian jalaali calendar)
393 lines (346 loc) • 14.5 kB
JavaScript
import React, {Component} from 'react';
import DaysViewHeading from './DaysViewHeading';
import DaysOfWeek from './DaysOfWeek';
import MonthSelector from './MonthSelector';
import Day from './Day';
import {getDaysOfMonth} from '../utils/moment-helper';
import moment from 'moment-jalaali';
import onClickOutside from 'react-onclickoutside';
import {defaultStyles} from './DefaultStyles';
import PropTypes from 'prop-types';
export class Calendar extends Component {
static propTypes = {
min: PropTypes.object,
max: PropTypes.object,
styles: PropTypes.object,
selectedDayObj: PropTypes.object,
defaultMonth: PropTypes.object,
onSelect: PropTypes.func,
onClickOutside: PropTypes.func,
containerProps: PropTypes.object,
isGregorian: PropTypes.bool,
calendarClass: PropTypes.string,
onNextMonth: PropTypes.func,
onPrevMonth: PropTypes.func,
selectedDayArray: PropTypes.array,
isRange: PropTypes.bool,
firstCal: PropTypes.bool,
secondHover: PropTypes.bool,
thirdSelectReset: PropTypes.bool,
onMouseEnterProp: PropTypes.func,
};
static childContextTypes = {
nextMonth: PropTypes.func.isRequired,
prevMonth: PropTypes.func.isRequired,
setCalendarMode: PropTypes.func.isRequired,
setMonth: PropTypes.func.isRequired,
setType: PropTypes.func.isRequired
};
static defaultProps = {
styles: defaultStyles,
containerProps: {},
selectedDayArray: [],
isGregorian: true,
thirdSelectReset: true,
};
state = {
month: this.props.defaultMonth || this.props.selectedDayObj || moment(this.props.min),
selectedDayObj: this.props.selectedDayObj || null,
selectedDayArray: this.props.selectedDayArray,
mode: 'days',
isGregorian: this.props.isGregorian,
hoveredDay: null,
isRange: this.props.isRange ? this.props.isRange : false,
firstCal: this.props.firstCal ? this.props.firstCal : false,
secondHover: this.props.secondHover ? this.props.secondHover : false,
};
getChildContext() {
return {
nextMonth: this.nextMonth.bind(this),
prevMonth: this.prevMonth.bind(this),
setCalendarMode: this.setMode.bind(this),
setMonth: this.setMonth.bind(this),
setType: this.setMonth.bind(this)
};
}
componentWillReceiveProps({selectedDayObj, selectedDayArray, defaultMonth, min, secondHover}) {
if (this.state.isRange === false) {
if (this.props.selectedDayObj !== selectedDayObj) {
this.selectDay(selectedDayObj);
} else if (defaultMonth && this.props.defaultMonth !== defaultMonth && this.state.month === this.props.defaultMonth) {
this.setMonth(defaultMonth);
} else if (min && this.props.min !== min && this.state.month.isSame(this.props.min)) {
this.setMonth(min.clone());
}
}
else {
if (this.props.secondHover !== secondHover) {
this.setState({secondHover: secondHover});
this.handleHoverOnSecond(secondHover);
}
if (this.props.selectedDayArray !== selectedDayArray) {
this.setState({selectedDayArray: selectedDayArray});
} if (defaultMonth && this.props.defaultMonth !== defaultMonth && this.state.month === this.props.defaultMonth) {
this.setMonth(defaultMonth);
} else if (min && this.props.min !== min && this.state.month.isSame(this.props.min)) {
this.setMonth(min.clone());
}
}
}
setMode(mode) {
this.setState({mode});
}
setMonth(month) {
this.setState({month});
}
setType(type) {
this.setState({type});
}
nextMonth() {
const {isGregorian} = this.state;
const monthFormat = isGregorian ? 'Month' : 'jMonth';
if (this.props.onNextMonth) {
this.props.onNextMonth(this.state.month.clone().add(1, monthFormat));
}
this.setState({
month: this.state.month.clone().add(1, monthFormat)
});
}
prevMonth() {
const {isGregorian} = this.state;
const monthFormat = isGregorian ? 'Month' : 'jMonth';
if (this.props.onPrevMonth) {
this.props.onPrevMonth(this.state.month.clone().subtract(1, monthFormat));
}
this.setState({
month: this.state.month.clone().subtract(1, monthFormat)
});
}
sameDay(selectedDayArray, givenDay, isGregorian) {
for (let i = 0; i < selectedDayArray.length; i++) {
const format = isGregorian ? 'YYYYMMDD' : 'jYYYYjMMjDD';
if (givenDay.format(format) === selectedDayArray[i].format(format))
return true;
}
return false
}
hover(selectedDay, hoveredDay, day, selected) {
let hover = false;
if (selectedDay.length === 1 && !!hoveredDay) {
if (hoveredDay.isSameOrAfter(selectedDay[0]) && day.isBetween(selectedDay[0], hoveredDay, null, '(]')) {
hover = true;
}
}
if (selectedDay.length === 1 && hoveredDay === null) {
hover = false;
}
if (selectedDay.length === 1 && !this.state.firstCal && !this.state.secondHover) {
hover = false;
} else if (selectedDay.length === 2) {
if (selectedDay[1].isSameOrAfter(selectedDay[0]) && day.isBetween(selectedDay[0], selectedDay[1], null, '()')) {
hover = true;
} else if (day.isBetween(selectedDay[1], selectedDay[0], null, '()')) {
hover = true;
}
}
if (selected)
return false;
return hover;
}
selectDay(givenDay) {
if (this.state.isRange === false) {
const {month, isGregorian} = this.state;
const yearMonthFormat = isGregorian ? 'YYYYMM' : 'jYYYYjMM';
// Because there's no `m1.isSame(m2, 'jMonth')`
if (givenDay.format(yearMonthFormat) !== month.format(yearMonthFormat)) {
this.setState({month: givenDay});
}
this.setState({selectedDayObj: givenDay});
}
else {
const {month, isGregorian, selectedDayArray} = this.state;
const {syncSelectedDay} = this.props;
const yearMonthFormat = isGregorian ? 'YYYYMM' : 'jYYYYjMM';
if (this.sameDay(selectedDayArray, givenDay, isGregorian)) {
const format = isGregorian ? 'YYYYMMDD' : 'jYYYYjMMjDD';
const days = selectedDayArray.filter(day => {
if (givenDay.format(format) !== day.format(format))
return day;
});
this.setState({selectedDayArray: days});
syncSelectedDay({selectedDayArray: days});
} else {
if (selectedDayArray.length === 2) {
if (givenDay.isAfter(selectedDayArray[0]) && !this.props.thirdSelectReset) {
let temp = this.state.selectedDayArray;
temp[1] = givenDay;
this.setState({selectedDayArray: temp});
syncSelectedDay({selectedDayArray: temp});
return
}
else {
this.setState({selectedDayArray: [givenDay]});
syncSelectedDay({selectedDayArray: [givenDay]});
return;
}
}
if (selectedDayArray.length === 1 && givenDay.isAfter(selectedDayArray[0])) {
this.setState({selectedDayArray: [...selectedDayArray, givenDay]});
syncSelectedDay({selectedDayArray: [...selectedDayArray, givenDay]});
} else {
this.setState({selectedDayArray: [givenDay]});
syncSelectedDay({selectedDayArray: [givenDay]});
}
}
if (givenDay.format(yearMonthFormat) !== month.format(yearMonthFormat)) {
const {isGregorian} = this.state;
const monthFormat = isGregorian ? 'Month' : 'jMonth';
if (this.props.onPrevMonth && (givenDay.format(yearMonthFormat) < month.format(yearMonthFormat))) {
this.props.onPrevMonth(this.state.month.clone().subtract(1, monthFormat));
}
if (this.props.onNextMonth && (givenDay.format(yearMonthFormat) > month.format(yearMonthFormat))) {
this.props.onNextMonth(this.state.month.clone().add(1, monthFormat));
}
this.setState({month: givenDay});
}
}
}
handleHoverOnSecond(secondHover) {
if (this.state.selectedDayArray.length === 1 && secondHover) {
let month = moment(this.state.selectedDayArray.length[0]).month();
let day = moment(this.state.selectedDayArray.length[0]).day();
let year = moment(this.state.selectedDayArray.length[0]).year();
// Really big time help to hover over all remain date of first column
var firstDay = new Date(2100, month + 1, 0);
this.setState({
hoveredDay: moment(firstDay)
});
}
}
handleClickOnDayArray = selectedDayArray => {
const {onSelect} = this.props;
this.selectDay(selectedDayArray);
if (onSelect) {
onSelect(selectedDayArray);
}
}
handleClickOnDayObj = selectedDayObj => {
const {onSelect} = this.props;
this.selectDay(selectedDayObj);
if (onSelect) {
onSelect(selectedDayObj);
}
};
handleMouseOverOnDay(hoveredDay) {
const {selectedDayArray} = this.state;
if (!!selectedDayArray.length) {
this.setState({hoveredDay: hoveredDay});
}
};
handleClickOutside(event) {
if (this.props.onClickOutside) {
this.props.onClickOutside(event);
}
}
days = null;
lastRenderedMonth = null;
renderMonthSelector() {
const {month, isGregorian} = this.state;
const {styles} = this.props;
return (<MonthSelector styles={styles} isGregorian={isGregorian} selectedMonth={month}/>);
}
selectedDay(selectedDay, day) {
let selected = false;
for (let i = 0; i < selectedDay.length; i++) {
if (selectedDay[i].isSame(day, 'day')) {
selected = true;
break;
}
}
return selected;
}
renderDays() {
const {month, selectedDayObj, isGregorian, hoveredDay, selectedDayArray} = this.state;
const {children, min, max, styles, outsideClickIgnoreClass} = this.props;
let days;
if (this.lastRenderedMonth === month) {
days = this.days;
} else {
days = getDaysOfMonth(month, isGregorian);
this.days = days;
this.lastRenderedMonth = month;
}
const monthFormat = isGregorian ? 'MM' : 'jMM';
const dateFormat = isGregorian ? 'YYYYMMDD' : 'jYYYYjMMjDD';
return (
<div className={this.props.calendarClass}
onMouseLeave={this.onMouseLeave.bind(this)}
onMouseEnter={this.onMouseEnter.bind(this)}
>
{children}
<DaysViewHeading isGregorian={isGregorian} styles={styles} month={month}/>
<DaysOfWeek styles={styles} isGregorian={isGregorian}/>
<div className={styles.dayPickerContainer}>
{
days.map(day => {
const isCurrentMonth = day.format(monthFormat) === month.format(monthFormat);
const disabled = (min ? day.isBefore(min) : false) || (max ? day.isAfter(max) : false);
const selected = (this.state.isRange === false) ? (selectedDayObj ? selectedDayObj.isSame(day, 'day') : false) : this.selectedDay(selectedDayArray, day);
let hover = this.hover(selectedDayArray, hoveredDay, day, selected);
return (
<Day
isGregorian={isGregorian}
key={day.format(dateFormat)}
onClick={(this.state.isRange) ? this.handleClickOnDayArray : this.handleClickOnDayObj}
onMouseOver={(this.state.isRange) ? this.handleMouseOverOnDay.bind(this) : null}
day={day}
disabled={disabled}
selected={selected}
isCurrentMonth={isCurrentMonth}
styles={styles}
hovered={(this.state.isRange === false) ? false : hover}
/>
);
})
}
</div>
</div>
);
}
onMouseLeave() {
if (this.state.isRange) {
this.setState({
hoveredDay: null
})
if (this.props.onMouseEnterProp) {
this.props.onMouseEnterProp(false);
}
}
}
onMouseEnter() {
if (this.state.isRange) {
if (this.props.onMouseEnterProp) {
this.props.onMouseEnterProp(true);
}
}
}
render() {
const {
selectedDayObj,
min,
max,
onClickOutside,
outsideClickIgnoreClass,
styles,
className
} = this.props;
const {mode, isGregorian} = this.state;
const jalaaliClassName = isGregorian ? '' : 'jalaali ';
return (
<div className={styles.calendarContainer + ' ' + jalaaliClassName + className}>
{mode === 'monthSelector' ? this.renderMonthSelector() : this.renderDays()}
</div>
);
}
}
export default onClickOutside(Calendar);