2018-05-28 10:55:19 +00:00
|
|
|
/** @format */
|
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2018-07-22 23:43:21 +00:00
|
|
|
import { Component } from '@wordpress/element';
|
2018-05-28 10:55:19 +00:00
|
|
|
import moment from 'moment';
|
2018-09-11 07:28:50 +00:00
|
|
|
import 'core-js/fn/object/assign';
|
|
|
|
import 'core-js/fn/array/from';
|
2018-05-28 10:55:19 +00:00
|
|
|
import {
|
|
|
|
DayPickerRangeController,
|
|
|
|
isInclusivelyAfterDay,
|
|
|
|
isInclusivelyBeforeDay,
|
|
|
|
} from 'react-dates';
|
|
|
|
import { partial } from 'lodash';
|
|
|
|
import { __, sprintf } from '@wordpress/i18n';
|
|
|
|
import classnames from 'classnames';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import 'react-dates/lib/css/_datepicker.css';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
2018-07-22 23:43:21 +00:00
|
|
|
import DateInput from './input';
|
2018-08-02 22:20:48 +00:00
|
|
|
import { isMobileViewport } from 'lib/ui';
|
2018-05-28 10:55:19 +00:00
|
|
|
import phrases from './phrases';
|
2018-08-02 22:20:48 +00:00
|
|
|
import { validateDateInputForRange } from 'lib/date';
|
2018-05-28 10:55:19 +00:00
|
|
|
import './style.scss';
|
|
|
|
|
2018-08-31 17:27:21 +00:00
|
|
|
/**
|
|
|
|
* This is wrapper for a [react-dates](https://github.com/airbnb/react-dates) powered calendar.
|
|
|
|
*/
|
2018-05-28 10:55:19 +00:00
|
|
|
class DateRange extends Component {
|
|
|
|
constructor( props ) {
|
|
|
|
super( props );
|
|
|
|
|
|
|
|
this.onDatesChange = this.onDatesChange.bind( this );
|
|
|
|
this.onFocusChange = this.onFocusChange.bind( this );
|
|
|
|
this.onInputChange = this.onInputChange.bind( this );
|
|
|
|
this.getOutsideRange = this.getOutsideRange.bind( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
onDatesChange( { startDate, endDate } ) {
|
2018-07-22 23:43:21 +00:00
|
|
|
const { onUpdate, shortDateFormat } = this.props;
|
|
|
|
onUpdate( {
|
|
|
|
after: startDate,
|
|
|
|
before: endDate,
|
2018-07-02 02:51:00 +00:00
|
|
|
afterText: startDate ? startDate.format( shortDateFormat ) : '',
|
|
|
|
beforeText: endDate ? endDate.format( shortDateFormat ) : '',
|
2018-07-22 23:43:21 +00:00
|
|
|
afterError: null,
|
|
|
|
beforeError: null,
|
2018-05-28 10:55:19 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
onFocusChange( focusedInput ) {
|
2018-07-22 23:43:21 +00:00
|
|
|
this.props.onUpdate( {
|
|
|
|
focusedInput: ! focusedInput ? 'startDate' : focusedInput,
|
2018-05-28 10:55:19 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2018-07-22 23:43:21 +00:00
|
|
|
onInputChange( input, event ) {
|
|
|
|
const value = event.target.value;
|
2018-07-22 23:43:21 +00:00
|
|
|
const { after, before, shortDateFormat } = this.props;
|
|
|
|
const { date, error } = validateDateInputForRange(
|
|
|
|
input,
|
|
|
|
value,
|
|
|
|
before,
|
|
|
|
after,
|
|
|
|
shortDateFormat
|
|
|
|
);
|
|
|
|
this.props.onUpdate( {
|
|
|
|
[ input ]: date,
|
2018-07-22 23:43:21 +00:00
|
|
|
[ input + 'Text' ]: value,
|
|
|
|
[ input + 'Error' ]: value.length > 0 ? error : null,
|
|
|
|
} );
|
2018-05-28 10:55:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getOutsideRange() {
|
2018-08-31 17:19:13 +00:00
|
|
|
const { invalidDays } = this.props;
|
|
|
|
if ( 'string' === typeof invalidDays ) {
|
|
|
|
switch ( invalidDays ) {
|
2018-05-28 10:55:19 +00:00
|
|
|
case 'past':
|
|
|
|
return day => isInclusivelyBeforeDay( day, moment() );
|
|
|
|
case 'future':
|
|
|
|
return day => isInclusivelyAfterDay( day, moment() );
|
|
|
|
case 'none':
|
|
|
|
default:
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
2018-08-31 17:19:13 +00:00
|
|
|
return 'function' === typeof invalidDays ? invalidDays : undefined;
|
2018-05-28 10:55:19 +00:00
|
|
|
}
|
|
|
|
|
2018-07-22 23:43:21 +00:00
|
|
|
setTnitialVisibleMonth( isDoubleCalendar, before ) {
|
|
|
|
return () => {
|
|
|
|
const visibleDate = before || moment();
|
|
|
|
if ( isDoubleCalendar ) {
|
|
|
|
return visibleDate.clone().subtract( 1, 'month' );
|
|
|
|
}
|
|
|
|
return visibleDate;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-05-28 10:55:19 +00:00
|
|
|
render() {
|
2018-07-22 23:43:21 +00:00
|
|
|
const {
|
|
|
|
after,
|
|
|
|
before,
|
|
|
|
focusedInput,
|
|
|
|
afterText,
|
|
|
|
beforeText,
|
|
|
|
afterError,
|
|
|
|
beforeError,
|
|
|
|
shortDateFormat,
|
|
|
|
} = this.props;
|
2018-05-28 10:55:19 +00:00
|
|
|
const isOutsideRange = this.getOutsideRange();
|
|
|
|
const isMobile = isMobileViewport();
|
2018-07-22 23:43:21 +00:00
|
|
|
const isDoubleCalendar = isMobile && window.innerWidth > 624;
|
2018-05-28 10:55:19 +00:00
|
|
|
return (
|
2018-07-22 23:43:21 +00:00
|
|
|
<div
|
|
|
|
className={ classnames( 'woocommerce-calendar', {
|
|
|
|
'is-mobile': isMobile,
|
|
|
|
} ) }
|
|
|
|
>
|
|
|
|
<div className="woocommerce-calendar__inputs">
|
|
|
|
<DateInput
|
2018-07-02 02:51:00 +00:00
|
|
|
value={ afterText }
|
|
|
|
onChange={ partial( this.onInputChange, 'after' ) }
|
2018-07-22 23:43:21 +00:00
|
|
|
dateFormat={ shortDateFormat }
|
|
|
|
label={ __( 'Start Date', 'wc-admin' ) }
|
|
|
|
error={ afterError }
|
|
|
|
describedBy={ sprintf(
|
2018-05-28 10:55:19 +00:00
|
|
|
__(
|
|
|
|
"Date input describing a selected date range's start date in format %s",
|
2018-07-10 12:48:06 +00:00
|
|
|
'wc-admin'
|
2018-05-28 10:55:19 +00:00
|
|
|
),
|
2018-06-27 03:54:01 +00:00
|
|
|
shortDateFormat
|
2018-05-28 10:55:19 +00:00
|
|
|
) }
|
2018-07-22 23:43:21 +00:00
|
|
|
/>
|
|
|
|
<div className="woocommerce-calendar__inputs-to">{ __( 'to', 'wc-admin' ) }</div>
|
|
|
|
<DateInput
|
2018-07-02 02:51:00 +00:00
|
|
|
value={ beforeText }
|
|
|
|
onChange={ partial( this.onInputChange, 'before' ) }
|
2018-07-22 23:43:21 +00:00
|
|
|
dateFormat={ shortDateFormat }
|
|
|
|
label={ __( 'End Date', 'wc-admin' ) }
|
|
|
|
error={ beforeError }
|
|
|
|
describedBy={ sprintf(
|
2018-05-28 10:55:19 +00:00
|
|
|
__(
|
|
|
|
"Date input describing a selected date range's end date in format %s",
|
2018-07-10 12:48:06 +00:00
|
|
|
'wc-admin'
|
2018-05-28 10:55:19 +00:00
|
|
|
),
|
2018-06-27 03:54:01 +00:00
|
|
|
shortDateFormat
|
2018-05-28 10:55:19 +00:00
|
|
|
) }
|
2018-07-22 23:43:21 +00:00
|
|
|
/>
|
2018-05-28 10:55:19 +00:00
|
|
|
</div>
|
2018-07-22 23:43:21 +00:00
|
|
|
<div className="woocommerce-calendar__react-dates">
|
2018-05-28 10:55:19 +00:00
|
|
|
<DayPickerRangeController
|
|
|
|
onDatesChange={ this.onDatesChange }
|
|
|
|
onFocusChange={ this.onFocusChange }
|
|
|
|
focusedInput={ focusedInput }
|
2018-07-02 02:51:00 +00:00
|
|
|
startDate={ after }
|
|
|
|
endDate={ before }
|
2018-05-28 10:55:19 +00:00
|
|
|
orientation={ 'horizontal' }
|
2018-07-22 23:43:21 +00:00
|
|
|
numberOfMonths={ isDoubleCalendar ? 2 : 1 }
|
2018-05-28 10:55:19 +00:00
|
|
|
isOutsideRange={ isOutsideRange }
|
|
|
|
minimumNights={ 0 }
|
|
|
|
hideKeyboardShortcutsPanel
|
|
|
|
noBorder
|
2018-07-22 23:43:21 +00:00
|
|
|
initialVisibleMonth={ this.setTnitialVisibleMonth( isDoubleCalendar, before ) }
|
2018-05-28 10:55:19 +00:00
|
|
|
phrases={ phrases }
|
2018-07-04 01:50:12 +00:00
|
|
|
firstDayOfWeek={ Number( wcSettings.date.dow ) }
|
2018-05-28 10:55:19 +00:00
|
|
|
/>
|
|
|
|
</div>
|
2018-07-22 23:43:21 +00:00
|
|
|
</div>
|
2018-05-28 10:55:19 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DateRange.propTypes = {
|
2018-08-31 17:27:21 +00:00
|
|
|
/**
|
|
|
|
* A moment date object representing the selected start. `null` for no selection.
|
|
|
|
*/
|
2018-07-02 02:51:00 +00:00
|
|
|
after: PropTypes.object,
|
2018-08-31 17:27:21 +00:00
|
|
|
/**
|
|
|
|
* A string error message, shown to the user.
|
|
|
|
*/
|
|
|
|
afterError: PropTypes.string,
|
|
|
|
/**
|
|
|
|
* The start date in human-readable format. Displayed in the text input.
|
|
|
|
*/
|
|
|
|
afterText: PropTypes.string,
|
|
|
|
/**
|
|
|
|
* A moment date object representing the selected end. `null` for no selection.
|
|
|
|
*/
|
2018-07-02 02:51:00 +00:00
|
|
|
before: PropTypes.object,
|
2018-08-31 17:27:21 +00:00
|
|
|
/**
|
|
|
|
* A string error message, shown to the user.
|
|
|
|
*/
|
|
|
|
beforeError: PropTypes.string,
|
|
|
|
/**
|
|
|
|
* The end date in human-readable format. Displayed in the text input.
|
|
|
|
*/
|
|
|
|
beforeText: PropTypes.string,
|
|
|
|
/**
|
|
|
|
* String identifying which is the currently focused input (start or end).
|
|
|
|
*/
|
|
|
|
focusedInput: PropTypes.string,
|
|
|
|
/**
|
|
|
|
* Optionally invalidate certain days. `past`, `future`, `none`, or function are accepted.
|
|
|
|
* A function will be passed to react-dates' `isOutsideRange` prop
|
|
|
|
*/
|
2018-08-31 17:19:13 +00:00
|
|
|
invalidDays: PropTypes.oneOfType( [
|
2018-05-28 10:55:19 +00:00
|
|
|
PropTypes.oneOf( [ 'past', 'future', 'none' ] ),
|
|
|
|
PropTypes.func,
|
|
|
|
] ),
|
2018-08-31 17:27:21 +00:00
|
|
|
/**
|
|
|
|
* A function called upon selection of a date.
|
|
|
|
*/
|
|
|
|
onUpdate: PropTypes.func.isRequired,
|
|
|
|
/**
|
|
|
|
* The date format in moment.js-style tokens.
|
|
|
|
*/
|
2018-07-22 23:43:21 +00:00
|
|
|
shortDateFormat: PropTypes.string.isRequired,
|
2018-05-28 10:55:19 +00:00
|
|
|
};
|
|
|
|
|
2018-08-31 17:19:13 +00:00
|
|
|
export default DateRange;
|