@geneui/components
Version:
The Gene UI components library designed for BI tools
576 lines (566 loc) • 23.8 kB
JavaScript
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 };