wix-style-react
Version:
279 lines (239 loc) • 9.48 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
var _excluded = ["dataHook", "className", "size", "suffix", "prefix", "status", "statusMessage", "border", "disabled", "placeholder", "readOnly", "width", "value", "timeStyle", "onChange", "step", "noRightBorderRadius", "noLeftBorderRadius"];
import React, { useContext, useMemo, useCallback, useRef, useImperativeHandle, useEffect } from 'react';
import PropTypes from 'prop-types';
import Input from '../Input';
import DropdownBase from '../DropdownBase';
import { dataHooks } from './constants';
import { st, classes } from './TimeInputNext.st.css';
import { supportedWixlocales } from 'wix-design-systems-locale-utils';
import { WixStyleReactEnvironmentContext } from '../WixStyleReactEnvironmentProvider/context';
import { getTimeSlots, getTimeSlot, getClosestTimeSlot, getSuggestedOption, isInputValid, getErorMessageByLocale } from './TimeInputNextUtils';
export var DEFAULT_STEP = 15;
export var DEFAULT_TIME_STYLE = 'short';
var TimeInputNext = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
var dataHook = _ref.dataHook,
className = _ref.className,
size = _ref.size,
suffix = _ref.suffix,
prefix = _ref.prefix,
status = _ref.status,
statusMessage = _ref.statusMessage,
border = _ref.border,
disabled = _ref.disabled,
placeholder = _ref.placeholder,
readOnly = _ref.readOnly,
width = _ref.width,
value = _ref.value,
timeStyle = _ref.timeStyle,
onChange = _ref.onChange,
step = _ref.step,
noRightBorderRadius = _ref.noRightBorderRadius,
noLeftBorderRadius = _ref.noLeftBorderRadius,
rest = _objectWithoutProperties(_ref, _excluded);
var inputRef = useRef();
useImperativeHandle(ref, function () {
return {
focus: function focus() {
return inputRef.current.focus();
}
};
});
var context = useContext(WixStyleReactEnvironmentContext);
var locale = rest.locale || context.locale || 'en';
var timeSlots = useMemo(function () {
return getTimeSlots({
value: value,
timeStyle: timeStyle,
locale: locale,
step: step
});
}, [value, timeStyle, locale, step]);
var _React$useState = React.useState(false),
_React$useState2 = _slicedToArray(_React$useState, 2),
isDropdownOpen = _React$useState2[0],
setIsDropdownOpen = _React$useState2[1];
var _React$useState3 = React.useState(''),
_React$useState4 = _slicedToArray(_React$useState3, 2),
inputValue = _React$useState4[0],
setInputValue = _React$useState4[1];
var _React$useState5 = React.useState(null),
_React$useState6 = _slicedToArray(_React$useState5, 2),
selectedId = _React$useState6[0],
setSelectedId = _React$useState6[1];
var _React$useState7 = React.useState(null),
_React$useState8 = _slicedToArray(_React$useState7, 2),
highlightedOptionId = _React$useState8[0],
setHighlightedOptionId = _React$useState8[1];
var _React$useState9 = React.useState(false),
_React$useState10 = _slicedToArray(_React$useState9, 2),
error = _React$useState10[0],
setError = _React$useState10[1];
var validationError = error ? 'error' : undefined;
var validationErrorMessage = error ? getErorMessageByLocale(locale) : undefined;
useEffect(function () {
var timeSlot = value ? getTimeSlot({
value: value,
timeStyle: timeStyle,
locale: locale
}) : getClosestTimeSlot({
value: value,
timeSlots: timeSlots
});
setInputValue(timeSlot.value);
setSelectedId(timeSlot.id);
}, [value, timeStyle, locale, timeSlots]);
var onSelect = useCallback(function (option) {
setInputValue(option.value);
setSelectedId(option.id);
setIsDropdownOpen(false);
onChange && onChange({
date: new Date(option.id)
});
}, [onChange]);
var _onKeyDown = useCallback(function (e, delegateKeyDown) {
if (e.key === ' ' || e.key === 'Spacebar') {
return;
}
if (!isDropdownOpen && e.key === 'ArrowDown') {
setIsDropdownOpen(true);
return e.preventDefault();
}
if (isDropdownOpen) {
delegateKeyDown(e);
if (e.key === 'Escape') {
setIsDropdownOpen(false);
return e.preventDefault();
} // should be handled by error validation
if (!highlightedOptionId && e.key === 'Enter') {
setIsDropdownOpen(false);
return e.preventDefault();
}
}
}, [isDropdownOpen, highlightedOptionId]);
var onInputChanged = function onInputChanged(e) {
setInputValue(e.target.value); // validate input
if (isInputValid(e.target.value)) {
setError(false);
} else {
setError(true);
setIsDropdownOpen(false);
} // open dropdown when value is selected with keyboard and value is typed again
if (!isDropdownOpen) {
setIsDropdownOpen(true);
} // stop selecting option when input value changes and doesn't match anymore
if (e.target.value !== timeSlots[selectedId]) {
setSelectedId(null);
}
var suggestedOption = getSuggestedOption(e.target.value, timeSlots);
setHighlightedOptionId(suggestedOption ? suggestedOption.id : null);
};
var onBlur = function onBlur() {
var highlightedOption = timeSlots.find(function (slot) {
return slot.id === highlightedOptionId;
});
if (highlightedOption) {
setInputValue(highlightedOption.value);
setSelectedId(highlightedOption.id);
}
if (isDropdownOpen) {
setIsDropdownOpen(false);
}
};
return /*#__PURE__*/React.createElement("div", {
className: st(classes.root, className),
style: {
width: width
},
"data-hook": dataHook,
"data-value": selectedId,
"data-locale": locale,
"data-time-style": timeStyle
}, /*#__PURE__*/React.createElement(DropdownBase, {
dataHook: dataHooks.TimeInputNextDropdown,
open: isDropdownOpen,
onClickOutside: function onClickOutside() {
return setIsDropdownOpen(false);
},
options: timeSlots,
onSelect: onSelect,
selectedId: selectedId,
maxHeight: "216px",
markedOptionId: highlightedOptionId,
focusOnOption: highlightedOptionId,
focusOnSelectedOption: true,
onMouseDown: function onMouseDown(e) {
return e.preventDefault();
}
}, function (_ref2) {
var delegateKeyDown = _ref2.delegateKeyDown;
return /*#__PURE__*/React.createElement(Input, {
dataHook: dataHooks.TimeInputNextInput,
size: size,
status: status || validationError,
statusMessage: statusMessage || validationErrorMessage,
suffix: suffix && /*#__PURE__*/React.createElement(Input.Affix, null, suffix),
prefix: prefix && /*#__PURE__*/React.createElement(Input.Affix, null, prefix),
border: border,
disabled: disabled,
placeholder: placeholder,
value: inputValue,
onChange: onInputChanged,
onInputClicked: function onInputClicked() {
return !readOnly && setIsDropdownOpen(true);
},
onKeyDown: function onKeyDown(e) {
return _onKeyDown(e, delegateKeyDown);
},
ref: inputRef,
readOnly: readOnly,
noLeftBorderRadius: noLeftBorderRadius,
noRightBorderRadius: noRightBorderRadius,
onBlur: onBlur
});
}));
});
TimeInputNext.displayName = 'TimeInputNext';
TimeInputNext.propTypes = {
/** Control the border style of input */
border: PropTypes.oneOf(['standard', 'round', 'bottomLine']),
/** Specifies a CSS class name to be appended to the component’s root element */
className: PropTypes.string,
/** Applies a data-hook HTML attribute that can be used in the tests */
dataHook: PropTypes.string,
/** Specifies whether component is disabled */
disabled: PropTypes.bool,
/** Sets locale and formats time according to it */
locale: PropTypes.oneOf(supportedWixlocales),
/** Defines a callback function which is called on every time value changes */
onChange: PropTypes.func,
/** Sets a placeholder message to display */
placeholder: PropTypes.string,
/** Pass a component you want to show as the prefix of the input, e.g., text, icon */
prefix: PropTypes.node,
/** Specifies whether input is read only */
readOnly: PropTypes.bool,
/** Controls the size of the input */
size: PropTypes.oneOf(['small', 'medium', 'large']),
/** Specify the status of a field */
status: PropTypes.oneOf(['error', 'warning', 'loading']),
/** Defines the message to display on status icon hover. If not given or empty there will be no tooltip. */
statusMessage: PropTypes.node,
/** Specifies the interval between time values shown in dropdown */
step: PropTypes.number,
/** Pass a component you want to show as the suffix of the input, e.g., text, icon */
suffix: PropTypes.node,
/** Specifies what time formatting style to use when calling `format()` */
timeStyle: PropTypes.string,
/** Specifies the current value of the input */
value: PropTypes.object,
/** Controls the width of the component. `auto` will resize the input to match width of its contents, while `100%` will take up the full parent container width. */
width: PropTypes.oneOf(['auto', '100%'])
};
TimeInputNext.defaultProps = {
width: 'auto',
step: DEFAULT_STEP,
timeStyle: DEFAULT_TIME_STYLE
};
export default TimeInputNext;