UNPKG

@atlaskit/datetime-picker

Version:

A date time picker allows the user to select an associated date and time.

545 lines (544 loc) 22.9 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized"; import _inherits from "@babel/runtime/helpers/inherits"; import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn"; import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _extends from "@babel/runtime/helpers/extends"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } /** @jsx jsx */ import { Component } from 'react'; import { css, jsx } from '@emotion/react'; import { format, isValid, lastDayOfMonth, parseISO } from 'date-fns'; import pick from 'lodash/pick'; import { createAndFireEvent, withAnalyticsContext, withAnalyticsEvents } from '@atlaskit/analytics-next'; import Calendar from '@atlaskit/calendar'; import CalendarIcon from '@atlaskit/icon/glyph/calendar'; import { UNSAFE_LAYERING } from '@atlaskit/layering'; import { createLocalizationProvider } from '@atlaskit/locale'; import { getBooleanFF } from '@atlaskit/platform-feature-flags'; import Select, { mergeStyles } from '@atlaskit/select'; import { N0, N50A, N60A } from '@atlaskit/theme/colors'; import { layers } from '@atlaskit/theme/constants'; import { defaultDateFormat, EmptyComponent, padToTwo, placeholderDatetime } from '../internal'; import FixedLayer from '../internal/fixed-layer'; import { makeSingleValue } from '../internal/single-value'; import { convertTokens } from './utils'; var packageName = "@atlaskit/datetime-picker"; var packageVersion = "13.0.0"; function getValidDate(iso) { var date = parseISO(iso); return isValid(date) ? { day: date.getDate(), month: date.getMonth() + 1, year: date.getFullYear() } : {}; } function getShortISOString(date) { return format(date, convertTokens('YYYY-MM-DD')); } var menuStyles = css({ zIndex: layers.dialog(), backgroundColor: "var(--ds-surface-overlay, ".concat(N0, ")"), borderRadius: "var(--ds-border-radius, 3px)", boxShadow: "var(--ds-shadow-overlay, ".concat("0 4px 8px -2px ".concat(N50A, ", 0 0 1px ").concat(N60A), ")"), overflow: 'hidden' }); var Menu = function Menu(_ref) { var selectProps = _ref.selectProps, innerProps = _ref.innerProps; return jsx(UNSAFE_LAYERING, { isDisabled: getBooleanFF('platform.design-system-team.layering_qmiw3') ? false : true }, jsx(FixedLayer, { inputValue: selectProps.inputValue, containerRef: selectProps.calendarContainerRef, content: jsx("div", _extends({ css: menuStyles }, innerProps), jsx(Calendar, _extends({}, getValidDate(selectProps.calendarValue), getValidDate(selectProps.calendarView), { disabled: selectProps.calendarDisabled, disabledDateFilter: selectProps.calendarDisabledDateFilter, minDate: selectProps.calendarMinDate, maxDate: selectProps.calendarMaxDate, nextMonthLabel: selectProps.nextMonthLabel, onChange: selectProps.onCalendarChange, onSelect: selectProps.onCalendarSelect, previousMonthLabel: selectProps.previousMonthLabel, calendarRef: selectProps.calendarRef, selected: [selectProps.calendarValue], locale: selectProps.calendarLocale, testId: selectProps.testId && "".concat(selectProps.testId, "--calendar"), weekStartDay: selectProps.calendarWeekStartDay }))), testId: selectProps.testId })); }; var datePickerDefaultProps = { appearance: 'default', autoFocus: false, defaultIsOpen: false, defaultValue: '', disabled: [], disabledDateFilter: function disabledDateFilter(_) { return false; }, hideIcon: false, icon: CalendarIcon, id: '', innerProps: {}, isDisabled: false, isInvalid: false, name: '', // These disables are here for proper typing when used as defaults. They // should *not* use the `noop` function. /* eslint-disable @repo/internal/react/use-noop */ onBlur: function onBlur(_event) {}, onChange: function onChange(_value) {}, onFocus: function onFocus(_event) {}, /* eslint-enable @repo/internal/react/use-noop */ selectProps: {}, spacing: 'default', locale: 'en-US' // Not including a default prop for value as it will // Make the component a controlled component }; var DatePicker = /*#__PURE__*/function (_Component) { _inherits(DatePicker, _Component); var _super = _createSuper(DatePicker); function DatePicker(props) { var _this; _classCallCheck(this, DatePicker); _this = _super.call(this, props); _defineProperty(_assertThisInitialized(_this), "calendarRef", null); _defineProperty(_assertThisInitialized(_this), "containerRef", null); // All state needs to be accessed via this function so that the state is mapped from props // correctly to allow controlled/uncontrolled usage. _defineProperty(_assertThisInitialized(_this), "getSafeState", function () { return _objectSpread(_objectSpread(_objectSpread({}, _this.state), pick(_this.props, ['value', 'isOpen'])), pick(_this.props.selectProps, ['inputValue'])); }); _defineProperty(_assertThisInitialized(_this), "isDateDisabled", function (date) { return _this.props.disabled.indexOf(date) > -1; }); _defineProperty(_assertThisInitialized(_this), "onCalendarChange", function (_ref2) { var iso = _ref2.iso; var _iso$split = iso.split('-'), _iso$split2 = _slicedToArray(_iso$split, 3), year = _iso$split2[0], month = _iso$split2[1], date = _iso$split2[2]; var newIso = iso; var parsedDate = parseInt(date, 10); var parsedMonth = parseInt(month, 10); var parsedYear = parseInt(year, 10); var lastDayInMonth = lastDayOfMonth(new Date(parsedYear, parsedMonth - 1) // This needs to be -1, because the Date constructor expects an index of the given month ).getDate(); if (lastDayInMonth < parsedDate) { newIso = "".concat(year, "-").concat(padToTwo(parsedMonth), "-").concat(padToTwo(lastDayInMonth)); } else { newIso = "".concat(year, "-").concat(padToTwo(parsedMonth), "-").concat(padToTwo(parsedDate)); } _this.setState({ calendarValue: newIso }); }); _defineProperty(_assertThisInitialized(_this), "onCalendarSelect", function (_ref3) { var iso = _ref3.iso; _this.setState({ selectInputValue: '', isOpen: false, calendarValue: iso, value: iso }); _this.props.onChange(iso); }); _defineProperty(_assertThisInitialized(_this), "onInputClick", function () { if (!_this.props.isDisabled && !_this.getSafeState().isOpen) { _this.setState({ isOpen: true }); } }); _defineProperty(_assertThisInitialized(_this), "onContainerBlur", function (event) { var _this$containerRef; var newlyFocusedElement = event.relatedTarget; if (!((_this$containerRef = _this.containerRef) !== null && _this$containerRef !== void 0 && _this$containerRef.contains(newlyFocusedElement))) { _this.setState({ isOpen: false }); } }); _defineProperty(_assertThisInitialized(_this), "onSelectBlur", function (event) { var _this$containerRef2; var newlyFocusedElement = event.relatedTarget; if (_this.getSafeState().clearingFromIcon) { // Don't close menu if blurring after the user has clicked clear _this.setState({ clearingFromIcon: false }); } else if (!((_this$containerRef2 = _this.containerRef) !== null && _this$containerRef2 !== void 0 && _this$containerRef2.contains(newlyFocusedElement))) { // Don't close menu if focus is staying within the date picker's // container. Makes keyboard accessibility of calendar possible _this.setState({ isOpen: false, isFocused: false }); } _this.props.onBlur(event); }); _defineProperty(_assertThisInitialized(_this), "onSelectFocus", function (event) { var _this$getSafeState = _this.getSafeState(), clearingFromIcon = _this$getSafeState.clearingFromIcon, value = _this$getSafeState.value; if (clearingFromIcon) { // Don't open menu if focussing after the user has clicked clear _this.setState({ clearingFromIcon: false }); } else { _this.setState({ isOpen: true, calendarValue: value, isFocused: true }); } _this.props.onFocus(event); }); _defineProperty(_assertThisInitialized(_this), "onTextInput", function (event) { var value = event.target.value; if (value) { var parsed = _this.parseDate(value); // Only try to set the date if we have month & day if (parsed && isValid(parsed)) { // We format the parsed date to YYYY-MM-DD here because // this is the format expected by the @atlaskit/calendar component _this.setState({ calendarValue: getShortISOString(parsed) }); } } _this.setState({ isOpen: true }); }); _defineProperty(_assertThisInitialized(_this), "getSafeCalendarValue", function (calendarValue) { // If `calendarValue` has a year that is greater than 9999, default to // today's date var yearIsOverLimit = calendarValue.match(/^\d{5,}/); if (yearIsOverLimit) { return getShortISOString(new Date()); } return calendarValue; }); _defineProperty(_assertThisInitialized(_this), "onInputKeyDown", function (event) { var _this$getSafeState2 = _this.getSafeState(), value = _this$getSafeState2.value, calendarValue = _this$getSafeState2.calendarValue; var keyPressed = event.key.toLowerCase(); switch (keyPressed) { case 'escape': _this.setState({ isOpen: false }); break; case 'backspace': case 'delete': { var inputCount = getBooleanFF('platform.design-system-team.date-picker-input-a11y-fix_cbbxs') ? 1 : 0; if (value && event.target instanceof HTMLInputElement && event.target.value.length <= inputCount) { // If being cleared from keyboard, don't change behaviour _this.setState({ clearingFromIcon: false, value: '' }); } break; } case 'enter': if (!_this.state.isOpen) { return; } // Prevent form submission when a date is selected // using enter. See https://product-fabric.atlassian.net/browse/DSP-2501 // for more details. event.preventDefault(); if (!_this.isDateDisabled(calendarValue)) { var _this$getSafeState3 = _this.getSafeState(), _value2 = _this$getSafeState3.value; // Get a safe `calendarValue` in case the value exceeds the maximum // allowed by ISO 8601 var safeCalendarValue = _this.getSafeCalendarValue(calendarValue); var valueChanged = safeCalendarValue !== _value2; _this.setState({ selectInputValue: '', isOpen: false, value: safeCalendarValue, calendarValue: safeCalendarValue }); if (valueChanged) { _this.props.onChange(safeCalendarValue); } } break; default: break; } }); _defineProperty(_assertThisInitialized(_this), "onClear", function () { var changedState = { value: '', calendarValue: _this.props.defaultValue || getShortISOString(new Date()) }; if (!_this.props.hideIcon) { changedState = _objectSpread(_objectSpread({}, changedState), {}, { clearingFromIcon: true }); } _this.setState(changedState); _this.props.onChange(''); }); _defineProperty(_assertThisInitialized(_this), "onSelectChange", function (_value, action) { // Used for native clear event in React Select // Triggered when clicking ClearIndicator or backspace with no value if (action.action === 'clear') { _this.onClear(); } }); _defineProperty(_assertThisInitialized(_this), "refCalendar", function (ref) { _this.calendarRef = ref; }); _defineProperty(_assertThisInitialized(_this), "handleSelectInputChange", function (selectInputValue, actionMeta) { var onInputChange = _this.props.selectProps.onInputChange; if (onInputChange) { onInputChange(selectInputValue, actionMeta); } _this.setState({ selectInputValue: selectInputValue }); }); _defineProperty(_assertThisInitialized(_this), "getContainerRef", function (ref) { var oldRef = _this.containerRef; _this.containerRef = ref; // Cause a re-render if we're getting the container ref for the first time // as the layered menu requires it for dimension calculation if (oldRef == null && ref != null) { _this.forceUpdate(); } }); /** * There are two props that can change how the date is parsed. * The priority of props used is: * 1. parseInputValue * 2. locale */ _defineProperty(_assertThisInitialized(_this), "parseDate", function (date) { var _this$props = _this.props, parseInputValue = _this$props.parseInputValue, dateFormat = _this$props.dateFormat; if (parseInputValue) { return parseInputValue(date, dateFormat || defaultDateFormat); } var _this$getSafeState4 = _this.getSafeState(), l10n = _this$getSafeState4.l10n; return l10n.parseDate(date); }); /** * There are multiple props that can change how the date is formatted. * The priority of props used is: * 1. formatDisplayLabel * 2. dateFormat * 3. locale */ _defineProperty(_assertThisInitialized(_this), "formatDate", function (value) { var _this$props2 = _this.props, formatDisplayLabel = _this$props2.formatDisplayLabel, dateFormat = _this$props2.dateFormat; var _this$getSafeState5 = _this.getSafeState(), l10n = _this$getSafeState5.l10n; if (formatDisplayLabel) { return formatDisplayLabel(value, dateFormat || defaultDateFormat); } var date = parseISO(value); return dateFormat ? format(date, convertTokens(dateFormat)) : l10n.formatDate(date); }); _defineProperty(_assertThisInitialized(_this), "getPlaceholder", function () { var placeholder = _this.props.placeholder; if (placeholder) { return placeholder; } var _this$getSafeState6 = _this.getSafeState(), l10n = _this$getSafeState6.l10n; return l10n.formatDate(placeholderDatetime); }); _this.state = { isOpen: _this.props.defaultIsOpen, isFocused: false, clearingFromIcon: false, selectInputValue: _this.props.selectProps.inputValue || '', value: _this.props.value || _this.props.defaultValue, calendarValue: _this.props.value || _this.props.defaultValue || getShortISOString(new Date()), l10n: createLocalizationProvider(_this.props.locale), locale: _this.props.locale }; return _this; } _createClass(DatePicker, [{ key: "render", value: function render() { var _this$props3 = this.props, appearance = _this$props3.appearance, autoFocus = _this$props3.autoFocus, disabled = _this$props3.disabled, hideIcon = _this$props3.hideIcon, icon = _this$props3.icon, id = _this$props3.id, innerProps = _this$props3.innerProps, isDisabled = _this$props3.isDisabled, disabledDateFilter = _this$props3.disabledDateFilter, maxDate = _this$props3.maxDate, minDate = _this$props3.minDate, isInvalid = _this$props3.isInvalid, name = _this$props3.name, nextMonthLabel = _this$props3.nextMonthLabel, previousMonthLabel = _this$props3.previousMonthLabel, selectProps = _this$props3.selectProps, spacing = _this$props3.spacing, locale = _this$props3.locale, testId = _this$props3.testId, weekStartDay = _this$props3.weekStartDay; var _this$getSafeState7 = this.getSafeState(), value = _this$getSafeState7.value, calendarValue = _this$getSafeState7.calendarValue, isOpen = _this$getSafeState7.isOpen, selectInputValue = _this$getSafeState7.selectInputValue; var actualSelectInputValue; if (getBooleanFF('platform.design-system-team.date-picker-input-a11y-fix_cbbxs')) { actualSelectInputValue = selectInputValue || (value ? this.formatDate(value) : undefined); } else { actualSelectInputValue = selectInputValue; } var menuIsOpen = isOpen && !isDisabled; var showClearIndicator = Boolean((value || selectInputValue) && !hideIcon); var dropDownIcon = appearance === 'subtle' || hideIcon || showClearIndicator ? null : icon; var SingleValue = makeSingleValue({ lang: this.props.locale }); var selectComponents = _objectSpread({ DropdownIndicator: dropDownIcon, Menu: Menu, SingleValue: SingleValue }, !showClearIndicator && { ClearIndicator: EmptyComponent }); var _selectProps$styles = selectProps.styles, selectStyles = _selectProps$styles === void 0 ? {} : _selectProps$styles; var disabledStyle = isDisabled ? { pointerEvents: 'none', color: "var(--ds-icon-disabled, inherit)" } : {}; var calendarProps = { calendarContainerRef: this.containerRef, calendarRef: this.refCalendar, calendarDisabled: disabled, calendarDisabledDateFilter: disabledDateFilter, calendarMaxDate: maxDate, calendarMinDate: minDate, calendarValue: value && getShortISOString(parseISO(value)), calendarView: calendarValue, onCalendarChange: this.onCalendarChange, onCalendarSelect: this.onCalendarSelect, calendarLocale: locale, calendarWeekStartDay: weekStartDay, nextMonthLabel: nextMonthLabel, previousMonthLabel: previousMonthLabel }; return ( // These event handlers must be on this element because the events come // from different child elements. jsx("div", _extends({}, innerProps, { role: "presentation", onBlur: this.onContainerBlur, onClick: this.onInputClick, onInput: this.onTextInput, onKeyDown: this.onInputKeyDown, ref: this.getContainerRef, "data-testid": testId && "".concat(testId, "--container") }), jsx("input", { name: name, type: "hidden", value: value, "data-testid": testId && "".concat(testId, "--input") }), jsx(Select, _extends({ appearance: this.props.appearance, enableAnimation: false, menuIsOpen: menuIsOpen, closeMenuOnSelect: true, autoFocus: autoFocus, instanceId: id, isDisabled: isDisabled, onBlur: this.onSelectBlur, onFocus: this.onSelectFocus, inputValue: actualSelectInputValue, onInputChange: this.handleSelectInputChange, components: selectComponents, onChange: this.onSelectChange, styles: mergeStyles(selectStyles, { control: function control(base) { return _objectSpread(_objectSpread({}, base), disabledStyle); }, indicatorsContainer: function indicatorsContainer(base) { return _objectSpread(_objectSpread({}, base), {}, { paddingLeft: "var(--ds-space-025, 2px)", // ICON_PADDING = 2 paddingRight: "var(--ds-space-075, 6px)" // 8 - ICON_PADDING = 6 }); } }), placeholder: this.getPlaceholder(), value: value && { label: this.formatDate(value), value: value } }, selectProps, calendarProps, { isClearable: true, spacing: spacing, isInvalid: isInvalid, testId: testId }))) ); } }], [{ key: "getDerivedStateFromProps", value: function getDerivedStateFromProps(nextProps, prevState) { if (nextProps.locale !== prevState.locale) { return { l10n: createLocalizationProvider(nextProps.locale), locale: nextProps.locale }; } else { return null; } } }]); return DatePicker; }(Component); _defineProperty(DatePicker, "defaultProps", datePickerDefaultProps); export { DatePicker as DatePickerWithoutAnalytics }; export default withAnalyticsContext({ componentName: 'datePicker', packageName: packageName, packageVersion: packageVersion })(withAnalyticsEvents({ onChange: createAndFireEvent('atlaskit')({ action: 'selectedDate', actionSubject: 'datePicker', attributes: { componentName: 'datePicker', packageName: packageName, packageVersion: packageVersion } }) })(DatePicker));