UNPKG

wix-style-react

Version:
279 lines (239 loc) • 9.48 kB
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;