DateRangeFilterPicker
This commit is contained in:
parent
bbb42e601c
commit
524bb0c96f
|
@ -1,3 +1,56 @@
|
|||
`DatePicker` (component)
|
||||
========================
|
||||
|
||||
|
||||
|
||||
Props
|
||||
-----
|
||||
|
||||
### `date`
|
||||
|
||||
- Type: Object
|
||||
- Default: null
|
||||
|
||||
A moment date object representing the selected date. `null` for no selection.
|
||||
|
||||
### `text`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
The date in human-readable format. Displayed in the text input.
|
||||
|
||||
### `error`
|
||||
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
A string error message, shown to the user.
|
||||
|
||||
### `invalidDays`
|
||||
|
||||
- Type: One of type: enum, func
|
||||
- Default: null
|
||||
|
||||
(Coming Soon) Optionally invalidate certain days. `past`, `future`, `none`, or function are accepted.
|
||||
A function will be passed to react-dates' `isOutsideRange` prop
|
||||
|
||||
### `onUpdate`
|
||||
|
||||
- **Required**
|
||||
- Type: Function
|
||||
- Default: null
|
||||
|
||||
A function called upon selection of a date or input change.
|
||||
|
||||
### `dateFormat`
|
||||
|
||||
- **Required**
|
||||
- Type: String
|
||||
- Default: null
|
||||
|
||||
The date format in moment.js-style tokens.
|
||||
|
||||
`DateRange` (component)
|
||||
=======================
|
||||
|
||||
|
|
|
@ -144,8 +144,8 @@ The query string represented in object form
|
|||
|
||||
Which type of autocompleter should be used in the Search
|
||||
|
||||
`DatePicker` (component)
|
||||
========================
|
||||
`DateRangeFilterPicker` (component)
|
||||
===================================
|
||||
|
||||
Select a range of dates or single dates.
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ Function called when selected results change, passed result list.
|
|||
### `type`
|
||||
|
||||
- **Required**
|
||||
- Type: One of: 'products', 'product_cats', 'orders', 'customers', 'coupons', 'taxes', 'variations'
|
||||
- Type: One of: 'countries', 'coupons', 'customers', 'emails', 'orders', 'products', 'product_cats', 'taxes', 'usernames', 'variations'
|
||||
- Default: null
|
||||
|
||||
The object type to be used in searching.
|
||||
|
@ -39,7 +39,7 @@ A placeholder for the search input.
|
|||
### `selected`
|
||||
|
||||
- Type: Array
|
||||
- id: Number
|
||||
- id: One of type: number, string
|
||||
- label: String
|
||||
- Default: `[]`
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Props
|
|||
|
||||
### `id`
|
||||
|
||||
- Type: Number
|
||||
- Type: One of type: number, string
|
||||
- Default: null
|
||||
|
||||
The ID for this item, used in the remove function.
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import 'core-js/fn/object/assign';
|
||||
import 'core-js/fn/array/from';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Component } from '@wordpress/element';
|
||||
import { Dropdown, DatePicker as WpDatePicker } from '@wordpress/components';
|
||||
import { partial } from 'lodash';
|
||||
import { TAB } from '@wordpress/keycodes';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import DateInput from './input';
|
||||
import { toMoment } from '@woocommerce/date';
|
||||
import { H, Section } from '../section';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class DatePicker extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
||||
this.onDateChange = this.onDateChange.bind( this );
|
||||
this.onInputChange = this.onInputChange.bind( this );
|
||||
}
|
||||
|
||||
handleKeyDown( isOpen, onToggle, { keyCode } ) {
|
||||
if ( TAB === keyCode && isOpen ) {
|
||||
onToggle();
|
||||
}
|
||||
}
|
||||
|
||||
handleFocus( isOpen, onToggle ) {
|
||||
if ( ! isOpen ) {
|
||||
onToggle();
|
||||
}
|
||||
}
|
||||
|
||||
onDateChange( onToggle, dateString ) {
|
||||
const { onUpdate, dateFormat } = this.props;
|
||||
const date = moment( dateString );
|
||||
onUpdate( {
|
||||
date,
|
||||
text: dateString ? date.format( dateFormat ) : '',
|
||||
error: null,
|
||||
} );
|
||||
onToggle();
|
||||
}
|
||||
|
||||
onInputChange( event ) {
|
||||
const value = event.target.value;
|
||||
const { dateFormat } = this.props;
|
||||
const date = toMoment( dateFormat, value );
|
||||
const error = date ? null : __( 'Invalid date', 'wc-admin' );
|
||||
|
||||
this.props.onUpdate( {
|
||||
date,
|
||||
text: value,
|
||||
error: value.length > 0 ? error : null,
|
||||
} );
|
||||
}
|
||||
|
||||
render() {
|
||||
const { date, text, dateFormat, error } = this.props;
|
||||
// @TODO: make upstream Gutenberg change to invalidate certain days.
|
||||
// const isOutsideRange = getOutsideRange( invalidDays );
|
||||
return (
|
||||
<Dropdown
|
||||
position="bottom center"
|
||||
focusOnMount={ false }
|
||||
renderToggle={ ( { isOpen, onToggle } ) => (
|
||||
<DateInput
|
||||
value={ text }
|
||||
onChange={ this.onInputChange }
|
||||
dateFormat={ dateFormat }
|
||||
label={ __( 'Choose a date', 'wc-admin' ) }
|
||||
error={ error }
|
||||
describedBy={ sprintf(
|
||||
__( 'Date input describing a selected date in format %s', 'wc-admin' ),
|
||||
dateFormat
|
||||
) }
|
||||
onFocus={ partial( this.handleFocus, isOpen, onToggle ) }
|
||||
aria-expanded={ isOpen }
|
||||
focusOnMount={ false }
|
||||
onKeyDown={ partial( this.handleKeyDown, isOpen, onToggle ) }
|
||||
errorPosition="top center"
|
||||
/>
|
||||
) }
|
||||
renderContent={ ( { onToggle } ) => (
|
||||
<Section component={ false }>
|
||||
<H className="woocommerce-calendar__date-picker-title">
|
||||
{ __( 'select a date', 'wc-admin' ) }
|
||||
</H>
|
||||
<div className="woocommerce-calendar__react-dates is-core-datepicker">
|
||||
<WpDatePicker
|
||||
currentDate={ date }
|
||||
onChange={ partial( this.onDateChange, onToggle ) }
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DatePicker.propTypes = {
|
||||
/**
|
||||
* A moment date object representing the selected date. `null` for no selection.
|
||||
*/
|
||||
date: PropTypes.object,
|
||||
/**
|
||||
* The date in human-readable format. Displayed in the text input.
|
||||
*/
|
||||
text: PropTypes.string,
|
||||
/**
|
||||
* A string error message, shown to the user.
|
||||
*/
|
||||
error: PropTypes.string,
|
||||
/**
|
||||
* (Coming Soon) Optionally invalidate certain days. `past`, `future`, `none`, or function are accepted.
|
||||
* A function will be passed to react-dates' `isOutsideRange` prop
|
||||
*/
|
||||
invalidDays: PropTypes.oneOfType( [
|
||||
PropTypes.oneOf( [ 'past', 'future', 'none' ] ),
|
||||
PropTypes.func,
|
||||
] ),
|
||||
/**
|
||||
* A function called upon selection of a date or input change.
|
||||
*/
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
/**
|
||||
* The date format in moment.js-style tokens.
|
||||
*/
|
||||
dateFormat: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DatePicker;
|
|
@ -7,11 +7,7 @@ import 'core-js/fn/array/from';
|
|||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
import { Component } from '@wordpress/element';
|
||||
import {
|
||||
DayPickerRangeController,
|
||||
isInclusivelyAfterDay,
|
||||
isInclusivelyBeforeDay,
|
||||
} from 'react-dates';
|
||||
import { DayPickerRangeController } from 'react-dates';
|
||||
import moment from 'moment';
|
||||
import { partial } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -27,6 +23,7 @@ import { validateDateInputForRange } from '@woocommerce/date';
|
|||
*/
|
||||
import DateInput from './input';
|
||||
import phrases from './phrases';
|
||||
import { getOutsideRange } from './utils';
|
||||
|
||||
/**
|
||||
* This is wrapper for a [react-dates](https://github.com/airbnb/react-dates) powered calendar.
|
||||
|
@ -38,7 +35,6 @@ class DateRange extends Component {
|
|||
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 } ) {
|
||||
|
@ -76,22 +72,6 @@ class DateRange extends Component {
|
|||
} );
|
||||
}
|
||||
|
||||
getOutsideRange() {
|
||||
const { invalidDays } = this.props;
|
||||
if ( 'string' === typeof invalidDays ) {
|
||||
switch ( invalidDays ) {
|
||||
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;
|
||||
}
|
||||
|
||||
setTnitialVisibleMonth( isDoubleCalendar, before ) {
|
||||
return () => {
|
||||
const visibleDate = before || moment();
|
||||
|
@ -114,8 +94,9 @@ class DateRange extends Component {
|
|||
shortDateFormat,
|
||||
isViewportMobile,
|
||||
isViewportSmall,
|
||||
invalidDays,
|
||||
} = this.props;
|
||||
const isOutsideRange = this.getOutsideRange();
|
||||
const isOutsideRange = getOutsideRange( invalidDays );
|
||||
const isDoubleCalendar = isViewportMobile && ! isViewportSmall;
|
||||
return (
|
||||
<div
|
|
@ -1,30 +1,60 @@
|
|||
```jsx
|
||||
import { DateRange } from '@woocommerce/components';
|
||||
import { DateRange, DatePicker } from '@woocommerce/components';
|
||||
import moment from 'moment';
|
||||
|
||||
const dateFormat = 'MM/DD/YYYY';
|
||||
|
||||
const MyDateRange = withState( {
|
||||
after: moment( '2018-09-10' ),
|
||||
afterText: '09/10/2018',
|
||||
before: moment( '2018-09-20' ),
|
||||
beforeText: '09/20/2018',
|
||||
} )( ( { after, afterText, before, beforeText, setState } ) => {
|
||||
function onUpdate( { after, afterText, before, beforeText } ) {
|
||||
setState( { after, afterText, before, beforeText } );
|
||||
after: null,
|
||||
afterText: '',
|
||||
before: null,
|
||||
beforeText: '',
|
||||
afterError: null,
|
||||
beforeError: null,
|
||||
focusedInput: 'startDate',
|
||||
} )( ( { after, afterText, before, beforeText, afterError, beforeError, focusedInput, setState } ) => {
|
||||
function onRangeUpdate( update ) {
|
||||
setState( update );
|
||||
}
|
||||
|
||||
|
||||
function onDatePickerUpdate( { date, text, error } ) {
|
||||
setState( {
|
||||
after: date,
|
||||
afterText: text,
|
||||
afterError: error,
|
||||
} );
|
||||
}
|
||||
|
||||
return (
|
||||
<DateRange
|
||||
after={ after }
|
||||
afterText={ afterText }
|
||||
before={ before }
|
||||
beforeText={ beforeText }
|
||||
onUpdate={ onUpdate }
|
||||
shortDateFormat={ dateFormat }
|
||||
focusedInput="startDate"
|
||||
invalidDays="none"
|
||||
/>
|
||||
<div>
|
||||
<H>Date Range Picker</H>
|
||||
<Section component={ false }>
|
||||
<DateRange
|
||||
after={ after }
|
||||
afterText={ afterText }
|
||||
before={ before }
|
||||
beforeText={ beforeText }
|
||||
onUpdate={ onRangeUpdate }
|
||||
shortDateFormat={ dateFormat }
|
||||
focusedInput={ focusedInput }
|
||||
invalidDays="future"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<H>Date Picker</H>
|
||||
<Section component={ false }>
|
||||
<DatePicker
|
||||
date={ after }
|
||||
text={ afterText }
|
||||
error={ afterError }
|
||||
onUpdate={ onDatePickerUpdate }
|
||||
dateFormat={ dateFormat }
|
||||
invalidDays="none"
|
||||
onUpdate={ onDatePickerUpdate }
|
||||
invalidDays="future"
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
} );
|
||||
```
|
||||
|
|
|
@ -7,7 +7,17 @@ import classnames from 'classnames';
|
|||
import { uniqueId } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const DateInput = ( { value, onChange, dateFormat, label, describedBy, error } ) => {
|
||||
const DateInput = ( {
|
||||
value,
|
||||
onChange,
|
||||
dateFormat,
|
||||
label,
|
||||
describedBy,
|
||||
error,
|
||||
onFocus,
|
||||
onKeyDown,
|
||||
errorPosition,
|
||||
} ) => {
|
||||
const classes = classnames( 'woocommerce-calendar__input', {
|
||||
'is-empty': value.length === 0,
|
||||
'is-error': error,
|
||||
|
@ -24,12 +34,14 @@ const DateInput = ( { value, onChange, dateFormat, label, describedBy, error } )
|
|||
id={ id }
|
||||
aria-describedby={ `${ id }-message` }
|
||||
placeholder={ dateFormat.toLowerCase() }
|
||||
onFocus={ onFocus }
|
||||
onKeyDown={ onKeyDown }
|
||||
/>
|
||||
{ error && (
|
||||
<Popover
|
||||
className="woocommerce-calendar__input-error"
|
||||
focusOnMount={ false }
|
||||
position="bottom center"
|
||||
position={ errorPosition }
|
||||
>
|
||||
{ error }
|
||||
</Popover>
|
||||
|
@ -49,6 +61,13 @@ DateInput.propTypes = {
|
|||
label: PropTypes.string.isRequired,
|
||||
describedBy: PropTypes.string.isRequired,
|
||||
error: PropTypes.string,
|
||||
errorPosition: PropTypes.string,
|
||||
onFocus: PropTypes.func,
|
||||
};
|
||||
|
||||
DateInput.defaultProps = {
|
||||
onFocus: () => {},
|
||||
errorPosition: 'bottom center',
|
||||
};
|
||||
|
||||
export default DateInput;
|
||||
|
|
|
@ -60,7 +60,23 @@
|
|||
outline: 2px solid #bfe7f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make exceptions for wp Core DatePicker.
|
||||
&.is-core-datepicker {
|
||||
.components-datetime__date {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.CalendarDay__default {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.CalendarDay__selected {
|
||||
background: $woocommerce-700;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-calendar__inputs {
|
||||
padding: 1em;
|
||||
|
@ -143,32 +159,50 @@
|
|||
}
|
||||
|
||||
.woocommerce-filters-date__content {
|
||||
.woocommerce-calendar__input-error {
|
||||
display: none;
|
||||
|
||||
.components-popover__content {
|
||||
background-color: $core-grey-dark-400;
|
||||
color: $white;
|
||||
padding: 0.5em;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.components-popover {
|
||||
.components-popover__content {
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:not(.no-arrow):not(.is-mobile).is-bottom::before {
|
||||
border-bottom-color: $core-grey-dark-400;
|
||||
z-index: 1;
|
||||
top: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-mobile .woocommerce-calendar__input-error .components-popover__content {
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-calendar__input-error {
|
||||
display: none;
|
||||
|
||||
.components-popover__content {
|
||||
background-color: $core-grey-dark-400;
|
||||
color: $white;
|
||||
padding: 0.5em;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.components-popover {
|
||||
.components-popover__content {
|
||||
min-width: 100px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:not(.no-arrow):not(.is-mobile).is-bottom::before {
|
||||
border-bottom-color: $core-grey-dark-400;
|
||||
z-index: 1;
|
||||
top: -6px;
|
||||
}
|
||||
|
||||
&:not(.no-arrow):not(.is-mobile).is-top::after {
|
||||
border-top-color: $core-grey-dark-400;
|
||||
z-index: 1;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-calendar__date-picker-title {
|
||||
@include font-size( 12 );
|
||||
font-weight: 100;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
color: $core-grey-dark-300;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
background-color: $white;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import moment from 'moment';
|
||||
|
||||
export function getOutsideRange( invalidDays ) {
|
||||
if ( 'string' === typeof invalidDays ) {
|
||||
switch ( invalidDays ) {
|
||||
case 'past':
|
||||
return day => moment().isAfter( day, 'day' );
|
||||
case 'future':
|
||||
return day => moment().isBefore( day, 'day' );
|
||||
case 'none':
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return 'function' === typeof invalidDays ? invalidDays : undefined;
|
||||
}
|
|
@ -12,7 +12,7 @@ import classnames from 'classnames';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import ComparePeriods from './compare-periods';
|
||||
import DateRange from '../../calendar';
|
||||
import DateRange from '../../calendar/date-range';
|
||||
import { H, Section } from '../../section';
|
||||
import PresetPeriods from './preset-periods';
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ const shortDateFormat = __( 'MM/DD/YYYY', 'wc-admin' );
|
|||
/**
|
||||
* Select a range of dates or single dates.
|
||||
*/
|
||||
class DatePicker extends Component {
|
||||
class DateRangeFilterPicker extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.state = this.getResetState();
|
||||
|
@ -156,7 +156,7 @@ class DatePicker extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
DatePicker.propTypes = {
|
||||
DateRangeFilterPicker.propTypes = {
|
||||
/**
|
||||
* The `path` parameter supplied by React-Router.
|
||||
*/
|
||||
|
@ -167,8 +167,8 @@ DatePicker.propTypes = {
|
|||
query: PropTypes.object,
|
||||
};
|
||||
|
||||
DatePicker.defaultProps = {
|
||||
DateRangeFilterPicker.defaultProps = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
export default DatePicker;
|
||||
export default DateRangeFilterPicker;
|
||||
|
|
|
@ -12,7 +12,7 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import AdvancedFilters from './advanced';
|
||||
import CompareFilter from './compare';
|
||||
import DatePicker from './date';
|
||||
import DateRangeFilterPicker from './date';
|
||||
import FilterPicker from './filter';
|
||||
import { H, Section } from '../section';
|
||||
|
||||
|
@ -64,7 +64,7 @@ class ReportFilters extends Component {
|
|||
<Section component="div" className="woocommerce-filters">
|
||||
<div className="woocommerce-filters__basic-filters">
|
||||
{ showDatePicker && (
|
||||
<DatePicker key={ JSON.stringify( query ) } query={ query } path={ path } />
|
||||
<DateRangeFilterPicker key={ JSON.stringify( query ) } query={ query } path={ path } />
|
||||
) }
|
||||
{ filters.map( config => {
|
||||
if ( config.showFilters( query ) ) {
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
background-color: $white;
|
||||
}
|
||||
|
||||
.woocommerce-calendar__input-error .components-popover__content {
|
||||
background-color: $core-grey-dark-400;
|
||||
}
|
||||
|
||||
&.is-mobile {
|
||||
.components-popover__content {
|
||||
width: 100%;
|
||||
|
|
|
@ -12,8 +12,9 @@ export { default as ChartPlaceholder } from './chart/placeholder';
|
|||
export { default as Card } from './card';
|
||||
export { default as Count } from './count';
|
||||
export { default as CompareFilter } from './filters/compare';
|
||||
export { default as DatePicker } from './filters/date';
|
||||
export { default as DateRange } from './calendar';
|
||||
export { default as DateRangeFilterPicker } from './filters/date';
|
||||
export { default as DateRange } from './calendar/date-range';
|
||||
export { default as DatePicker } from './calendar/date-picker';
|
||||
export { default as DropdownButton } from './dropdown-button';
|
||||
export { default as EllipsisMenu } from './ellipsis-menu';
|
||||
export { default as EmptyContent } from './empty-content';
|
||||
|
|
Loading…
Reference in New Issue