Skip to content

Commit

Permalink
Refactor DatePicker component to use react hooks and change it from c…
Browse files Browse the repository at this point in the history
…lass

component to function component.

It was already done as a part of different PR WordPress#22897. After code review
it looks like the original author is not able to find time to reply or
address review comments. I'm doing takeover from here and will take care
of PR until it gets merged.
  • Loading branch information
amustaque97 committed Nov 24, 2021
1 parent 90f3242 commit b960568
Showing 1 changed file with 68 additions and 76 deletions.
144 changes: 68 additions & 76 deletions packages/components/src/date-time/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import DayPickerSingleDateController from 'react-dates/lib/components/DayPickerS
/**
* WordPress dependencies
*/
import { Component, createRef, useEffect, useRef } from '@wordpress/element';
import { useEffect, useRef } from '@wordpress/element';
import { isRTL, _n, sprintf } from '@wordpress/i18n';

/**
Expand Down Expand Up @@ -70,43 +70,40 @@ function DatePickerDay( { day, events = [] } ) {
);
}

class DatePicker extends Component {
constructor() {
super( ...arguments );

this.onChangeMoment = this.onChangeMoment.bind( this );
this.nodeRef = createRef();
this.onMonthPreviewedHandler = this.onMonthPreviewedHandler.bind(
this
);
}

onMonthPreviewedHandler( newMonthDate ) {
this.props.onMonthPreviewed?.( newMonthDate.toISOString() );
this.keepFocusInside();
}
function DatePicker( {
currentDate,
onChange,
events,
isInvalidDate,
onMonthPreviewed,
} ) {
const nodeRef = useRef();
const onMonthPreviewedHandler = ( newMonthDate ) => {
onMonthPreviewed?.( newMonthDate.toISOString() );
keepFocusInside();
};

/*
* Todo: We should remove this function ASAP.
* It is kept because focus is lost when we click on the previous and next month buttons.
* This focus loss closes the date picker popover.
* Ideally we should add an upstream commit on react-dates to fix this issue.
*/
keepFocusInside() {
if ( ! this.nodeRef.current ) {
const keepFocusInside = () => {
if ( ! nodeRef.current ) {
return;
}

const { ownerDocument } = this.nodeRef.current;
const { ownerDocument } = nodeRef.current;
const { activeElement } = ownerDocument;

// If focus was lost.
if (
! activeElement ||
! this.nodeRef.current.contains( ownerDocument.activeElement )
! nodeRef.current.contains( ownerDocument.activeElement )
) {
// Retrieve the focus region div.
const focusRegion = this.nodeRef.current.querySelector(
const focusRegion = nodeRef.current.querySelector(
'.DayPicker_focusRegion'
);
if ( ! focusRegion ) {
Expand All @@ -115,11 +112,9 @@ class DatePicker extends Component {
// Keep the focus on focus region.
focusRegion.focus();
}
}

onChangeMoment( newDate ) {
const { currentDate, onChange } = this.props;
};

const onChangeMoment = ( newDate ) => {
// If currentDate is null, use now as momentTime to designate hours, minutes, seconds.
const momentDate = currentDate ? moment( currentDate ) : moment();
const momentTime = {
Expand All @@ -131,71 +126,68 @@ class DatePicker extends Component {
onChange( newDate.set( momentTime ).format( TIMEZONELESS_FORMAT ) );

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

/**
* Create a Moment object from a date string. With no currentDate supplied, default to a Moment
* Create a Moment object from a date string. With no date supplied, default to a Moment
* object representing now. If a null value is passed, return a null value.
*
* @param {?string} currentDate Date representing the currently selected date or null to signify no selection.
* @param {?string} date Date representing the currently selected date or null to signify no selection.
* @return {?moment.Moment} Moment object for selected date or null.
*/
getMomentDate( currentDate ) {
if ( null === currentDate ) {
const getMomentDate = ( date ) => {
if ( null === date ) {
return null;
}
return currentDate ? moment( currentDate ) : moment();
}
return date ? moment( date ) : moment();
};

getEventsPerDay( day ) {
if ( ! this.props.events?.length ) {
const getEventsPerDay = ( day ) => {
if ( ! events?.length ) {
return [];
}

return this.props.events.filter( ( eventDay ) =>
return events.filter( ( eventDay ) =>
day.isSame( eventDay.date, 'day' )
);
}

render() {
const { currentDate, isInvalidDate } = this.props;
const momentDate = this.getMomentDate( currentDate );

return (
<div className="components-datetime__date" ref={ this.nodeRef }>
<DayPickerSingleDateController
date={ momentDate }
daySize={ 30 }
focused
hideKeyboardShortcutsPanel
// This is a hack to force the calendar to update on month or year change
// https://github.com/airbnb/react-dates/issues/240#issuecomment-361776665
key={ `datepicker-controller-${
momentDate ? momentDate.format( 'MM-YYYY' ) : 'null'
}` }
noBorder
numberOfMonths={ 1 }
onDateChange={ this.onChangeMoment }
transitionDuration={ 0 }
weekDayFormat="ddd"
dayAriaLabelFormat={ ARIAL_LABEL_TIME_FORMAT }
isRTL={ isRTL() }
isOutsideRange={ ( date ) => {
return isInvalidDate && isInvalidDate( date.toDate() );
} }
onPrevMonthClick={ this.onMonthPreviewedHandler }
onNextMonthClick={ this.onMonthPreviewedHandler }
renderDayContents={ ( day ) => (
<DatePickerDay
day={ day }
events={ this.getEventsPerDay( day ) }
/>
) }
/>
</div>
);
}
};

const momentDate = getMomentDate( currentDate );

return (
<div className="components-datetime__date" ref={ nodeRef }>
<DayPickerSingleDateController
date={ momentDate }
daySize={ 30 }
focused
hideKeyboardShortcutsPanel
// This is a hack to force the calendar to update on month or year change
// https://github.com/airbnb/react-dates/issues/240#issuecomment-361776665
key={ `datepicker-controller-${
momentDate ? momentDate.format( 'MM-YYYY' ) : 'null'
}` }
noBorder
numberOfMonths={ 1 }
onDateChange={ onChangeMoment }
transitionDuration={ 0 }
weekDayFormat="ddd"
dayAriaLabelFormat={ ARIAL_LABEL_TIME_FORMAT }
isRTL={ isRTL() }
isOutsideRange={ ( date ) => {
return isInvalidDate && isInvalidDate( date.toDate() );
} }
onPrevMonthClick={ onMonthPreviewedHandler }
onNextMonthClick={ onMonthPreviewedHandler }
renderDayContents={ ( day ) => (
<DatePickerDay
day={ day }
events={ getEventsPerDay( day ) }
/>
) }
/>
</div>
);
}

export default DatePicker;

0 comments on commit b960568

Please sign in to comment.