d2-ui
Version:
364 lines (323 loc) • 10.5 kB
JSX
import React from 'react';
import StylePropable from '../mixins/style-propable';
import WindowListenable from '../mixins/window-listenable';
import DateTime from '../utils/date-time';
import KeyCode from '../utils/key-code';
import Transitions from '../styles/transitions';
import CalendarMonth from './calendar-month';
import CalendarYear from './calendar-year';
import CalendarToolbar from './calendar-toolbar';
import DateDisplay from './date-display';
import SlideInTransitionGroup from '../transition-groups/slide-in';
import ClearFix from '../clearfix';
import getMuiTheme from '../styles/getMuiTheme';
const daysArray = [...Array(7)];
const Calendar = React.createClass({
propTypes: {
DateTimeFormat: React.PropTypes.func.isRequired,
disableYearSelection: React.PropTypes.bool,
firstDayOfWeek: React.PropTypes.number,
initialDate: React.PropTypes.object,
locale: React.PropTypes.string.isRequired,
maxDate: React.PropTypes.object,
minDate: React.PropTypes.object,
mode: React.PropTypes.oneOf(['portrait', 'landscape']),
onDayTouchTap: React.PropTypes.func,
open: React.PropTypes.bool,
shouldDisableDate: React.PropTypes.func,
},
contextTypes: {
muiTheme: React.PropTypes.object,
},
//for passing default theme context to children
childContextTypes: {
muiTheme: React.PropTypes.object,
},
mixins: [
StylePropable,
WindowListenable,
],
getDefaultProps() {
return {
disableYearSelection: false,
initialDate: new Date(),
minDate: DateTime.addYears(new Date(), -100),
maxDate: DateTime.addYears(new Date(), 100),
};
},
getInitialState() {
return {
muiTheme: this.context.muiTheme || getMuiTheme(),
displayDate: DateTime.getFirstDayOfMonth(this.props.initialDate),
displayMonthDay: true,
selectedDate: this.props.initialDate,
transitionDirection: 'left',
transitionEnter: true,
};
},
getChildContext() {
return {
muiTheme: this.state.muiTheme,
};
},
//to update theme inside state whenever a new theme is passed down
//from the parent / owner using context
componentWillReceiveProps(nextProps, nextContext) {
let newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme;
this.setState({muiTheme: newMuiTheme});
if (nextProps.initialDate !== this.props.initialDate) {
let d = nextProps.initialDate || new Date();
this.setState({
displayDate: DateTime.getFirstDayOfMonth(d),
selectedDate: d,
});
}
},
windowListeners: {
keydown: '_handleWindowKeyDown',
},
_yearSelector() {
if (this.props.disableYearSelection) return;
return (
<CalendarYear
key={'years'}
displayDate={this.state.displayDate}
onYearTouchTap={this._handleYearTouchTap}
selectedDate={this.state.selectedDate}
minDate={this.props.minDate}
maxDate={this.props.maxDate}
/>
);
},
getSelectedDate() {
return this.state.selectedDate;
},
isSelectedDateDisabled() {
if (!this.state.displayMonthDay) {
return false;
}
return this.refs.calendar.isSelectedDateDisabled();
},
_addSelectedDays(days) {
this._setSelectedDate(DateTime.addDays(this.state.selectedDate, days));
},
_addSelectedMonths(months) {
this._setSelectedDate(DateTime.addMonths(this.state.selectedDate, months));
},
_addSelectedYears(years) {
this._setSelectedDate(DateTime.addYears(this.state.selectedDate, years));
},
_setDisplayDate(d, newSelectedDate) {
let newDisplayDate = DateTime.getFirstDayOfMonth(d);
let direction = newDisplayDate > this.state.displayDate ? 'left' : 'right';
if (newDisplayDate !== this.state.displayDate) {
this.setState({
displayDate: newDisplayDate,
transitionDirection: direction,
selectedDate: newSelectedDate || this.state.selectedDate,
});
}
},
_setSelectedDate(date) {
let adjustedDate = date;
if (DateTime.isBeforeDate(date, this.props.minDate)) {
adjustedDate = this.props.minDate;
} else if (DateTime.isAfterDate(date, this.props.maxDate)) {
adjustedDate = this.props.maxDate;
}
let newDisplayDate = DateTime.getFirstDayOfMonth(adjustedDate);
if (newDisplayDate !== this.state.displayDate) {
this._setDisplayDate(newDisplayDate, adjustedDate);
} else {
this.setState({
selectedDate: adjustedDate,
});
}
},
_handleDayTouchTap(e, date) {
this._setSelectedDate(date);
if (this.props.onDayTouchTap) this.props.onDayTouchTap(e, date);
},
_handleMonthChange(months) {
this.setState({
transitionDirection: months >= 0 ? 'left' : 'right',
displayDate: DateTime.addMonths(this.state.displayDate, months),
});
},
_handleYearTouchTap(e, year) {
let date = DateTime.clone(this.state.selectedDate);
date.setFullYear(year);
this._setSelectedDate(date, e);
},
_getToolbarInteractions() {
return {
prevMonth: DateTime.monthDiff(this.state.displayDate, this.props.minDate) > 0,
nextMonth: DateTime.monthDiff(this.state.displayDate, this.props.maxDate) < 0,
};
},
_handleMonthDayClick() {
this.setState({
displayMonthDay: true,
});
},
_handleYearClick() {
this.setState({
displayMonthDay: false,
});
},
_handleWindowKeyDown(e) {
if (this.props.open) {
switch (e.keyCode) {
case KeyCode.UP:
if (e.altKey && e.shiftKey) {
this._addSelectedYears(-1);
} else if (e.shiftKey) {
this._addSelectedMonths(-1);
} else {
this._addSelectedDays(-7);
}
break;
case KeyCode.DOWN:
if (e.altKey && e.shiftKey) {
this._addSelectedYears(1);
} else if (e.shiftKey) {
this._addSelectedMonths(1);
} else {
this._addSelectedDays(7);
}
break;
case KeyCode.RIGHT:
if (e.altKey && e.shiftKey) {
this._addSelectedYears(1);
} else if (e.shiftKey) {
this._addSelectedMonths(1);
} else {
this._addSelectedDays(1);
}
break;
case KeyCode.LEFT:
if (e.altKey && e.shiftKey) {
this._addSelectedYears(-1);
} else if (e.shiftKey) {
this._addSelectedMonths(-1);
} else {
this._addSelectedDays(-1);
}
break;
}
}
},
render() {
let yearCount = DateTime.yearDiff(this.props.maxDate, this.props.minDate) + 1;
let weekCount = DateTime.getWeekArray(this.state.displayDate, this.props.firstDayOfWeek).length;
let toolbarInteractions = this._getToolbarInteractions();
let isLandscape = this.props.mode === 'landscape';
let styles = {
root: {
fontSize: 12,
},
calendarContainer: {
width: isLandscape ? 320 : '100%',
height: weekCount === 5 ? 284 :
weekCount === 6 ? 324 : 244,
float: isLandscape ? 'right' : 'none',
transition: Transitions.easeOut('150ms', 'height'),
overflow: 'hidden',
},
yearContainer: {
width: 280,
overflow: 'hidden',
height: yearCount < 6 ? yearCount * 56 + 10 :
weekCount === 5 ? 284 :
weekCount === 6 ? 324 : 244,
float: isLandscape ? 'right' : 'none',
},
dateDisplay: {
width: isLandscape ? 120 : '',
height: isLandscape ?
weekCount === 5 ? 238 :
weekCount === 6 ? 278 :
198 : 'auto',
float: isLandscape ? 'left' : 'none',
},
weekTitle: {
padding: '0 14px',
lineHeight: '12px',
opacity: '0.5',
height: 12,
fontWeight: '500',
margin: 0,
},
weekTitleDay: {
listStyle: 'none',
float: 'left',
width: 37,
textAlign: 'center',
margin: '0 2px',
},
};
const weekTitleDayStyle = this.prepareStyles(styles.weekTitleDay);
const {
DateTimeFormat,
locale,
firstDayOfWeek,
} = this.props;
return (
<ClearFix style={this.mergeStyles(styles.root)}>
<DateDisplay
DateTimeFormat={DateTimeFormat}
locale={locale}
disableYearSelection={this.props.disableYearSelection}
style={styles.dateDisplay}
selectedDate={this.state.selectedDate}
handleMonthDayClick={this._handleMonthDayClick}
handleYearClick={this._handleYearClick}
monthDaySelected={this.state.displayMonthDay}
mode={this.props.mode}
weekCount={weekCount}
/>
{this.state.displayMonthDay &&
<div style={this.prepareStyles(styles.calendarContainer)}>
<CalendarToolbar
DateTimeFormat={DateTimeFormat}
locale={locale}
displayDate={this.state.displayDate}
onMonthChange={this._handleMonthChange}
prevMonth={toolbarInteractions.prevMonth}
nextMonth={toolbarInteractions.nextMonth}
/>
<ClearFix
elementType="ul"
style={styles.weekTitle}
>
{daysArray.map((e, i) => (
<li key={i} style={weekTitleDayStyle}>
{DateTime.localizedWeekday(DateTimeFormat, locale, i, firstDayOfWeek)}
</li>
))}
</ClearFix>
<SlideInTransitionGroup direction={this.state.transitionDirection}>
<CalendarMonth
key={this.state.displayDate.toDateString()}
ref="calendar"
displayDate={this.state.displayDate}
onDayTouchTap={this._handleDayTouchTap}
selectedDate={this.state.selectedDate}
minDate={this.props.minDate}
maxDate={this.props.maxDate}
shouldDisableDate={this.props.shouldDisableDate}
firstDayOfWeek={this.props.firstDayOfWeek}
/>
</SlideInTransitionGroup>
</div>
}
{!this.state.displayMonthDay &&
<div style={this.prepareStyles(styles.yearContainer)}>
{this._yearSelector()}
</div>
}
</ClearFix>
);
},
});
export default Calendar;