kafka-ui/kafka-ui-react-app/src/components/common/Input/Input.tsx

203 lines
5.2 KiB
TypeScript

import React from 'react';
import { RegisterOptions, useFormContext } from 'react-hook-form';
import SearchIcon from 'components/common/Icons/SearchIcon';
import { ErrorMessage } from '@hookform/error-message';
import * as S from './Input.styled';
import { InputLabel } from './InputLabel.styled';
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement>,
Omit<S.InputProps, 'search'> {
name?: string;
hookFormOptions?: RegisterOptions;
search?: boolean;
positiveOnly?: boolean;
withError?: boolean;
label?: React.ReactNode;
hint?: React.ReactNode;
clearIcon?: React.ReactNode;
// Some may only accept integer, like `Number of Partitions`
// some may accept decimal
integerOnly?: boolean;
}
function inputNumberCheck(
key: string,
positiveOnly: boolean,
integerOnly: boolean,
getValues: (name: string) => string,
componentName: string
) {
let isValid = true;
if (!((key >= '0' && key <= '9') || key === '-' || key === '.')) {
// If not a valid digit char.
isValid = false;
} else {
// If there is any restriction.
if (positiveOnly) {
isValid = !(key === '-');
}
if (isValid && integerOnly) {
isValid = !(key === '.');
}
// Check invalid format
const value = getValues(componentName);
if (isValid && (key === '-' || key === '.')) {
if (!positiveOnly) {
if (key === '-') {
if (value !== '') {
// '-' should not appear anywhere except the start of the string
isValid = false;
}
}
}
if (!integerOnly) {
if (key === '.') {
if (value === '' || value.indexOf('.') !== -1) {
// '.' should not appear at the start of the string or appear twice
isValid = false;
}
}
}
}
}
return isValid;
}
function pasteNumberCheck(
text: string,
positiveOnly: boolean,
integerOnly: boolean
) {
let value: string;
value = text;
let sign = '';
if (!positiveOnly) {
if (value.charAt(0) === '-') {
sign = '-';
}
}
if (integerOnly) {
value = value.replace(/\D/g, '');
} else {
value = value.replace(/[^\d.]/g, '');
if (value.indexOf('.') !== value.lastIndexOf('.')) {
const strs = value.split('.');
value = '';
for (let i = 0; i < strs.length; i += 1) {
value += strs[i];
if (i === 0) {
value += '.';
}
}
}
}
value = sign + value;
return value;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const {
name,
hookFormOptions,
search,
inputSize = 'L',
type,
positiveOnly,
integerOnly,
withError = false,
label,
hint,
clearIcon,
...rest
} = props;
const methods = useFormContext();
const fieldId = React.useId();
const isHookFormField = !!name && !!methods.register;
const keyPressEventHandler = (
event: React.KeyboardEvent<HTMLInputElement>
) => {
const { key } = event;
if (type === 'number') {
// Manually prevent input of non-digit and non-minus for all number inputs
// and prevent input of negative numbers for positiveOnly inputs
if (
!inputNumberCheck(
key,
typeof positiveOnly === 'boolean' ? positiveOnly : false,
typeof integerOnly === 'boolean' ? integerOnly : false,
methods.getValues,
typeof name === 'string' ? name : ''
)
) {
event.preventDefault();
}
}
};
const pasteEventHandler = (event: React.ClipboardEvent<HTMLInputElement>) => {
if (type === 'number') {
const { clipboardData } = event;
// The 'clipboardData' does not have key 'Text', but has key 'text' instead.
const text = clipboardData.getData('text');
// Check the format of pasted text.
const value = pasteNumberCheck(
text,
typeof positiveOnly === 'boolean' ? positiveOnly : false,
typeof integerOnly === 'boolean' ? integerOnly : false
);
// if paste value contains non-numeric characters or
// negative for positiveOnly fields then prevent paste
if (value !== text) {
event.preventDefault();
// for react-hook-form fields only set transformed value
if (isHookFormField) {
methods.setValue(name, value);
}
}
}
};
let inputOptions = { ...rest };
if (isHookFormField) {
// extend input options with react-hook-form options
// if the field is a part of react-hook-form form
inputOptions = { ...rest, ...methods.register(name, hookFormOptions) };
}
return (
<div>
{label && <InputLabel htmlFor={rest.id || fieldId}>{label}</InputLabel>}
<S.Wrapper>
{search && <SearchIcon />}
<S.Input
id={fieldId}
inputSize={inputSize}
search={!!search}
type={type}
onKeyPress={keyPressEventHandler}
onPaste={pasteEventHandler}
ref={ref}
{...inputOptions}
/>
{clearIcon}
{withError && isHookFormField && (
<S.FormError>
<ErrorMessage name={name} />
</S.FormError>
)}
{hint && <S.InputHint>{hint}</S.InputHint>}
</S.Wrapper>
</div>
);
});
export default Input;