UNPKG

@procore/core-react

Version:
546 lines (541 loc) 20.9 kB
var _excluded = ["disabled", "maxLength", "maxValue", "minValue", "nextRef", "onChange", "placeholder", "prevRef", "tabIndex", "type", "value"], _excluded2 = ["clearRef", "disabled", "error", "segmentRefs", "variant", "onChange", "onChangeSegment", "onClear", "tabIndex", "value", "aria-label"]; function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } import { Clear } from '@procore/core-icons/dist'; import { getDatePartsWithPlaceholders } from '@procore/globalization-toolkit'; import { isSameDay } from 'date-fns'; import React from 'react'; import { Button } from '../Button/Button'; import { useOverlayTriggerContext } from '../OverlayTrigger/OverlayTrigger'; import { useDateTime } from '../_hooks/DateTime'; import { useI18nContext } from '../_hooks/I18n'; import { getMaxYear, maxMonth, minYear, normalizeNewDate } from '../_utils/CalendarHelpers'; import { StyledCalendar, StyledDateInput, StyledDateInputDelimiter, StyledDateInputIconContainer, StyledDateInputSegment, StyledDateSegmentsContainer } from './DateInput.styles'; var segmentMaxLengths = { day: 2, month: 2, year: 4 }; var dateInputLocales = { 'fr-CA': { placeholders: { day: 'jj', month: 'mm', year: 'aaaa' } }, 'fr-FR': { placeholders: { day: 'jj', month: 'mm', year: 'aaaa' } }, es: { placeholders: { day: 'dd', month: 'mm', year: 'aaaa' } }, 'es-ES': { placeholders: { day: 'dd', month: 'mm', year: 'aaaa' } }, 'pt-BR': { placeholders: { day: 'dd', month: 'mm', year: 'aaaa' } }, 'is-IS': { placeholders: { day: 'dd', month: 'mm', year: 'áááá' } }, 'de-DE': { placeholders: { day: 'tt', month: 'mm', year: 'jjjj' } }, 'pl-PL': { placeholders: { day: 'dd', month: 'mm', year: 'rrrr' } }, 'nb-NO': { placeholders: { day: 'dd', month: 'mm', year: 'åååå' } }, 'zh-TW': { placeholders: { day: 'dd', month: 'mm', year: 'yyyy' } }, 'it-IT': { placeholders: { day: 'gg', month: 'mm', year: 'aaaa' } }, 'pt-PT': { placeholders: { day: 'dd', month: 'mm', year: 'aaaa' } } }; var psuedoSegmentOrder = ['day', 'month', 'year']; export function isValidYearRange(year) { return year > 1700 && year < 2122; } function noop() {} function getLastDate(month, year) { return normalizeNewDate(year, Math.max(0, month), 0).getDate(); } function getSegmentProps(onChangeSegment, type, placeholder, dateInput, getAriaLabel) { var maxLength = segmentMaxLengths[type]; if (type === 'day') { return { 'aria-label': getAriaLabel('day', dateInput.day), maxLength: maxLength, maxValue: getLastDate(dateInput.month, dateInput.year), minValue: 1, onChange: function onChange(value) { dateInput.setDay(value); onChangeSegment(type, value); }, placeholder: placeholder, value: dateInput.day }; } else if (type === 'month') { return { 'aria-label': getAriaLabel('month', dateInput.month), maxLength: maxLength, maxValue: maxMonth, minValue: 1, onChange: function onChange(value) { dateInput.setMonth(value); onChangeSegment(type, value); }, placeholder: placeholder, value: dateInput.month }; } else { return { 'aria-label': getAriaLabel('year', dateInput.year), maxLength: maxLength, maxValue: getMaxYear(), minValue: minYear, onChange: function onChange(value) { dateInput.setYear(value); onChangeSegment(type, value); }, placeholder: placeholder, value: dateInput.year }; } } function setFocusTo(target) { return target && target.current && target.current.focus(); } function focusTargetOrFirst() { for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) { refs[_key] = arguments[_key]; } return function handler() { var event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { target: null }; var ref = refs.reduce(function (acc, ref) { return ref.current && ref.current === event.target ? ref : acc; }, refs[0]); setFocusTo(ref); }; } function clampDay(day, month, year) { if (day > 0 && month > 0 && year) { var date = normalizeNewDate(year, month - 1, day); if (date.getMonth() !== month - 1) { return getLastDate(month, year); } } return day; } function useDateInput(_ref) { var onChange = _ref.onChange, value_ = _ref.value, log = _ref.log; var dateTime = useDateTime(); // logging called too frequenlty if not in memo var value = React.useMemo(function () { return dateTime.shiftUtcToZonedTime(value_, log); }, [value_]); var _React$useState = React.useState(value ? value.getDate() : -1), _React$useState2 = _slicedToArray(_React$useState, 2), day = _React$useState2[0], setRawDay = _React$useState2[1]; var _React$useState3 = React.useState(value ? value.getMonth() + 1 : -1), _React$useState4 = _slicedToArray(_React$useState3, 2), month = _React$useState4[0], setRawMonth = _React$useState4[1]; var _React$useState5 = React.useState(value ? value.getFullYear() : -1), _React$useState6 = _slicedToArray(_React$useState5, 2), year = _React$useState6[0], setRawYear = _React$useState6[1]; var hasValues = day >= 0 || month >= 0 || year >= 0; var isInvalid = day < 0 || month < 0 || year < 0; var setAll = function setAll(day, month, year) { var clampedDay = clampDay(day, month, year); var date = clampedDay > 0 && month > 0 && year > 0 ? normalizeNewDate(year, month - 1, clampedDay) : null; setRawDay(clampedDay); setRawMonth(month); setRawYear(year); // Call DateSelect.onChange API when a supported year range or deleted date // isValidYearRange protects shiftZonedTimeToUtc // when date is null, year should be -1. -1 means it was cleared, either by button or backspace key /** * - going from non-valid date to a valid date * - is a valid year range * - going from one valid date to another, different, date * - is a valid year range * - change to invalid date */ var validDate = date && isValidYearRange(year); if (!value && validDate || value && validDate && !isSameDay(date, value) || value && date === null) { onChange(dateTime.shiftZonedTimeToUtc(date)); } }; var setDay = function setDay(value) { setAll(value, month, year); }; var setMonth = function setMonth(value) { setAll(day, value, year); }; var setYear = function setYear(value) { setAll(day, month, value); }; var clear = function clear() { setAll(-1, -1, -1); }; React.useEffect(function () { if (value) { setRawDay(value.getDate()); setRawMonth(value.getMonth() + 1); setRawYear(value.getFullYear()); } else if (value === undefined) { setRawDay(-1); setRawMonth(-1); setRawYear(-1); } }, [value]); return { clear: clear, day: day, hasValues: hasValues, month: month, setDay: setDay, setMonth: setMonth, setYear: setYear, year: year }; } function getSegmentText(_ref2) { var maxLength = _ref2.maxLength, placeholder = _ref2.placeholder, type = _ref2.type, value = _ref2.value; if (value < 0) { return placeholder; } if (type === 'year' && !isValidYearRange(value)) { return "".concat(value, "____").slice(0, 4); } return String(value).padStart(maxLength, '0'); } var getTodaySegmentValue = function getTodaySegmentValue(type) { var today = new Date(); if (type === 'month') { // months start from zero return today.getMonth() + 1; } else if (type === 'day') { return today.getDate(); } else { return today.getFullYear(); } }; var DateSegment = /*#__PURE__*/React.forwardRef(function DateSegment(_ref3, ref) { var _ref3$disabled = _ref3.disabled, disabled = _ref3$disabled === void 0 ? false : _ref3$disabled, maxLength = _ref3.maxLength, maxValue = _ref3.maxValue, minValue = _ref3.minValue, nextRef = _ref3.nextRef, _ref3$onChange = _ref3.onChange, onChange = _ref3$onChange === void 0 ? function (value) {} : _ref3$onChange, placeholder = _ref3.placeholder, prevRef = _ref3.prevRef, _ref3$tabIndex = _ref3.tabIndex, tabIndex = _ref3$tabIndex === void 0 ? 0 : _ref3$tabIndex, type = _ref3.type, _ref3$value = _ref3.value, value = _ref3$value === void 0 ? 0 : _ref3$value, props = _objectWithoutProperties(_ref3, _excluded); var I18n = useI18nContext(); var dateTime = useDateTime(); var _React$useState7 = React.useState(''), _React$useState8 = _slicedToArray(_React$useState7, 2), keyBuffer = _React$useState8[0], setKeyBuffer = _React$useState8[1]; var _useOverlayTriggerCon = useOverlayTriggerContext(), show = _useOverlayTriggerCon.show; var contains = function contains(key, keys) { return keys.indexOf(key) >= 0; }; var onKeyDown = function onKeyDown(event) { event.stopPropagation(); var key = event.key; var isOpenKey = contains(key, ['Enter', ' ']); if (isOpenKey) { event.preventDefault(); event.stopPropagation(); show(event); } if (contains(key, ['Up', 'ArrowUp', 'Down', 'ArrowDown', 'Backspace'])) { event.preventDefault(); } if (contains(key, ['Up', 'ArrowUp', 'Down', 'ArrowDown']) && value === -1) { onChange(getTodaySegmentValue(type)); } else if (contains(key, ['Up', 'ArrowUp'])) { onChange(value + 1 > maxValue ? minValue : Math.max(1, value + 1)); } else if (contains(key, ['Down', 'ArrowDown'])) { onChange(value - 1 < minValue ? maxValue : value - 1); } else if (contains(key, ['Left', 'ArrowLeft'])) { setFocusTo(prevRef); } else if (contains(key, ['Right', 'ArrowRight'])) { setFocusTo(nextRef); } else if (contains(key, ['Backspace', 'Delete'])) { setKeyBuffer(''); onChange(-1); // the segment is currently empty, go to the previous segment if (value === -1) { setFocusTo(prevRef); } } else if (!isNaN(parseInt(key, 10))) { if (keyBuffer.length === 0) { // current buffer is empty, initialize it setKeyBuffer(key); onChange(parseInt(key, 10)); } else { // current buffer has text, add to it var newBuffer = keyBuffer + key; setKeyBuffer(newBuffer); onChange(Math.min(parseInt(newBuffer, 10), maxValue)); } } }; React.useEffect(function () { if (keyBuffer.length >= maxLength || keyBuffer.length === 1 && (type === 'day' && value > 3 || type === 'month' && value > 1)) { setFocusTo(nextRef); // Needed for year to reset as it does not move focus setKeyBuffer(''); } }); var getMonthName = function getMonthName(month) { var date = new Date(); // months start from zero date.setMonth(month - 1); return dateTime.format(date, 'none', { month: 'long' }); }; var valueText = value === -1 ? I18n.t('core.dateInput.segment.ariaValueText.empty') : type === 'month' ? "".concat(value, " - ").concat(getMonthName(value)) : "".concat(value); return /*#__PURE__*/React.createElement(StyledDateInputSegment, _extends({ ref: ref }, props, { role: "spinbutton", "aria-valuetext": valueText, "aria-valuenow": value == -1 ? getTodaySegmentValue(type) : value, "aria-valuemax": maxValue, "aria-valuemin": minValue, "data-placeholder": value < 0, $disabled: disabled, $isYear: type === 'year', onBlur: function onBlur() { if (type === 'year' && value !== -1 && value < 100) { onChange(2000 + value); } setKeyBuffer(''); }, onKeyDown: onKeyDown, tabIndex: disabled ? -1 : tabIndex }), getSegmentText({ value: value, placeholder: placeholder, type: type, maxLength: maxLength })); }); /** * @deprecatedSince 11 * @deprecated Intended for internal library development. */ export var DateInput = /*#__PURE__*/React.forwardRef(function DateInput(_ref4, ref) { var _dateInputLocales$I; var clearRef = _ref4.clearRef, disabled = _ref4.disabled, _ref4$error = _ref4.error, error = _ref4$error === void 0 ? false : _ref4$error, _ref4$segmentRefs = _ref4.segmentRefs, segmentRefs = _ref4$segmentRefs === void 0 ? {} : _ref4$segmentRefs, variant = _ref4.variant, _ref4$onChange = _ref4.onChange, onChange = _ref4$onChange === void 0 ? noop : _ref4$onChange, _ref4$onChangeSegment = _ref4.onChangeSegment, onChangeSegment = _ref4$onChangeSegment === void 0 ? noop : _ref4$onChangeSegment, _ref4$onClear = _ref4.onClear, onClear = _ref4$onClear === void 0 ? noop : _ref4$onClear, _ref4$tabIndex = _ref4.tabIndex, tabIndex = _ref4$tabIndex === void 0 ? 0 : _ref4$tabIndex, value = _ref4.value, ariaLabel = _ref4['aria-label'], props = _objectWithoutProperties(_ref4, _excluded2); var I18n = useI18nContext(); var dateInputRef = ref; var segment1Ref = segmentRefs.segmentOne || /*#__PURE__*/React.createRef(); var segment2Ref = segmentRefs.segmentTwo || /*#__PURE__*/React.createRef(); var segment3Ref = segmentRefs.segmentThree || /*#__PURE__*/React.createRef(); var placeholders = ((_dateInputLocales$I = dateInputLocales[I18n.locale]) === null || _dateInputLocales$I === void 0 ? void 0 : _dateInputLocales$I.placeholders) || { day: 'dd', month: 'mm', year: 'yyyy' }; var dateParts = getDatePartsWithPlaceholders(I18n.locale, placeholders); var delimiter = dateParts.filter(function (part) { return part.type === 'literal'; }).reduce(function (acc, curr) { return [].concat(_toConsumableArray(acc), [curr.value]); }, []); var segmentParts = dateParts.filter(function (part) { return ['month', 'day', 'year'].includes(part.type); }).reduce(function (acc, curr) { return [].concat(_toConsumableArray(acc), [curr.type]); }, []); var segments = I18n.locale === 'pseudo' ? psuedoSegmentOrder : _toConsumableArray(segmentParts); var dateTime = useDateTime(); var dateInput = useDateInput({ onChange: onChange, value: value, log: 'Display date' }); var onClickClear = function onClickClear(e) { setFocusTo(segment1Ref); dateInput.clear(); onClear(e); }; var getSegmentAriaLabel = function getSegmentAriaLabel(type, value) { return value && value !== -1 ? I18n.t(type, { value: value, scope: 'core.dateInput.segment.ariaLabel.withValue' }) : I18n.t(type, { scope: 'core.dateInput.segment.ariaLabel.withoutValue' }); }; var dateLabel = value && isValidYearRange(dateInput.year) ? dateTime.format(value, 'weekday-date') : undefined; var groupAriaLabel; if (ariaLabel && dateLabel) { groupAriaLabel = "".concat(ariaLabel, ", ").concat(dateLabel); } else if (ariaLabel) { groupAriaLabel = ariaLabel; } else { groupAriaLabel = dateLabel; } return ( /*#__PURE__*/ // eslint-disable-next-line jsx-a11y/role-supports-aria-props -- aria-invalid on group is valid per WCAG guidance (UXI-1529) React.createElement(StyledDateInput, _extends({ role: "group", "aria-label": groupAriaLabel, "aria-invalid": error || variant === 'error' || !isValidYearRange(dateInput.year) && dateInput.year !== -1 ? 'true' : 'false', $disabled: disabled || variant === 'disabled', $error: error || variant === 'error' || // -1 means placeholder yyyy is shown !isValidYearRange(dateInput.year) && dateInput.year !== -1, ref: dateInputRef }, props, { onClick: function onClick(e) { var _props$onClick; focusTargetOrFirst(segment1Ref, segment2Ref, segment3Ref)(e); (_props$onClick = props.onClick) === null || _props$onClick === void 0 ? void 0 : _props$onClick.call(props, e); } }), /*#__PURE__*/React.createElement(StyledDateSegmentsContainer, null, /*#__PURE__*/React.createElement(DateSegment, _extends({ disabled: disabled, nextRef: segment2Ref, ref: segment1Ref, tabIndex: tabIndex, type: segments[0] }, getSegmentProps(onChangeSegment, segments[0], placeholders[segments[0]], dateInput, getSegmentAriaLabel))), delimiter[0] && /*#__PURE__*/React.createElement(StyledDateInputDelimiter, { "aria-hidden": true, $visible: Boolean(value) }, delimiter[0]), /*#__PURE__*/React.createElement(DateSegment, _extends({ disabled: disabled, nextRef: segment3Ref, prevRef: segment1Ref, ref: segment2Ref, tabIndex: tabIndex, type: segments[1] }, getSegmentProps(onChangeSegment, segments[1], placeholders[segments[1]], dateInput, getSegmentAriaLabel))), delimiter[1] && /*#__PURE__*/React.createElement(StyledDateInputDelimiter, { "aria-hidden": true, $visible: Boolean(value) }, delimiter[1]), /*#__PURE__*/React.createElement(DateSegment, _extends({ disabled: disabled, prevRef: segment2Ref, ref: segment3Ref, tabIndex: tabIndex, type: segments[2] }, getSegmentProps(onChangeSegment, segments[2], placeholders[segments[2]], dateInput, getSegmentAriaLabel))), delimiter[2] && /*#__PURE__*/React.createElement(StyledDateInputDelimiter, { "aria-hidden": true, $visible: Boolean(value) }, delimiter[2])), /*#__PURE__*/React.createElement(StyledDateInputIconContainer, null, dateInput.hasValues ? /*#__PURE__*/React.createElement(Button, { "aria-label": I18n.t('core.dateInput.clearButton.ariaLabel'), onClick: onClickClear, ref: clearRef, size: "sm", variant: "tertiary", icon: /*#__PURE__*/React.createElement(Clear, { size: "sm" }) }) : /*#__PURE__*/React.createElement(StyledCalendar, null))) ); }); DateInput.displayName = 'DateInput'; //# sourceMappingURL=DateInput.js.map