Skip to content
This repository has been archived by the owner on Jul 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1069 from woocommerce/update/calendar-component-i…
Browse files Browse the repository at this point in the history
…nline-picker

Add Single Date Picker
  • Loading branch information
psealock committed Dec 21, 2018
2 parents 7026653 + 8f7b340 commit 32885f1
Show file tree
Hide file tree
Showing 15 changed files with 363 additions and 80 deletions.
53 changes: 53 additions & 0 deletions docs/components/calendar.md
Original file line number Diff line number Diff line change
@@ -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)
=======================

Expand Down
4 changes: 2 additions & 2 deletions docs/components/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions docs/components/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -39,7 +39,7 @@ A placeholder for the search input.
### `selected`

- Type: Array
- id: Number
- id: One of type: number, string
- label: String
- Default: `[]`

Expand Down
2 changes: 1 addition & 1 deletion docs/components/tag.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
141 changes: 141 additions & 0 deletions packages/components/src/calendar/date-picker.js
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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.
Expand All @@ -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 } ) {
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down
66 changes: 47 additions & 19 deletions packages/components/src/calendar/example.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,58 @@
```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"
/>
</Section>
</div>
)
} );
```
Loading

0 comments on commit 32885f1

Please sign in to comment.