UNPKG

@geneui/components

Version:

The Gene UI components library designed for BI tools

576 lines (566 loc) 23.8 kB
import { _ as _extends } from '../_rollupPluginBabelHelpers-e8fb2e5c.js'; import React__default, { forwardRef, useRef, useState, useImperativeHandle, useEffect, useMemo, useCallback } from 'react'; import PropTypes from 'prop-types'; import { c as classnames } from '../index-031ff73c.js'; import { f as timePickerConfig, s as screenTypes } from '../configs-00612ce0.js'; import { n as noop } from '../index-a0e4e333.js'; import useDeviceType from '../hooks/useDeviceType.js'; import CustomScrollbar from '../Scrollbar/index.js'; import Icon from '../Icon/index.js'; import ExtendedInput from '../ExtendedInput/index.js'; import NumberInput from '../ValidatableNumberInput/index.js'; import '../dateValidation-67caec66.js'; import 'react-dom'; import useKeyDown from '../hooks/useKeyDown.js'; import useClickOutside from '../hooks/useClickOutside.js'; import { P as PopoverV2 } from '../index-08898b29.js'; import { s as styleInject } from '../style-inject.es-746bb8ed.js'; import '../hooks/useWindowSize.js'; import '../hooks/useDebounce.js'; import '../_commonjsHelpers-24198af3.js'; import '../useEllipsisDetection-4d997d5d.js'; import '../SuggestionList/index.js'; import '../config-1053d64d.js'; import '../callAfterDelay-7272faca.js'; import '../index-6d7e99cd.js'; import '../tslib.es6-f211516f.js'; import '../GeneUIProvider/index.js'; import '../hooks/useMount.js'; import '../index-122432cd.js'; import '../debounce-4419bc2f.js'; const TimePickerPopover = /*#__PURE__*/forwardRef((_ref, ref) => { let { children, readOnly, value, positions, ...props } = _ref; const rootRef = useRef(null); const [isOpen, setIsOpen] = useState(false); useImperativeHandle(ref, () => ({ toggleOpen() { let isPopoverOpen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : isOpen; setIsOpen(!isPopoverOpen); } })); const openPopover = () => !readOnly && !isOpen && setIsOpen(true); const closePopover = () => setIsOpen(false); useKeyDown(openPopover, [openPopover], rootRef, ['Enter']); useKeyDown(closePopover, [closePopover], rootRef, ['Tab', 'Escape']); const handleOutsideClick = useClickOutside(event => { !rootRef.current.contains(event.target) && closePopover(); }); useEffect(() => { setIsOpen(false); }, [value]); return /*#__PURE__*/React__default.createElement(PopoverV2, _extends({ behave: "open", scrollbarNeeded: false, contentRef: handleOutsideClick, isOpen: isOpen, position: positions }, props), /*#__PURE__*/React__default.createElement("div", { tabIndex: 1, ref: rootRef, onClick: openPopover }, children)); }); var css_248z = "[data-gene-ui-version=\"2.16.5\"] .time-picker-holder{width:100%}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder.read-only .cursor-pointer{cursor:default}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder.mobile{caret-color:#0000;color:#0000;text-shadow:0 0 0 rgba(var(--background-sc-rgb),.75)}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder.disabled{opacity:.6;pointer-events:none}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder>ul{align-items:center;display:flex;width:100%}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder>ul>li.no-shrink{align-items:center;display:flex;flex-shrink:0;justify-content:center}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder>ul>li.no-shrink>span{display:block;font-weight:600;margin:0 .5rem}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder>ul>li.shrink-auto{flex:auto}[data-gene-ui-version=\"2.16.5\"] .time-picker-holder>ul>li.icon-holder{margin-inline-end:.5rem;width:4rem}[data-gene-ui-version=\"2.16.5\"] .time-picker-drop-holder{display:flex;width:100%}[data-gene-ui-version=\"2.16.5\"] .time-picker-drop-holder>li{width:100%}[data-gene-ui-version=\"2.16.5\"] .time-picker-drop-holder>li+li{border-inline-start:1px solid rgba(var(--background-sc-rgb),.08)}[data-gene-ui-version=\"2.16.5\"] .time-picker-drop{font:600 1.4rem/1.8rem var(--font-family);padding:1rem 0;width:100%}[data-gene-ui-version=\"2.16.5\"] .time-picker-drop ul{width:100%}[data-gene-ui-version=\"2.16.5\"] .time-picker-drop ul li{align-items:center;cursor:pointer;display:flex;height:4rem;justify-content:center;text-align:center;transition:background .4s,color .4s;width:100%}@media (hover:hover){[data-gene-ui-version=\"2.16.5\"] .time-picker-drop ul li:hover{background:rgba(var(--background-sc-rgb),.05)}}[data-gene-ui-version=\"2.16.5\"] .time-picker-drop ul li.active{color:var(--hero)}"; styleInject(css_248z); function generateTimeValues(format, max) { const numbers = []; const formatLength = format.length; for (let i = 0; i <= max; i++) { if (formatLength === 1) { numbers.push(String(i)); } else { numbers.push(i < 10 ? "0".concat(i) : String(i)); } } return numbers; } const checkFormatValidation = (format, value) => format.length === 1 && value.length <= 2 && Number(value[0]) !== 0 || format.length === 2 && value.length === 2; const checkHourRange = (value, format) => value.length <= 2 && (isLongHour(format) && Number(value) < 24 || isShortHour(format) && Number(value) < 12); // seconds and minutes cant be equal or higher then 60 const checkRange = value => value.length <= 2 && Number(value) < 60; const isLongHour = str => str === 'HH' || str === 'H'; const isShortHour = str => str === 'hh' || str === 'h'; function convertToFormat(value, format, notEmpty) { if (!value && notEmpty) { return format.length === 1 ? '0' : '00'; } if (format.length === 2 && value.length === 1) { return "0".concat(value); } if (format.length === 1 && value.length === 2) { return Number(value).toString(); } return value; } function TimePicker(_ref) { let { value, onChange, showSeconds, appearance, hourFormat, minuteFormat, secondFormat, separator, className, disabled, readOnly, screenType, onBlur, positions, ...restProps } = _ref; const { isMobile } = useDeviceType(screenType); const [hour, setHour] = useState(''); const [minute, setMinute] = useState(''); const [second, setSecond] = useState(''); const [inputValue, setInputValue] = useState(''); const [minuteFieldError, setMinuteFieldError] = useState(false); const [secondFieldError, setSecondFieldError] = useState(false); const [hourFieldError, setHourFieldError] = useState(false); // We save every value that selected from popup, because we need to // close current popup when user select value from popup const [hourPopupValue, setHourPopupValue] = useState(null); const [minutePopupValue, setMinutePopupValue] = useState(null); const [secondPopupValue, setSecondPopupValue] = useState(null); const childRef = useRef(); // for replacing special symbols const numberRegExp = useMemo(() => { const numberRegExpString = "[^0-9".concat(separator, "]"); return new RegExp(numberRegExpString, 'g'); }, [separator]); const combinedValue = useCallback(function (hour, minute, second) { let notEmpty = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; if (hour || minute || second) { const formated = [convertToFormat(hour, hourFormat, notEmpty), convertToFormat(minute, minuteFormat, notEmpty), convertToFormat(second, secondFormat, notEmpty)]; return showSeconds ? formated.join(separator) : "".concat(formated[0]).concat(separator).concat(formated[1]); } return ''; }, [showSeconds, separator, hourFormat, minuteFormat, secondFormat]); const checkHasError = useCallback((currentValue, format) => !currentValue || !checkFormatValidation(format, currentValue) || !checkRange(currentValue), []); const checkTimeValidation = useCallback(function (value) { let needToSetState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; const hasError = checkHasError(value || hour, hourFormat); needToSetState && setHourFieldError(hasError); return hasError; }, [checkHasError, hour, hourFormat]); const checkMinuteValidation = useCallback(function (value) { let needToSetState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; const hasError = checkHasError(value || minute, minuteFormat); needToSetState && setMinuteFieldError(hasError); return hasError; }, [checkHasError, minute, minuteFormat]); const checkSecondValidation = useCallback(function (value) { let needToSetState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; const hasError = checkHasError(value || second, secondFormat); needToSetState && showSeconds && setSecondFieldError(hasError); return !showSeconds || hasError; }, [checkHasError, second, secondFormat, showSeconds]); const handleHourPopover = useCallback((e, open) => !open && checkTimeValidation(), [checkTimeValidation]); const handleMinutePopover = useCallback((e, open) => !open && checkMinuteValidation(), [checkMinuteValidation]); const handleSecondPopover = useCallback((e, open) => !open && checkSecondValidation(), [checkSecondValidation]); const handlePopoverToggle = useCallback((e, open) => { if (open) return; const timeError = checkTimeValidation(); const minuteError = checkMinuteValidation(); const secondError = checkSecondValidation(); // need to reset all data when user click outside and any of this fields has error if (timeError || minuteError || secondError) { setInputValue(''); setHour(''); setMinute(''); setSecond(''); } }, [checkTimeValidation, checkMinuteValidation, checkSecondValidation]); const handleChange = useCallback((e, value) => { // replacing event target's default value, because we have more then one input // and value can be changes from popup, too const changedEvent = { ...e, target: { ...e.target, value } }; onChange(changedEvent); }, [onChange]); const handleHourChange = useCallback(e => { const { value } = e.target; const isInRange = checkHourRange(value, hourFormat); if (isInRange) { if (!checkTimeValidation(value, false)) { setHourFieldError(false); } setHour(value); // second argument needs because we call onChange event immediately after setState handleChange(e, combinedValue(value, minute, second)); } }, [hourFormat, checkTimeValidation, handleChange, combinedValue, minute, second]); const handleMinuteChange = useCallback(e => { const { value } = e.target; const isInRange = checkRange(value); if (isInRange) { if (!checkMinuteValidation(value, false)) { setMinuteFieldError(false); } setMinute(value); // second argument needs because we call onChange event immediately after setState handleChange(e, combinedValue(hour, value, second)); } }, [checkMinuteValidation, handleChange, combinedValue, hour, second]); const handleSecondChange = useCallback(e => { const { value } = e.target; const isInRange = checkRange(value); if (isInRange) { if (!checkSecondValidation(value, false)) { setSecondFieldError(false); } setSecond(value); // second argument needs because we call onChange event immediately after setState handleChange(e, combinedValue(hour, minute, value)); } }, [checkSecondValidation, handleChange, combinedValue, hour, minute]); const handleChangeFromPopup = useCallback((e, value, key) => { let time = ''; if (key === 'second') { time = combinedValue(hour, minute, value); setSecondFieldError(false); setSecondPopupValue(value); } else if (key === 'hour') { time = combinedValue(value, minute, second); setHourFieldError(false); setHourPopupValue(value); } else { time = combinedValue(hour, value, second); setMinuteFieldError(false); setMinutePopupValue(value); } const values = time.split(separator); setHour(values[0]); setMinute(values[1]); showSeconds && setSecond(values[2]); setInputValue(time); handleChange(e, time); }, [hour, minute, second, separator, showSeconds, handleChange, combinedValue]); const handleInputChange = e => { const { value } = e.target; // replacing special chars that not allowed const replacedValue = value.replace(numberRegExp, ''); const timeParts = replacedValue.split(separator); const lastChar = replacedValue[replacedValue.length - 1]; const secondLastChar = replacedValue[replacedValue.length - 2]; if (lastChar === separator && lastChar === secondLastChar) return; if (value === '') { setHour(''); setMinute(''); setSecond(''); setInputValue(''); handleChange(e, ''); return; } if (typeof timeParts[showSeconds ? 3 : 2] !== 'undefined') return; if (timeParts.some(item => item.length > 2)) return; let outOfRange = false; timeParts[0] && setHour(timeParts[0]); timeParts[1] && setMinute(timeParts[1]); showSeconds && timeParts[2] && setSecond(timeParts[2]); if (typeof timeParts[0] !== 'undefined') { const isInRange = checkHourRange(timeParts[0], hourFormat); if (isInRange) { setHour(timeParts[0]); } else { outOfRange = true; } } if (typeof timeParts[1] !== 'undefined') { const isInRange = checkRange(timeParts[1]); if (isInRange) { setMinute(timeParts[1]); } else { outOfRange = true; } } if (showSeconds && typeof timeParts[2] !== 'undefined') { const isInRange = checkRange(timeParts[2]); if (isInRange) { setSecond(timeParts[2]); } else { outOfRange = true; } } !checkTimeValidation(timeParts[0], false) && setHourFieldError(false); !checkMinuteValidation(timeParts[1], false) && setMinuteFieldError(false); (!showSeconds || !checkSecondValidation(timeParts[2], false)) && setSecondFieldError(false); if (!outOfRange) { setInputValue(replacedValue); onChange(e); } }; useEffect(() => { if ((disabled || readOnly) && childRef.current) { childRef.current.toggleOpen(true); } }, [disabled, readOnly]); useEffect(() => { if (hour) { const formattedHour = convertToFormat(hour, hourFormat); setHour(formattedHour); setInputValue(combinedValue(formattedHour, minute, second, false)); } }, [hourFormat]); useEffect(() => { if (minute) { const formattedMinute = convertToFormat(minute, minuteFormat); setMinute(formattedMinute); setInputValue(combinedValue(hour, formattedMinute, second, false)); } }, [minuteFormat]); useEffect(() => { if (second) { const formattedSecond = convertToFormat(second, secondFormat); setSecond(formattedSecond); setInputValue(combinedValue(hour, minute, formattedSecond, false)); } }, [secondFormat]); useEffect(() => { if (!inputValue) return; const [splitHour, splitMinute, splitSecond] = inputValue.split(separator); !showSeconds && setSecond(''); showSeconds && (inputValue || hour) && !second && setSecond(convertToFormat(second, secondFormat)); setInputValue(combinedValue(splitHour, splitMinute, splitSecond)); }, [showSeconds]); useEffect(() => { if (value) { const [splitHour, splitMinute, splitSecond] = value.split(separator); splitHour && setHour(splitHour); splitMinute && setMinute(splitMinute); showSeconds && splitSecond && setSecond(splitSecond); setInputValue(value); } }, [value, separator, showSeconds]); const timeDropDown = useMemo(() => function (numbers, active, key) { return /*#__PURE__*/React__default.createElement("div", { className: "time-picker-drop" }, /*#__PURE__*/React__default.createElement(CustomScrollbar, { autoHeight: true, autoHeightMax: 200, size: "small" }, /*#__PURE__*/React__default.createElement("ul", null, numbers.map(i => /*#__PURE__*/React__default.createElement("li", { onClick: e => handleChangeFromPopup(e, i, key), key: i, className: classnames({ active: i === active }) }, /*#__PURE__*/React__default.createElement("span", null, i)))))); }, [handleChangeFromPopup]); const hours = useMemo(() => timeDropDown(generateTimeValues(hourFormat, isLongHour(hourFormat) ? 23 : 11), hour, 'hour'), [timeDropDown, hourFormat, hour]); const minutes = useMemo(() => timeDropDown(generateTimeValues(minuteFormat, 59), minute, 'minute'), [timeDropDown, minuteFormat, minute]); const seconds = useMemo(() => timeDropDown(generateTimeValues(secondFormat, 59), second, 'second'), [timeDropDown, secondFormat, second]); const handleInputBlur = useCallback(event => { const { currentTarget: { value } } = event; let formated = value; if (value) { formated = combinedValue(...value.split(separator)); setInputValue(formated); handleChange(event, formated); } onBlur(formated, event); }, [combinedValue, separator, handleChange, onBlur]); const handleMultiInputBlur = useCallback(event => { if (hour || minute || second) { setHour(convertToFormat(hour, hourFormat, true)); setMinute(convertToFormat(minute, minuteFormat, true)); showSeconds && setSecond(convertToFormat(second, secondFormat, true)); } onBlur("".concat(hour).concat(hour && minute && ':').concat(minute).concat(minute && second && ':').concat(second), event); }, [hour, minute, second, onBlur, hourFormat, minuteFormat, showSeconds, secondFormat]); const signleInputPopoverValue = useMemo(() => showSeconds ? convertToFormat(second, secondFormat, true) : convertToFormat(minute, minuteFormat, true), [second, secondFormat, minute, minuteFormat, showSeconds]); const handleIconClick = () => childRef.current && !readOnly && !disabled && childRef.current.toggleOpen(); return /*#__PURE__*/React__default.createElement("div", { className: classnames('time-picker-holder', className, { 'read-only': readOnly, mobile: isMobile, disabled }) }, appearance === timePickerConfig.appearance[0] ? /*#__PURE__*/React__default.createElement("ul", null, /*#__PURE__*/React__default.createElement("li", { className: "no-shrink icon-holder" }, /*#__PURE__*/React__default.createElement(Icon, { type: "bc-icon-clock" })), /*#__PURE__*/React__default.createElement("li", { className: "shrink-auto" }, /*#__PURE__*/React__default.createElement(TimePickerPopover, { value: hourPopupValue, toggleHandler: handleHourPopover, Content: hours, readOnly: readOnly, positions: positions }, /*#__PURE__*/React__default.createElement(NumberInput, { value: hour, isValid: !hourFieldError, forceAllowValidation: true, onChange: handleHourChange, placeholder: hourFormat, appearance: "minimal", showNumberIcon: false, readOnly: readOnly, writeProtected: isMobile, onBlur: handleMultiInputBlur }))), /*#__PURE__*/React__default.createElement("li", { className: "no-shrink" }, /*#__PURE__*/React__default.createElement("span", null, separator)), /*#__PURE__*/React__default.createElement("li", { className: "shrink-auto" }, /*#__PURE__*/React__default.createElement(TimePickerPopover, { value: minutePopupValue, toggleHandler: handleMinutePopover, Content: minutes, readOnly: readOnly, positions: positions }, /*#__PURE__*/React__default.createElement(NumberInput, { value: minute, isValid: !minuteFieldError, forceAllowValidation: true, onChange: handleMinuteChange, placeholder: minuteFormat, appearance: "minimal", showNumberIcon: false, readOnly: readOnly, writeProtected: isMobile, onBlur: handleMultiInputBlur }))), showSeconds && /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("li", { className: "no-shrink" }, /*#__PURE__*/React__default.createElement("span", null, separator)), /*#__PURE__*/React__default.createElement("li", { className: "shrink-auto" }, /*#__PURE__*/React__default.createElement(TimePickerPopover, { value: secondPopupValue, toggleHandler: handleSecondPopover, Content: seconds, readOnly: readOnly, positions: positions }, /*#__PURE__*/React__default.createElement(NumberInput, { value: second, forceAllowValidation: true, isValid: !secondFieldError, onChange: handleSecondChange, placeholder: secondFormat, appearance: "minimal", showNumberIcon: false, readOnly: readOnly, writeProtected: isMobile, onBlur: handleMultiInputBlur }))))) : /*#__PURE__*/React__default.createElement(TimePickerPopover, { toggleHandler: handlePopoverToggle, readOnly: readOnly, value: signleInputPopoverValue, ref: childRef, Content: /*#__PURE__*/React__default.createElement("ul", { className: "time-picker-drop-holder" }, /*#__PURE__*/React__default.createElement("li", null, hours), /*#__PURE__*/React__default.createElement("li", null, minutes), showSeconds && /*#__PURE__*/React__default.createElement("li", null, seconds)), positions: positions }, /*#__PURE__*/React__default.createElement(ExtendedInput, _extends({ placeholder: "".concat(hourFormat).concat(separator).concat(minuteFormat).concat(showSeconds ? "".concat(separator).concat(secondFormat) : ''), icon: "bc-icon-clock", onChange: handleInputChange, value: inputValue, isValid: !(hourFieldError || minuteFieldError || secondFieldError), itemsDirection: "end", readOnly: readOnly, writeProtected: isMobile, onIconClick: handleIconClick, clickableIcon: true, onBlur: handleInputBlur }, restProps)))); } TimePicker.propTypes = { /** * Define is seconds field will shown or no */ showSeconds: PropTypes.bool, /** * Select view with multiple inputs or with single inputs */ appearance: PropTypes.oneOf(timePickerConfig.appearance), /** * Format for hour field */ hourFormat: PropTypes.oneOf(['HH', 'H', 'hh', 'h']), /** * Format for hour field */ minuteFormat: PropTypes.oneOf(['mm', 'm']), /** * Format for hour field */ secondFormat: PropTypes.oneOf(['ss', 's']), /** * Time field separator */ separator: PropTypes.string, /** * Fires an event when field is changed((event: SyntheticEvent) => void). */ onChange: PropTypes.func, /** * Fires an event when field is blurred((date: Date, event: SyntheticEvent) => void). */ onBlur: PropTypes.func, /** * Additional classname */ className: PropTypes.string, /** * Value for input field */ value: PropTypes.string, /** * disabled for input field */ disabled: PropTypes.bool, /** * Makes Time picker readonly when set to "true" */ readOnly: PropTypes.bool, /** * The switch between mobile and desktop version of Dropdown will be applied automatically, when the prop is not specified. * When the prop is present it must be changed from outside. */ screenType: PropTypes.oneOf(screenTypes), /** * preferred positions by priority ['bottom','top', 'left', 'right'] * default is ['bottom','top', 'left', 'right'] * if you'd like, you can limit the positions ['top', 'left'] */ positions: PropTypes.array }; TimePicker.defaultProps = { appearance: timePickerConfig.appearance[0], showSeconds: true, hourFormat: 'HH', minuteFormat: 'mm', secondFormat: 'ss', separator: ':', onChange: noop, onBlur: noop, disabled: false, readOnly: false }; export { TimePicker as default };