thinkful-ui
Version:
Shared navigation and UI resources for Thinkful.
195 lines (163 loc) • 5.88 kB
JSX
const React = require('react');
const moment = require('moment-timezone');
const cx = require('classnames');
const _ = require('lodash');
const { Icon } = require('../Icon')
require('./datepicker.less');
/**
* Day component
8 @property date {DateTime} of the date to be displayed
*/
class Day extends React.Component {
static propTypes = {
day: React.PropTypes.string
}
render () {
const {date, unclickable, otherMonth, active, onClick} = this.props;
const classes = cx(
"day",
{unclickable: unclickable},
{'other-month': otherMonth},
{active: active},
{today: moment().dayOfYear() === moment(date).dayOfYear()});
const isToday = moment(date).dayOfYear() === moment().dayOfYear();
const isFirstOfMonth = moment(date).date() === 1;
const monthName = moment(date).format('MMM')
return (
<div
className={classes}
onClick={onClick.bind()}>
{isFirstOfMonth && <div className="day-tiny-text">{monthName}</div>}
{isToday && <div className="day-tiny-text">Today</div>}
{moment(date).date()}
</div>
);
}
}
class DatePicker extends React.Component {
static displayName = "DatePicker"
static defaultProps = {
defaultDate: moment(),
}
constructor() {
super();
this.state = {
activeIndex: null,
days: [],
monthsNavigated: 0,
value: null,
visible: false,
}
}
componentDidMount() {
this._generateDays(this.props.defaultDate);
document.addEventListener('click', this._checkClickAway.bind(this))
}
componentWillUnmount() {
document.removeEventListener('click', this._checkClickAway.bind(this))
}
componentWillReceiveProps(newProps) {
if (this.props.defaultDate.dayOfYear() !== newProps.defaultDate.dayOfYear()) {
this._generateDays(newProps.defaultDate);
}
}
_generateDays(defaultDate=false) {
let {monthsNavigated, activeIndex} = this.state;
// If called on initial render, check defaultDate to determine if calendar
// should start on a month different than the current one
if (defaultDate) {
monthsNavigated = defaultDate.month() - moment().month();
}
const startDay = moment().add(monthsNavigated, 'month').
startOf('month').startOf('week').startOf('day');
const endDay = moment().add(monthsNavigated, 'month').
endOf('month').endOf('week').startOf('day');
const totalDays = endDay.diff(startDay, 'days') + 1;
const days = _.map(Array(totalDays), (i, idx) => {
return {
dateObj: moment(startDay).add(idx, 'day'),
dayOfYear: moment(startDay).add(idx, 'day').dayOfYear()
}});
// Keep existing activeIndex if it is defined and a new defaultDate has not come thru
activeIndex = (!! defaultDate || ! activeIndex || activeIndex === -1) ?
_.findIndex(days, {dayOfYear: moment(defaultDate || '').dayOfYear()}) : activeIndex;
this.setState({
days: days,
activeIndex: activeIndex,
monthsNavigated: monthsNavigated
});
}
_checkClickAway(event) {
// Close the picker if the click wasn't on the dropdown button or calendar
if (
(this.dropdownButton && !this.dropdownButton.contains(event.target)) &&
(this.calendar && !this.calendar.contains(event.target))) {
this.setState({visible: false});
}
}
_handleClick(event, newDay) {
const {days} = this.state;
const {handleChange} = this.props;
const newActiveIndex = _.findIndex(days, {dayOfYear: newDay});
this.setState({
activeIndex: newActiveIndex,
value: days[newActiveIndex].dateObj,
});
this._toggleOpen();
handleChange(days[newActiveIndex].dateObj);
}
_navigateForward() {
this.state.monthsNavigated = this.state.monthsNavigated + 1;
this._generateDays();
}
_navigateBack() {
this.state.monthsNavigated = this.state.monthsNavigated - 1;
this._generateDays();
}
_toggleOpen() {
this.setState({visible: !this.state.visible});
}
render() {
const {className, placeholder} = this.props;
const {days, activeIndex, monthsNavigated, value, visible} = this.state;
const activeDay = days[activeIndex] && days[activeIndex].dateObj;
const datePickerClasses = cx('date-picker', {hidden: !visible});
return (
<div className={cx("date-picker-container", className)}>
<div
className="button date-picker-button"
onClick={this._toggleOpen.bind(this)}
ref={c => this.dropdownButton = c}>
{!value && placeholder ? placeholder : moment(activeDay).format('MM/DD/YYYY')}
<Icon name="navigatedown" />
</div>
<div className={datePickerClasses} ref={c => this.calendar = c}>
<Icon
name="navigateleft"
onClick={this._navigateBack.bind(this)} />
<Icon
name="navigateright"
onClick={this._navigateForward.bind(this)} />
<div className="selected-day">
{moment(activeDay).format('dddd, MMMM Do')}
</div>
<div className="day-headings">
{['S', 'M', 'T', 'W', 'H', 'F', 'S'].
map((day, key) => <div key={key} className="day-heading">{day}</div>)}
</div>
<div className="days-container">
{days.map((day, key) => <Day
date={day.dateObj}
key={key}
active={key===activeIndex}
otherMonth={day.dateObj.month() !==
moment().add(monthsNavigated, 'month').month()}
unclickable={false}
onClick={event => this._handleClick(event, day.dayOfYear)}/>)}
</div>
</div>
</div>
);
}
}
export {DatePicker}