Skip to content

Commit

Permalink
fix: support textfield repeats
Browse files Browse the repository at this point in the history
  • Loading branch information
SKairinos committed Jun 20, 2023
1 parent f274997 commit 0e31951
Showing 1 changed file with 145 additions and 85 deletions.
230 changes: 145 additions & 85 deletions src/components/form/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,54 @@ import {
import { wrap } from '../../helpers';
import ClickableTooltip from '../ClickableTooltip';

export type TextFieldProps = Omit<MuiTextFieldProps, 'defaultValue'> & {
validate?: FieldValidator | StringSchema;
type StringArraySchema = ArraySchema<Array<string | undefined> | undefined, AnyObject, '', ''>;
type Validate = FieldValidator | StringSchema | StringArraySchema;
type Split = string | RegExp;

type BaseTextFieldProps = Omit<MuiTextFieldProps, 'defaultValue'> & {
name: string;
};

type StringArraySchema = ArraySchema<Array<string | undefined> | undefined, AnyObject, '', ''>;
type RepeatTextFieldProps = BaseTextFieldProps & {
repeat?: Array<Omit<BaseTextFieldProps, (
'required' |
'split'
)>>;
};

interface ITextField {
(
props: TextFieldProps,
context?: any
): React.ReactElement<any, any> | null;
export type TextFieldProps<
SingleValue extends boolean = true
> = RepeatTextFieldProps & (
SingleValue extends true
? { validate?: FieldValidator | StringSchema; }
: {
validate?: FieldValidator | StringArraySchema;
split: Split;
}
);

interface ITextField<SingleValue extends boolean> {
// eslint-disable-next-line @typescript-eslint/prefer-function-type
(
props: Omit<TextFieldProps, 'validate'> & {
validate?: FieldValidator | StringArraySchema;
split: string | RegExp;
},
props: TextFieldProps<SingleValue>,
context?: any
): React.ReactElement<any, any> | null;
}

const TextField: ITextField = ({
const TextField: ITextField<true> & ITextField<false> = ({
validate,
split,
required = false,
name,
type = 'text',
InputProps = {},
onKeyUp,
onBlur,
repeat = [],
...otherTextFieldProps
}: Omit<TextFieldProps, 'validate'> & {
validate?: FieldValidator | StringSchema | StringArraySchema;
split?: string | RegExp
}: RepeatTextFieldProps & {
validate?: Validate;
split?: Split;
}) => {
const [validateRepeat, setValidateRepeat] = React.useState(YupString());

if (validate === undefined) {
validate = (split === undefined)
? YupString()
Expand All @@ -71,78 +84,125 @@ const TextField: ITextField = ({
validate = validate.required();
}

const fieldConfig: FieldConfig = {
// Internal TextField.
const TextField: React.FC<BaseTextFieldProps & {
validate: Validate;
split?: Split;
}> = ({
validate,
split,
name,
type,
validate: async (value) => {
if (validate instanceof Schema) {
try {
validate.validateSync(value);
} catch (error) {
if (error instanceof ValidationError) {
return error.errors[0];
type = 'text',
InputProps = {},
onKeyUp,
onBlur
}) => {
const fieldConfig: FieldConfig = {
name,
type,
validate: async (value) => {
if (validate instanceof Schema) {
try {
validate.validateSync(value);
} catch (error) {
if (error instanceof ValidationError) {
return error.errors[0];
}
throw error;
}
} else if (validate !== undefined) {
return await validate(value);
}
throw error;
}
} else if (validate !== undefined) {
return await validate(value);
}
}
};

return (
<Field {...fieldConfig}>
{({ meta, form }: FieldProps) => {
const [showError, setShowError] = React.useState(false);

let {
endAdornment,
...otherInputProps
} = InputProps;

if (showError &&
meta.error !== undefined &&
meta.error !== ''
) {
endAdornment = <>
{endAdornment}
<InputAdornment position='end'>
<ClickableTooltip title={meta.error}>
<ErrorOutlineIcon color='error' />
</ClickableTooltip>
</InputAdornment>
</>;
}
};

onKeyUp = wrap({
after: (event: React.KeyboardEvent<HTMLDivElement>) => {
let value: string | string[] = (event.target as HTMLTextAreaElement).value;
if (split !== undefined) value = value.split(split);
form.setFieldValue(name, value, true);
}
}, onKeyUp);

onBlur = wrap({
after: () => { setShowError(true); }
}, onBlur);

return (
<MuiTextField
defaultValue={meta.initialValue}
name={name}
type={type}
onKeyUp={onKeyUp}
onBlur={onBlur}
InputProps={{
return (
<Field {...fieldConfig}>
{({ meta, form }: FieldProps) => {
const [showError, setShowError] = React.useState(false);

let {
endAdornment,
...otherInputProps
}}
{...otherTextFieldProps}
/>
);
}}
</Field>
);
} = InputProps;

if (showError &&
meta.error !== undefined &&
meta.error !== ''
) {
endAdornment = <>
{endAdornment}
<InputAdornment position='end'>
<ClickableTooltip title={meta.error}>
<ErrorOutlineIcon color='error' />
</ClickableTooltip>
</InputAdornment>
</>;
}

onKeyUp = wrap({
after: (event: React.KeyboardEvent<HTMLDivElement>) => {
let value: string | string[] = (event.target as HTMLTextAreaElement).value;
if (split !== undefined) value = value.split(split);
form.setFieldValue(name, value, true);
}
}, onKeyUp);

onBlur = wrap({
after: () => { setShowError(true); }
}, onBlur);

return (
<MuiTextField
defaultValue={meta.initialValue}
name={name}
type={type}
onKeyUp={onKeyUp}
onBlur={onBlur}
InputProps={{
endAdornment,
...otherInputProps
}}
{...otherTextFieldProps}
/>
);
}}
</Field>
);
};

if (repeat.length > 0) {
onKeyUp = wrap({
after: (event: React.KeyboardEvent<HTMLDivElement>) => {
setValidateRepeat(YupString().test(
`matches-${name}`,
`doesn't match ${name}`,
(repeatValue) => {
const value = (event.target as HTMLTextAreaElement).value;
return value === repeatValue;
}
));
}
}, onKeyUp);
}

return <>
<TextField
validate={validate}
split={split}
name={name}
onKeyUp={onKeyUp}
{...otherTextFieldProps}
/>
{repeat.map(textFieldProps =>
<TextField
key={textFieldProps.name}
validate={validateRepeat}
{...otherTextFieldProps}
{...textFieldProps}
/>
)}
</>;
};

export default TextField;

0 comments on commit 0e31951

Please sign in to comment.