react-dates-rtl
Version:
Based on react-dates by airbnb [with RTL support]
241 lines (205 loc) • 7.09 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import shallowCompare from 'react-addons-shallow-compare';
import momentPropTypes from 'react-moment-proptypes';
import { forbidExtraProps, nonNegativeInteger } from 'airbnb-prop-types';
import moment from 'moment';
import cx from 'classnames';
import { addEventListener, removeEventListener } from 'consolidated-events';
import { CalendarDayPhrases } from '../defaultPhrases';
import getPhrasePropTypes from '../utils/getPhrasePropTypes';
import CalendarMonth from './CalendarMonth';
import isTransitionEndSupported from '../utils/isTransitionEndSupported';
import getTransformStyles from '../utils/getTransformStyles';
import getCalendarMonthWidth from '../utils/getCalendarMonthWidth';
import ScrollableOrientationShape from '../shapes/ScrollableOrientationShape';
import {
HORIZONTAL_ORIENTATION,
VERTICAL_ORIENTATION,
VERTICAL_SCROLLABLE,
DAY_SIZE,
} from '../../constants';
const propTypes = forbidExtraProps({
enableOutsideDays: PropTypes.bool,
firstVisibleMonthIndex: PropTypes.number,
initialMonth: momentPropTypes.momentObj,
isAnimating: PropTypes.bool,
numberOfMonths: PropTypes.number,
modifiers: PropTypes.object,
orientation: ScrollableOrientationShape,
onDayClick: PropTypes.func,
onDayMouseEnter: PropTypes.func,
onDayMouseLeave: PropTypes.func,
onMonthTransitionEnd: PropTypes.func,
renderDay: PropTypes.func,
transformValue: PropTypes.string,
daySize: nonNegativeInteger,
focusedDate: momentPropTypes.momentObj, // indicates focusable day
isFocused: PropTypes.bool, // indicates whether or not to move focus to focusable day
// i18n
monthFormat: PropTypes.string,
phrases: PropTypes.shape(getPhrasePropTypes(CalendarDayPhrases)),
isRTL: PropTypes.bool,
});
const defaultProps = {
enableOutsideDays: false,
firstVisibleMonthIndex: 0,
initialMonth: moment(),
isAnimating: false,
numberOfMonths: 1,
modifiers: {},
orientation: HORIZONTAL_ORIENTATION,
onDayClick() {},
onDayMouseEnter() {},
onDayMouseLeave() {},
onMonthTransitionEnd() {},
renderDay: null,
transformValue: 'none',
daySize: DAY_SIZE,
focusedDate: null,
isFocused: false,
// i18n
monthFormat: 'MMMM YYYY', // english locale
phrases: CalendarDayPhrases,
isRTL: false,
};
function getMonths(initialMonth, numberOfMonths) {
let month = initialMonth.clone().subtract(1, 'month');
const months = [];
for (let i = 0; i < numberOfMonths + 2; i += 1) {
months.push(month);
month = month.clone().add(1, 'month');
}
return months;
}
export default class CalendarMonthGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
months: getMonths(props.initialMonth, props.numberOfMonths),
};
this.isTransitionEndSupported = isTransitionEndSupported();
this.onTransitionEnd = this.onTransitionEnd.bind(this);
}
componentDidMount() {
this.eventHandle = addEventListener(
this.container,
'transitionend',
this.onTransitionEnd,
);
}
componentWillReceiveProps(nextProps) {
const { initialMonth, numberOfMonths } = nextProps;
const { months } = this.state;
const hasMonthChanged = !this.props.initialMonth.isSame(initialMonth, 'month');
const hasNumberOfMonthsChanged = this.props.numberOfMonths !== numberOfMonths;
let newMonths = months;
if (hasMonthChanged && !hasNumberOfMonthsChanged) {
if (initialMonth.isAfter(this.props.initialMonth)) {
newMonths = months.slice(1);
newMonths.push(months[months.length - 1].clone().add(1, 'month'));
} else {
newMonths = months.slice(0, months.length - 1);
newMonths.unshift(months[0].clone().subtract(1, 'month'));
}
}
if (hasNumberOfMonthsChanged) {
newMonths = getMonths(initialMonth, numberOfMonths);
}
this.setState({
months: newMonths,
});
}
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
componentDidUpdate() {
const { isAnimating, onMonthTransitionEnd } = this.props;
// For IE9, immediately call onMonthTransitionEnd instead of
// waiting for the animation to complete
if (!this.isTransitionEndSupported && isAnimating) {
onMonthTransitionEnd();
}
}
componentWillUnmount() {
removeEventListener(this.eventHandle);
}
onTransitionEnd() {
this.props.onMonthTransitionEnd();
}
render() {
const {
enableOutsideDays,
firstVisibleMonthIndex,
isAnimating,
modifiers,
numberOfMonths,
monthFormat,
orientation,
transformValue,
daySize,
onDayMouseEnter,
onDayMouseLeave,
onDayClick,
renderDay,
onMonthTransitionEnd,
focusedDate,
isFocused,
phrases,
isRTL,
} = this.props;
const { months } = this.state;
const isVertical = orientation === VERTICAL_ORIENTATION;
const isVerticalScrollable = orientation === VERTICAL_SCROLLABLE;
const isHorizontal = orientation === HORIZONTAL_ORIENTATION;
const className = cx('CalendarMonthGrid', {
'CalendarMonthGrid--horizontal': isHorizontal,
'CalendarMonthGrid--vertical': isVertical,
'CalendarMonthGrid--vertical-scrollable': isVerticalScrollable,
'CalendarMonthGrid--animating': isAnimating,
});
const calendarMonthWidth = getCalendarMonthWidth(daySize);
const width = isVertical || isVerticalScrollable ?
calendarMonthWidth :
(numberOfMonths + 2) * calendarMonthWidth;
const style = {
...getTransformStyles(transformValue),
width,
};
return (
<div
ref={(ref) => { this.container = ref; }}
className={className}
style={style}
onTransitionEnd={onMonthTransitionEnd}
>
{months.map((month, i) => {
const isVisible =
(i >= firstVisibleMonthIndex) && (i < firstVisibleMonthIndex + numberOfMonths);
return (
<CalendarMonth
key={month.format('YYYY-MM')}
month={month}
isVisible={isVisible}
enableOutsideDays={enableOutsideDays}
modifiers={modifiers}
monthFormat={monthFormat}
orientation={orientation}
onDayMouseEnter={onDayMouseEnter}
onDayMouseLeave={onDayMouseLeave}
onDayClick={onDayClick}
renderDay={renderDay}
daySize={daySize}
focusedDate={isVisible ? focusedDate : null}
isFocused={isFocused}
phrases={phrases}
isRTL={isRTL}
/>
);
})}
</div>
);
}
}
CalendarMonthGrid.propTypes = propTypes;
CalendarMonthGrid.defaultProps = defaultProps;