UNPKG

thinkful-ui

Version:

Shared navigation and UI resources for Thinkful.

195 lines (163 loc) 5.88 kB
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}