@procore/core-react
Version:
React library of Procore Design Guidelines
546 lines (541 loc) • 20.9 kB
JavaScript
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