Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new DateTimeControl component #285

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ One way to ensure all dependencies are loaded is to use the [`@wordpress/depende
## Components

- [`ConditionalComponent`](src/components/ConditionalComponent)
- [`DateTimeControl`](src/components/DateTimeControl)
- [`FetchAllTermSelectControl`](src/components/FetchAllTermSelectControl)
- [`FileControls`](src/components/FileControls)
- [`ImageControl`](src/components/ImageControl)
Expand Down
27 changes: 27 additions & 0 deletions src/components/DateTimeControl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# DateTimeControl

The `DateTimeControl` component provides a date and time picker with timezone information.

## Props

- `label` (string): The label for the date/time control.
- `id` (string): The ID for the base control.
- `onChange` (Function): Callback function to handle date/time change.
- `value` (string): The current date/time value in UTC format.

## Usage

```js
import DateTimeControl from './components/datetime-control';

...

<DateTimeControl
label={ __( 'Event Start time/date', 'my-project' ) }
id="example-start-time-date"
value={ eventStart }
onChange={ ( newValue ) =>
setAttributes( { eventStart: newValue } )
}
/>
```
101 changes: 101 additions & 0 deletions src/components/DateTimeControl/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
DateTimePicker,
BaseControl,
Popover,
Button,
} from '@wordpress/components';
import { gmdate, date, getSettings as getDateSettings } from '@wordpress/date';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

import TimeZone from './timezone';

/**
* DateTimeControl component.
*
* @param {object} props - Component properties.
* @param {string} props.label - The label for the date/time control.
* @param {string} props.id - The ID for the base control.
* @param {Function} props.onChange - Callback function to handle date/time change.
* @param {string} props.value - The current date/time value in UTC format.
*
* @returns {ReactNode|null} The DateTimeControl component.

Check warning on line 22 in src/components/DateTimeControl/index.js

View workflow job for this annotation

GitHub Actions / ESLint

src/components/DateTimeControl/index.js#L22

The type 'ReactNode' is undefined (jsdoc/no-undefined-types)
*/
function DateTimeControl( { label, id, onChange, value } ) {
const [ isDatePickerVisible, setIsDatePickerVisible ] = useState( false );
const dateSettings = getDateSettings();

/**
* Convert a date string in the current site local time into a UTC formatted as a MySQL date.
*
* @param {string} localDateString Local date.
* @returns {string} UTC formatted as a MySQL date.
*/
const convertToUTC = ( localDateString ) => {
const localDate = wp.date.getDate( localDateString );
return gmdate( 'Y-m-d H:i:s', localDate );
};

/**
* Convert a UTC date in MySQL format into a date string localised to the current site timezone.
*
* @param {string} utcDateString UTC date string.
* @param {string} format Format of returned date.
* @returns {string|null} Localised date string.
*/
const convertFromUTC = ( utcDateString, format = 'Y-m-d H:i:s' ) => {
const utcDate = new Date( utcDateString + ' +00:00' );

if ( utcDate instanceof Date && ! isNaN( utcDate ) ) {
return date( format, utcDate );
}

return null;
};

return (
<BaseControl id={ id } label={ label }>
{ value && (
<p>
{ convertFromUTC( value, dateSettings.formats.datetime ) }
<TimeZone />
</p>
) }

<Button
variant="link"
onClick={ () =>
setIsDatePickerVisible( ! isDatePickerVisible ) }
>
{ __( 'Edit webinar start time/date', 'block-editor-components' ) }
</Button>

{ isDatePickerVisible && (
<Popover
onFocusOutside={ () =>
setIsDatePickerVisible( ! isDatePickerVisible ) }
>
<div style={ { padding: '1.5em' } }>
<DateTimePicker
currentDate={ convertFromUTC( value ) || '' }
is12Hour={ false }
onChange={ ( newValue ) =>
onChange( convertToUTC( newValue ) ) }
/>
<Button
size="small"
style={ { marginTop: '1em' } }
variant="primary"
onClick={ () =>
setIsDatePickerVisible( ! isDatePickerVisible ) }
>
{ __( 'Done', 'block-editor-components' ) }
</Button>
</div>
</Popover>
) }
</BaseControl>
);
}

export default DateTimeControl;
53 changes: 53 additions & 0 deletions src/components/DateTimeControl/timezone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Tooltip } from '@wordpress/components';
import { getSettings as getDateSettings } from '@wordpress/date';
import { __ } from '@wordpress/i18n';

/**
* TimeZone component.
*
* This component determines the user's timezone offset and compares it with the system timezone offset.
* If they match, it returns null. Otherwise, it displays the timezone abbreviation and details in a tooltip.
*
* @returns {ReactNode|null} The timezone abbreviation and details in a tooltip, or null if the user's timezone matches the system timezone.

Check warning on line 11 in src/components/DateTimeControl/timezone.js

View workflow job for this annotation

GitHub Actions / ESLint

src/components/DateTimeControl/timezone.js#L11

The type 'ReactNode' is undefined (jsdoc/no-undefined-types)
*/
const TimeZone = () => {
const { timezone } = getDateSettings();

// Convert timezone offset to hours.
const userTimezoneOffset = -1 * ( new Date().getTimezoneOffset() / 60 );

// System timezone and user timezone match, nothing needed.
// Compare as numbers because it comes over as string.
if ( Number( timezone.offset ) === userTimezoneOffset ) {
return null;
}

const offsetSymbol = Number( timezone.offset ) >= 0 ? '+' : '';
const zoneAbbr =
timezone.abbr !== '' && isNaN( Number( timezone.abbr ) )
? timezone.abbr
: `UTC${ offsetSymbol }${ timezone.offsetFormatted }`;

// Replace underscore with space in strings like `America/Costa_Rica`.
const prettyTimezoneString = timezone.string.replace( '_', ' ' );

const timezoneDetail =
timezone.string === 'UTC'
? __( 'Coordinated Universal Time' )
: `(${ zoneAbbr }) ${ prettyTimezoneString }`;

// When the prettyTimezoneString is empty, there is no additional timezone
// detail information to show in a Tooltip.
const hasNoAdditionalTimezoneDetail =
prettyTimezoneString.trim().length === 0;

return hasNoAdditionalTimezoneDetail ? (
<>{ zoneAbbr }</>
) : (
<Tooltip placement="top" text={ timezoneDetail }>
<span>{ zoneAbbr }</span>
</Tooltip>
);
};

export default TimeZone;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as ConditionalComponent } from './components/ConditionalCompone
export { default as FetchAllTermSelectControl } from './components/FetchAllTermSelectControl';
export { default as FileControls } from './components/FileControls';
export { default as GenericServerSideEdit } from './components/GenericServerSideEdit';
export { default as DateTimeControl } from './components/DateTimeControl';
export { default as ImageControl } from './components/ImageControl';
export { default as InnerBlockSlider } from './components/InnerBlockSlider';
export { default as InnerBlocksDisplaySingle } from './components/InnerBlockSlider/inner-block-single-display';
Expand Down
Loading