UNPKG

@hhgtech/hhg-components

Version:
408 lines (392 loc) • 15.8 kB
import { _ as __rest } from './tslib.es6-ea4dfe68.js'; import React__default, { forwardRef, useState, useRef, useMemo, useCallback, useEffect, useImperativeHandle } from 'react'; import cn from 'classnames'; import dayjs from 'dayjs'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { theme } from './miscTheme.js'; import { I as ISO_FORMAT } from './index-5e947517.js'; import { L as LOCALE } from './Locale-f270bd9d.js'; const KEYCODE = { BACKSPACE: 8, SPACEBAR: 32, ZERO: 48, }; const PATTERN_NUMBER = /^([0-9]*)$/; const INPUT_DATE_PLACEHOLDER = { DD: 'Day', MM: 'Month', YYYY: 'Year', }; const INPUT_DATE_FORMAT_TYPING = { DD: (day, cb) => { if (!day) { cb === null || cb === void 0 ? void 0 : cb(''); return ''; } const dayNumber = parseInt(day); if (dayNumber > 31) { cb === null || cb === void 0 ? void 0 : cb(day[0]); return day[0]; } /** Day cannot be over number 31 */ if (dayNumber >= 4) { const dayString = String(dayNumber).padStart(2, '0'); cb === null || cb === void 0 ? void 0 : cb(dayString); return dayString; } cb === null || cb === void 0 ? void 0 : cb(day); return day; }, MM: (month, cb) => { if (!month) { cb === null || cb === void 0 ? void 0 : cb(''); return ''; } const monthNumber = parseInt(month); if (monthNumber > 12) { cb === null || cb === void 0 ? void 0 : cb(month[0]); return month[0]; } /** Month cannot be over number 12 */ if (monthNumber >= 2) { const monthString = String(monthNumber).padStart(2, '0'); cb === null || cb === void 0 ? void 0 : cb(monthString); return monthString; } cb === null || cb === void 0 ? void 0 : cb(month); return month; }, YYYY: (year, cb) => { if (!year) { cb === null || cb === void 0 ? void 0 : cb(''); return ''; } cb === null || cb === void 0 ? void 0 : cb(year); return year; }, }; const INPUT_DATE_DEFAULT_DISABLED = [false, true, true]; const getInputDateInputDisabled = (valueString) => { return valueString.map((_, index) => { if (index === 0) { return false; } if (index !== 0 && !!valueString[index - 1]) { return false; } return true; }); }; const cssInputNotShrink = css ` max-width: 100%; color: ${theme.colors.gray800}; font-size: var(--input-font-size, 16px); font-weight: 400; line-height: calc(var(--input-height, 56px) * 24 / 56); opacity: 0; transition: 0.3s linear opacity; vertical-align: middle; &::placeholder { color: transparent; font-weight: 400; } `; const cssInputShrink = css ` opacity: 1; line-height: calc(var(--input-height, 56px) * 24 / 56); transition: 0.3 linear opacity; &::placeholder { color: ${theme.colors.gray600}; } `; const cssPlaceholderNotShrink = css ` position: absolute; top: calc(var(--input-height, 56px) / 2); left: var(--input-padding, 16px); color: ${theme.colors.gray600}; transform: translateY(-50%); transition: 0.125s linear all; vertical-align: middle; `; const cssPlaceholderShrink = css ` top: calc(var(--input-height, 56px) * 7 / 56); font-size: calc(var(--input-font-size, 16px) * 12 / 16); line-height: calc(var(--input-height, 56px) * 18 / 56); transform: translateY(0); transition: 0.125s linear all; `; const StyledInputDate = styled.div ` --input-gap: 8px; --input-height: 56px; --input-max-width: 320px; --input-font-size: 16px; --input-line-height: 24px; --input-padding-x: 16px; --input-padding-y: 16px; --input-border-radius: 8px; --input-border-color: ${theme.colors.gray200}; display: flex; gap: var(--input-gap, 8px); max-width: var(--input-max-width, 320px); font-weight: 400; * { box-sizing: border-box; } .input_date-input_container { position: relative; display: flex; align-items: center; width: 100%; height: var(--input-height, 56px); max-width: calc((100% / 3) - (var(--input-gap, 8px) * 2 / 3)); line-height: var(--input-line-height, 24px); padding: var(--input-padding-y, 16px) var(--input-padding-x, 16px); border-radius: var(--input-border-radius, 8px); border: 1px solid var(--input-border-color, ${theme.colors.gray200}); .input_date-input_placeholder { ${cssPlaceholderNotShrink} } .input_date-input { ${cssInputNotShrink} } &:has([disabled]) { pointer-events: none; } &:focus-within, &.input_date-input_shrink { --input-padding-y: 8px; align-items: flex-end; .input_date-input_placeholder { ${cssPlaceholderShrink} } .input_date-input { ${cssInputShrink} } } &.input_date-input_active { border-color: ${theme.colors.primary600}; box-shadow: 0px 0px 5px ${theme.colors.primary400}; transition: 0.25s linear all; } &.input_date-input_error { border-color: ${theme.colors.red600}; } } `; // eslint-disable-next-line @typescript-eslint/no-var-requires const customParseFormat = require('dayjs/plugin/customParseFormat'); dayjs.extend(customParseFormat); const { BACKSPACE, SPACEBAR, ZERO } = KEYCODE; const formatInputDateValue = (value, format) => { if (typeof value === 'undefined') { return null; } if (typeof value === 'string') { const dayInJS = dayjs(value, format, true); if (dayInJS.isValid()) { return dayInJS.toDate(); } return null; } const dayInJS = dayjs(value); if (dayInJS.isValid()) { return dayInJS.toDate(); } return null; }; const formatInputDateValueString = (value, format) => { if (!value) { return [undefined, undefined, undefined]; } const dayInJS = dayjs(value); return dayInJS.format(format).split('/'); }; const InputDate = forwardRef((props, ref) => { const { id, name, locale = LOCALE.Vietnam, className, style, defaultValue: defaultValueProps, value: valueProps, onChange: onChangeProps, error, disabled: disabledProps, labelTuples, placeholderTuples, onFocus, onBlur, autoComplete = 'off' } = props, restProps = __rest(props, ["id", "name", "locale", "className", "style", "defaultValue", "value", "onChange", "error", "disabled", "labelTuples", "placeholderTuples", "onFocus", "onBlur", "autoComplete"]); const [activeInput, setActiveInput] = useState(-1); const inputRef = useRef([]); const [value, setValue] = useState(formatInputDateValue(defaultValueProps || valueProps, ISO_FORMAT[locale].dateFormat)); const [valueString, setValueString] = useState(['', '', '']); const [disabled, setDisabled] = useState(() => getInputDateInputDisabled(formatInputDateValueString(defaultValueProps || valueProps, ISO_FORMAT[locale].dateFormat))); const { dateFormat } = useMemo(() => { return (ISO_FORMAT === null || ISO_FORMAT === void 0 ? void 0 : ISO_FORMAT[locale]) || ISO_FORMAT['vi-VN']; }, [locale]); const orders = useMemo(() => dateFormat.split('/'), [dateFormat]); const defaultValue = useMemo(() => formatInputDateValueString(defaultValueProps, dateFormat), [defaultValueProps, dateFormat]); const label = useMemo(() => orders.map((keyOrd, idx) => (labelTuples === null || labelTuples === void 0 ? void 0 : labelTuples[idx]) || INPUT_DATE_PLACEHOLDER[keyOrd]), [labelTuples]); const placeholder = useMemo(() => orders.map((keyOrd, idx) => (placeholderTuples === null || placeholderTuples === void 0 ? void 0 : placeholderTuples[idx]) || keyOrd), [placeholderTuples]); const focusPrevInput = useCallback(() => { var _a, _b; if (activeInput === 0 || activeInput === -1) { return; } const curInput = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a[activeInput]; if (curInput) { curInput.blur(); } const prevInput = (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b[activeInput - 1]; if (prevInput) { prevInput.focus(); } }, [activeInput]); const focusNextInput = useCallback(() => { var _a, _b; if (activeInput === orders.length - 1) { return; } const curInput = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a[activeInput]; if (curInput) { curInput.blur(); } const nextInput = (_b = inputRef.current) === null || _b === void 0 ? void 0 : _b[activeInput + 1]; if (nextInput) { nextInput.focus(); } }, [activeInput, orders]); const changeCodeAtFocus = useCallback(() => { let count = 0; function changeFocus() { const target = inputRef.current[activeInput]; const nextTarget = inputRef.current[activeInput + 1]; const disabledNext = disabled[activeInput + 1]; if (!nextTarget || count === 1) { return; } if (disabledNext) { requestAnimationFrame(changeFocus); count++; } if (target.value.length === target.maxLength) { focusNextInput(); } } requestAnimationFrame(changeFocus); }, [...disabled, activeInput, focusNextInput]); const onChange = useCallback((valStr) => { const valString = valStr || valueString; const dayString = orders .map((_, index) => { const val = valString[index]; return val; }) .filter(Boolean) .join('/'); const dayInJS = dayjs(dayString, dateFormat, true); if (dayInJS.isValid()) { setValue(dayInJS.toDate()); onChangeProps === null || onChangeProps === void 0 ? void 0 : onChangeProps(dayInJS.toDate(), dayString); } else { setValue(null); onChangeProps === null || onChangeProps === void 0 ? void 0 : onChangeProps(dayString, dayString); } }, [orders, dateFormat, ...valueString]); const inputOnChange = useCallback((index) => ((e) => { const val = e.target.value; const valString = [...valueString]; if (!val) { valString[index] = ''; setValueString(valString); onChange(valString); if (index === 0) { setDisabled(INPUT_DATE_DEFAULT_DISABLED); } return; } if (!val.trim().match(PATTERN_NUMBER)) { return; } const inputType = orders[index]; INPUT_DATE_FORMAT_TYPING[inputType](val, (newVal) => { valString[index] = newVal; setDisabled(getInputDateInputDisabled(valString)); setValueString(valString); changeCodeAtFocus(); }); onChange(valString); }), [...valueString, orders, changeCodeAtFocus, onChange]); const inputOnFocus = useCallback((e) => { const target = e.target; const inputIndex = inputRef.current.findIndex((input) => input === target); if (inputIndex !== -1) { setActiveInput(inputIndex); } if (inputIndex === 0) { onFocus === null || onFocus === void 0 ? void 0 : onFocus(e); } }, [...valueString, orders, onFocus]); const inputOnBlur = useCallback((e) => { setActiveInput(-1); const target = e.target; const inputIndex = inputRef.current.findIndex((input) => input === target); if (inputIndex !== -1) { const newVal = target.value; const valString = [...valueString]; if (newVal !== '') { valString[inputIndex] = String(newVal).padStart(target.maxLength, '0'); setDisabled(getInputDateInputDisabled(valString)); setValueString(valString); onChange(valString); } } onBlur === null || onBlur === void 0 ? void 0 : onBlur(e); }, [...valueString, orders, onChange, onBlur]); // Handle cases of backspace, delete, left arrow, right arrow, space const inputOnKeyDown = useCallback((e) => { const target = inputRef.current[activeInput]; if (target.value === '0' && (e.keyCode === ZERO || e.key === '0')) { e.preventDefault(); } if (e.keyCode === BACKSPACE || e.key === 'Backspace') { if (!target.value) { focusPrevInput(); } } if (e.keyCode === SPACEBAR || e.key === ' ' || e.key === 'Spacebar' || e.key === 'Space') { e.preventDefault(); } }, [activeInput, focusPrevInput, focusNextInput]); const onClick = useCallback((e) => { const target = e.target; const targetIndex = inputRef.current.findIndex((input) => input === target); if (targetIndex === -1) { const focusIndex = !!value ? inputRef.current.length - 1 : 0; inputRef.current[focusIndex].focus(); setActiveInput(focusIndex); return; } inputRef.current[targetIndex].focus(); setActiveInput(targetIndex); }, [inputRef.current, defaultValue, value]); useEffect(() => { setValue(formatInputDateValue(valueProps, dateFormat)); }, [valueProps, dateFormat]); useEffect(() => { if (value) { const valueStringUpdated = formatInputDateValueString(value, dateFormat); setValueString(valueStringUpdated); setDisabled(getInputDateInputDisabled(valueStringUpdated)); } }, [value, dateFormat]); useImperativeHandle(ref, () => inputRef.current[0], []); return (React__default.createElement(StyledInputDate, Object.assign({ className: className, style: style, onClick: onClick }, restProps), orders.map((inputName, idxOrd) => { const inputLabel = label[idxOrd]; const inputDefaultValue = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue[idxOrd]; const inputValue = valueString[idxOrd]; const inputDisabled = disabled[idxOrd]; const inputPlaceholder = placeholder[idxOrd]; const inputMaxLength = inputName.length; return (React__default.createElement("div", { key: inputName, className: cn('input_date-input_container', { 'input_date-input_active': activeInput === idxOrd, 'input_date-input_shrink': !!inputValue, 'input_date-input_error': error, }) }, React__default.createElement("span", { className: "input_date-input_placeholder" }, inputLabel), React__default.createElement("input", { ref: (ref) => (inputRef.current[idxOrd] = ref), name: `${id || ''}-${name || ''}-${inputName}`, type: "text", inputMode: "numeric", defaultValue: inputDefaultValue, value: inputValue, maxLength: inputMaxLength, disabled: disabledProps || inputDisabled, placeholder: inputPlaceholder, onChange: inputOnChange(idxOrd), onBlur: inputOnBlur, onFocus: inputOnFocus, onKeyDown: inputOnKeyDown, className: "input_date-input", autoComplete: autoComplete }))); }))); }); export { InputDate as I };