UNPKG

react-dates

Version:

A responsive and accessible date range picker component built with React

296 lines (258 loc) 8.5 kB
import React from 'react'; import ReactDOM from 'react-dom'; import moment from 'moment'; import cx from 'classnames'; import Portal from 'react-portal'; import isTouchDevice from '../utils/isTouchDevice'; import getResponsiveContainerStyles from '../utils/getResponsiveContainerStyles'; import isInclusivelyAfterDay from '../utils/isInclusivelyAfterDay'; import DateRangePickerInputController from './DateRangePickerInputController'; import DayPickerRangeController from './DayPickerRangeController'; import CloseButton from '../svg/close.svg'; import DateRangePickerShape from '../shapes/DateRangePickerShape'; import { START_DATE, END_DATE, HORIZONTAL_ORIENTATION, VERTICAL_ORIENTATION, ANCHOR_LEFT, ANCHOR_RIGHT, } from '../../constants'; const propTypes = DateRangePickerShape; const defaultProps = { startDateId: START_DATE, endDateId: END_DATE, focusedInput: null, minimumNights: 1, isDayBlocked: () => false, isOutsideRange: day => !isInclusivelyAfterDay(day, moment()), enableOutsideDays: false, numberOfMonths: 2, showClearDates: false, disabled: false, required: false, reopenPickerOnClearDates: false, keepOpenOnDateSelect: false, initialVisibleMonth: () => moment(), navPrev: null, navNext: null, orientation: HORIZONTAL_ORIENTATION, anchorDirection: ANCHOR_LEFT, horizontalMargin: 0, withPortal: false, withFullScreenPortal: false, onDatesChange() {}, onFocusChange() {}, onPrevMonthClick() {}, onNextMonthClick() {}, // i18n displayFormat: () => moment.localeData().longDateFormat('L'), monthFormat: 'MMMM YYYY', phrases: { closeDatePicker: 'Close', clearDates: 'Clear Dates', }, }; export default class DateRangePicker extends React.Component { constructor(props) { super(props); this.state = { dayPickerContainerStyles: {}, }; this.isTouchDevice = isTouchDevice(); this.onOutsideClick = this.onOutsideClick.bind(this); this.responsivizePickerPosition = this.responsivizePickerPosition.bind(this); } componentDidMount() { window.addEventListener('resize', this.responsivizePickerPosition); this.responsivizePickerPosition(); } componentWillUnmount() { window.removeEventListener('resize', this.responsivizePickerPosition); } onOutsideClick() { const { focusedInput, onFocusChange } = this.props; if (!focusedInput) return; onFocusChange(null); } getDayPickerContainerClasses() { const { focusedInput, orientation, withPortal, withFullScreenPortal, anchorDirection, } = this.props; const showDatepicker = focusedInput === START_DATE || focusedInput === END_DATE; const dayPickerClassName = cx('DateRangePicker__picker', { 'DateRangePicker__picker--show': showDatepicker, 'DateRangePicker__picker--invisible': !showDatepicker, 'DateRangePicker__picker--direction-left': anchorDirection === ANCHOR_LEFT, 'DateRangePicker__picker--direction-right': anchorDirection === ANCHOR_RIGHT, 'DateRangePicker__picker--horizontal': orientation === HORIZONTAL_ORIENTATION, 'DateRangePicker__picker--vertical': orientation === VERTICAL_ORIENTATION, 'DateRangePicker__picker--portal': withPortal || withFullScreenPortal, 'DateRangePicker__picker--full-screen-portal': withFullScreenPortal, }); return dayPickerClassName; } getDayPickerDOMNode() { return ReactDOM.findDOMNode(this.dayPicker); } responsivizePickerPosition() { const { anchorDirection, horizontalMargin, withPortal, withFullScreenPortal } = this.props; const { dayPickerContainerStyles } = this.state; const isAnchoredLeft = anchorDirection === ANCHOR_LEFT; if (!withPortal && !withFullScreenPortal) { const containerRect = this.dayPickerContainer.getBoundingClientRect(); const currentOffset = dayPickerContainerStyles[anchorDirection] || 0; const containerEdge = isAnchoredLeft ? containerRect[ANCHOR_RIGHT] : containerRect[ANCHOR_LEFT]; this.setState({ dayPickerContainerStyles: getResponsiveContainerStyles( anchorDirection, currentOffset, containerEdge, horizontalMargin ), }); } } maybeRenderDayPickerWithPortal() { const { focusedInput, withPortal, withFullScreenPortal } = this.props; if (withPortal || withFullScreenPortal) { return ( <Portal isOpened={focusedInput !== null}> {this.renderDayPicker()} </Portal> ); } return this.renderDayPicker(); } renderDayPicker() { const { isDayBlocked, isOutsideRange, numberOfMonths, orientation, monthFormat, navPrev, navNext, onPrevMonthClick, onNextMonthClick, onDatesChange, onFocusChange, withPortal, withFullScreenPortal, enableOutsideDays, initialVisibleMonth, focusedInput, startDate, endDate, minimumNights, keepOpenOnDateSelect, } = this.props; const { dayPickerContainerStyles } = this.state; const onOutsideClick = !withFullScreenPortal ? this.onOutsideClick : undefined; return ( <div ref={ref => { this.dayPickerContainer = ref; }} className={this.getDayPickerContainerClasses()} style={dayPickerContainerStyles} > <DayPickerRangeController ref={ref => { this.dayPicker = ref; }} orientation={orientation} enableOutsideDays={enableOutsideDays} numberOfMonths={numberOfMonths} onDayMouseEnter={this.onDayMouseEnter} onDayMouseLeave={this.onDayMouseLeave} onDayMouseDown={this.onDayClick} onDayTouchTap={this.onDayClick} onPrevMonthClick={onPrevMonthClick} onNextMonthClick={onNextMonthClick} onDatesChange={onDatesChange} onFocusChange={onFocusChange} focusedInput={focusedInput} startDate={startDate} endDate={endDate} monthFormat={monthFormat} withPortal={withPortal || withFullScreenPortal} hidden={!focusedInput} initialVisibleMonth={initialVisibleMonth} onOutsideClick={onOutsideClick} navPrev={navPrev} navNext={navNext} minimumNights={minimumNights} isOutsideRange={isOutsideRange} isDayBlocked={isDayBlocked} keepOpenOnDateSelect={keepOpenOnDateSelect} /> {withFullScreenPortal && <button className="DateRangePicker__close" type="button" onClick={this.onOutsideClick} > <span className="screen-reader-only"> {this.props.phrases.closeDatePicker} </span> <CloseButton /> </button> } </div> ); } render() { const { startDate, startDateId, startDatePlaceholderText, endDate, endDateId, endDatePlaceholderText, focusedInput, showClearDates, disabled, required, phrases, isOutsideRange, withPortal, withFullScreenPortal, displayFormat, reopenPickerOnClearDates, keepOpenOnDateSelect, onDatesChange, onFocusChange, } = this.props; return ( <div className="DateRangePicker"> <DateRangePickerInputController startDate={startDate} startDateId={startDateId} startDatePlaceholderText={startDatePlaceholderText} isStartDateFocused={focusedInput === START_DATE} endDate={endDate} endDateId={endDateId} endDatePlaceholderText={endDatePlaceholderText} isEndDateFocused={focusedInput === END_DATE} displayFormat={displayFormat} showClearDates={showClearDates} showCaret={!withPortal && !withFullScreenPortal} disabled={disabled} required={required} reopenPickerOnClearDates={reopenPickerOnClearDates} keepOpenOnDateSelect={keepOpenOnDateSelect} isOutsideRange={isOutsideRange} withFullScreenPortal={withFullScreenPortal} onDatesChange={onDatesChange} onFocusChange={onFocusChange} phrases={phrases} /> {this.maybeRenderDayPickerWithPortal()} </div> ); } } DateRangePicker.propTypes = propTypes; DateRangePicker.defaultProps = defaultProps;