optimizely-oui
Version:
Optimizely's Component Library.
577 lines (505 loc) • 23.4 kB
JavaScript
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;