woocommerce/plugins/woocommerce-admin/client/components/calendar/index.js

233 lines
6.0 KiB
JavaScript
Raw Normal View History

2018-05-28 10:55:19 +00:00
/** @format */
/**
* External dependencies
*/
import 'core-js/fn/object/assign';
import 'core-js/fn/array/from';
import { __, sprintf } from '@wordpress/i18n';
import classnames from 'classnames';
import { Component } from '@wordpress/element';
2018-05-28 10:55:19 +00:00
import {
DayPickerRangeController,
isInclusivelyAfterDay,
isInclusivelyBeforeDay,
} from 'react-dates';
import moment from 'moment';
2018-05-28 10:55:19 +00:00
import { partial } from 'lodash';
import PropTypes from 'prop-types';
import { withViewportMatch } from '@wordpress/viewport';
2018-05-28 10:55:19 +00:00
import 'react-dates/lib/css/_datepicker.css';
/**
* WooCommerce dependencies
*/
import { validateDateInputForRange } from '@woocommerce/date';
2018-05-28 10:55:19 +00:00
/**
* Internal dependencies
*/
2018-07-22 23:43:21 +00:00
import DateInput from './input';
2018-05-28 10:55:19 +00:00
import phrases from './phrases';
import './style.scss';
/**
* 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 } ) {
const { onUpdate, shortDateFormat } = this.props;
onUpdate( {
after: startDate,
before: endDate,
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 ) {
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;
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() {
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;
}
}
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() {
const {
after,
before,
focusedInput,
afterText,
beforeText,
afterError,
beforeError,
shortDateFormat,
isViewportMobile,
isViewportSmall,
} = this.props;
2018-05-28 10:55:19 +00:00
const isOutsideRange = this.getOutsideRange();
const isDoubleCalendar = isViewportMobile && ! isViewportSmall;
2018-05-28 10:55:19 +00:00
return (
2018-07-22 23:43:21 +00:00
<div
className={ classnames( 'woocommerce-calendar', {
'is-mobile': isViewportMobile,
2018-07-22 23:43:21 +00:00
} ) }
>
<div className="woocommerce-calendar__inputs">
<DateInput
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",
'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
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",
'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 }
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 = {
/**
* A moment date object representing the selected start. `null` for no selection.
*/
after: PropTypes.object,
/**
* 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.
*/
before: PropTypes.object,
/**
* 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
*/
invalidDays: PropTypes.oneOfType( [
2018-05-28 10:55:19 +00:00
PropTypes.oneOf( [ 'past', 'future', 'none' ] ),
PropTypes.func,
] ),
/**
* A function called upon selection of a date.
*/
onUpdate: PropTypes.func.isRequired,
/**
* The date format in moment.js-style tokens.
*/
shortDateFormat: PropTypes.string.isRequired,
2018-05-28 10:55:19 +00:00
};
export default withViewportMatch( {
isViewportMobile: '< medium',
isViewportSmall: '< small',
} )( DateRange );