@gechiui/components
Version:
UI components for GeChiUI.
266 lines (243 loc) • 8.48 kB
JavaScript
import { Fragment } from "@gechiui/element";
/**
* External dependencies
*/
import classnames from 'classnames';
import { isInteger } from 'lodash';
import moment from 'moment';
/**
* GeChiUI dependencies
*/
import { createElement, useState, useMemo, useEffect } from '@gechiui/element';
import { __ } from '@gechiui/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';
function from12hTo24h(hours, isPm) {
return isPm ? (hours % 12 + 12) % 24 : hours % 12;
}
/**
* <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(_ref) {
let {
as,
value,
onUpdate,
...props
} = _ref;
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} GCValidDateTimeFormat
*
* @param {Object} props Component props.
* @param {boolean} props.is12Hour Should the time picker showed in 12 hour format or 24 hour format.
* @param {GCValidDateTimeFormat} props.currentTime The initial current time the time picker should render.
* @param {Function} props.onChange Callback function when the date changed.
*/
export function TimePicker(_ref2) {
let {
is12Hour,
currentTime,
onChange
} = _ref2;
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) {
// If the 12-hour format is being used and the 'PM' period is selected, then
// the incoming value (which ranges 1-12) should be increased by 12 to match
// the expected 24-hour format.
let adjustedValue = value;
if (name === 'hours' && is12Hour) {
adjustedValue = from12hTo24h(value, am === 'PM');
} // Clone the date and call the specific setter function according to `name`.
const newDate = date.clone()[name](adjustedValue);
changeDate(newDate);
}
function updateAmPm(value) {
return () => {
if (am === value) {
return;
}
const parsedHours = parseInt(hours, 10);
const newDate = date.clone().hours(from12hTo24h(parsedHours, value === 'PM'));
changeDate(newDate);
};
}
const dayFormat = createElement("div", {
className: "components-datetime__time-field components-datetime__time-field-day"
}, createElement(UpdateOnBlurAsIntegerField, {
"aria-label": __('日'),
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": __('月'),
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"
}, __('1月')), createElement("option", {
value: "02"
}, __('2月')), createElement("option", {
value: "03"
}, __('3月')), createElement("option", {
value: "04"
}, __('4月')), createElement("option", {
value: "05"
}, __('5月')), createElement("option", {
value: "06"
}, __('6月')), createElement("option", {
value: "07"
}, __('7月')), createElement("option", {
value: "08"
}, __('8月')), createElement("option", {
value: "09"
}, __('9月')), createElement("option", {
value: "10"
}, __('10月')), createElement("option", {
value: "11"
}, __('11月')), createElement("option", {
value: "12"
}, __('12月'))));
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"
}, __('日期')), createElement("div", {
className: "components-datetime__time-wrapper"
}, dayMonthFormat, createElement("div", {
className: "components-datetime__time-field components-datetime__time-field-year"
}, createElement(UpdateOnBlurAsIntegerField, {
"aria-label": __('年'),
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"
}, __('时间')), createElement("div", {
className: "components-datetime__time-wrapper"
}, createElement("div", {
className: "components-datetime__time-field components-datetime__time-field-time"
}, createElement(UpdateOnBlurAsIntegerField, {
"aria-label": __('小时'),
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": __('分钟'),
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, {
variant: am === 'AM' ? 'primary' : 'secondary',
onClick: updateAmPm('AM'),
className: "components-datetime__time-am-button"
}, __('上午')), createElement(Button, {
variant: am === 'PM' ? 'primary' : 'secondary',
onClick: updateAmPm('PM'),
className: "components-datetime__time-pm-button"
}, __('下午'))), createElement(TimeZone, null))));
}
export default TimePicker;
//# sourceMappingURL=time.js.map