@atlaskit/datetime-picker
Version:
A date time picker allows the user to select an associated date and time.
352 lines (349 loc) • 14.9 kB
JavaScript
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";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
var _excluded = ["selectProps"],
_excluded2 = ["styles"];
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; } }
import React from 'react';
// eslint-disable-next-line no-restricted-imports
import { format, isValid } from 'date-fns';
import pick from 'lodash/pick';
import { createAndFireEvent, withAnalyticsContext, withAnalyticsEvents } from '@atlaskit/analytics-next';
import { createLocalizationProvider } from '@atlaskit/locale';
import Select, { components, CreatableSelect, mergeStyles } from '@atlaskit/select';
// eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
import { gridSize } from '@atlaskit/theme/constants';
import { defaultTimeFormat, defaultTimes, EmptyComponent, placeholderDatetime } from '../internal';
import FixedLayer from '../internal/fixed-layer';
import parseTime from '../internal/parse-time';
import { makeSingleValue } from '../internal/single-value';
import { convertTokens } from './utils';
var packageName = "@atlaskit/datetime-picker";
var packageVersion = "13.0.0";
var menuStyles = {
/* Need to remove default absolute positioning as that causes issues with position fixed */
position: 'static',
/* Need to add overflow to the element with max-height, otherwise causes overflow issues in IE11 */
overflowY: 'auto',
/* React-Popper has already offset the menu so we need to reset the margin, otherwise the offset value is doubled */
margin: 0
};
var FixedLayerMenu = function FixedLayerMenu(_ref) {
var selectProps = _ref.selectProps,
rest = _objectWithoutProperties(_ref, _excluded);
return /*#__PURE__*/React.createElement(FixedLayer, {
inputValue: selectProps.inputValue,
containerRef: selectProps.fixedLayerRef,
content: /*#__PURE__*/React.createElement(components.Menu, _extends({}, rest, {
menuShouldScrollIntoView: false
})),
testId: selectProps.testId
});
};
var timePickerDefaultProps = {
appearance: 'default',
autoFocus: false,
defaultIsOpen: false,
defaultValue: '',
hideIcon: false,
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 */
parseInputValue: function parseInputValue(time, _timeFormat) {
return parseTime(time);
},
selectProps: {},
spacing: 'default',
times: defaultTimes,
timeIsEditable: false,
locale: 'en-US'
// Not including a default prop for value as it will
// Make the component a controlled component
};
var TimePicker = /*#__PURE__*/function (_React$Component) {
_inherits(TimePicker, _React$Component);
var _super = _createSuper(TimePicker);
function TimePicker() {
var _this;
_classCallCheck(this, TimePicker);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call.apply(_super, [this].concat(args));
_defineProperty(_assertThisInitialized(_this), "containerRef", null);
_defineProperty(_assertThisInitialized(_this), "state", {
isOpen: _this.props.defaultIsOpen,
clearingFromIcon: false,
value: _this.props.defaultValue,
isFocused: false
});
// 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({}, _this.state), pick(_this.props, ['value', 'isOpen']));
});
_defineProperty(_assertThisInitialized(_this), "onChange", function (newValue, action) {
var rawValue = newValue ? newValue.value || newValue : '';
var value = rawValue.toString();
var changedState = {
value: value
};
if (action && action.action === 'clear') {
changedState = _objectSpread(_objectSpread({}, changedState), {}, {
clearingFromIcon: true
});
}
_this.setState(changedState);
_this.props.onChange(value);
});
/**
* Only allow custom times if timeIsEditable prop is true
*/
_defineProperty(_assertThisInitialized(_this), "onCreateOption", function (inputValue) {
if (_this.props.timeIsEditable) {
var _this$props = _this.props,
parseInputValue = _this$props.parseInputValue,
_timeFormat2 = _this$props.timeFormat;
var sanitizedInput;
try {
sanitizedInput = parseInputValue(inputValue, _timeFormat2 || defaultTimeFormat);
} catch (e) {
return; // do nothing, the main validation should happen in the form
}
var includesSeconds = !!(_timeFormat2 && /[:.]?(s|ss)/.test(_timeFormat2));
var formatFormat = includesSeconds ? 'HH:mm:ss' : 'HH:mm';
var formattedValue = format(sanitizedInput, formatFormat) || '';
_this.setState({
value: formattedValue
});
_this.props.onChange(formattedValue);
} else {
_this.onChange(inputValue);
}
});
_defineProperty(_assertThisInitialized(_this), "onMenuOpen", function () {
// Don't open menu after the user has clicked clear
if (_this.getSafeState().clearingFromIcon) {
_this.setState({
clearingFromIcon: false
});
} else {
_this.setState({
isOpen: true
});
}
});
_defineProperty(_assertThisInitialized(_this), "onMenuClose", function () {
// Don't close menu after the user has clicked clear
if (_this.getSafeState().clearingFromIcon) {
_this.setState({
clearingFromIcon: false
});
} else {
_this.setState({
isOpen: false
});
}
});
_defineProperty(_assertThisInitialized(_this), "setContainerRef", 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();
}
});
_defineProperty(_assertThisInitialized(_this), "onBlur", function (event) {
_this.setState({
isFocused: false
});
_this.props.onBlur(event);
});
_defineProperty(_assertThisInitialized(_this), "onFocus", function (event) {
_this.setState({
isFocused: true
});
_this.props.onFocus(event);
});
_defineProperty(_assertThisInitialized(_this), "onSelectKeyDown", function (event) {
var key = event.key;
var keyPressed = key.toLowerCase();
if (_this.getSafeState().clearingFromIcon && (keyPressed === 'backspace' || keyPressed === 'delete')) {
// If being cleared from keyboard, don't change behaviour
_this.setState({
clearingFromIcon: false
});
}
});
return _this;
}
_createClass(TimePicker, [{
key: "render",
value: function render() {
var _this2 = this;
var _this$props2 = this.props,
appearance = _this$props2.appearance,
autoFocus = _this$props2.autoFocus,
formatDisplayLabel = _this$props2.formatDisplayLabel,
hideIcon = _this$props2.hideIcon,
id = _this$props2.id,
innerProps = _this$props2.innerProps,
isDisabled = _this$props2.isDisabled,
locale = _this$props2.locale,
name = _this$props2.name,
placeholder = _this$props2.placeholder,
selectProps = _this$props2.selectProps,
spacing = _this$props2.spacing,
testId = _this$props2.testId,
isInvalid = _this$props2.isInvalid,
timeIsEditable = _this$props2.timeIsEditable,
timeFormat = _this$props2.timeFormat,
times = _this$props2.times;
var ICON_PADDING = 2;
var l10n = createLocalizationProvider(locale);
var _this$getSafeState = this.getSafeState(),
_this$getSafeState$va = _this$getSafeState.value,
value = _this$getSafeState$va === void 0 ? '' : _this$getSafeState$va,
isOpen = _this$getSafeState.isOpen;
var _selectProps$styles = selectProps.styles,
selectStyles = _selectProps$styles === void 0 ? {} : _selectProps$styles,
otherSelectProps = _objectWithoutProperties(selectProps, _excluded2);
var SelectComponent = timeIsEditable ? CreatableSelect : Select;
/**
* There are multiple props that can change how the time is formatted.
* The priority of props used is:
* 1. formatDisplayLabel
* 2. timeFormat
* 3. locale
*/
var formatTime = function formatTime(time) {
if (formatDisplayLabel) {
return formatDisplayLabel(time, timeFormat || defaultTimeFormat);
}
var date = parseTime(time);
if (!(date instanceof Date)) {
return '';
}
if (!isValid(date)) {
return time;
}
if (timeFormat) {
return format(date, convertTokens(timeFormat));
}
return l10n.formatTime(date);
};
var options = times.map(function (time) {
return {
label: formatTime(time),
value: time
};
});
var labelAndValue = value && {
label: formatTime(value),
value: value
};
var SingleValue = makeSingleValue({
lang: this.props.locale
});
var selectComponents = _objectSpread({
DropdownIndicator: EmptyComponent,
Menu: FixedLayerMenu,
SingleValue: SingleValue
}, hideIcon && {
ClearIndicator: EmptyComponent
});
var renderIconContainer = Boolean(!hideIcon && value);
var mergedStyles = mergeStyles(selectStyles, {
control: function control(base) {
return _objectSpread({}, base);
},
menu: function menu(base) {
return _objectSpread(_objectSpread(_objectSpread({}, base), menuStyles), {}, {
// Fixed positioned elements no longer inherit width from their parent, so we must explicitly set the
// menu width to the width of our container
width: _this2.containerRef ? _this2.containerRef.getBoundingClientRect().width : 'auto'
});
},
indicatorsContainer: function indicatorsContainer(base) {
return _objectSpread(_objectSpread({}, base), {}, {
paddingLeft: renderIconContainer ? ICON_PADDING : 0,
paddingRight: renderIconContainer ? gridSize() - ICON_PADDING : 0
});
}
});
return /*#__PURE__*/React.createElement("div", _extends({}, innerProps, {
ref: this.setContainerRef,
"data-testid": testId && "".concat(testId, "--container")
}), /*#__PURE__*/React.createElement("input", {
name: name,
type: "hidden",
value: value,
"data-testid": testId && "".concat(testId, "--input"),
onKeyDown: this.onSelectKeyDown
}), /*#__PURE__*/React.createElement(SelectComponent, _extends({
appearance: appearance,
autoFocus: autoFocus,
components: selectComponents,
instanceId: id,
isClearable: true,
isDisabled: isDisabled,
menuIsOpen: isOpen && !isDisabled,
menuPlacement: "auto",
openMenuOnFocus: true,
onBlur: this.onBlur,
onCreateOption: this.onCreateOption,
onChange: this.onChange,
options: options,
onFocus: this.onFocus,
onMenuOpen: this.onMenuOpen,
onMenuClose: this.onMenuClose,
placeholder: placeholder || l10n.formatTime(placeholderDatetime),
styles: mergedStyles,
value: labelAndValue,
spacing: spacing
// @ts-ignore caused by prop not part of @atlaskit/select
,
fixedLayerRef: this.containerRef,
isInvalid: isInvalid,
testId: testId
}, otherSelectProps)));
}
}]);
return TimePicker;
}(React.Component);
_defineProperty(TimePicker, "defaultProps", timePickerDefaultProps);
export { TimePicker as TimePickerWithoutAnalytics };
export default withAnalyticsContext({
componentName: 'timePicker',
packageName: packageName,
packageVersion: packageVersion
})(withAnalyticsEvents({
onChange: createAndFireEvent('atlaskit')({
action: 'selectedTime',
actionSubject: 'timePicker',
attributes: {
componentName: 'timePicker',
packageName: packageName,
packageVersion: packageVersion
}
})
})(TimePicker));