diff --git a/package-lock.json b/package-lock.json
index ad7ab6276..949c3ef66 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18475,7 +18475,7 @@
"dev": true,
"requires": {
"is-ssh": "^1.3.0",
- "parse-url": ">=6.0.1"
+ "parse-url": "^6.0.0"
}
},
"git-url-parse": {
@@ -21478,8 +21478,7 @@
}
},
"parse-url": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz",
+ "version": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz",
"integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==",
"dev": true,
"requires": {
diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
index df7157dd4..b3bcef090 100644
--- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
+++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap
@@ -53,9 +53,20 @@ export default function CustomDataForm(props) {
category,
};
const validationResponses = await Promise.all(
- Object.keys(validations).map((fieldName) =>
- runValidationTasks(fieldName, modelFields[fieldName])
- )
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(
+ ...modelFields[fieldName].map((item) =>
+ runValidationTasks(fieldName, item)
+ )
+ );
+ return promises;
+ }
+ promises.push(
+ runValidationTasks(fieldName, modelFields[fieldName])
+ );
+ return promises;
+ }, [])
);
if (validationResponses.some((r) => r.hasError)) {
return;
@@ -206,7 +217,7 @@ export default function CustomDataForm(props) {
children=\\"create\\"
type=\\"submit\\"
variation=\\"primary\\"
- isDisabled={Object.values(errors).some((e) => e.hasError)}
+ isDisabled={Object.values(errors).some((e) => e?.hasError)}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
>
@@ -297,9 +308,20 @@ export default function NestedJson(props) {
bio,
};
const validationResponses = await Promise.all(
- Object.keys(validations).map((fieldName) =>
- runValidationTasks(fieldName, modelFields[fieldName])
- )
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(
+ ...modelFields[fieldName].map((item) =>
+ runValidationTasks(fieldName, item)
+ )
+ );
+ return promises;
+ }
+ promises.push(
+ runValidationTasks(fieldName, modelFields[fieldName])
+ );
+ return promises;
+ }, [])
);
if (validationResponses.some((r) => r.hasError)) {
return;
@@ -423,7 +445,7 @@ export default function NestedJson(props) {
children=\\"Submit\\"
type=\\"submit\\"
variation=\\"primary\\"
- isDisabled={Object.values(errors).some((e) => e.hasError)}
+ isDisabled={Object.values(errors).some((e) => e?.hasError)}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
>
@@ -519,9 +541,20 @@ export default function CustomWithSectionalElements(props) {
name,
};
const validationResponses = await Promise.all(
- Object.keys(validations).map((fieldName) =>
- runValidationTasks(fieldName, modelFields[fieldName])
- )
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(
+ ...modelFields[fieldName].map((item) =>
+ runValidationTasks(fieldName, item)
+ )
+ );
+ return promises;
+ }
+ promises.push(
+ runValidationTasks(fieldName, modelFields[fieldName])
+ );
+ return promises;
+ }, [])
);
if (validationResponses.some((r) => r.hasError)) {
return;
@@ -613,7 +646,7 @@ export default function CustomWithSectionalElements(props) {
children=\\"Submit\\"
type=\\"submit\\"
variation=\\"primary\\"
- isDisabled={Object.values(errors).some((e) => e.hasError)}
+ isDisabled={Object.values(errors).some((e) => e?.hasError)}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
>
@@ -706,9 +739,20 @@ export default function MyPostForm(props) {
profile_url,
};
const validationResponses = await Promise.all(
- Object.keys(validations).map((fieldName) =>
- runValidationTasks(fieldName, modelFields[fieldName])
- )
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(
+ ...modelFields[fieldName].map((item) =>
+ runValidationTasks(fieldName, item)
+ )
+ );
+ return promises;
+ }
+ promises.push(
+ runValidationTasks(fieldName, modelFields[fieldName])
+ );
+ return promises;
+ }, [])
);
if (validationResponses.some((r) => r.hasError)) {
return;
@@ -756,7 +800,7 @@ export default function MyPostForm(props) {
children=\\"Submit\\"
type=\\"submit\\"
variation=\\"primary\\"
- isDisabled={Object.values(errors).some((e) => e.hasError)}
+ isDisabled={Object.values(errors).some((e) => e?.hasError)}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
>
@@ -963,9 +1007,20 @@ export default function MyPostForm(props) {
post_url,
};
const validationResponses = await Promise.all(
- Object.keys(validations).map((fieldName) =>
- runValidationTasks(fieldName, modelFields[fieldName])
- )
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(
+ ...modelFields[fieldName].map((item) =>
+ runValidationTasks(fieldName, item)
+ )
+ );
+ return promises;
+ }
+ promises.push(
+ runValidationTasks(fieldName, modelFields[fieldName])
+ );
+ return promises;
+ }, [])
);
if (validationResponses.some((r) => r.hasError)) {
return;
@@ -1017,7 +1072,7 @@ export default function MyPostForm(props) {
children=\\"Submit\\"
type=\\"submit\\"
variation=\\"primary\\"
- isDisabled={Object.values(errors).some((e) => e.hasError)}
+ isDisabled={Object.values(errors).some((e) => e?.hasError)}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
>
@@ -1150,7 +1205,7 @@ export default function MyPostForm(props) {
children=\\"Submit\\"
type=\\"submit\\"
variation=\\"primary\\"
- isDisabled={Object.values(errors).some((e) => e.hasError)}
+ isDisabled={Object.values(errors).some((e) => e?.hasError)}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
>
@@ -1239,6 +1294,7 @@ export default function InputGalleryCreateForm(props) {
const [attend, setAttend] = React.useState(undefined);
const [maybeSlide, setMaybeSlide] = React.useState(undefined);
const [maybeCheck, setMaybeCheck] = React.useState(undefined);
+ const [arrayTypeField, setArrayTypeField] = React.useState(undefined);
const [timestamp, setTimestamp] = React.useState(undefined);
const [ippy, setIppy] = React.useState(undefined);
const [timeisnow, setTimeisnow] = React.useState(undefined);
@@ -1249,6 +1305,7 @@ export default function InputGalleryCreateForm(props) {
attend: [{ type: \\"Required\\" }],
maybeSlide: [],
maybeCheck: [],
+ arrayTypeField: [],
timestamp: [],
ippy: [{ type: \\"IpAddress\\" }],
timeisnow: [],
@@ -1272,14 +1329,26 @@ export default function InputGalleryCreateForm(props) {
attend,
maybeSlide,
maybeCheck,
+ arrayTypeField,
timestamp,
ippy,
timeisnow,
};
const validationResponses = await Promise.all(
- Object.keys(validations).map((fieldName) =>
- runValidationTasks(fieldName, modelFields[fieldName])
- )
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(
+ ...modelFields[fieldName].map((item) =>
+ runValidationTasks(fieldName, item)
+ )
+ );
+ return promises;
+ }
+ promises.push(
+ runValidationTasks(fieldName, modelFields[fieldName])
+ );
+ return promises;
+ }, [])
);
if (validationResponses.some((r) => r.hasError)) {
return;
@@ -1431,6 +1500,26 @@ export default function InputGalleryCreateForm(props) {
rowGap=\\"inherit\\"
templateColumns=\\"repeat(1, auto)\\"
{...getOverrideProps(overrides, \\"RowGrid5\\")}
+ >
+ {
+ const { value } = e.target;
+ await runValidationTasks(\\"arrayTypeField\\", value);
+ setArrayTypeField(value);
+ }}
+ errorMessage={errors.arrayTypeField?.errorMessage}
+ hasError={errors.arrayTypeField?.hasError}
+ {...getOverrideProps(overrides, \\"arrayTypeField\\")}
+ >
+
+
e.hasError)}
+ isDisabled={Object.values(errors).some((e) => e?.hasError)}
{...getOverrideProps(overrides, \\"SubmitButton\\")}
>
@@ -1538,6 +1627,7 @@ export declare type InputGalleryCreateFormInputValues = {
attend?: ValidationFunction;
maybeSlide?: ValidationFunction;
maybeCheck?: ValidationFunction;
+ arrayTypeField?: ValidationFunction;
timestamp?: ValidationFunction;
ippy?: ValidationFunction;
timeisnow?: ValidationFunction;
@@ -1555,10 +1645,12 @@ export declare type InputGalleryCreateFormOverridesProps = {
RowGrid4?: GridProps;
maybeCheck?: CheckboxFieldProps;
RowGrid5?: GridProps;
- timestamp?: TextFieldProps;
+ arrayTypeField?: TextFieldProps;
RowGrid6?: GridProps;
- ippy?: TextFieldProps;
+ timestamp?: TextFieldProps;
RowGrid7?: GridProps;
+ ippy?: TextFieldProps;
+ RowGrid8?: GridProps;
timeisnow?: TextFieldProps;
} & EscapeHatchProps;
export declare type InputGalleryCreateFormProps = React.PropsWithChildren<{
diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts
index 2cdf4dc5c..182e4f211 100644
--- a/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts
+++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts
@@ -39,8 +39,8 @@ import { lowerCaseFirst } from '../helpers';
import { ImportCollection, ImportSource } from '../imports';
import { getActionIdentifier } from '../workflow';
import { buildTargetVariable } from './event-targets';
-import { setFieldState } from './form-state';
import { buildOnValidateType } from './type-helper';
+import { capitalizeFirstLetter, setFieldState } from './form-state';
export const buildMutationBindings = (form: StudioForm) => {
const {
@@ -258,10 +258,7 @@ export const createValidationExpression = (validationRules: FieldValidationConfi
return factory.createArrayLiteralExpression(validateExpressions, true);
};
-export const addFormAttributes = (
- { name: componentName, componentType }: StudioComponent | StudioComponentChild,
- formMetadata: FormMetadata,
-) => {
+export const addFormAttributes = (component: StudioComponent | StudioComponentChild, formMetadata: FormMetadata) => {
const attributes: JsxAttribute[] = [];
/*
boolean => RadioGroupField
@@ -272,10 +269,11 @@ export const addFormAttributes = (
componentType => SelectField && boolean
const value = Boolean(e.target.checked)
-
+
*/
- if (componentName in formMetadata.fieldConfigs) {
- const fieldConfig = formMetadata.fieldConfigs[componentName];
+
+ if (component.name in formMetadata.fieldConfigs) {
+ const fieldConfig = formMetadata.fieldConfigs[component.name];
/*
if the componetName is a dotPath we need to change the access expression to the following
- bio.user.favorites.Quote => errors['bio.user.favorites.Quote']?.errorMessage
@@ -283,16 +281,16 @@ export const addFormAttributes = (
- bio => errors.bio?.errorMessage
*/
const errorKey =
- componentName.split('.').length > 1
+ component.name.split('.').length > 1
? factory.createElementAccessExpression(
factory.createIdentifier('errors'),
- factory.createStringLiteral(componentName),
+ factory.createStringLiteral(component.name),
)
: factory.createPropertyAccessExpression(
factory.createIdentifier('errors'),
- factory.createIdentifier(componentName),
+ factory.createIdentifier(component.name),
);
- attributes.push(buildOnChangeStatement(componentName, componentType, fieldConfig));
+ attributes.push(buildOnChangeStatement(component, fieldConfig));
attributes.push(
factory.createJsxAttribute(
factory.createIdentifier('errorMessage'),
@@ -317,9 +315,24 @@ export const addFormAttributes = (
),
),
);
+ if (fieldConfig.isArray) {
+ attributes.push(
+ factory.createJsxAttribute(
+ factory.createIdentifier('value'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createIdentifier(`current${capitalizeFirstLetter(component.name)}Value`),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('ref'),
+ factory.createJsxExpression(undefined, factory.createIdentifier(`${lowerCaseFirst(component.name)}Ref`)),
+ ),
+ );
+ }
}
- if (componentName === 'SubmitButton') {
+ if (component.name === 'SubmitButton') {
attributes.push(
factory.createJsxAttribute(
factory.createIdentifier('isDisabled'),
@@ -355,8 +368,9 @@ export const addFormAttributes = (
],
undefined,
factory.createToken(SyntaxKind.EqualsGreaterThanToken),
- factory.createPropertyAccessExpression(
+ factory.createPropertyAccessChain(
factory.createIdentifier('e'),
+ factory.createToken(SyntaxKind.QuestionDotToken),
factory.createIdentifier('hasError'),
),
),
@@ -366,7 +380,7 @@ export const addFormAttributes = (
),
);
}
- if (componentName === 'CancelButton') {
+ if (component.name === 'CancelButton') {
attributes.push(
factory.createJsxAttribute(
factory.createIdentifier('onClick'),
@@ -398,7 +412,58 @@ export const addFormAttributes = (
return attributes;
};
-export const buildOnChangeStatement = (fieldName: string, fieldType: string, fieldConfig: FieldConfigMetadata) => {
+export const buildOnChangeStatement = (
+ component: StudioComponent | StudioComponentChild,
+ fieldConfig: FieldConfigMetadata,
+) => {
+ const { name: fieldName, componentType: fieldType } = component;
+ const { dataType, isArray } = fieldConfig;
+ if (isArray) {
+ return factory.createJsxAttribute(
+ factory.createIdentifier('onChange'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createArrowFunction(
+ [factory.createModifier(SyntaxKind.AsyncKeyword)],
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('e'),
+ undefined,
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ buildTargetVariable(fieldType, dataType),
+ factory.createExpressionStatement(
+ factory.createAwaitExpression(
+ factory.createCallExpression(factory.createIdentifier('runValidationTasks'), undefined, [
+ factory.createStringLiteral(fieldName),
+ factory.createIdentifier('value'),
+ ]),
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier(`setCurrent${capitalizeFirstLetter(fieldName)}Value`),
+ undefined,
+ [factory.createIdentifier('value')],
+ ),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ );
+ }
return factory.createJsxAttribute(
factory.createIdentifier('onChange'),
factory.createJsxExpression(
@@ -794,9 +859,13 @@ export const buildModelFieldObject = (fieldConfigs: Record
- runValidationTasks(fieldName, modelFields[fieldName])
- )
+ Object.keys(validations).reduce((promises, fieldName) => {
+ if (Array.isArray(modelFields[fieldName])) {
+ promises.push(...modelFields[fieldName].map(item => runValidationTasks(fieldName, item)));
+ }
+ promises.push(runValidationTasks(fieldName, modelFields[fieldName]))
+ return promises
+ }, [])
);
if (validationResponses.some((r) => r.hasError)) {
@@ -831,7 +900,7 @@ export const onSubmitValidationRun = [
undefined,
[factory.createIdentifier('validations')],
),
- factory.createIdentifier('map'),
+ factory.createIdentifier('reduce'),
),
undefined,
[
@@ -843,22 +912,122 @@ export const onSubmitValidationRun = [
undefined,
undefined,
undefined,
- factory.createIdentifier('fieldName'),
+ factory.createIdentifier('promises'),
+ undefined,
+ undefined,
+ ),
+ factory.createParameterDeclaration(
undefined,
undefined,
undefined,
+ factory.createIdentifier('fieldName'),
+ undefined,
+ undefined,
),
],
undefined,
factory.createToken(SyntaxKind.EqualsGreaterThanToken),
- factory.createCallExpression(factory.createIdentifier('runValidationTasks'), undefined, [
- factory.createIdentifier('fieldName'),
- factory.createElementAccessExpression(
- factory.createIdentifier('modelFields'),
- factory.createIdentifier('fieldName'),
- ),
- ]),
+ factory.createBlock(
+ [
+ factory.createIfStatement(
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('Array'),
+ factory.createIdentifier('isArray'),
+ ),
+ undefined,
+ [
+ factory.createElementAccessExpression(
+ factory.createIdentifier('modelFields'),
+ factory.createIdentifier('fieldName'),
+ ),
+ ],
+ ),
+ factory.createBlock(
+ [
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('promises'),
+ factory.createIdentifier('push'),
+ ),
+ undefined,
+ [
+ factory.createSpreadElement(
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createElementAccessExpression(
+ factory.createIdentifier('modelFields'),
+ factory.createIdentifier('fieldName'),
+ ),
+ factory.createIdentifier('map'),
+ ),
+ undefined,
+ [
+ factory.createArrowFunction(
+ undefined,
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('item'),
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createCallExpression(
+ factory.createIdentifier('runValidationTasks'),
+ undefined,
+ [
+ factory.createIdentifier('fieldName'),
+ factory.createIdentifier('item'),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ factory.createReturnStatement(factory.createIdentifier('promises')),
+ ],
+ true,
+ ),
+ undefined,
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('promises'),
+ factory.createIdentifier('push'),
+ ),
+ undefined,
+ [
+ factory.createCallExpression(
+ factory.createIdentifier('runValidationTasks'),
+ undefined,
+ [
+ factory.createIdentifier('fieldName'),
+ factory.createElementAccessExpression(
+ factory.createIdentifier('modelFields'),
+ factory.createIdentifier('fieldName'),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ factory.createReturnStatement(factory.createIdentifier('promises')),
+ ],
+ true,
+ ),
),
+ factory.createArrayLiteralExpression([], false),
],
),
],
diff --git a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts
index 432c1ca8a..2024c2de8 100644
--- a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts
+++ b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts
@@ -58,6 +58,7 @@ import {
getDeclarationFilename,
transpile,
} from '../react-studio-template-renderer-helper';
+import { generateArrayFieldComponent } from '../utils/forms/array-field-component';
import { addUseEffectWrapper } from '../utils/generate-react-hooks';
import { RequiredKeys } from '../utils/type-utils';
import {
@@ -68,7 +69,7 @@ import {
buildValidations,
runValidationTasksFunction,
} from './form-renderer-helper';
-import { buildUseStateExpression, getUseStateHooks } from './form-state';
+import { buildUseStateExpression, capitalizeFirstLetter, getUseStateHooks } from './form-state';
import { generateOnValidationType, validationFunctionType, validationResponseType } from './type-helper';
export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
@@ -127,6 +128,12 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
importsText += result + EOL;
});
+ if (this.componentMetadata.formMetadata) {
+ if (Object.values(this.componentMetadata.formMetadata?.fieldConfigs).some(({ isArray }) => isArray)) {
+ printer.printNode(EmitHint.Unspecified, generateArrayFieldComponent(), file);
+ }
+ }
+
const wrappedFunction = this.renderFunctionWrapper(this.component.name, variableStatements, jsx, false);
const result = printer.printNode(EmitHint.Unspecified, wrappedFunction, file);
@@ -168,6 +175,13 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
componentText += propsPrinted;
});
+ if (this.componentMetadata.formMetadata) {
+ if (Object.values(this.componentMetadata.formMetadata?.fieldConfigs).some(({ isArray }) => isArray)) {
+ const arrayFieldComponent = printer.printNode(EmitHint.Unspecified, generateArrayFieldComponent(), file);
+ componentText += arrayFieldComponent;
+ }
+ }
+
const result = printer.printNode(EmitHint.Unspecified, wrappedFunction, file);
componentText += result;
@@ -367,6 +381,36 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer<
}
}
+ this.importCollection.addMappedImport(ImportValue.VALIDATE_FIELD);
+ // Add value state and ref array type fields in ArrayField wrapper
+ Object.entries(formMetadata.fieldConfigs).forEach(([field, config]) => {
+ if (config.isArray) {
+ statements.push(
+ buildUseStateExpression(`current${capitalizeFirstLetter(field)}Value`, factory.createStringLiteral('')),
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createIdentifier(`${field}Ref`),
+ undefined,
+ undefined,
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('React'),
+ factory.createIdentifier('createRef'),
+ ),
+ undefined,
+ [],
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ );
+ }
+ });
statements.push(buildValidations(formMetadata.fieldConfigs));
statements.push(runValidationTasksFunction);
diff --git a/packages/codegen-ui-react/lib/react-component-renderer.ts b/packages/codegen-ui-react/lib/react-component-renderer.ts
index 264c0d916..d01a5b80c 100644
--- a/packages/codegen-ui-react/lib/react-component-renderer.ts
+++ b/packages/codegen-ui-react/lib/react-component-renderer.ts
@@ -36,6 +36,7 @@ import {
getStateName,
getSetStateName,
hasChildrenProp,
+ isFixedPropertyWithValue,
} from './react-component-render-helper';
import {
buildOpeningElementControlEvents,
@@ -44,6 +45,7 @@ import {
} from './workflow';
import { ImportCollection, ImportSource, ImportValue } from './imports';
import { addFormAttributes } from './forms';
+import { renderArrayFieldComponent } from './utils/forms/array-field-component';
export class ReactComponentRenderer extends ComponentRendererBase<
TPropIn,
@@ -73,6 +75,23 @@ export class ReactComponentRenderer extends ComponentRendererBase<
this.importCollection.addImport(ImportSource.UI_REACT, this.component.componentType);
+ // Add ArrayField wrapper to element if Array type
+ if (this.componentMetadata.formMetadata?.fieldConfigs[this.component.name]?.isArray) {
+ this.importCollection.addImport(ImportSource.UI_REACT, 'Icon');
+ this.importCollection.addImport(ImportSource.UI_REACT, 'Badge');
+ this.importCollection.addImport(ImportSource.UI_REACT, 'ScrollView');
+ this.importCollection.addImport(ImportSource.UI_REACT, 'Divider');
+ return renderArrayFieldComponent(
+ this.component.name,
+ `${
+ isFixedPropertyWithValue(this.component.properties.label)
+ ? this.component.properties.label.value
+ : this.component.name
+ }`,
+ element,
+ );
+ }
+
return element;
}
diff --git a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts
index ac1409c79..94a4ef0f8 100644
--- a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts
+++ b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts
@@ -61,6 +61,7 @@ export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer<
renderComponentInternal() {
const { printer, file } = buildPrinter(this.fileName, this.renderConfig);
const utilsStatements: (ts.VariableStatement | ts.TypeAliasDeclaration | ts.FunctionDeclaration)[] = [];
+ const skipReactImport = true;
this.utils.forEach((util) => {
if (util === 'validation') {
@@ -71,16 +72,29 @@ export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer<
utilsStatements.push(getFetchByPathNodeFunction());
}
});
+ utilsStatements.push(...generateFormatUtil());
- const { componentText } = transpile(
- utilsStatements.map((util) => printer.printNode(EmitHint.Unspecified, util, file)).join(EOL),
- this.renderConfig,
- );
+ let componentText = `/* eslint-disable */${EOL}`;
+ const imports = this.importCollection.buildImportStatements(skipReactImport);
+ imports.forEach((importStatement) => {
+ const result = printer.printNode(EmitHint.Unspecified, importStatement, file);
+ componentText += result + EOL;
+ });
+ componentText += EOL;
+
+ utilsStatements.forEach((util) => {
+ const result = printer.printNode(EmitHint.Unspecified, util, file);
+ componentText += result + EOL;
+ });
+
+ componentText += EOL;
+
+ const { componentText: transpliedText } = transpile(componentText, this.renderConfig);
return {
- componentText,
+ componentText: transpliedText,
renderComponentToFilesystem: async (outputPath: string) => {
- await this.renderComponentToFilesystem(componentText)(this.fileName)(outputPath);
+ await this.renderComponentToFilesystem(transpliedText)(this.fileName)(outputPath);
},
};
}
diff --git a/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts b/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts
new file mode 100644
index 000000000..defe73128
--- /dev/null
+++ b/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts
@@ -0,0 +1,883 @@
+/*
+ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License").
+ You may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+import { factory, JsxChild, JsxTagNamePropertyAccess, NodeFlags, SyntaxKind } from 'typescript';
+import { capitalizeFirstLetter } from '../../forms/form-state';
+import { lowerCaseFirst } from '../../helpers';
+
+export const generateArrayFieldComponent = () => {
+ const iconPath = 'M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z';
+ return factory.createFunctionDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('ArrayField'),
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createObjectBindingPattern([
+ factory.createBindingElement(
+ undefined,
+ undefined,
+ factory.createIdentifier('defaultValues'),
+ factory.createArrayLiteralExpression([], false),
+ ),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('onChange'), undefined),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('inputFieldRef'), undefined),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('children'), undefined),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('hasError'), undefined),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('setFieldValue'), undefined),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('currentFieldValue'), undefined),
+ ]),
+ undefined,
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createBlock(
+ [
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createArrayBindingPattern([
+ factory.createBindingElement(
+ undefined,
+ undefined,
+ factory.createIdentifier('selectedBadgeIndex'),
+ undefined,
+ ),
+ factory.createBindingElement(
+ undefined,
+ undefined,
+ factory.createIdentifier('setSelectedBadgeIndex'),
+ undefined,
+ ),
+ ]),
+ undefined,
+ undefined,
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('React'),
+ factory.createIdentifier('useState'),
+ ),
+ undefined,
+ [],
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createArrayBindingPattern([
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('items'), undefined),
+ factory.createBindingElement(undefined, undefined, factory.createIdentifier('setItems'), undefined),
+ ]),
+ undefined,
+ undefined,
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('React'),
+ factory.createIdentifier('useState'),
+ ),
+ undefined,
+ [factory.createIdentifier('defaultValues')],
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createIdentifier('removeItem'),
+ undefined,
+ undefined,
+ factory.createArrowFunction(
+ [factory.createModifier(SyntaxKind.AsyncKeyword)],
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('removeIndex'),
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createIdentifier('newItems'),
+ undefined,
+ undefined,
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('items'),
+ factory.createIdentifier('filter'),
+ ),
+ undefined,
+ [
+ factory.createArrowFunction(
+ undefined,
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('value'),
+ undefined,
+ undefined,
+ ),
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('index'),
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBinaryExpression(
+ factory.createIdentifier('index'),
+ factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken),
+ factory.createIdentifier('removeIndex'),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createAwaitExpression(
+ factory.createCallExpression(factory.createIdentifier('onChange'), undefined, [
+ factory.createIdentifier('newItems'),
+ ]),
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(factory.createIdentifier('setSelectedBadgeIndex'), undefined, [
+ factory.createIdentifier('undefined'),
+ ]),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(factory.createIdentifier('setItems'), undefined, [
+ factory.createIdentifier('newItems'),
+ ]),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createIdentifier('addItem'),
+ undefined,
+ undefined,
+ factory.createArrowFunction(
+ [factory.createModifier(SyntaxKind.AsyncKeyword)],
+ undefined,
+ [],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ factory.createIfStatement(
+ factory.createBinaryExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('currentFieldValue'),
+ factory.createIdentifier('length'),
+ ),
+ factory.createToken(SyntaxKind.AmpersandAmpersandToken),
+ factory.createPrefixUnaryExpression(
+ SyntaxKind.ExclamationToken,
+ factory.createIdentifier('hasError'),
+ ),
+ ),
+ factory.createBlock(
+ [
+ factory.createVariableStatement(
+ undefined,
+ factory.createVariableDeclarationList(
+ [
+ factory.createVariableDeclaration(
+ factory.createIdentifier('newItems'),
+ undefined,
+ undefined,
+ factory.createArrayLiteralExpression(
+ [factory.createSpreadElement(factory.createIdentifier('items'))],
+ false,
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ factory.createIfStatement(
+ factory.createBinaryExpression(
+ factory.createIdentifier('selectedBadgeIndex'),
+ factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken),
+ factory.createIdentifier('undefined'),
+ ),
+ factory.createBlock(
+ [
+ factory.createExpressionStatement(
+ factory.createBinaryExpression(
+ factory.createElementAccessExpression(
+ factory.createIdentifier('newItems'),
+ factory.createIdentifier('selectedBadgeIndex'),
+ ),
+ factory.createToken(SyntaxKind.EqualsToken),
+ factory.createIdentifier('currentFieldValue'),
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(factory.createIdentifier('setItems'), undefined, [
+ factory.createIdentifier('newItems'),
+ ]),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier('setSelectedBadgeIndex'),
+ undefined,
+ [factory.createIdentifier('undefined')],
+ ),
+ ),
+ ],
+ true,
+ ),
+ factory.createBlock(
+ [
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('newItems'),
+ factory.createIdentifier('push'),
+ ),
+ undefined,
+ [factory.createIdentifier('currentFieldValue')],
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(factory.createIdentifier('setItems'), undefined, [
+ factory.createIdentifier('newItems'),
+ ]),
+ ),
+ ],
+ true,
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createAwaitExpression(
+ factory.createCallExpression(factory.createIdentifier('onChange'), undefined, [
+ factory.createIdentifier('newItems'),
+ ]),
+ ),
+ ),
+ ],
+ true,
+ ),
+ undefined,
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ ],
+ NodeFlags.Const,
+ ),
+ ),
+ factory.createReturnStatement(
+ factory.createParenthesizedExpression(
+ factory.createJsxElement(
+ factory.createJsxOpeningElement(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('React'),
+ factory.createIdentifier('Fragment'),
+ ) as JsxTagNamePropertyAccess,
+ undefined,
+ factory.createJsxAttributes([]),
+ ),
+ [
+ factory.createJsxExpression(undefined, factory.createIdentifier('children')),
+ factory.createJsxElement(
+ factory.createJsxOpeningElement(
+ factory.createIdentifier('Flex'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(
+ factory.createIdentifier('justifyContent'),
+ factory.createStringLiteral('flex-end'),
+ ),
+ ]),
+ ),
+ [
+ factory.createJsxElement(
+ factory.createJsxOpeningElement(
+ factory.createIdentifier('Button'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(
+ factory.createIdentifier('children'),
+ factory.createStringLiteral('Cancel'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('type'),
+ factory.createStringLiteral('button'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('onClick'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createArrowFunction(
+ undefined,
+ undefined,
+ [],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier('setFieldValue'),
+ undefined,
+ [factory.createStringLiteral('')],
+ ),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ ),
+ ]),
+ ),
+ [],
+ factory.createJsxClosingElement(factory.createIdentifier('Button')),
+ ),
+ factory.createJsxElement(
+ factory.createJsxOpeningElement(
+ factory.createIdentifier('Button'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(
+ factory.createIdentifier('children'),
+ factory.createStringLiteral('Save'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('variation'),
+ factory.createStringLiteral('primary'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('isDisabled'),
+ factory.createJsxExpression(undefined, factory.createIdentifier('hasError')),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('onClick'),
+ factory.createJsxExpression(undefined, factory.createIdentifier('addItem')),
+ ),
+ ]),
+ ),
+ [],
+ factory.createJsxClosingElement(factory.createIdentifier('Button')),
+ ),
+ ],
+ factory.createJsxClosingElement(factory.createIdentifier('Flex')),
+ ),
+ factory.createJsxExpression(
+ undefined,
+ factory.createBinaryExpression(
+ factory.createPrefixUnaryExpression(
+ SyntaxKind.ExclamationToken,
+ factory.createPrefixUnaryExpression(
+ SyntaxKind.ExclamationToken,
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('items'),
+ factory.createIdentifier('length'),
+ ),
+ ),
+ ),
+ factory.createToken(SyntaxKind.AmpersandAmpersandToken),
+ factory.createJsxElement(
+ factory.createJsxOpeningElement(
+ factory.createIdentifier('ScrollView'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(
+ factory.createIdentifier('height'),
+ factory.createStringLiteral('inherit'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('width'),
+ factory.createStringLiteral('inherit'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('maxHeight'),
+ factory.createJsxExpression(undefined, factory.createStringLiteral('7rem')),
+ ),
+ ]),
+ ),
+ [
+ factory.createJsxExpression(
+ undefined,
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('items'),
+ factory.createIdentifier('map'),
+ ),
+ undefined,
+ [
+ factory.createArrowFunction(
+ undefined,
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('value'),
+ undefined,
+ undefined,
+ ),
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('index'),
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ factory.createReturnStatement(
+ factory.createParenthesizedExpression(
+ factory.createJsxElement(
+ factory.createJsxOpeningElement(
+ factory.createIdentifier('Badge'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(
+ factory.createIdentifier('key'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createIdentifier('index'),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('style'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createObjectLiteralExpression(
+ [
+ factory.createPropertyAssignment(
+ factory.createIdentifier('cursor'),
+ factory.createStringLiteral('pointer'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('alignItems'),
+ factory.createStringLiteral('center'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('marginRight'),
+ factory.createNumericLiteral('3'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('marginTop'),
+ factory.createNumericLiteral('3'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('backgroundColor'),
+ factory.createConditionalExpression(
+ factory.createBinaryExpression(
+ factory.createIdentifier('index'),
+ factory.createToken(SyntaxKind.EqualsEqualsEqualsToken),
+ factory.createIdentifier('selectedBadgeIndex'),
+ ),
+ factory.createToken(SyntaxKind.QuestionToken),
+ factory.createStringLiteral('#B8CEF9'),
+ factory.createToken(SyntaxKind.ColonToken),
+ factory.createStringLiteral(''),
+ ),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('onClick'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createArrowFunction(
+ undefined,
+ undefined,
+ [],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier('setSelectedBadgeIndex'),
+ undefined,
+ [factory.createIdentifier('index')],
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier('setFieldValue'),
+ undefined,
+ [
+ factory.createElementAccessExpression(
+ factory.createIdentifier('items'),
+ factory.createIdentifier('index'),
+ ),
+ ],
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createPropertyAccessChain(
+ factory.createPropertyAccessChain(
+ factory.createIdentifier('inputFieldRef'),
+ factory.createToken(SyntaxKind.QuestionDotToken),
+ factory.createIdentifier('current'),
+ ),
+ factory.createToken(SyntaxKind.QuestionDotToken),
+ factory.createIdentifier('focus'),
+ ),
+ undefined,
+ [],
+ ),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ ),
+ ]),
+ ),
+ [
+ factory.createJsxExpression(undefined, factory.createIdentifier('value')),
+ factory.createJsxSelfClosingElement(
+ factory.createIdentifier('Icon'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(
+ factory.createIdentifier('style'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createObjectLiteralExpression(
+ [
+ factory.createPropertyAssignment(
+ factory.createIdentifier('cursor'),
+ factory.createStringLiteral('pointer'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('paddingLeft'),
+ factory.createNumericLiteral('3'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('width'),
+ factory.createNumericLiteral('20'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('height'),
+ factory.createNumericLiteral('20'),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('viewBox'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createObjectLiteralExpression(
+ [
+ factory.createPropertyAssignment(
+ factory.createIdentifier('width'),
+ factory.createNumericLiteral('20'),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('height'),
+ factory.createNumericLiteral('20'),
+ ),
+ ],
+ false,
+ ),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('paths'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createArrayLiteralExpression(
+ [
+ factory.createObjectLiteralExpression(
+ [
+ factory.createPropertyAssignment(
+ factory.createIdentifier('d'),
+ factory.createStringLiteral(iconPath),
+ ),
+ factory.createPropertyAssignment(
+ factory.createIdentifier('stroke'),
+ factory.createStringLiteral('black'),
+ ),
+ ],
+ true,
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('ariaLabel'),
+ factory.createStringLiteral('button'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('onClick'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createArrowFunction(
+ undefined,
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('event'),
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('event'),
+ factory.createIdentifier('stopPropagation'),
+ ),
+ undefined,
+ [],
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier('removeItem'),
+ undefined,
+ [factory.createIdentifier('index')],
+ ),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ ),
+ ]),
+ ),
+ ],
+ factory.createJsxClosingElement(factory.createIdentifier('Badge')),
+ ),
+ ),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ factory.createJsxClosingElement(factory.createIdentifier('ScrollView')),
+ ),
+ ),
+ ),
+ factory.createJsxSelfClosingElement(
+ factory.createIdentifier('Divider'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(
+ factory.createIdentifier('orientation'),
+ factory.createStringLiteral('horizontal'),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('marginTop'),
+ factory.createJsxExpression(undefined, factory.createNumericLiteral('5')),
+ ),
+ ]),
+ ),
+ ],
+ factory.createJsxClosingElement(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('React'),
+ factory.createIdentifier('Fragment'),
+ ) as JsxTagNamePropertyAccess,
+ ),
+ ),
+ ),
+ ),
+ ],
+ true,
+ ),
+ );
+};
+/*
+ {
+ setModelFields({ ...modelFields, breeds: items });
+ setCurrentBreedsValue('');
+ }}
+ currentBreedsValue = { currentBreedsValue }
+ hasError = { errors.breeds?.hasError }
+ setFieldValue = { setCurrentBreedsValue }
+ inputFieldRef={ breedsRef }
+ >
+
+
+ */
+
+export const renderArrayFieldComponent = (fieldName: string, label: string, inputField: JsxChild) =>
+ factory.createJsxElement(
+ factory.createJsxOpeningElement(
+ factory.createIdentifier('ArrayField'),
+ undefined,
+ factory.createJsxAttributes([
+ factory.createJsxAttribute(factory.createIdentifier('label'), factory.createStringLiteral(label)),
+ factory.createJsxAttribute(
+ factory.createIdentifier('onChange'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createArrowFunction(
+ [factory.createModifier(SyntaxKind.AsyncKeyword)],
+ undefined,
+ [
+ factory.createParameterDeclaration(
+ undefined,
+ undefined,
+ undefined,
+ factory.createIdentifier('items'),
+ undefined,
+ undefined,
+ ),
+ ],
+ undefined,
+ factory.createToken(SyntaxKind.EqualsGreaterThanToken),
+ factory.createBlock(
+ [
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier(`set${capitalizeFirstLetter(fieldName)}`),
+ undefined,
+ [factory.createIdentifier('items')],
+ ),
+ ),
+ factory.createExpressionStatement(
+ factory.createCallExpression(
+ factory.createIdentifier(`setCurrent${capitalizeFirstLetter(fieldName)}Value`),
+ undefined,
+ [factory.createStringLiteral('')],
+ ),
+ ),
+ ],
+ true,
+ ),
+ ),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier(`currentFieldValue`),
+ factory.createJsxExpression(
+ undefined,
+ factory.createIdentifier(`current${capitalizeFirstLetter(fieldName)}Value`),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('hasError'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createPropertyAccessChain(
+ factory.createPropertyAccessExpression(
+ factory.createIdentifier('errors'),
+ factory.createIdentifier(fieldName),
+ ),
+ factory.createToken(SyntaxKind.QuestionDotToken),
+ factory.createIdentifier('hasError'),
+ ),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('setFieldValue'),
+ factory.createJsxExpression(
+ undefined,
+ factory.createIdentifier(`setCurrent${capitalizeFirstLetter(fieldName)}Value`),
+ ),
+ ),
+ factory.createJsxAttribute(
+ factory.createIdentifier('inputFieldRef'),
+ factory.createJsxExpression(undefined, factory.createIdentifier(`${lowerCaseFirst(fieldName)}Ref`)),
+ ),
+ ]),
+ ),
+ [inputField],
+ factory.createJsxClosingElement(factory.createIdentifier('ArrayField')),
+ );
diff --git a/packages/codegen-ui/example-schemas/datastore/input-gallery.json b/packages/codegen-ui/example-schemas/datastore/input-gallery.json
index 8ae5d3af5..568555922 100644
--- a/packages/codegen-ui/example-schemas/datastore/input-gallery.json
+++ b/packages/codegen-ui/example-schemas/datastore/input-gallery.json
@@ -3,112 +3,119 @@
"InputGallery": {
"name": "InputGallery",
"fields": {
- "id": {
- "name": "id",
- "isArray": false,
- "type": "ID",
- "isRequired": true,
- "attributes": []
- },
- "num": {
- "name": "num",
- "isArray": false,
- "type": "Int",
- "isRequired": false,
- "attributes": []
- },
- "rootbeer": {
- "name": "rootbeer",
- "isArray": false,
- "type": "Float",
- "isRequired": false,
- "attributes": []
- },
- "attend": {
- "name": "attend",
- "isArray": false,
- "type": "Boolean",
- "isRequired": false,
- "attributes": []
- },
- "maybeSlide": {
- "name": "maybeSlide",
- "isArray": false,
- "type": "Boolean",
- "isRequired": false,
- "attributes": []
- },
- "maybeCheck": {
- "name": "maybeCheck",
- "isArray": false,
- "type": "Boolean",
- "isRequired": false,
- "attributes": []
- },
- "timestamp": {
- "name": "timestamp",
- "isArray": false,
- "type": "AWSTimestamp",
- "isRequired": false,
- "attributes": []
- },
- "ippy": {
- "name": "ippy",
- "isArray": false,
- "type": "AWSIPAddress",
- "isRequired": false,
- "attributes": []
- },
- "timeisnow": {
- "name": "timeisnow",
- "isArray": false,
- "type": "AWSTime",
- "isRequired": false,
- "attributes": []
- },
- "createdAt": {
- "name": "createdAt",
- "isArray": false,
- "type": "AWSDateTime",
- "isRequired": false,
- "attributes": [],
- "isReadOnly": true
- },
- "updatedAt": {
- "name": "updatedAt",
- "isArray": false,
- "type": "AWSDateTime",
- "isRequired": false,
- "attributes": [],
- "isReadOnly": true
- }
+ "id": {
+ "name": "id",
+ "isArray": false,
+ "type": "ID",
+ "isRequired": true,
+ "attributes": []
+ },
+ "num": {
+ "name": "num",
+ "isArray": false,
+ "type": "Int",
+ "isRequired": false,
+ "attributes": []
+ },
+ "rootbeer": {
+ "name": "rootbeer",
+ "isArray": false,
+ "type": "Float",
+ "isRequired": false,
+ "attributes": []
+ },
+ "attend": {
+ "name": "attend",
+ "isArray": false,
+ "type": "Boolean",
+ "isRequired": false,
+ "attributes": []
+ },
+ "maybeSlide": {
+ "name": "maybeSlide",
+ "isArray": false,
+ "type": "Boolean",
+ "isRequired": false,
+ "attributes": []
+ },
+ "maybeCheck": {
+ "name": "maybeCheck",
+ "isArray": false,
+ "type": "Boolean",
+ "isRequired": false,
+ "attributes": []
+ },
+ "arrayTypeField": {
+ "name": "arrayTypeField",
+ "isArray": true,
+ "type": "String",
+ "isRequired": false,
+ "attributes": []
+ },
+ "timestamp": {
+ "name": "timestamp",
+ "isArray": false,
+ "type": "AWSTimestamp",
+ "isRequired": false,
+ "attributes": []
+ },
+ "ippy": {
+ "name": "ippy",
+ "isArray": false,
+ "type": "AWSIPAddress",
+ "isRequired": false,
+ "attributes": []
+ },
+ "timeisnow": {
+ "name": "timeisnow",
+ "isArray": false,
+ "type": "AWSTime",
+ "isRequired": false,
+ "attributes": []
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "isArray": false,
+ "type": "AWSDateTime",
+ "isRequired": false,
+ "attributes": [],
+ "isReadOnly": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "isArray": false,
+ "type": "AWSDateTime",
+ "isRequired": false,
+ "attributes": [],
+ "isReadOnly": true
+ }
},
"syncable": true,
"pluralName": "InputGalleries",
"attributes": [
- {
- "type": "model",
- "properties": {}
- },
- {
- "type": "auth",
- "properties": {
- "rules": [
- {
- "allow": "private",
- "provider": "iam",
- "operations": [
- "create",
- "update",
- "delete",
- "read"
- ]
- }
- ]
+ {
+ "type": "model",
+ "properties": {}
+ },
+ {
+ "type": "auth",
+ "properties": {
+ "rules": [
+ {
+ "allow": "private",
+ "provider": "iam",
+ "operations": [
+ "create",
+ "update",
+ "delete",
+ "read"
+ ]
}
+ ]
}
+ }
]
- }
+ }
},
"enums": {},
"nonModels": {},
diff --git a/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts b/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts
index f2bca94c2..ea8f77bba 100644
--- a/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts
+++ b/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts
@@ -29,7 +29,7 @@ describe('mapModelFieldsConfigs', () => {
models: {
Dog: {
fields: {
- name: { dataType: 'String', readOnly: false, required: false, isArray: false },
+ name: { dataType: 'String', readOnly: false, required: false, isArray: true },
},
},
},
@@ -41,7 +41,7 @@ describe('mapModelFieldsConfigs', () => {
expect(modelFieldsConfigs.name).toStrictEqual({
label: 'Name',
dataType: 'String',
- inputType: { type: 'TextField', required: false, readOnly: false, name: 'name', value: 'name' },
+ inputType: { type: 'TextField', isArray: true, required: false, readOnly: false, name: 'name', value: 'name' },
});
});
@@ -121,6 +121,7 @@ describe('mapModelFieldsConfigs', () => {
required: true,
type: 'TextField',
value: 'id',
+ isArray: false,
},
label: 'Id',
},
@@ -155,6 +156,7 @@ describe('mapModelFieldsConfigs', () => {
required: false,
type: 'TextField',
value: 'name',
+ isArray: false,
},
label: 'Name',
},
@@ -195,6 +197,7 @@ describe('mapModelFieldsConfigs', () => {
required: false,
type: 'SelectField',
value: 'ownerId',
+ isArray: false,
},
label: 'Owner id',
},
@@ -240,6 +243,7 @@ describe('mapModelFieldsConfigs', () => {
{ value: { value: nonEnglishAlphabetTest }, displayValue: { value: nonEnglishAlphabetTest } },
],
},
+ isArray: false,
},
label: 'City',
},
@@ -298,15 +302,4 @@ describe('getFieldTypeMapKey', () => {
}),
).toBe('NonModel');
});
-
- it('should return `Array` if isArray is true', () => {
- expect(
- getFieldTypeMapKey({
- dataType: { nonModel: 'Misc' },
- readOnly: false,
- required: false,
- isArray: true,
- }),
- ).toBe('Array');
- });
});
diff --git a/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts b/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts
index 162e3b306..9a50b8e34 100644
--- a/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts
+++ b/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts
@@ -77,10 +77,6 @@ export const FIELD_TYPE_MAP: {
defaultComponent: 'JSONField',
supportedComponents: new Set(['TextField', 'JSONField']),
},
- Array: {
- defaultComponent: 'ArrayField',
- supportedComponents: new Set(['ArrayField']),
- },
AWSPhone: {
defaultComponent: 'PhoneNumberField',
supportedComponents: new Set(['PhoneNumberField']),
diff --git a/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts b/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts
index 666ea5411..2bc82af98 100644
--- a/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts
+++ b/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts
@@ -327,20 +327,6 @@ export function getFormDefinitionInputElement(
},
};
break;
- case 'ArrayField':
- formDefinitionElement = {
- componentType: 'ArrayField',
- props: {
- label: config.label || baseConfig?.label || FORM_DEFINITION_DEFAULTS.field.inputType.label,
- descriptiveText: config.inputType?.descriptiveText ?? baseConfig?.inputType?.descriptiveText,
- isRequired: isRequiredValue,
- isReadOnly: getFirstDefinedValue([config.inputType?.readOnly, baseConfig?.inputType?.readOnly]),
- placeholder: config.inputType?.placeholder || baseConfig?.inputType?.placeholder,
- defaultValues: defaultStringValue ? [defaultStringValue] : undefined,
- },
- };
- break;
-
default:
throw new InvalidInputError(`componentType ${componentType} could not be mapped`);
}
diff --git a/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts b/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts
index 1daa46d7a..a39507af6 100644
--- a/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts
+++ b/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts
@@ -29,10 +29,6 @@ import {
import { FIELD_TYPE_MAP } from './field-type-map';
export function getFieldTypeMapKey(field: GenericDataField): FieldTypeMapKeys {
- if (field.isArray) {
- return 'Array';
- }
-
if (typeof field.dataType === 'object' && 'enum' in field.dataType) {
return 'Enum';
}
@@ -69,6 +65,7 @@ export function getFieldConfigFromModelField({
readOnly: field.readOnly,
name: fieldName,
value: fieldName,
+ isArray: field.isArray,
},
};
diff --git a/packages/codegen-ui/lib/types/form/form-definition-element.ts b/packages/codegen-ui/lib/types/form/form-definition-element.ts
index 5cbc61ee6..f856ff95d 100644
--- a/packages/codegen-ui/lib/types/form/form-definition-element.ts
+++ b/packages/codegen-ui/lib/types/form/form-definition-element.ts
@@ -154,18 +154,6 @@ export type FormDefinitionPasswordFieldElement = {
};
};
-export type FormDefinitionArrayFieldElement = {
- componentType: 'ArrayField';
- props: {
- label: string;
- descriptiveText?: string;
- isRequired?: boolean;
- isReadOnly?: boolean;
- placeholder?: string;
- defaultValues?: string[];
- };
-};
-
export type FormDefinitionButtonElement = {
name: string;
componentType: 'Button';
@@ -188,7 +176,6 @@ export type FormDefinitionInputElement = (
| FormDefinitionCheckboxFieldElement
| FormDefinitionRadioGroupFieldElement
| FormDefinitionPasswordFieldElement
- | FormDefinitionArrayFieldElement
) &
FormDefinitionInputElementCommon;
diff --git a/packages/codegen-ui/lib/types/form/form-definition.ts b/packages/codegen-ui/lib/types/form/form-definition.ts
index 4113b0f28..501bc56ab 100644
--- a/packages/codegen-ui/lib/types/form/form-definition.ts
+++ b/packages/codegen-ui/lib/types/form/form-definition.ts
@@ -55,6 +55,5 @@ export type FieldTypeMapKeys =
| 'AWSJSON'
| 'AWSPhone'
| 'Enum'
- | 'Array'
| 'Relationship'
| 'NonModel';
diff --git a/packages/codegen-ui/lib/types/form/input-config.ts b/packages/codegen-ui/lib/types/form/input-config.ts
index 584ac9328..9569d9cc2 100644
--- a/packages/codegen-ui/lib/types/form/input-config.ts
+++ b/packages/codegen-ui/lib/types/form/input-config.ts
@@ -63,4 +63,6 @@ export type StudioFieldInputConfig = {
step?: number;
value?: string;
+
+ isArray?: boolean;
};
diff --git a/packages/codegen-ui/lib/utils/form-component-metadata.ts b/packages/codegen-ui/lib/utils/form-component-metadata.ts
index cfe591039..5a775c54a 100644
--- a/packages/codegen-ui/lib/utils/form-component-metadata.ts
+++ b/packages/codegen-ui/lib/utils/form-component-metadata.ts
@@ -21,6 +21,7 @@ import {
FieldValidationConfiguration,
FormDefinitionElement,
FormDefinitionInputElement,
+ StudioFieldInputConfig,
} from '../types';
export const getFormFieldStateName = (formName: string) => {
@@ -40,7 +41,6 @@ export const mapFormMetadata = (form: StudioForm, formDefinition: FormDefinition
const updatedConfigs = configs;
const metadata: FieldConfigMetadata = {
validationRules: [],
- isArray: config.componentType === 'ArrayField',
};
if ('validations' in config && config.validations) {
metadata.validationRules = config.validations.map((validation) => {
@@ -52,6 +52,12 @@ export const mapFormMetadata = (form: StudioForm, formDefinition: FormDefinition
if ('dataType' in config && config.dataType) {
metadata.dataType = config.dataType;
}
+ if (form.fields[name] && 'inputType' in form.fields[name]) {
+ const { inputType } = form.fields[name] as { inputType: StudioFieldInputConfig };
+ if (inputType.isArray) {
+ metadata.isArray = inputType.isArray;
+ }
+ }
updatedConfigs[name] = metadata;
return updatedConfigs;
}, {}),
diff --git a/packages/test-generator/lib/forms/custom-form-create-dog.json b/packages/test-generator/lib/forms/custom-form-create-dog.json
index 0e9a91b37..86d0852d5 100644
--- a/packages/test-generator/lib/forms/custom-form-create-dog.json
+++ b/packages/test-generator/lib/forms/custom-form-create-dog.json
@@ -1,48 +1,48 @@
{
- "id": "123",
- "name": "CustomFormCreateDog",
- "formActionType": "create",
- "dataType": {
- "dataSourceType": "Custom",
- "dataTypeName": "Dog"
- },
- "fields": {
- "name": {
- "label": "Name",
- "inputType": {
- "type": "TextField"
- },
- "validations": [{"type": "GreaterThanChar", "numValues": ["1"], "validationMessage": "Name must be longer than 1 character"}]
- },
- "age": {
- "label": "Age",
- "inputType": {
- "type": "NumberField"
- },
- "validations": [{"type": "GreaterThanNum","numValues": ["0"], "validationMessage": "Age must be greater than 0"}]
- },
- "email": {
- "label": "Email",
- "inputType": {
- "type": "EmailField"
- }
- },
- "ip": {
- "label": "IP Address",
- "inputType": {
- "type": "IPAddressField",
- "required": true
- }
- }
- },
- "sectionalElements": {
- "formHeading": {
- "type": "Heading",
- "position": {"fixed": "first"},
- "text": "Register your dog"
- }
- },
- "style": {},
- "cta": {},
- "schemaVersion": "1.0"
- }
\ No newline at end of file
+ "id": "123",
+ "name": "CustomFormCreateDog",
+ "formActionType": "create",
+ "dataType": {
+ "dataSourceType": "Custom",
+ "dataTypeName": "Dog"
+ },
+ "fields": {
+ "name": {
+ "label": "Name",
+ "inputType": {
+ "type": "TextField"
+ },
+ "validations": [{"type": "GreaterThanChar", "numValues": ["1"], "validationMessage": "Name must be longer than 1 character"}]
+ },
+ "age": {
+ "label": "Age",
+ "inputType": {
+ "type": "NumberField"
+ },
+ "validations": [{"type": "GreaterThanNum","numValues": ["0"], "validationMessage": "Age must be greater than 0"}]
+ },
+ "email": {
+ "label": "Email",
+ "inputType": {
+ "type": "EmailField"
+ }
+ },
+ "ip": {
+ "label": "IP Address",
+ "inputType": {
+ "type": "IPAddressField",
+ "required": true
+ }
+ }
+ },
+ "sectionalElements": {
+ "formHeading": {
+ "type": "Heading",
+ "position": {"fixed": "first"},
+ "text": "Register your dog"
+ }
+ },
+ "style": {},
+ "cta": {},
+ "schemaVersion": "1.0"
+}
\ No newline at end of file
diff --git a/packages/test-generator/lib/generators/NodeTestGenerator.ts b/packages/test-generator/lib/generators/NodeTestGenerator.ts
index 8c2e89d4d..1cdca03d5 100644
--- a/packages/test-generator/lib/generators/NodeTestGenerator.ts
+++ b/packages/test-generator/lib/generators/NodeTestGenerator.ts
@@ -141,11 +141,11 @@ export class NodeTestGenerator extends TestGenerator {
return indexRenderer.renderComponent();
}
- writeUtilsFileToDisk(utils: string[]) {
+ writeUtilsFileToDisk(utils: UtilTemplateType[]) {
this.utilsRendererManager.renderSchemaToTemplate(utils);
}
- renderUtilsFile(utils: string[]) {
+ renderUtilsFile(utils: UtilTemplateType[]) {
const utilsRenderer = this.utilsRendererFactory.buildRenderer(utils);
return utilsRenderer.renderComponent();
}
diff --git a/packages/test-generator/lib/generators/TestGenerator.ts b/packages/test-generator/lib/generators/TestGenerator.ts
index 19b792875..8d6f08587 100644
--- a/packages/test-generator/lib/generators/TestGenerator.ts
+++ b/packages/test-generator/lib/generators/TestGenerator.ts
@@ -162,7 +162,7 @@ export abstract class TestGenerator {
}
};
- const generateUtilsFile = (utils: string[]) => {
+ const generateUtilsFile = (utils: UtilTemplateType[]) => {
try {
if (this.params.writeToDisk) {
this.writeUtilsFileToDisk(utils);