Skip to content

Commit

Permalink
Rewrite DatePicker, TimePicker and DateTimePicker in TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
noisysocks committed May 4, 2022
1 parent 03778f5 commit 9bcc66e
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 96 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
"@types/npm-package-arg": "6.1.1",
"@types/prettier": "2.4.4",
"@types/qs": "6.9.7",
"@types/react-dates": "17.1.10",
"@types/requestidlecallback": "0.3.4",
"@types/semver": "7.3.8",
"@types/sprintf-js": "1.1.2",
Expand Down
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@emotion/serialize": "^1.0.2",
"@emotion/styled": "^11.6.0",
"@emotion/utils": "1.0.0",
"@types/react-dates": "17.1.10",
"@use-gesture/react": "^10.2.6",
"@wordpress/a11y": "file:../a11y",
"@wordpress/compose": "file:../compose",
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/button-group/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-nocheck
/**
* External dependencies
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/components/src/date-time/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,10 @@ A callback invoked when selecting the previous/next month in the date picker. Th

- Type: `Function`
- Required: No

### events

List of events to show in the date picker. Each event will appear as a dot on the day of the event.

- Type: `Array`
- Required: No
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
*/
import moment from 'moment';
import classnames from 'classnames';
import type { Moment } from 'moment';

// react-dates doesn't tree-shake correctly, so we import from the individual
// component here, to avoid including too much of the library
import DayPickerSingleDateController from 'react-dates/lib/components/DayPickerSingleDateController';
// component here, to avoid including too much of the library.
// @ts-ignore
import UntypedDayPickerSingleDateController from 'react-dates/lib/components/DayPickerSingleDateController';
import type { DayPickerSingleDateController } from 'react-dates';
const TypedDayPickerSingleDateController = UntypedDayPickerSingleDateController as DayPickerSingleDateController;

/**
* WordPress dependencies
Expand All @@ -18,15 +22,18 @@ import { isRTL, _n, sprintf } from '@wordpress/i18n';
* Internal dependencies
*/
import { getMomentDate } from './utils';
import type { DatePickerEvent, DatePickerProps } from './types';

/**
* Module Constants
*/
const TIMEZONELESS_FORMAT = 'YYYY-MM-DDTHH:mm:ss';
const ARIAL_LABEL_TIME_FORMAT = 'dddd, LL';

function DatePickerDay( { day, events = [] } ) {
const ref = useRef();
interface DatePickerDayProps {
day: Moment;
events?: DatePickerEvent[];
}

function DatePickerDay( { day, events = [] }: DatePickerDayProps ) {
const ref = useRef< HTMLDivElement >( null );

/*
* a11y hack to make the `There is/are n events` string
Expand All @@ -36,7 +43,7 @@ function DatePickerDay( { day, events = [] } ) {
*/
useEffect( () => {
// Bail when no parent node.
if ( ! ref?.current?.parentNode ) {
if ( ! ( ref?.current?.parentNode instanceof Element ) ) {
return;
}

Expand Down Expand Up @@ -81,9 +88,9 @@ function DatePicker( {
events,
isInvalidDate,
onMonthPreviewed,
} ) {
const nodeRef = useRef();
const onMonthPreviewedHandler = ( newMonthDate ) => {
}: DatePickerProps ) {
const nodeRef = useRef< HTMLDivElement >( null );
const onMonthPreviewedHandler = ( newMonthDate: Moment ) => {
onMonthPreviewed?.( newMonthDate.toISOString() );
keepFocusInside();
};
Expand Down Expand Up @@ -111,15 +118,19 @@ function DatePicker( {
const focusRegion = nodeRef.current.querySelector(
'.DayPicker_focusRegion'
);
if ( ! focusRegion ) {
if ( ! ( focusRegion instanceof HTMLElement ) ) {
return;
}
// Keep the focus on focus region.
focusRegion.focus();
}
};

const onChangeMoment = ( newDate ) => {
const onChangeMoment = ( newDate: Moment | null ) => {
if ( ! newDate ) {
return;
}

// If currentDate is null, use now as momentTime to designate hours, minutes, seconds.
const momentDate = currentDate ? moment( currentDate ) : moment();
const momentTime = {
Expand All @@ -128,13 +139,13 @@ function DatePicker( {
seconds: 0,
};

onChange( newDate.set( momentTime ).format( TIMEZONELESS_FORMAT ) );
onChange?.( newDate.set( momentTime ).format( TIMEZONELESS_FORMAT ) );

// Keep focus on the date picker.
keepFocusInside();
};

const getEventsPerDay = ( day ) => {
const getEventsPerDay = ( day: Moment ) => {
if ( ! events?.length ) {
return [];
}
Expand All @@ -148,7 +159,7 @@ function DatePicker( {

return (
<div className="components-datetime__date" ref={ nodeRef }>
<DayPickerSingleDateController
<TypedDayPickerSingleDateController
date={ momentDate }
daySize={ 30 }
focused
Expand All @@ -166,7 +177,7 @@ function DatePicker( {
dayAriaLabelFormat={ ARIAL_LABEL_TIME_FORMAT }
isRTL={ isRTL() }
isOutsideRange={ ( date ) => {
return isInvalidDate && isInvalidDate( date.toDate() );
return !! isInvalidDate && isInvalidDate( date.toDate() );
} }
onPrevMonthClick={ onMonthPreviewedHandler }
onNextMonthClick={ onMonthPreviewedHandler }
Expand All @@ -176,6 +187,7 @@ function DatePicker( {
events={ getEventsPerDay( day ) }
/>
) }
onFocusChange={ () => {} }
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// See: https://github.com/airbnb/react-dates#initialize
import 'react-dates/initialize';
import { noop } from 'lodash';
import type { ForwardedRef } from 'react';

/**
* WordPress dependencies
Expand All @@ -18,19 +19,20 @@ import { __, _x } from '@wordpress/i18n';
import Button from '../button';
import { default as DatePicker } from './date';
import { default as TimePicker } from './time';
import type { DateTimePickerProps } from './types';

export { DatePicker, TimePicker };

function DateTimePicker(
function UnforwardedDateTimePicker(
{
currentDate,
is12Hour,
isInvalidDate,
onMonthPreviewed = noop,
onChange,
events,
},
ref
}: DateTimePickerProps,
ref: ForwardedRef< any >
) {
const [ calendarHelpIsVisible, setCalendarHelpIsVisible ] = useState(
false
Expand Down Expand Up @@ -148,7 +150,7 @@ function DateTimePicker(
<Button
className="components-datetime__date-reset-button"
variant="link"
onClick={ () => onChange( null ) }
onClick={ () => onChange?.( null ) }
>
{ __( 'Reset' ) }
</Button>
Expand All @@ -167,4 +169,29 @@ function DateTimePicker(
);
}

export default forwardRef( DateTimePicker );
/**
* DateTimePicker is a React component that renders a calendar and clock for
* date and time selection. The calendar and clock components can be accessed
* individually using the `DatePicker` and `TimePicker` components respectively.
*
* @example
* ```jsx
* import { DateTimePicker } from '@wordpress/components';
* import { useState } from '@wordpress/element';
*
* const MyDateTimePicker = () => {
* const [ date, setDate ] = useState( new Date() );
*
* return (
* <DateTimePicker
* currentDate={ date }
* onChange={ ( newDate ) => setDate( newDate ) }
* is12Hour={ true }
* />
* );
* };
* ```
*/
export const DateTimePicker = forwardRef( UnforwardedDateTimePicker );

export default DateTimePicker;

0 comments on commit 9bcc66e

Please sign in to comment.