UNPKG

@ftrack/react-toolbox

Version:

A set of React components implementing Google's Material Design specification with the power of CSS Modules.

166 lines (144 loc) 5.33 kB
import PropTypes from 'prop-types'; import React, { Component } from 'react'; import TransitionGroup from 'react-transition-group/TransitionGroup'; import CSSTransition from 'react-transition-group/CSSTransition'; import { SlideLeft, SlideRight } from '../animations'; import time from '../utils/time.js'; import utils from '../utils/utils.js'; import CalendarMonth from './CalendarMonth.js'; const DIRECTION_STEPS = { left: -1, right: 1 }; const factory = (IconButton) => { class Calendar extends Component { static propTypes = { disabledDates: PropTypes.array, display: PropTypes.oneOf(['months', 'years']), enabledDates: PropTypes.array, handleSelect: PropTypes.func, locale: PropTypes.oneOfType([ PropTypes.string, PropTypes.object ]), maxDate: PropTypes.object, minDate: PropTypes.object, onChange: PropTypes.func, selectedDate: PropTypes.object, sundayFirstDayOfWeek: PropTypes.bool, theme: PropTypes.shape({ active: PropTypes.string, calendar: PropTypes.string, next: PropTypes.string, prev: PropTypes.string, years: PropTypes.string }), viewDate: PropTypes.object }; static defaultProps = { display: 'months', selectedDate: new Date() }; state = { viewDate: this.props.selectedDate }; componentWillMount () { document.body.addEventListener('keydown', this.handleKeys); } componentDidUpdate () { if (this.refs.activeYear) { this.scrollToActive(); } } componentWillUnmount () { document.body.removeEventListener('keydown', this.handleKeys); } scrollToActive () { this.refs.years.scrollTop = this.refs.activeYear.offsetTop - this.refs.years.offsetHeight / 2 + this.refs.activeYear.offsetHeight / 2; } handleDayClick = (day) => { this.props.onChange(time.setDay(this.state.viewDate, day), true); }; handleYearClick = (event) => { const year = parseInt(event.currentTarget.id); const viewDate = time.setYear(this.props.selectedDate, year); this.setState({ viewDate }); this.props.onChange(viewDate, false); }; handleKeys = (e) => { const { selectedDate } = this.props; if (e.which === 37 || e.which === 38 || e.which === 39 || e.which === 40 || e.which === 13) e.preventDefault(); switch (e.which) { case 13: this.props.handleSelect(); break; // enter case 37: this.handleDayArrowKey(time.addDays(selectedDate, -1)); break; // left case 38: this.handleDayArrowKey(time.addDays(selectedDate, -7)); break; // up case 39: this.handleDayArrowKey(time.addDays(selectedDate, 1)); break; // right case 40: this.handleDayArrowKey(time.addDays(selectedDate, 7)); break; // down default: break; } } handleDayArrowKey = (date) => { this.setState({ viewDate: date }); this.props.onChange(date, false); } changeViewMonth = (event) => { const direction = event.currentTarget.id; this.setState({ direction, viewDate: time.addMonths(this.state.viewDate, DIRECTION_STEPS[direction]) }); }; renderYears () { return ( <ul data-react-toolbox='years' ref="years" className={this.props.theme.years}> {utils.range(1900, 2100).map(year => ( <li children={year} className={year === this.state.viewDate.getFullYear() ? this.props.theme.active : ''} id={year} key={year} onClick={this.handleYearClick} ref={year === this.state.viewDate.getFullYear() ? 'activeYear' : undefined} /> ))} </ul> ); } renderMonths () { const { theme } = this.props; const animation = this.state.direction === 'left' ? SlideLeft : SlideRight; const key = this.state.viewDate.getMonth(); return ( <div data-react-toolbox='calendar'> <IconButton id='left' className={theme.prev} icon='chevron_left' onClick={this.changeViewMonth} /> <IconButton id='right' className={theme.next} icon='chevron_right' onClick={this.changeViewMonth} /> <TransitionGroup > <CSSTransition key={key} classNames={animation} timeout={{ enter: 350, exit: 350 }}> <CalendarMonth enabledDates={this.props.enabledDates} disabledDates={this.props.disabledDates} key={key} locale={this.props.locale} maxDate={this.props.maxDate} minDate={this.props.minDate} onDayClick={this.handleDayClick} selectedDate={this.props.selectedDate} sundayFirstDayOfWeek={this.props.sundayFirstDayOfWeek} theme={this.props.theme} viewDate={this.state.viewDate} /> </CSSTransition> </TransitionGroup> </div> ); } render () { return ( <div className={this.props.theme.calendar}> {this.props.display === 'months' ? this.renderMonths() : this.renderYears()} </div> ); } } return Calendar; }; export default factory;