Skip to content

Commit 1229510

Browse files
committed
feat: Added array support. Added a type description for every field. Fixed index issues when entering and exiting a field. Fixed scrolling issues when entering and exiting a field.
1 parent 93dd7b0 commit 1229510

9 files changed

Lines changed: 97 additions & 14 deletions

File tree

src/ArrayFieldRenderer.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import { FormFieldArray, SpecificFormFieldRendererProps } from './types.js';
3+
import { Box } from 'ink';
4+
import TextInput from 'ink-text-input';
5+
6+
export const ArrayFieldRenderer: React.FC<
7+
SpecificFormFieldRendererProps<FormFieldArray>
8+
> = props => {
9+
const regex = props.field.regex;
10+
11+
const change = (value: string) => {
12+
if (regex) {
13+
const arr = value.split(/(?<!\\),/);
14+
15+
if (arr.some((i) => !regex.test(i))) {
16+
props.onError(`"${value}" does not pass the regex ${regex}`);
17+
props.onChange(value as any);
18+
}
19+
} else {
20+
props.onChange(value as any);
21+
}
22+
};
23+
24+
return (
25+
<Box borderStyle={'round'} width="100%" flexDirection="column">
26+
<Box>
27+
<TextInput
28+
value={props.value?.toString() ?? ''}
29+
onChange={value => {
30+
props.onClearError();
31+
change(value);
32+
}}
33+
placeholder={props.field.placeholder ?? 'value1, value2, value3...'}
34+
onSubmit={() => props.onSetEditingField(undefined)}
35+
/>
36+
</Box>
37+
</Box>
38+
);
39+
};

src/Form.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,18 @@ export const Form: React.FC<FormProps> = props => {
4747
}
4848
}, []);
4949

50+
const onSetEditingField = (field?: string) => {
51+
const isEditing = !!editingField;
52+
if (!isEditing && field) {
53+
const elementNum = sections[currentTab].fields.findIndex((f) => f.name === field);
54+
setFocusedElement(elementNum + 1);
55+
}
56+
57+
setEditingField(field);
58+
}
59+
5060
const onChangeTab = (tab: number) => {
5161
setCurrentTab(tab);
52-
focusManager.focus('0');
5362
setFocusedElement(0)
5463
}
5564

@@ -107,7 +116,7 @@ export const Form: React.FC<FormProps> = props => {
107116
<FullScreen>
108117
<Box width="100%" height="90%" flexDirection="column" overflowY="hidden">
109118
<FormHeader {...props} headerRef={headerRef} form={form} currentTab={currentTab} onChangeTab={onChangeTab} editingField={editingField} />
110-
<ScrollArea height={fullHeight - headerHeight} key={currentTab} isStart={focusedElement === 0}>
119+
<ScrollArea height={fullHeight - headerHeight} key={currentTab} isStart={focusedElement === 0} editingMode={!!editingField}>
111120
{!editingField && sections[currentTab].description && (
112121
<Box marginX={4}>
113122
<DescriptionRenderer description={sections[currentTab]?.description} />
@@ -126,13 +135,13 @@ export const Form: React.FC<FormProps> = props => {
126135
? null
127136
: sections[currentTab].fields.map((field, index) => (
128137
<FormFieldRenderer
129-
id={index + ''}
138+
id={`${index}`}
130139
field={field}
131140
key={field.name + currentTab}
132141
form={form}
133142
value={value[currentTab][field.name]}
134143
onChange={v => setValueAndPropagate(currentTab, { ...value[currentTab], [field.name]: v })}
135-
onSetEditingField={setEditingField}
144+
onSetEditingField={onSetEditingField}
136145
editingField={editingField}
137146
customManagers={props.customManagers}
138147
/>

src/FormFieldRenderer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export const FormFieldRenderer: React.FC<FormFieldRendererProps<any>> = props =>
105105
<Box>
106106
<Text>{props.field.label ?? props.field.name}</Text>
107107
{props.field.required && <Text color="red">*</Text>}
108+
<Text dimColor>{` (${props.field.type})`}</Text>
108109
<Text>: </Text>
109110
</Box>
110111
<Box>{component}</Box>

src/ScrollArea.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ const reducer = (state, action) => {
3434
}
3535
};
3636

37-
export function ScrollArea({height, isStart, children}) {
37+
export function ScrollArea({height, isStart, children, editingMode}) {
3838
const [state, dispatch] = React.useReducer(reducer, {
3939
height: 10,
4040
scrollTop: 0,
4141
});
4242
const focusManager = useFocusManager();
4343
const [canScroll, setCanScroll] = useState(true);
44+
const [isEditing, setIsEditing] = useState(false);
4445
const innerRef = React.useRef();
4546

4647
useLayoutEffect(() => {
@@ -59,6 +60,15 @@ export function ScrollArea({height, isStart, children}) {
5960
focusManager.enableFocus();
6061
}, [height, isStart, children.length]);
6162

63+
// Scroll to the top when entering or exiting edit mode
64+
if (!isEditing && editingMode) {
65+
setIsEditing(true);
66+
dispatch({ type: 'RESET' })
67+
} else if (isEditing && !editingMode) {
68+
setIsEditing(false);
69+
dispatch({ type: 'RESET' })
70+
}
71+
6272
React.useEffect(() => {
6373
const dimensions = measureElement(innerRef.current);
6474

src/demo/Test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ export function Test() {
5959
{ type: 'string', name: 'plugin', label: 'plugin', required: true },
6060
{ type: 'string', name: 'version', label: 'version', required: true },
6161
{ type: 'string', name: 'directory', label: 'directory' },
62-
{ type: 'string', name: 'directories', label: 'directories' },
62+
{ type: 'array', name: 'directories', label: 'directories' },
6363
]
6464
},
6565
{
6666
title: "asdf-plugin",
6767
fields: [
6868
{ type: 'string', name: 'plugin', label: 'plugin', required: true },
6969
{ type: 'string', name: 'versions', label: 'version'},
70-
{ type: 'string', name: 'gitUrl', label: 'plugin' },
70+
{ type: 'string', name: 'gitUrl', label: 'gitUrl' },
7171
]
7272
},
7373
]

src/demo/codify.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@ import { Form } from '../Form.js';
33
import React from 'react';
44
import { Test } from './Test.js';
55

6-
const options = [
7-
{label: 'Millenium Falcon', value: 'falcon'},
8-
{label: 'TIE Advanced X1', value: 'tieadv'},
9-
{label: 'X-Wing', value: 'xwing'},
10-
{label: 'Raizorcrest', value: 'mando'},
11-
];
12-
136
console.log('Test');
147
console.log('Test 1');
158
console.log('Test 2');
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
FormFieldArray,
3+
FormFieldManager,
4+
FormFieldValueRendererProps,
5+
SpecificFormFieldRendererProps,
6+
TypeOfField,
7+
} from '../types.js';
8+
import React from 'react';
9+
import { ArrayFieldRenderer } from '../ArrayFieldRenderer.js';
10+
11+
export class ArrayFormFieldManager implements FormFieldManager<FormFieldArray> {
12+
public type: TypeOfField<FormFieldArray> = 'array';
13+
14+
public renderField: React.FC<SpecificFormFieldRendererProps<FormFieldArray>> = props => (
15+
<ArrayFieldRenderer {...props} />
16+
);
17+
18+
public renderValue: React.FC<FormFieldValueRendererProps<FormFieldArray>> = props => <>{`[${props.value}]`}</>;
19+
}

src/managers/managers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { SelectFormFieldManager } from './SelectFormFieldManager.js';
44
import { FormField, FormFieldManager, TypeOfField } from '../types.js';
55
import { StringFormFieldManager } from './StringFormFieldManager.js';
66
import { BooleanFormFieldManager } from './BooleanFormFieldManager.js';
7+
import { ArrayFormFieldManager } from './ArrayFormFieldManager.js';
78

89
export const managers: FormFieldManager<FormField>[] = [
910
new FloatFormFieldManager(),
1011
new IntegerFormFieldManager(),
1112
new SelectFormFieldManager(),
1213
new StringFormFieldManager(),
1314
new BooleanFormFieldManager(),
15+
new ArrayFormFieldManager(),
1416
];
1517

1618
export const getManager = (

src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ export type AbstractFormField<T extends string, V> = {
122122
onChange?: (value: V, name: string) => void;
123123
};
124124

125+
export type FormFieldArray = AbstractFormField<'array', string[]> & {
126+
maxLength?: number;
127+
minLength?: number;
128+
129+
/** If supplied, it validates per item*/
130+
regex?: RegExp;
131+
132+
placeholder?: string;
133+
};
134+
125135
export type FormFieldBoolean = AbstractFormField<'boolean', boolean> & {};
126136

127137
export type FormFieldString = AbstractFormField<'string', string> & {

0 commit comments

Comments
 (0)