UNPKG

@wordpress/components

Version:
251 lines (234 loc) 8.04 kB
import { Fragment } from "@wordpress/element"; /** * External dependencies */ import classnames from 'classnames'; import { isInteger } from 'lodash'; import moment from 'moment'; /** * WordPress dependencies */ import { createElement, useState, useMemo, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import Button from '../button'; import ButtonGroup from '../button-group'; import TimeZone from './timezone'; /** * Module Constants */ const TIMEZONELESS_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; /** * <UpdateOnBlurAsIntegerField> * A shared component to parse, validate, and handle remounting of the underlying form field element like <input> and <select>. * * @param {Object} props Component props. * @param {string} props.as Render the component as specific element tag, defaults to "input". * @param {number|string} props.value The default value of the component which will be parsed to integer. * @param {Function} props.onUpdate Call back when blurred and validated. */ function UpdateOnBlurAsIntegerField({ as, value, onUpdate, ...props }) { function handleBlur(event) { const { target } = event; if (value === target.value) { return; } const parsedValue = parseInt(target.value, 10); // Run basic number validation on the input. if (!isInteger(parsedValue) || typeof props.max !== 'undefined' && parsedValue > props.max || typeof props.min !== 'undefined' && parsedValue < props.min) { // If validation failed, reset the value to the previous valid value. target.value = value; } else { // Otherwise, it's valid, call onUpdate. onUpdate(target.name, parsedValue); } } return createElement(as || 'input', { // Re-mount the input value to accept the latest value as the defaultValue. key: value, defaultValue: value, onBlur: handleBlur, ...props }); } /** * <TimePicker> * * @typedef {Date|string|number} WPValidDateTimeFormat * * @param {Object} props Component props. * @param {boolean} props.is12Hour Should the time picker showed in 12 hour format or 24 hour format. * @param {WPValidDateTimeFormat} props.currentTime The initial current time the time picker should render. * @param {Function} props.onChange Callback function when the date changed. */ export function TimePicker({ is12Hour, currentTime, onChange }) { const [date, setDate] = useState(() => // Truncate the date at the minutes, see: #15495. moment(currentTime).startOf('minutes')); // Reset the state when currentTime changed. useEffect(() => { setDate(currentTime ? moment(currentTime).startOf('minutes') : moment()); }, [currentTime]); const { day, month, year, minutes, hours, am } = useMemo(() => ({ day: date.format('DD'), month: date.format('MM'), year: date.format('YYYY'), minutes: date.format('mm'), hours: date.format(is12Hour ? 'hh' : 'HH'), am: date.format('H') <= 11 ? 'AM' : 'PM' }), [date, is12Hour]); /** * Function that sets the date state and calls the onChange with a new date. * The date is truncated at the minutes. * * @param {Object} newDate The date object. */ function changeDate(newDate) { setDate(newDate); onChange(newDate.format(TIMEZONELESS_FORMAT)); } function update(name, value) { // Clone the date and call the specific setter function according to `name`. const newDate = date.clone()[name](value); changeDate(newDate); } function updateAmPm(value) { return () => { if (am === value) { return; } const parsedHours = parseInt(hours, 10); const newDate = date.clone().hours(value === 'PM' ? (parsedHours % 12 + 12) % 24 : parsedHours % 12); changeDate(newDate); }; } const dayFormat = createElement("div", { className: "components-datetime__time-field components-datetime__time-field-day" }, createElement(UpdateOnBlurAsIntegerField, { "aria-label": __('Day'), className: "components-datetime__time-field-day-input", type: "number" // The correct function to call in moment.js is "date" not "day". , name: "date", value: day, step: 1, min: 1, max: 31, onUpdate: update })); const monthFormat = createElement("div", { className: "components-datetime__time-field components-datetime__time-field-month" }, createElement(UpdateOnBlurAsIntegerField, { as: "select", "aria-label": __('Month'), className: "components-datetime__time-field-month-select", name: "month", value: month // The value starts from 0, so we have to -1 when setting month. , onUpdate: (key, value) => update(key, value - 1) }, createElement("option", { value: "01" }, __('January')), createElement("option", { value: "02" }, __('February')), createElement("option", { value: "03" }, __('March')), createElement("option", { value: "04" }, __('April')), createElement("option", { value: "05" }, __('May')), createElement("option", { value: "06" }, __('June')), createElement("option", { value: "07" }, __('July')), createElement("option", { value: "08" }, __('August')), createElement("option", { value: "09" }, __('September')), createElement("option", { value: "10" }, __('October')), createElement("option", { value: "11" }, __('November')), createElement("option", { value: "12" }, __('December')))); const dayMonthFormat = is12Hour ? createElement(Fragment, null, dayFormat, monthFormat) : createElement(Fragment, null, monthFormat, dayFormat); return createElement("div", { className: classnames('components-datetime__time') }, createElement("fieldset", null, createElement("legend", { className: "components-datetime__time-legend invisible" }, __('Date')), createElement("div", { className: "components-datetime__time-wrapper" }, dayMonthFormat, createElement("div", { className: "components-datetime__time-field components-datetime__time-field-year" }, createElement(UpdateOnBlurAsIntegerField, { "aria-label": __('Year'), className: "components-datetime__time-field-year-input", type: "number", name: "year", step: 1, min: 0, max: 9999, value: year, onUpdate: update })))), createElement("fieldset", null, createElement("legend", { className: "components-datetime__time-legend invisible" }, __('Time')), createElement("div", { className: "components-datetime__time-wrapper" }, createElement("div", { className: "components-datetime__time-field components-datetime__time-field-time" }, createElement(UpdateOnBlurAsIntegerField, { "aria-label": __('Hours'), className: "components-datetime__time-field-hours-input", type: "number", name: "hours", step: 1, min: is12Hour ? 1 : 0, max: is12Hour ? 12 : 23, value: hours, onUpdate: update }), createElement("span", { className: "components-datetime__time-separator", "aria-hidden": "true" }, ":"), createElement(UpdateOnBlurAsIntegerField, { "aria-label": __('Minutes'), className: "components-datetime__time-field-minutes-input", type: "number", name: "minutes", step: 1, min: 0, max: 59, value: minutes, onUpdate: update })), is12Hour && createElement(ButtonGroup, { className: "components-datetime__time-field components-datetime__time-field-am-pm" }, createElement(Button, { isPrimary: am === 'AM', isSecondary: am !== 'AM', onClick: updateAmPm('AM'), className: "components-datetime__time-am-button" }, __('AM')), createElement(Button, { isPrimary: am === 'PM', isSecondary: am !== 'PM', onClick: updateAmPm('PM'), className: "components-datetime__time-pm-button" }, __('PM'))), createElement(TimeZone, null)))); } export default TimePicker; //# sourceMappingURL=time.js.map