Skip to content

Commit 5b1a638

Browse files
committed
improove input component
1 parent a535060 commit 5b1a638

3 files changed

Lines changed: 50 additions & 23 deletions

File tree

source/components/Form/Form.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React, { useMemo, useState } from "react";
22
import { Box, Text, useFocus, useFocusManager, useInput } from "ink";
3-
import { Field } from "./interfaces.js";
3+
import { Field, Validate } from "./interfaces.js";
44
import { BooleanInput } from "../BooleanInput.js";
55
import { MultiSelectInput } from "../MultiSelectInput.js";
66
import { Button } from "../Button.js";
77
import { InputWithLabel } from "../InputWithLabel.js";
88
import { useArrowFocus } from "../../hooks/useArrowFocus.js";
99
import figlet from "figlet";
10+
import { IntNumbersRegExp, NumberRegExp } from "../../utils/regexps.js";
1011

1112
interface Props {
1213
title?: string;
@@ -44,7 +45,7 @@ export const Form: React.FC<Props> = (props) => {
4445
const onSubmit = () => {
4546
const errors: Record<string, string> = {};
4647
for (const field of props.fields) {
47-
if (field.required && !result[field.name]) {
48+
if (field.required && field.type === "string" && !result[field.name]) {
4849
errors[field.name] = "This field is required";
4950
}
5051

@@ -56,8 +57,8 @@ export const Form: React.FC<Props> = (props) => {
5657
errors[field.name] = "This field is required";
5758
}
5859

59-
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
60-
const errorMessage = field.validate?.(result[field.name] as any);
60+
const validate = field.validate as Validate<unknown> | undefined;
61+
const errorMessage = validate?.(result[field.name]);
6162

6263
if (errorMessage) {
6364
errors[field.name] = errorMessage;
@@ -182,6 +183,21 @@ const FieldView = <T extends Field>(field: FieldViewProps<T>) => {
182183
);
183184
}
184185

186+
if (field.type === "number") {
187+
return (
188+
<Box borderColor={borderColor} borderStyle="round" flexDirection="column">
189+
<InputWithLabel
190+
validateRegExp={field.int ? IntNumbersRegExp : NumberRegExp}
191+
label={field.label}
192+
focus={field.focused}
193+
value={(field.value as number)?.toString() || ""}
194+
onChange={field.onChange}
195+
/>
196+
{ErrorText}
197+
</Box>
198+
);
199+
}
200+
185201
if (field.type === "boolean") {
186202
return (
187203
<Box

source/components/Form/interfaces.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,23 @@ export type Field = (
22
| StringInputField
33
| BooleanInputField
44
| StringArrayInputField
5+
| NumberInputField
56
) &
67
InputFieldBase;
78

8-
type StringInputField = {
9-
type: "string";
10-
deafultValue?: string;
11-
validate?: (value?: string) => string | undefined;
12-
};
13-
14-
type BooleanInputField = {
15-
type: "boolean";
16-
deafultValue?: boolean;
17-
validate?: (value?: boolean) => string | undefined;
18-
};
9+
export type Validate<V> = (value: V) => string | undefined;
10+
type InputField<K extends string, V> = {
11+
type: K;
12+
deafultValue?: V;
13+
} & (
14+
| { required: true; validate?: Validate<V> }
15+
| { required?: false | undefined; validate?: Validate<V | undefined> }
16+
);
1917

20-
type StringArrayInputField = {
21-
type: "string[]";
22-
deafultValue?: string[];
23-
validate?: (value?: string[]) => string | undefined;
24-
};
18+
type StringInputField = InputField<"string", string>;
19+
type BooleanInputField = InputField<"boolean", boolean>;
20+
type StringArrayInputField = InputField<"string[]", string[]>;
21+
type NumberInputField = InputField<"number", number> & { int?: boolean };
2522

2623
type InputFieldBase = {
2724
name: string;

source/components/InputWithLabel.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,27 @@ import TextInput, { Props as OriginalProps } from "ink-text-input";
44

55
interface Props extends OriginalProps {
66
label?: string;
7+
validateRegExp?: RegExp;
78
}
89

910
export const InputWithLabel: React.FC<Props> = (props) => {
10-
const { label, ...rest } = props;
11+
const { label, onChange, ...rest } = props;
12+
13+
const handleChange = (value: string) => {
14+
if (!props.validateRegExp || !value) {
15+
onChange(value);
16+
return;
17+
}
18+
19+
if (!props.validateRegExp.test(value)) {
20+
return;
21+
}
22+
23+
onChange(value);
24+
};
1125

1226
if (!label) {
13-
return <TextInput {...rest} />;
27+
return <TextInput {...rest} onChange={handleChange} />;
1428
}
1529

1630
return (
@@ -19,7 +33,7 @@ export const InputWithLabel: React.FC<Props> = (props) => {
1933
<Text color="grey">{label}</Text>
2034
</Box>
2135

22-
<TextInput {...rest} />
36+
<TextInput {...rest} onChange={handleChange} />
2337
</Box>
2438
);
2539
};

0 commit comments

Comments
 (0)