UNPKG

react-date-picker

Version:

A carefully crafted date picker for React

503 lines (387 loc) 11 kB
import React, { PropTypes } from 'react' import Component from 'react-class' import moment from 'moment' import assign from 'object-assign' import { Flex } from 'react-flex' import FORMAT from './utils/format' import toMoment from './toMoment' import weekDayNamesFactory from './utils/getWeekDayNames' import join from './join' import bemFactory from './bemFactory' const CLASS_NAME = 'react-date-picker__basic-month-view' const RENDER_DAY = (props) => { const divProps = assign({}, props) delete divProps.date delete divProps.dateMoment delete divProps.day delete divProps.timestamp return <div {...divProps} /> } const getWeekStartDay = (props) => { const locale = props.locale let weekStartDay = props.weekStartDay if (weekStartDay == null) { const localeData = props.localeData || moment.localeData(locale) weekStartDay = localeData._week ? localeData._week.dow : null } return weekStartDay } /** * Gets the number for the first day of the weekend * * @param {Object} props * @param {Number/String} props.weekStartDay * * @return {Number} */ const getWeekendStartDay = (props) => { const { weekendStartDay } = props if (weekendStartDay == null) { return getWeekStartDay(props) + 5 % 7 } return weekendStartDay } /** * Gets a moment that points to the first day of the week * * @param {Moment/Date/String} value] * @param {Object} props * @param {String} props.dateFormat * @param {String} props.locale * @param {Number/String} props.weekStartDay * * @return {Moment} */ const getWeekStartMoment = (value, props) => { const locale = props.locale const dateFormat = props.dateFormat const weekStartDay = getWeekStartDay(props) return toMoment(value, { locale, dateFormat }).day(weekStartDay) } /** * Returns an array of moments with the days in the month of the value * * @param {Moment/Date/String} value * * @param {Object} props * @param {String} props.locale * @param {String} props.dateFormat * @param {String} props.weekStartDay * @param {Boolean} props.alwaysShowPrevWeek * * @return {Moment[]} */ const getDaysInMonthView = (value, props) => { const { locale, dateFormat } = props const toMomentParam = { locale, dateFormat } const first = toMoment(value, toMomentParam).startOf('month') const beforeFirst = toMoment(value, toMomentParam).startOf('month').add(-1, 'days') const start = getWeekStartMoment(first, props) const result = [] let i = 0 if ( beforeFirst.isBefore(start) // and it doesn't start with a full week before and the // week has at least 1 day from current month (default) && (props.alwaysShowPrevWeek || !start.isSame(first)) ) { start.add(-1, 'weeks') } for (; i < 42; i++) { result.push(toMoment(start, toMomentParam)) start.add(1, 'days') } return result } /** * @param {Object} props * @param {String} props.locale * @param {Number} props.weekStartDay * @param {Array/Function} props.weekDayNames * * @return {String[]} */ const getWeekDayNames = (props) => { const { weekStartDay, weekDayNames, locale } = props let names = weekDayNames if (typeof names == 'function') { names = names(weekStartDay, locale) } else if (Array.isArray(names)) { names = [...names] let index = weekStartDay while (index > 0) { names.push(names.shift()) index-- } } return names } class BasicMonthView extends Component { componentWillMount() { this.updateBem(this.props) this.updateToMoment(this.props) } componentWillReceiveProps(nextProps) { if (nextProps.defaultClassName != this.props.defaultClassName) { this.updateBem(nextProps) } this.updateToMoment(nextProps) } updateBem(props) { this.bem = bemFactory(props.defaultClassName) } updateToMoment(props) { this.toMoment = (value, dateFormat) => { return toMoment(value, { locale: props.locale, dateFormat: dateFormat || props.dateFormat }) } } prepareProps(thisProps) { const props = assign({}, thisProps) props.viewMoment = props.viewMoment || this.toMoment(props.viewDate) props.weekStartDay = getWeekStartDay(props) props.className = this.prepareClassName(props) return props } prepareClassName(props) { return join( props.className, `${CLASS_NAME} dp-month-view` ) } render() { const props = this.p = this.prepareProps(this.props) const { viewMoment } = props const daysInView = props.daysInView || getDaysInMonthView(viewMoment, props) let children = [ this.renderWeekDayNames(), this.renderDays(props, daysInView) ] if (props.renderChildren) { children = props.renderChildren(children, props) } const flexProps = assign({}, props) delete flexProps.alwaysShowPrevWeek delete flexProps.cleanup delete flexProps.dateFormat delete flexProps.daysInView delete flexProps.defaultClassName delete flexProps.defaultDate delete flexProps.defaultValue delete flexProps.forceValidDate delete flexProps.locale delete flexProps.moment delete flexProps.onClockEnterKey delete flexProps.onClockEscapeKey delete flexProps.onClockInputBlur delete flexProps.onClockInputFocus delete flexProps.onClockInputMouseDown delete flexProps.onFooterCancelClick delete flexProps.onFooterClearClick delete flexProps.onFooterOkClick delete flexProps.onFooterTodayClick delete flexProps.onRenderDay delete flexProps.renderChildren delete flexProps.renderDay delete flexProps.timestamp delete flexProps.value delete flexProps.viewDate delete flexProps.viewMoment delete flexProps.weekDayNames delete flexProps.weekNumbers delete flexProps.weekNumberName delete flexProps.weekStartDay if (typeof props.cleanup == 'function') { props.cleanup(flexProps) } return <Flex column wrap={false} inline alignItems="stretch" {...flexProps} children={children} /> } /** * Render the week number cell * @param {Moment[]} days The days in a week * @return {React.DOM} */ renderWeekNumber(props, days) { const firstDayOfWeek = days[0] const week = firstDayOfWeek.weeks() const weekNumberProps = { key: 'week', className: `${this.bem('cell')} ${this.bem('week-number')} dp-cell dp-weeknumber`, // week number week, // the days in this week days, date: firstDayOfWeek, children: week } const renderWeekNumber = props.renderWeekNumber let result if (renderWeekNumber) { result = renderWeekNumber(weekNumberProps) } if (result === undefined) { const divProps = assign({}, weekNumberProps) delete divProps.date delete divProps.days delete divProps.week result = <div {...divProps} /> } return result } /** * Render the given array of days * @param {Moment[]} days * * @return {React.DOM} */ renderDays(props, days) { const nodes = days.map((date) => this.renderDay(props, date)) const len = days.length const buckets = [] const bucketsLen = Math.ceil(len / 7) let i = 0 let weekStart let weekEnd for (; i < bucketsLen; i++) { weekStart = i * 7 weekEnd = (i + 1) * 7 buckets.push( [ props.weekNumbers && this.renderWeekNumber(props, days.slice(weekStart, weekEnd)) ].concat( nodes.slice(weekStart, weekEnd) ) ) } return buckets.map((bucket, index) => <div key={`row_${index}`} className={`${this.bem('row')} dp-week dp-row`} children={bucket} />) } renderDay(props, dateMoment) { const dayText = FORMAT.day(dateMoment, props.dayFormat) const classes = [ this.bem('cell'), this.bem('day'), 'dp-cell dp-day' ] let renderDayProps = { day: dayText, dateMoment, timestamp: +dateMoment, key: dayText, className: classes.join(' '), children: dayText } if (typeof props.onRenderDay === 'function') { renderDayProps = props.onRenderDay(renderDayProps) } const renderFunction = props.renderDay || RENDER_DAY let result = renderFunction(renderDayProps) if (result === undefined) { result = RENDER_DAY(renderDayProps) } return result } renderWeekDayNames() { const props = this.p const { weekNumbers, weekNumberName, weekDayNames, renderWeekDayNames, renderWeekDayName, weekStartDay } = props if (weekDayNames === false) { return null } const names = weekNumbers ? [weekNumberName].concat(getWeekDayNames(props)) : getWeekDayNames(props) const className = `${this.bem('row')} ${this.bem('week-day-names')} dp-row dp-week-day-names` const renderProps = { className, names } if (renderWeekDayNames) { return renderWeekDayNames(renderProps) } return <div className={className}> {names.map((name, index) => { const props = { weekStartDay, index, name, key: index, className: `${this.bem('cell')} ${this.bem('week-day-name')} dp-week-day-name`, children: name } if (renderWeekDayName) { return renderWeekDayName(props) } const divProps = assign({}, props) delete divProps.index delete divProps.weekStartDay delete divProps.name return <div {...divProps} /> })} </div> } } BasicMonthView.propTypes = { viewDate: PropTypes.any, viewMoment: PropTypes.any, locale: PropTypes.string, weekStartDay: PropTypes.number, // 0 is Sunday in the English locale // boolean prop to show/hide week numbers weekNumbers: PropTypes.bool, // the name to give to the week number column weekNumberName: PropTypes.string, weekDayNames(props, propName) { const value = props[propName] if (typeof value != 'function' && value !== false && !Array.isArray(value)) { return new Error(`"weekDayNames" should be a function, an array or the boolean "false"`) } return undefined }, renderWeekDayNames: PropTypes.func, renderWeekDayName: PropTypes.func, renderWeekNumber: PropTypes.func, renderDay: PropTypes.func, onRenderDay: PropTypes.func, alwaysShowPrevWeek: PropTypes.bool } BasicMonthView.defaultProps = { defaultClassName: CLASS_NAME, dateFormat: 'YYYY-MM-DD', alwaysShowPrevWeek: false, weekNumbers: true, weekNumberName: null, weekDayNames: weekDayNamesFactory } export default BasicMonthView export { getWeekStartDay, getWeekStartMoment, getWeekendStartDay, getDaysInMonthView }