UNPKG

optimizely-oui

Version:

Optimizely's Component Library.

577 lines (505 loc) 23.4 kB
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* eslint-disable react/jsx-no-bind */ import React from "react"; import classNames from "classnames"; import PropTypes from "prop-types"; import "react-dates/initialize"; import { DayPickerRangeController } from "react-dates"; import OutsideClickHandler from "react-outside-click-handler"; import moment from "moment"; import momentPropTypes from "react-moment-proptypes"; import Button from "../Button"; import ButtonRow from "../ButtonRow"; import Input from "../Input"; import CalendarNavButton from "./CalendarNavButton"; import { constants } from "./constants"; import { getDateString, getTimeString, getCustomMonthElement, isOutsideAcceptableDateRange, preserveTime } from "./fns"; var DateRangePicker = /*#__PURE__*/ function (_React$Component) { _inherits(DateRangePicker, _React$Component); function DateRangePicker(props) { var _this; _classCallCheck(this, DateRangePicker); _this = _possibleConstructorReturn(this, _getPrototypeOf(DateRangePicker).call(this, props)); _defineProperty(_assertThisInitialized(_this), "onFocusChange", function (focusedInput) { if (_this.props.keepOpenOnDateSelect || _this.props.keepOpenAlways) { _this.setState({ focusedInput: !focusedInput ? "startDate" : focusedInput, dayPickerIsOpen: true }); } else { _this.setState({ focusedInput: focusedInput, dayPickerIsOpen: !!focusedInput }); } }); _defineProperty(_assertThisInitialized(_this), "onDatesChange", function (_ref) { var startDate = _ref.startDate, endDate = _ref.endDate, _ref$resetTime = _ref.resetTime, resetTime = _ref$resetTime === void 0 ? false : _ref$resetTime; var newStartDate = startDate && moment(startDate); var newEndDate = endDate && moment(endDate); var _this$state = _this.state, prevStartDate = _this$state.startDate, prevEndDate = _this$state.endDate; if (!resetTime) { if (!prevStartDate && newStartDate) { newStartDate.startOf("day"); } else if (newStartDate) { newStartDate = preserveTime(prevStartDate, newStartDate); } if (!prevEndDate && newEndDate) { newEndDate.endOf("day"); } else if (newEndDate) { newEndDate = preserveTime(prevEndDate, newEndDate); } } // if no endDate selected, set to the end of the startDate by default newEndDate = newEndDate || newStartDate && moment(newStartDate).endOf("day"); _this.setState({ startDate: newStartDate, endDate: newEndDate, startDateString: getDateString(newStartDate), endDateString: getDateString(newEndDate), startTimeString: getTimeString(newStartDate), endTimeString: getTimeString(newEndDate) }); _this.props.onDatesChange && _this.props.onDatesChange({ startDate: newStartDate, endDate: newEndDate }); }); _defineProperty(_assertThisInitialized(_this), "onOutsideClick", function () { !_this.props.keepOpenAlways && _this.setState({ dayPickerIsOpen: false }); }); _defineProperty(_assertThisInitialized(_this), "onClose", function () { var dayPickerShouldRemainOpen = _this.props.keepOpenOnDateSelect || _this.props.keepOpenAlways; if (dayPickerShouldRemainOpen) { _this.setState({ dayPickerIsOpen: true }); } }); _defineProperty(_assertThisInitialized(_this), "isOutsideAcceptableDateRange", function (day) { var _this$props = _this.props, isOutsideRange = _this$props.isOutsideRange, isFutureDateSelectable = _this$props.isFutureDateSelectable, isPastDateSelectable = _this$props.isPastDateSelectable; return isOutsideAcceptableDateRange(day, isOutsideRange, isFutureDateSelectable, isPastDateSelectable); }); _defineProperty(_assertThisInitialized(_this), "renderPresetButtons", function (presetPanelOptions) { var presetButtons = presetPanelOptions.map(function (_ref2, index) { var label = _ref2.label, startDate = _ref2.startDate, endDate = _ref2.endDate, _ref2$resetTime = _ref2.resetTime, resetTime = _ref2$resetTime === void 0 ? true : _ref2$resetTime; var isSelected = startDate === _this.state.startDate && endDate === _this.state.endDate; var classes = classNames("oui-date-picker__range-preset-option", isSelected && "oui-date-picker__range-preset-option--active"); return React.createElement("li", { className: classes, key: index }, React.createElement(Button, { style: "unstyled", width: "full", onClick: function onClick() { return _this.onDatesChange({ startDate: startDate, endDate: endDate, resetTime: resetTime }); } }, React.createElement("span", { className: "link" }, label))); }); return presetButtons; }); _defineProperty(_assertThisInitialized(_this), "renderSidePanel", function () { var _this$state2 = _this.state, endDate = _this$state2.endDate, startDate = _this$state2.startDate; var presetPanelOptions = _this.props.presetPanelOptions; var panelOptionsArePresent = presetPanelOptions && presetPanelOptions.length > 0; var panelButtons = _this.props.panelButtons; return React.createElement("div", { className: "oui-date-picker__range-panel" }, panelOptionsArePresent && React.createElement("div", { className: "oui-date-picker__range-presets" }, React.createElement("ul", { className: "oui-date-picker__range-preset-list push-triple--bottom" }, _this.renderPresetButtons(presetPanelOptions))), // render panel buttons via render props if a function is provided panelButtons && (typeof panelButtons === "function" ? React.createElement(ButtonRow, { rightGroup: panelButtons({ endDate: endDate, startDate: startDate, onDatesChange: _this.onDatesChange }) }) : React.createElement(ButtonRow, { rightGroup: panelButtons }))); }); _defineProperty(_assertThisInitialized(_this), "onClearButtonClick", function (rangeSide) { if (rangeSide === "start") { _this.setState({ startDateString: "", startDate: null }); } else { _this.setState({ endDateString: "", endDate: null }); } }); _this.state = { endDate: _this.props.initialEndDate, focusedInput: _this.props.focusedInput || null, dayPickerIsOpen: !!_this.props.focusedInput || _this.props.keepOpenAlways, startDate: _this.props.initialStartDate, startDateString: getDateString(_this.props.initialStartDate), endDateString: getDateString(_this.props.initialEndDate), startTimeString: getTimeString(_this.props.initialStartDate), endTimeString: getTimeString(_this.props.initialEndDate) }; return _this; } _createClass(DateRangePicker, [{ key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { if (prevProps.focusedInput !== this.props.focusedInput) { this.onFocusChange(this.props.focusedInput); } } }, { key: "onTimeChange", value: function onTimeChange(event, timeToUpdate) { var newTime = event.target.value; var _newTime$split = newTime.split(":"), _newTime$split2 = _slicedToArray(_newTime$split, 2), hour = _newTime$split2[0], minute = _newTime$split2[1]; var startDate = this.state.startDate; var endDate = this.state.endDate; if (timeToUpdate === "startTime") { startDate.set({ hour: hour, minute: minute }); } else if (timeToUpdate === "endTime") { if (!endDate) { endDate = moment(startDate); } endDate.set({ hour: hour, minute: minute }); } this.setState({ startDate: startDate, endDate: endDate, startTimeString: getTimeString(startDate), endTimeString: getTimeString(endDate) }); this.props.onDatesChange && this.props.onDatesChange({ startDate: startDate, endDate: endDate }); } /** * Function called when either a start date * or end date has been changed. * The dates are passed as a parameter object with startDate and endDate * as keys as well as resetTime which is used to handle time defaults * and time preservation. * * The new dates are used to set the internal component state, before * passing those dates to this.props.onDatesChange() implemented by the * consumer. * * When resetTime = false (default behavior): * - By default, when selecting dates, resetTime will be false, since * we want to initally set startDate to 12:00am * and endDate to 11:59(:59.999)pm * - If the previous date (prevStartDate & prevEndDate) are not null, * we want to preserve the time of the previous selection since * the react-dates component blindly resets the time to 12pm. * When resetTime = true (primarily helpful for panelButtons): * - If resetTime is true, we allow the time of both startDate and endDate * to be the source of truth for time, ignoring what was previously set */ }, { key: "render", value: function render() { var _this2 = this; var _this$props2 = this.props, endDateInputId = _this$props2.endDateInputId, endDateInputLabel = _this$props2.endDateInputLabel, endDateInputPlaceholder = _this$props2.endDateInputPlaceholder, endTimeInputId = _this$props2.endTimeInputId, endTimeInputLabel = _this$props2.endTimeInputLabel, hasClearButton = _this$props2.hasClearButton, hasTimeInputs = _this$props2.hasTimeInputs, isAbsolutelyPositioned = _this$props2.isAbsolutelyPositioned, initialVisibleMonth = _this$props2.initialVisibleMonth, isBorderless = _this$props2.isBorderless, panelButtons = _this$props2.panelButtons, presetPanelOptions = _this$props2.presetPanelOptions, startDateInputId = _this$props2.startDateInputId, startDateInputLabel = _this$props2.startDateInputLabel, startDateInputPlaceholder = _this$props2.startDateInputPlaceholder, startTimeInputId = _this$props2.startTimeInputId, startTimeInputLabel = _this$props2.startTimeInputLabel; var _this$state3 = this.state, dayPickerIsOpen = _this$state3.dayPickerIsOpen, endDateString = _this$state3.endDateString, endTimeString = _this$state3.endTimeString, startDateString = _this$state3.startDateString, startTimeString = _this$state3.startTimeString; var infoPanelPosition = null; if (presetPanelOptions) { infoPanelPosition = "after"; } else if (!presetPanelOptions && panelButtons) { infoPanelPosition = "bottom"; } var computedClassNames = classNames("oui-date-picker", isBorderless && "oui-date-picker--borderless", isAbsolutelyPositioned && "oui-date-picker--absolute"); return React.createElement(OutsideClickHandler, { onOutsideClick: this.onOutsideClick }, React.createElement("div", { className: computedClassNames }, React.createElement("div", { className: "oui-date-picker__input-row" }, React.createElement("div", { className: classNames("push--right", isBorderless && "flex--1") }, React.createElement(Input, { hasClearButton: hasClearButton, id: startDateInputId, isReadOnly: true, label: startDateInputLabel, name: "startDate", onClearButtonClick: function onClearButtonClick() { return _this2.onClearButtonClick("start"); }, onFocus: function onFocus() { return _this2.onFocusChange("startDate"); }, placeholder: startDateInputPlaceholder, testSection: "date-range-picker-start-date-input", type: "text", value: startDateString })), hasTimeInputs && React.createElement("div", { className: classNames("push--right", isBorderless && "flex--1") }, React.createElement(Input, { id: startTimeInputId, isReadOnly: false, label: startTimeInputLabel, name: "startTime", onChange: function onChange(e) { return _this2.onTimeChange(e, "startTime"); }, testSection: "date-range-picker-start-time-input", type: "time", value: startTimeString })), React.createElement("div", { className: classNames(isBorderless && "flex--1", hasTimeInputs && "push--right") }, React.createElement(Input, { hasClearButton: hasClearButton, id: endDateInputId, isReadOnly: true, label: endDateInputLabel, name: "endDate", onClearButtonClick: function onClearButtonClick() { return _this2.onClearButtonClick("end"); }, onFocus: function onFocus() { return _this2.onFocusChange("endDate"); }, placeholder: endDateInputPlaceholder, testSection: "date-range-picker-end-date-input", type: "text", value: endDateString })), hasTimeInputs && React.createElement("div", { className: classNames(isBorderless && "flex--1") }, React.createElement(Input, { id: endTimeInputId, isReadOnly: false, label: endTimeInputLabel, name: "endTime", onChange: function onChange(e) { return _this2.onTimeChange(e, "endTime"); }, testSection: "date-range-picker-end-time-input", type: "time", value: endTimeString }))), dayPickerIsOpen && React.createElement(DayPickerRangeController, { calendarInfoPosition: infoPanelPosition, daySize: constants.DAY_SIZE, endDate: this.state.endDate, focusedInput: this.state.focusedInput, hideKeyboardShortcutsPanel: true, initialVisibleMonth: initialVisibleMonth, isOutsideRange: this.isOutsideAcceptableDateRange, navNext: React.createElement(CalendarNavButton, { type: "next" }), navPrev: React.createElement(CalendarNavButton, { type: "previous" }), numberOfMonths: 2, onClose: this.onClose, onDatesChange: this.onDatesChange, onFocusChange: this.onFocusChange, phrases: constants.PHRASES, renderCalendarInfo: this.renderSidePanel, renderMonthElement: getCustomMonthElement, startDate: this.state.startDate, transitionDuration: constants.TRANSITION_DURATION }))); } }]); return DateRangePicker; }(React.Component); var timeInputValidator = function timeInputValidator(props, propName, componentName) { if (props["hasTimeInputs"] === true) { if (props[propName] === undefined) { return new Error("Prop `".concat(propName, "` is marked as required, but its value is `undefined`.")); } else if (typeof props[propName] !== "string") { return new Error("Invalid prop `".concat(propName, "` of type `").concat(_typeof(props[propName]), "`, expected `string`.")); } } }; DateRangePicker.propTypes = { /** The id of the end date input, used to associate with a label */ endDateInputId: PropTypes.string.isRequired, /** The label of the end date input */ endDateInputLabel: PropTypes.string, /** The placeholder text of the end date input */ endDateInputPlaceholder: PropTypes.string, /** The id of the end time input, used to associate with a label */ endTimeInputId: timeInputValidator, /** The label of the end time input */ endTimeInputLabel: PropTypes.string, /** * Used to indicate the current active input, * aka which date is about to be selected, * or null if neither is active */ focusedInput: PropTypes.oneOf(["startDate", "endDate", null]), /** Whether or not to add a clear button to right of both inputs */ hasClearButton: PropTypes.bool, /** When true, adds time inputs next to date inputs */ hasTimeInputs: PropTypes.bool, /** An initial end date to populate the end date input with, * must be type moment() */ initialEndDate: momentPropTypes.momentObj, /** An initial end date to populate the end date input with, * must be type moment() */ initialStartDate: momentPropTypes.momentObj, /** Function to determine what month the date picker * should show on the left hand side */ initialVisibleMonth: PropTypes.func, /** When true, opens the calendar on top of other elements on the page */ isAbsolutelyPositioned: PropTypes.bool, /** When true, removes the border around the calendar portion */ isBorderless: PropTypes.bool, /** Determines if the user can choose a date in the future */ isFutureDateSelectable: PropTypes.bool, /** * Custom function to determine if a date is outside the acceptable range. * Function is called for each visible day, with a moment() object * passed as the first parameter. */ isOutsideRange: PropTypes.func, /** Determines if the user can choose a date in the past */ isPastDateSelectable: PropTypes.bool, /** * Determines if the calendar remains open no matter * where the user clicks on the page */ keepOpenAlways: PropTypes.bool, /** * Determines if the calendar should stay * open when a date has been selected, * but close when user clicks away */ keepOpenOnDateSelect: PropTypes.bool, /** * Function callback to perform when either a start date * or end date has been selected. * The dates are passed as a parameter object with startDate and endDate * as keys. * For access in panelButtons row items, use render prop function that returns * an array. */ onDatesChange: PropTypes.func, /** * An array of buttons to display with the * calendar, such as Cancel and Apply. If presetPanelOptions * are also defined, the buttons will be to the right of the calendar. * Otherwise, they are directly below the calendar. * Use render prop function that returns an array to get access to component's * onDatesChange method */ panelButtons: PropTypes.oneOfType([PropTypes.array, PropTypes.func]), /** * Array of objects used to render preset buttons to the right * of the calendar. Each object must have a startDate, endDate, * resetTime (optional), and label. */ presetPanelOptions: PropTypes.arrayOf(PropTypes.shape({ endDate: momentPropTypes.momentObj.isRequired, label: PropTypes.string.isRequired, resetTime: PropTypes.bool, startDate: momentPropTypes.momentObj.isRequired })), /** The id of the start date input, used to associate with a label */ startDateInputId: PropTypes.string.isRequired, /** The label of the start date input */ startDateInputLabel: PropTypes.string, /** The placeholder text of the start date input */ startDateInputPlaceholder: PropTypes.string, /** * The id of the start time input, used to associate with a label * Required if hasTimeInputs is true */ startTimeInputId: timeInputValidator, /** The label of the start time input */ startTimeInputLabel: PropTypes.string }; DateRangePicker.defaultProps = { endDateInputLabel: "End Date", endDateInputPlaceholder: "End Date", endTimeInputLabel: "End Time", focusedInput: null, isAbsolutelyPositioned: false, isFutureDateSelectable: true, isPastDateSelectable: true, initialStartDate: null, initialEndDate: null, keepOpenAlways: false, keepOpenOnDateSelect: false, startDateInputLabel: "Start Date", startDateInputPlaceholder: "Start Date", startTimeInputLabel: "Start Time" }; export default DateRangePicker;