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

Add Single Date Picker #1069

Merged
merged 2 commits into from
Dec 21, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
68 changes: 49 additions & 19 deletions packages/components/src/calendar/example.md
Original file line number Diff line number Diff line change
@@ -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"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: invalidDays and onUpdate are duplicated

/>
</Section>
</div>
)
} );
```
Loading