diff --git a/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/MemberCreateForm.d.ts b/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/MemberCreateForm.d.ts new file mode 100644 index 000000000..a4eed5ae7 --- /dev/null +++ b/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/MemberCreateForm.d.ts @@ -0,0 +1,46 @@ +/** ************************************************************************* + * The contents of this file were generated with Amplify Studio. * + * Please refrain from making any modifications to this file. * + * Any changes to this file will be overwritten when running amplify pull. * + ************************************************************************* */ +/* eslint-disable */ +import * as React from 'react'; +import { Team } from '../models'; +import { EscapeHatchProps } from '@aws-amplify/ui-react/internal'; +import { AutocompleteProps, GridProps, TextFieldProps } from '@aws-amplify/ui-react'; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction = ( + value: T, + validationResponse: ValidationResponse, +) => ValidationResponse | Promise; +export declare type MyMemberFormInputValues = { + name?: string; + team?: Team; +}; +export declare type MyMemberFormValidationValues = { + name?: ValidationFunction; + team?: ValidationFunction; +}; +export declare type FormProps = Partial & React.DOMAttributes; +export declare type MyMemberFormOverridesProps = { + MyMemberFormGrid?: FormProps; + name?: FormProps; + team?: FormProps; +} & EscapeHatchProps; +export declare type MyMemberFormProps = React.PropsWithChildren< + { + overrides?: MyMemberFormOverridesProps | undefined | null; + } & { + clearOnSuccess?: boolean; + onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onSuccess?: (fields: MyMemberFormInputValues) => void; + onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onValidate?: MyMemberFormValidationValues; + } +>; +export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; diff --git a/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/MemberCreateForm.jsx b/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/MemberCreateForm.jsx new file mode 100644 index 000000000..bb40ce4c4 --- /dev/null +++ b/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/MemberCreateForm.jsx @@ -0,0 +1,364 @@ +/** ************************************************************************* + * The contents of this file were generated with Amplify Studio. * + * Please refrain from making any modifications to this file. * + * Any changes to this file will be overwritten when running amplify pull. * + ************************************************************************* */ + +/* eslint-disable */ +import * as React from 'react'; +import { fetchByPath, validateField } from './utils'; +import { Member, Team } from '../models'; +import { getOverrideProps, useDataStoreBinding } from '@aws-amplify/ui-react/internal'; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from '@aws-amplify/ui-react'; +import { DataStore } from 'aws-amplify'; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, +}) { + const { tokens } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ((currentFieldValue !== undefined || currentFieldValue !== null || currentFieldValue !== '') && !hasError) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + + {label} + {!!items?.length && ( + + {items.map((value, index) => { + return ( + { + setSelectedBadgeIndex(index); + setFieldValue(getBadgeText ? getBadgeText(items[index]) : items[index]); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + { + event.stopPropagation(); + removeItem(index); + }} + /> + + ); + })} + + )} + + + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); +} +export default function MyMemberForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + name: undefined, + team: undefined, + }; + const [name, setName] = React.useState(initialValues.name); + const [team, setTeam] = React.useState(initialValues.team); + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setName(initialValues.name); + setTeam(initialValues.team); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(undefined); + setErrors({}); + }; + const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = React.useState(undefined); + const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); + const teamRef = React.createRef(); + const teamRecords = useDataStoreBinding({ + type: 'collection', + model: Team, + }).items; + const getDisplayValue = { + team: (record) => record?.id, + }; + const validations = { + name: [], + team: [], + }; + const runValidationTasks = async (fieldName, currentValue, getDisplayValue) => { + const value = getDisplayValue ? getDisplayValue(currentValue) : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + return ( + { + event.preventDefault(); + let modelFields = { + name, + team, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item, getDisplayValue[fieldName]), + ), + ); + return promises; + } + promises.push(runValidationTasks(fieldName, modelFields[fieldName], getDisplayValue[fieldName])); + return promises; + }, []), + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + await DataStore.save(new Member(modelFields)); + if (onSuccess) { + onSuccess(modelFields); + } + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...rest} + {...getOverrideProps(overrides, 'MyMemberForm')} + > + + + + + + + + { + let { value } = e.target; + if (onChange) { + const modelFields = { + name: value, + team, + }; + const result = onChange(modelFields); + value = result?.name ?? value; + } + if (errors.name?.hasError) { + runValidationTasks('name', value); + } + setName(value); + }} + onBlur={() => runValidationTasks('name', name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, 'name')} + > + { + let value = items[0]; + if (onChange) { + const modelFields = { + name, + team: value, + }; + const result = onChange(modelFields); + value = result?.team ?? value; + } + setTeam(value); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(undefined); + }} + currentFieldValue={currentTeamValue} + label={'Team'} + items={team ? [team] : []} + hasError={errors.team?.hasError} + getBadgeText={getDisplayValue.team} + setFieldValue={currentTeamDisplayValue} + inputFieldRef={teamRef} + defaultFieldValue={undefined} + > + ({ + id: r.id, + label: getDisplayValue.team?.(r) ?? r.id, + }))} + onSelect={({ id, label }) => { + setCurrentTeamValue(teamRecords.find((r) => r.id === id)); + setCurrentTeamDisplayValue(label); + }} + onChange={(e) => { + let { value } = e.target; + if (errors.team?.hasError) { + runValidationTasks('team', value); + } + setCurrentTeamDisplayValue(value); + setCurrentTeamValue(undefined); + }} + onBlur={() => runValidationTasks('team', team)} + errorMessage={errors.team?.errorMessage} + hasError={errors.team?.hasError} + ref={teamRef} + {...getOverrideProps(overrides, 'team')} + > + + + ); +} diff --git a/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/schema.ts b/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/schema.ts new file mode 100644 index 000000000..f1a6b854e --- /dev/null +++ b/packages/codegen-ui-golden-files/lib/react/forms/datastore-create-form-with-belongs-to/schema.ts @@ -0,0 +1,273 @@ +/* + 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 { StudioForm } from '@aws-amplify/codegen-ui'; +import { Schema } from '@aws-amplify/datastore'; + +export const memberCreateForm: StudioForm = { + name: 'MemberCreateForm', + dataType: { dataSourceType: 'DataStore', dataTypeName: 'Member' }, + formActionType: 'create', + fields: {}, + sectionalElements: {}, + style: {}, + cta: {}, +}; + +export const ProjectSchema: Schema = { + models: { + Member: { + name: 'Member', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: false, + attributes: [], + }, + teamID: { + name: 'teamID', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + Team: { + name: 'Team', + isArray: false, + type: { + model: 'Team', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetName: 'teamMembersId', + }, + }, + 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: 'Members', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'key', + properties: { + name: 'byTeam', + fields: ['teamID'], + }, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Team: { + name: 'Team', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + Project: { + name: 'Project', + isArray: false, + type: { + model: 'Project', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'HAS_ONE', + associatedWith: 'Team', + targetName: 'teamProjectId', + }, + }, + Members: { + name: 'Members', + isArray: true, + type: { + model: 'Member', + }, + isRequired: false, + attributes: [], + isArrayNullable: true, + association: { + connectionType: 'HAS_MANY', + associatedWith: 'teamID', + }, + }, + createdAt: { + name: 'createdAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + updatedAt: { + name: 'updatedAt', + isArray: false, + type: 'AWSDateTime', + isRequired: false, + attributes: [], + isReadOnly: true, + }, + teamProjectId: { + name: 'teamProjectId', + isArray: false, + type: 'ID', + isRequired: false, + attributes: [], + }, + }, + syncable: true, + pluralName: 'Teams', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + Project: { + name: 'Project', + fields: { + id: { + name: 'id', + isArray: false, + type: 'ID', + isRequired: true, + attributes: [], + }, + name: { + name: 'name', + isArray: false, + type: 'String', + isRequired: true, + attributes: [], + }, + Team: { + name: 'Team', + isArray: false, + type: { + model: 'Team', + }, + isRequired: false, + attributes: [], + association: { + connectionType: 'BELONGS_TO', + targetName: 'projectTeamId', + }, + }, + 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: 'Projects', + attributes: [ + { + type: 'model', + properties: {}, + }, + { + type: 'auth', + properties: { + rules: [ + { + allow: 'public', + operations: ['create', 'update', 'delete', 'read'], + }, + ], + }, + }, + ], + }, + }, + enums: {}, + nonModels: {}, + version: 'f4d166560661ebce8a75b9c1b1735ed1', +}; 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 180e79864..d7ff5c9cb 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 @@ -862,48 +862,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( - {isEditing && children} - {!isEditing ? ( - <> - {label} - - - ) : ( - - {(currentFieldValue || isEditing) && ( - - )} - - - )} + {label} {!!items?.length && ( {items.map((value, index) => { @@ -955,6 +916,50 @@ function ArrayField({ ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); } export default function CustomDataForm(props) { const { onSubmit, onCancel, onValidate, onChange, overrides, ...rest } = @@ -1284,48 +1289,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( - {isEditing && children} - {!isEditing ? ( - <> - {label} - - - ) : ( - - {(currentFieldValue || isEditing) && ( - - )} - - - )} + {label} {!!items?.length && ( {items.map((value, index) => { @@ -1377,6 +1343,50 @@ function ArrayField({ ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); } export default function MyPostForm(props) { const { @@ -1849,48 +1859,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( - {isEditing && children} - {!isEditing ? ( - <> - {label} - - - ) : ( - - {(currentFieldValue || isEditing) && ( - - )} - - - )} + {label} {!!items?.length && ( {items.map((value, index) => { @@ -1942,6 +1913,50 @@ function ArrayField({ ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); } export default function NestedJson(props) { const { onSubmit, onValidate, onChange, overrides, ...rest } = props; @@ -2419,48 +2434,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( - {isEditing && children} - {!isEditing ? ( - <> - {label} - - - ) : ( - - {(currentFieldValue || isEditing) && ( - - )} - - - )} + {label} {!!items?.length && ( {items.map((value, index) => { @@ -2512,6 +2488,50 @@ function ArrayField({ ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); } export default function NestedJson(props) { const { initialData, onSubmit, onValidate, onChange, overrides, ...rest } = @@ -3429,48 +3449,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( - {isEditing && children} - {!isEditing ? ( - <> - {label} - - - ) : ( - - {(currentFieldValue || isEditing) && ( - - )} - - - )} + {label} {!!items?.length && ( {items.map((value, index) => { @@ -3522,6 +3503,50 @@ function ArrayField({ ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); } export default function BookCreateForm(props) { const { @@ -3700,7 +3725,7 @@ export default function BookCreateForm(props) { }} currentFieldValue={currentPrimaryAuthorValue} label={\\"Primary author\\"} - items={[primaryAuthor]} + items={primaryAuthor ? [primaryAuthor] : []} hasError={errors?.[\\"primary-author\\"]?.hasError} getBadgeText={getDisplayValue[\\"primary-author\\"]} setFieldValue={currentPrimaryAuthorDisplayValue} @@ -3712,11 +3737,11 @@ export default function BookCreateForm(props) { isRequired={false} isReadOnly={false} value={currentPrimaryAuthorDisplayValue} - suggestions={authorRecords.map((r) => ({ + options={authorRecords.map((r) => ({ id: r.id, label: getDisplayValue[\\"primary-author\\"]?.(r) ?? r.id, }))} - onSuggestionSelect={({ id, label }) => { + onSelect={({ id, label }) => { setCurrentPrimaryAuthorValue( authorRecords.find((r) => r.id === id) ); @@ -3755,7 +3780,7 @@ export default function BookCreateForm(props) { }} currentFieldValue={currentAuthorIdValue} label={\\"Author id\\"} - items={[authorId]} + items={authorId ? [authorId] : []} hasError={errors.authorId?.hasError} setFieldValue={setCurrentAuthorIdValue} inputFieldRef={authorIdRef} @@ -3766,11 +3791,11 @@ export default function BookCreateForm(props) { isRequired={false} isReadOnly={false} value={currentAuthorIdValue} - suggestions={authorRecords.map((r) => ({ + options={authorRecords.map((r) => ({ id: r.id, label: r.id, }))} - onSuggestionSelect={({ id }) => { + onSelect={({ id }) => { setCurrentAuthorIdValue(id); }} onChange={(e) => { @@ -3854,11 +3879,11 @@ export default function BookCreateForm(props: BookCreateFormProps): React.ReactE " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with belongsTo relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { Author, Book } from \\"../models\\"; +import { Member, Team } from \\"../models\\"; import { getOverrideProps, useDataStoreBinding, @@ -3921,48 +3946,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( - {isEditing && children} - {!isEditing ? ( - <> - {label} - - - ) : ( - - {(currentFieldValue || isEditing) && ( - - )} - - - )} + {label} {!!items?.length && ( {items.map((value, index) => { @@ -4014,8 +4000,52 @@ function ArrayField({ ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); } -export default function BookCreateForm(props) { +export default function MyMemberForm(props) { const { clearOnSuccess = true, onSuccess, @@ -4029,37 +4059,32 @@ export default function BookCreateForm(props) { } = props; const initialValues = { name: undefined, - primaryAuthor: undefined, + team: undefined, }; const [name, setName] = React.useState(initialValues.name); - const [primaryAuthor, setPrimaryAuthor] = React.useState( - initialValues.primaryAuthor - ); + const [team, setTeam] = React.useState(initialValues.team); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); - setPrimaryAuthor(initialValues.primaryAuthor); - setCurrentPrimaryAuthorValue(undefined); - setCurrentPrimaryAuthorDisplayValue(undefined); + setTeam(initialValues.team); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(undefined); setErrors({}); }; - const [ - currentPrimaryAuthorDisplayValue, - setCurrentPrimaryAuthorDisplayValue, - ] = React.useState(undefined); - const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = + const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = React.useState(undefined); - const primaryAuthorRef = React.createRef(); - const authorRecords = useDataStoreBinding({ + const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); + const teamRef = React.createRef(); + const teamRecords = useDataStoreBinding({ type: \\"collection\\", - model: Author, + model: Team, }).items; const getDisplayValue = { - primaryAuthor: (record) => record?.name, + team: (record) => record?.name, }; const validations = { name: [], - primaryAuthor: [], + team: [], }; const runValidationTasks = async ( fieldName, @@ -4087,7 +4112,7 @@ export default function BookCreateForm(props) { event.preventDefault(); let modelFields = { name, - primaryAuthor, + team, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -4120,7 +4145,7 @@ export default function BookCreateForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save(new Book(modelFields)); + await DataStore.save(new Member(modelFields)); if (onSuccess) { onSuccess(modelFields); } @@ -4134,7 +4159,7 @@ export default function BookCreateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"BookCreateForm\\")} + {...getOverrideProps(overrides, \\"MyMemberForm\\")} > ({ + value={currentTeamDisplayValue} + options={teamRecords.map((r) => ({ id: r.id, - label: getDisplayValue.primaryAuthor?.(r) ?? r.id, + label: getDisplayValue.team?.(r) ?? r.id, }))} - onSuggestionSelect={({ id, label }) => { - setCurrentPrimaryAuthorValue( - authorRecords.find((r) => r.id === id) - ); - setCurrentPrimaryAuthorDisplayValue(label); + onSelect={({ id, label }) => { + setCurrentTeamValue(teamRecords.find((r) => r.id === id)); + setCurrentTeamDisplayValue(label); }} onChange={(e) => { let { value } = e.target; - if (errors.primaryAuthor?.hasError) { - runValidationTasks(\\"primaryAuthor\\", value); + if (errors.team?.hasError) { + runValidationTasks(\\"team\\", value); } - setCurrentPrimaryAuthorDisplayValue(value); - setCurrentPrimaryAuthorValue(undefined); + setCurrentTeamDisplayValue(value); + setCurrentTeamValue(undefined); }} - onBlur={() => runValidationTasks(\\"primaryAuthor\\", primaryAuthor)} - errorMessage={errors.primaryAuthor?.errorMessage} - hasError={errors.primaryAuthor?.hasError} - ref={primaryAuthorRef} - {...getOverrideProps(overrides, \\"primaryAuthor\\")} + onBlur={() => runValidationTasks(\\"team\\", team)} + errorMessage={errors.team?.errorMessage} + hasError={errors.team?.hasError} + ref={teamRef} + {...getOverrideProps(overrides, \\"team\\")} > @@ -4249,9 +4272,9 @@ export default function BookCreateForm(props) { " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with belongsTo relationship 2`] = ` "import * as React from \\"react\\"; -import { Author } from \\"../models\\"; +import { Team } from \\"../models\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; export declare type ValidationResponse = { @@ -4259,40 +4282,40 @@ export declare type ValidationResponse = { errorMessage?: string; }; export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise; -export declare type BookCreateFormInputValues = { +export declare type MyMemberFormInputValues = { name?: string; - primaryAuthor?: Author; + team?: Team; }; -export declare type BookCreateFormValidationValues = { +export declare type MyMemberFormValidationValues = { name?: ValidationFunction; - primaryAuthor?: ValidationFunction; + team?: ValidationFunction; }; export declare type FormProps = Partial & React.DOMAttributes; -export declare type BookCreateFormOverridesProps = { - BookCreateFormGrid?: FormProps; +export declare type MyMemberFormOverridesProps = { + MyMemberFormGrid?: FormProps; name?: FormProps; - primaryAuthor?: FormProps; + team?: FormProps; } & EscapeHatchProps; -export declare type BookCreateFormProps = React.PropsWithChildren<{ - overrides?: BookCreateFormOverridesProps | undefined | null; +export declare type MyMemberFormProps = React.PropsWithChildren<{ + overrides?: MyMemberFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onSuccess?: (fields: BookCreateFormInputValues) => void; - onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; + onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onSuccess?: (fields: MyMemberFormInputValues) => void; + onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; - onValidate?: BookCreateFormValidationValues; + onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onValidate?: MyMemberFormValidationValues; }>; -export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; +export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { Author, Book, Title } from \\"../models\\"; +import { Author, Book } from \\"../models\\"; import { getOverrideProps, useDataStoreBinding, @@ -4355,48 +4378,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( - {isEditing && children} - {!isEditing ? ( - <> - {label} - - - ) : ( - - {(currentFieldValue || isEditing) && ( - - )} - - - )} + {label} {!!items?.length && ( {items.map((value, index) => { @@ -4448,6 +4432,50 @@ function ArrayField({ ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + + {isEditing && children} + {!isEditing ? ( + <> + {label} + + + ) : ( + + {(currentFieldValue || isEditing) && ( + + )} + + + )} + + ); } export default function BookCreateForm(props) { const { @@ -4464,24 +4492,17 @@ export default function BookCreateForm(props) { const initialValues = { name: undefined, primaryAuthor: undefined, - primaryTitle: undefined, }; const [name, setName] = React.useState(initialValues.name); const [primaryAuthor, setPrimaryAuthor] = React.useState( initialValues.primaryAuthor ); - const [primaryTitle, setPrimaryTitle] = React.useState( - initialValues.primaryTitle - ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { setName(initialValues.name); setPrimaryAuthor(initialValues.primaryAuthor); setCurrentPrimaryAuthorValue(undefined); setCurrentPrimaryAuthorDisplayValue(undefined); - setPrimaryTitle(initialValues.primaryTitle); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(undefined); setErrors({}); }; const [ @@ -4491,27 +4512,16 @@ export default function BookCreateForm(props) { const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = React.useState(undefined); const primaryAuthorRef = React.createRef(); - const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = - React.useState(undefined); - const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = - React.useState(undefined); - const primaryTitleRef = React.createRef(); const authorRecords = useDataStoreBinding({ type: \\"collection\\", model: Author, }).items; - const titleRecords = useDataStoreBinding({ - type: \\"collection\\", - model: Title, - }).items; const getDisplayValue = { primaryAuthor: (record) => record?.name, - primaryTitle: (record) => record?.name, }; const validations = { name: [], primaryAuthor: [], - primaryTitle: [], }; const runValidationTasks = async ( fieldName, @@ -4540,7 +4550,6 @@ export default function BookCreateForm(props) { let modelFields = { name, primaryAuthor, - primaryTitle, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -4627,7 +4636,6 @@ export default function BookCreateForm(props) { const modelFields = { name: value, primaryAuthor, - primaryTitle, }; const result = onChange(modelFields); value = result?.name ?? value; @@ -4650,7 +4658,6 @@ export default function BookCreateForm(props) { const modelFields = { name, primaryAuthor: value, - primaryTitle, }; const result = onChange(modelFields); value = result?.primaryAuthor ?? value; @@ -4661,7 +4668,7 @@ export default function BookCreateForm(props) { }} currentFieldValue={currentPrimaryAuthorValue} label={\\"Primary author\\"} - items={[primaryAuthor]} + items={primaryAuthor ? [primaryAuthor] : []} hasError={errors.primaryAuthor?.hasError} getBadgeText={getDisplayValue.primaryAuthor} setFieldValue={currentPrimaryAuthorDisplayValue} @@ -4673,11 +4680,11 @@ export default function BookCreateForm(props) { isRequired={false} isReadOnly={false} value={currentPrimaryAuthorDisplayValue} - suggestions={authorRecords.map((r) => ({ + options={authorRecords.map((r) => ({ id: r.id, label: getDisplayValue.primaryAuthor?.(r) ?? r.id, }))} - onSuggestionSelect={({ id, label }) => { + onSelect={({ id, label }) => { setCurrentPrimaryAuthorValue( authorRecords.find((r) => r.id === id) ); @@ -4698,69 +4705,15 @@ export default function BookCreateForm(props) { {...getOverrideProps(overrides, \\"primaryAuthor\\")} > - { - let value = items[0]; - if (onChange) { - const modelFields = { - name, - primaryAuthor, - primaryTitle: value, - }; - const result = onChange(modelFields); - value = result?.primaryTitle ?? value; - } - setPrimaryTitle(value); - setCurrentPrimaryTitleValue(undefined); - setCurrentPrimaryTitleDisplayValue(undefined); - }} - currentFieldValue={currentPrimaryTitleValue} - label={\\"Primary title\\"} - items={[primaryTitle]} - hasError={errors.primaryTitle?.hasError} - getBadgeText={getDisplayValue.primaryTitle} - setFieldValue={currentPrimaryTitleDisplayValue} - inputFieldRef={primaryTitleRef} - defaultFieldValue={undefined} - > - ({ - id: r.id, - label: getDisplayValue.primaryTitle?.(r) ?? r.id, - }))} - onSuggestionSelect={({ id, label }) => { - setCurrentPrimaryTitleValue(titleRecords.find((r) => r.id === id)); - setCurrentPrimaryTitleDisplayValue(label); - }} - onChange={(e) => { - let { value } = e.target; - if (errors.primaryTitle?.hasError) { - runValidationTasks(\\"primaryTitle\\", value); - } - setCurrentPrimaryTitleDisplayValue(value); - setCurrentPrimaryTitleValue(undefined); - }} - onBlur={() => runValidationTasks(\\"primaryTitle\\", primaryTitle)} - errorMessage={errors.primaryTitle?.errorMessage} - hasError={errors.primaryTitle?.hasError} - ref={primaryTitleRef} - {...getOverrideProps(overrides, \\"primaryTitle\\")} - > - ); } " `; -exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with hasOne relationship 2`] = ` "import * as React from \\"react\\"; -import { Author, Title } from \\"../models\\"; +import { Author } from \\"../models\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; export declare type ValidationResponse = { @@ -4771,19 +4724,16 @@ export declare type ValidationFunction = (value: T, validationResponse: Valid export declare type BookCreateFormInputValues = { name?: string; primaryAuthor?: Author; - primaryTitle?: Title; }; export declare type BookCreateFormValidationValues = { name?: ValidationFunction; primaryAuthor?: ValidationFunction; - primaryTitle?: ValidationFunction; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; export declare type BookCreateFormOverridesProps = { BookCreateFormGrid?: FormProps<GridProps>; name?: FormProps<TextFieldProps>; primaryAuthor?: FormProps<AutocompleteProps>; - primaryTitle?: FormProps<AutocompleteProps>; } & EscapeHatchProps; export declare type BookCreateFormProps = React.PropsWithChildren<{ overrides?: BookCreateFormOverridesProps | undefined | null; @@ -4800,25 +4750,176 @@ export default function BookCreateForm(props: BookCreateFormProps): React.ReactE " `; -exports[`amplify form renderer tests datastore form tests should generate a update form 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { Post } from \\"../models\\"; -import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { Author, Book, Title } from \\"../models\\"; +import { + getOverrideProps, + useDataStoreBinding, +} from \\"@aws-amplify/ui-react/internal\\"; import { + Autocomplete, + Badge, Button, + Divider, Flex, Grid, - TextAreaField, + Icon, + ScrollView, + Text, TextField, + useTheme, } from \\"@aws-amplify/ui-react\\"; import { DataStore } from \\"aws-amplify\\"; -export default function MyPostForm(props) { - const { - id, - post, - onSuccess, +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, +}) { + const { tokens } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ( + (currentFieldValue !== undefined || + currentFieldValue !== null || + currentFieldValue !== \\"\\") && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + <React.Fragment> + <Text>{label}</Text> + {!!items?.length && ( + <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> + {items.map((value, index) => { + return ( + <Badge + key={index} + style={{ + cursor: \\"pointer\\", + alignItems: \\"center\\", + marginRight: 3, + marginTop: 3, + backgroundColor: + index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", + }} + onClick={() => { + setSelectedBadgeIndex(index); + setFieldValue( + getBadgeText ? getBadgeText(items[index]) : items[index] + ); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + <Icon + style={{ + cursor: \\"pointer\\", + paddingLeft: 3, + width: 20, + height: 20, + }} + viewBox={{ width: 20, height: 20 }} + paths={[ + { + d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", + stroke: \\"black\\", + }, + ]} + ariaLabel=\\"button\\" + onClick={(event) => { + event.stopPropagation(); + removeItem(index); + }} + /> + </Badge> + ); + })} + </ScrollView> + )} + <Divider orientation=\\"horizontal\\" marginTop={5} /> + </React.Fragment> + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + <React.Fragment> + {isEditing && children} + {!isEditing ? ( + <> + <Text>{label}</Text> + <Button + onClick={() => { + setIsEditing(true); + }} + > + Add item + </Button> + </> + ) : ( + <Flex justifyContent=\\"flex-end\\"> + {(currentFieldValue || isEditing) && ( + <Button + children=\\"Cancel\\" + type=\\"button\\" + size=\\"small\\" + onClick={() => { + setFieldValue(defaultFieldValue); + setIsEditing(false); + setSelectedBadgeIndex(undefined); + }} + ></Button> + )} + <Button + size=\\"small\\" + variation=\\"link\\" + color={tokens.colors.brand.primary[80]} + isDisabled={hasError} + onClick={addItem} + > + {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} + </Button> + </Flex> + )} + </React.Fragment> + ); +} +export default function BookCreateForm(props) { + const { + clearOnSuccess = true, + onSuccess, onError, onSubmit, onCancel, @@ -4828,52 +4929,56 @@ export default function MyPostForm(props) { ...rest } = props; const initialValues = { - TextAreaFieldbbd63464: undefined, - caption: undefined, - username: undefined, - profile_url: undefined, - post_url: undefined, - metadata: undefined, + name: undefined, + primaryAuthor: undefined, + primaryTitle: undefined, }; - const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( - initialValues.TextAreaFieldbbd63464 - ); - const [caption, setCaption] = React.useState(initialValues.caption); - const [username, setUsername] = React.useState(initialValues.username); - const [profile_url, setProfile_url] = React.useState( - initialValues.profile_url + const [name, setName] = React.useState(initialValues.name); + const [primaryAuthor, setPrimaryAuthor] = React.useState( + initialValues.primaryAuthor ); - const [post_url, setPost_url] = React.useState(initialValues.post_url); - const [metadata, setMetadata] = React.useState( - initialValues.metadata ? JSON.stringify(initialValues.metadata) : undefined + const [primaryTitle, setPrimaryTitle] = React.useState( + initialValues.primaryTitle ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - const cleanValues = { ...initialValues, ...postRecord }; - setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); - setCaption(cleanValues.caption); - setUsername(cleanValues.username); - setProfile_url(cleanValues.profile_url); - setPost_url(cleanValues.post_url); - setMetadata(cleanValues.metadata); + setName(initialValues.name); + setPrimaryAuthor(initialValues.primaryAuthor); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(undefined); + setPrimaryTitle(initialValues.primaryTitle); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(undefined); setErrors({}); }; - const [postRecord, setPostRecord] = React.useState(post); - React.useEffect(() => { - const queryData = async () => { - const record = id ? await DataStore.query(Post, id) : post; - setPostRecord(record); - }; - queryData(); - }, [id, post]); - React.useEffect(resetStateValues, [postRecord]); + const [ + currentPrimaryAuthorDisplayValue, + setCurrentPrimaryAuthorDisplayValue, + ] = React.useState(undefined); + const [currentPrimaryAuthorValue, setCurrentPrimaryAuthorValue] = + React.useState(undefined); + const primaryAuthorRef = React.createRef(); + const [currentPrimaryTitleDisplayValue, setCurrentPrimaryTitleDisplayValue] = + React.useState(undefined); + const [currentPrimaryTitleValue, setCurrentPrimaryTitleValue] = + React.useState(undefined); + const primaryTitleRef = React.createRef(); + const authorRecords = useDataStoreBinding({ + type: \\"collection\\", + model: Author, + }).items; + const titleRecords = useDataStoreBinding({ + type: \\"collection\\", + model: Title, + }).items; + const getDisplayValue = { + primaryAuthor: (record) => record?.name, + primaryTitle: (record) => record?.name, + }; const validations = { - TextAreaFieldbbd63464: [], - caption: [], - username: [], - profile_url: [{ type: \\"URL\\" }], - post_url: [{ type: \\"URL\\" }], - metadata: [{ type: \\"JSON\\" }], + name: [], + primaryAuthor: [], + primaryTitle: [], }; const runValidationTasks = async ( fieldName, @@ -4900,25 +5005,30 @@ export default function MyPostForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata, + name, + primaryAuthor, + primaryTitle, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { if (Array.isArray(modelFields[fieldName])) { promises.push( ...modelFields[fieldName].map((item) => - runValidationTasks(fieldName, item) + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) ) ); return promises; } promises.push( - runValidationTasks(fieldName, modelFields[fieldName]) + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) ); return promises; }, []) @@ -4930,14 +5040,13 @@ export default function MyPostForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save( - Post.copyOf(postRecord, (updated) => { - Object.assign(updated, modelFields); - }) - ); + await DataStore.save(new Book(modelFields)); if (onSuccess) { onSuccess(modelFields); } + if (clearOnSuccess) { + resetStateValues(); + } } catch (err) { if (onError) { onError(modelFields, err.message); @@ -4945,17 +5054,17 @@ export default function MyPostForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"MyPostForm\\")} + {...getOverrideProps(overrides, \\"BookCreateForm\\")} > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button - children=\\"Reset\\" + children=\\"Clear\\" type=\\"reset\\" onClick={resetStateValues} - {...getOverrideProps(overrides, \\"ResetButton\\")} + {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> <Button @@ -4975,466 +5084,263 @@ export default function MyPostForm(props) { ></Button> </Flex> </Flex> - <TextAreaField - label=\\"Label\\" - defaultValue={TextAreaFieldbbd63464} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464: value, - caption, - username, - profile_url, - post_url, - metadata, - }; - const result = onChange(modelFields); - value = result?.TextAreaFieldbbd63464 ?? value; - } - if (errors.TextAreaFieldbbd63464?.hasError) { - runValidationTasks(\\"TextAreaFieldbbd63464\\", value); - } - setTextAreaFieldbbd63464(value); - }} - onBlur={() => - runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) - } - errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} - hasError={errors.TextAreaFieldbbd63464?.hasError} - {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} - ></TextAreaField> - <TextField - label=\\"Caption\\" - isRequired={false} - isReadOnly={false} - defaultValue={caption} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption: value, - username, - profile_url, - post_url, - metadata, - }; - const result = onChange(modelFields); - value = result?.caption ?? value; - } - if (errors.caption?.hasError) { - runValidationTasks(\\"caption\\", value); - } - setCaption(value); - }} - onBlur={() => runValidationTasks(\\"caption\\", caption)} - errorMessage={errors.caption?.errorMessage} - hasError={errors.caption?.hasError} - {...getOverrideProps(overrides, \\"caption\\")} - ></TextField> - <TextField - label=\\"Username\\" - isRequired={false} - isReadOnly={false} - defaultValue={username} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - TextAreaFieldbbd63464, - caption, - username: value, - profile_url, - post_url, - metadata, - }; - const result = onChange(modelFields); - value = result?.username ?? value; - } - if (errors.username?.hasError) { - runValidationTasks(\\"username\\", value); - } - setUsername(value); - }} - onBlur={() => runValidationTasks(\\"username\\", username)} - errorMessage={errors.username?.errorMessage} - hasError={errors.username?.hasError} - {...getOverrideProps(overrides, \\"username\\")} - ></TextField> <TextField - label=\\"Profile url\\" + label=\\"Name\\" isRequired={false} isReadOnly={false} - defaultValue={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url: value, - post_url, - metadata, + name: value, + primaryAuthor, + primaryTitle, }; const result = onChange(modelFields); - value = result?.profile_url ?? value; + value = result?.name ?? value; } - if (errors.profile_url?.hasError) { - runValidationTasks(\\"profile_url\\", value); + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); } - setProfile_url(value); + setName(value); }} - onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} - errorMessage={errors.profile_url?.errorMessage} - hasError={errors.profile_url?.hasError} - {...getOverrideProps(overrides, \\"profile_url\\")} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} ></TextField> - <TextField - label=\\"Post url\\" - isRequired={false} - isReadOnly={false} - defaultValue={post_url} - onChange={(e) => { - let { value } = e.target; + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url: value, - metadata, + name, + primaryAuthor: value, + primaryTitle, }; const result = onChange(modelFields); - value = result?.post_url ?? value; - } - if (errors.post_url?.hasError) { - runValidationTasks(\\"post_url\\", value); + value = result?.primaryAuthor ?? value; } - setPost_url(value); + setPrimaryAuthor(value); + setCurrentPrimaryAuthorValue(undefined); + setCurrentPrimaryAuthorDisplayValue(undefined); }} - onBlur={() => runValidationTasks(\\"post_url\\", post_url)} - errorMessage={errors.post_url?.errorMessage} - hasError={errors.post_url?.hasError} - {...getOverrideProps(overrides, \\"post_url\\")} - ></TextField> - <TextAreaField - label=\\"Metadata\\" - isRequired={false} - isReadOnly={false} - defaultValue={metadata} - onChange={(e) => { - let { value } = e.target; + currentFieldValue={currentPrimaryAuthorValue} + label={\\"Primary author\\"} + items={primaryAuthor ? [primaryAuthor] : []} + hasError={errors.primaryAuthor?.hasError} + getBadgeText={getDisplayValue.primaryAuthor} + setFieldValue={currentPrimaryAuthorDisplayValue} + inputFieldRef={primaryAuthorRef} + defaultFieldValue={undefined} + > + <Autocomplete + label=\\"Primary author\\" + isRequired={false} + isReadOnly={false} + value={currentPrimaryAuthorDisplayValue} + options={authorRecords.map((r) => ({ + id: r.id, + label: getDisplayValue.primaryAuthor?.(r) ?? r.id, + }))} + onSelect={({ id, label }) => { + setCurrentPrimaryAuthorValue( + authorRecords.find((r) => r.id === id) + ); + setCurrentPrimaryAuthorDisplayValue(label); + }} + onChange={(e) => { + let { value } = e.target; + if (errors.primaryAuthor?.hasError) { + runValidationTasks(\\"primaryAuthor\\", value); + } + setCurrentPrimaryAuthorDisplayValue(value); + setCurrentPrimaryAuthorValue(undefined); + }} + onBlur={() => runValidationTasks(\\"primaryAuthor\\", primaryAuthor)} + errorMessage={errors.primaryAuthor?.errorMessage} + hasError={errors.primaryAuthor?.hasError} + ref={primaryAuthorRef} + {...getOverrideProps(overrides, \\"primaryAuthor\\")} + ></Autocomplete> + </ArrayField> + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; if (onChange) { const modelFields = { - TextAreaFieldbbd63464, - caption, - username, - profile_url, - post_url, - metadata: value, + name, + primaryAuthor, + primaryTitle: value, }; const result = onChange(modelFields); - value = result?.metadata ?? value; - } - if (errors.metadata?.hasError) { - runValidationTasks(\\"metadata\\", value); + value = result?.primaryTitle ?? value; } - setMetadata(value); + setPrimaryTitle(value); + setCurrentPrimaryTitleValue(undefined); + setCurrentPrimaryTitleDisplayValue(undefined); }} - onBlur={() => runValidationTasks(\\"metadata\\", metadata)} - errorMessage={errors.metadata?.errorMessage} - hasError={errors.metadata?.hasError} - {...getOverrideProps(overrides, \\"metadata\\")} - ></TextAreaField> - <Flex - justifyContent=\\"space-between\\" - {...getOverrideProps(overrides, \\"CTAFlex\\")} + currentFieldValue={currentPrimaryTitleValue} + label={\\"Primary title\\"} + items={primaryTitle ? [primaryTitle] : []} + hasError={errors.primaryTitle?.hasError} + getBadgeText={getDisplayValue.primaryTitle} + setFieldValue={currentPrimaryTitleDisplayValue} + inputFieldRef={primaryTitleRef} + defaultFieldValue={undefined} > - <Button - children=\\"Reset\\" - type=\\"reset\\" - onClick={resetStateValues} - {...getOverrideProps(overrides, \\"ResetButton\\")} - ></Button> - <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> - <Button - children=\\"Submit\\" - type=\\"submit\\" - variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e?.hasError)} - {...getOverrideProps(overrides, \\"SubmitButton\\")} - ></Button> - </Flex> - </Flex> + <Autocomplete + label=\\"Primary title\\" + isRequired={false} + isReadOnly={false} + value={currentPrimaryTitleDisplayValue} + options={titleRecords.map((r) => ({ + id: r.id, + label: getDisplayValue.primaryTitle?.(r) ?? r.id, + }))} + onSelect={({ id, label }) => { + setCurrentPrimaryTitleValue(titleRecords.find((r) => r.id === id)); + setCurrentPrimaryTitleDisplayValue(label); + }} + onChange={(e) => { + let { value } = e.target; + if (errors.primaryTitle?.hasError) { + runValidationTasks(\\"primaryTitle\\", value); + } + setCurrentPrimaryTitleDisplayValue(value); + setCurrentPrimaryTitleValue(undefined); + }} + onBlur={() => runValidationTasks(\\"primaryTitle\\", primaryTitle)} + errorMessage={errors.primaryTitle?.errorMessage} + hasError={errors.primaryTitle?.hasError} + ref={primaryTitleRef} + {...getOverrideProps(overrides, \\"primaryTitle\\")} + ></Autocomplete> + </ArrayField> </Grid> ); } " `; -exports[`amplify form renderer tests datastore form tests should generate a update form 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate a create form with multiple hasOne relationships 2`] = ` "import * as React from \\"react\\"; -import { Post } from \\"../models\\"; +import { Author, Title } from \\"../models\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type MyPostFormInputValues = { - TextAreaFieldbbd63464?: string; - caption?: string; - username?: string; - profile_url?: string; - post_url?: string; - metadata?: string; +export declare type BookCreateFormInputValues = { + name?: string; + primaryAuthor?: Author; + primaryTitle?: Title; }; -export declare type MyPostFormValidationValues = { - TextAreaFieldbbd63464?: ValidationFunction<string>; - caption?: ValidationFunction<string>; - username?: ValidationFunction<string>; - profile_url?: ValidationFunction<string>; - post_url?: ValidationFunction<string>; - metadata?: ValidationFunction<string>; +export declare type BookCreateFormValidationValues = { + name?: ValidationFunction<string>; + primaryAuthor?: ValidationFunction<Author>; + primaryTitle?: ValidationFunction<Title>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type MyPostFormOverridesProps = { - MyPostFormGrid?: FormProps<GridProps>; - TextAreaFieldbbd63464?: FormProps<TextAreaFieldProps>; - caption?: FormProps<TextFieldProps>; - username?: FormProps<TextFieldProps>; - profile_url?: FormProps<TextFieldProps>; - post_url?: FormProps<TextFieldProps>; - metadata?: FormProps<TextAreaFieldProps>; +export declare type BookCreateFormOverridesProps = { + BookCreateFormGrid?: FormProps<GridProps>; + name?: FormProps<TextFieldProps>; + primaryAuthor?: FormProps<AutocompleteProps>; + primaryTitle?: FormProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type MyPostFormProps = React.PropsWithChildren<{ - overrides?: MyPostFormOverridesProps | undefined | null; +export declare type BookCreateFormProps = React.PropsWithChildren<{ + overrides?: BookCreateFormOverridesProps | undefined | null; } & { - id?: string; - post?: Post; - onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onSuccess?: (fields: MyPostFormInputValues) => void; - onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + clearOnSuccess?: boolean; + onSubmit?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onSuccess?: (fields: BookCreateFormInputValues) => void; + onError?: (fields: BookCreateFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; - onValidate?: MyPostFormValidationValues; + onChange?: (fields: BookCreateFormInputValues) => BookCreateFormInputValues; + onValidate?: BookCreateFormValidationValues; }>; -export default function MyPostForm(props: MyPostFormProps): React.ReactElement; +export default function BookCreateForm(props: BookCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should render a form with a javascript reserved word as the field name 1`] = ` +exports[`amplify form renderer tests datastore form tests should generate a update form 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { Blog } from \\"../models\\"; +import { Post } from \\"../models\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { - Badge, Button, - Divider, Flex, Grid, - Icon, - ScrollView, - SwitchField, - Text, + TextAreaField, TextField, - useTheme, } from \\"@aws-amplify/ui-react\\"; import { DataStore } from \\"aws-amplify\\"; -function ArrayField({ - items = [], - onChange, - label, - inputFieldRef, - children, - hasError, - setFieldValue, - currentFieldValue, - defaultFieldValue, - lengthLimit, - getBadgeText, -}) { - const { tokens } = useTheme(); - const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); - const [isEditing, setIsEditing] = React.useState(); - React.useEffect(() => { - if (isEditing) { - inputFieldRef?.current?.focus(); - } - }, [isEditing]); - const removeItem = async (removeIndex) => { - const newItems = items.filter((value, index) => index !== removeIndex); - await onChange(newItems); - setSelectedBadgeIndex(undefined); - }; - const addItem = async () => { - if ( - (currentFieldValue !== undefined || - currentFieldValue !== null || - currentFieldValue !== \\"\\") && - !hasError - ) { - const newItems = [...items]; - if (selectedBadgeIndex !== undefined) { - newItems[selectedBadgeIndex] = currentFieldValue; - setSelectedBadgeIndex(undefined); - } else { - newItems.push(currentFieldValue); - } - await onChange(newItems); - setIsEditing(false); - } - }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( - <React.Fragment> - {isEditing && children} - {!isEditing ? ( - <> - <Text>{label}</Text> - <Button - onClick={() => { - setIsEditing(true); - }} - > - Add item - </Button> - </> - ) : ( - <Flex justifyContent=\\"flex-end\\"> - {(currentFieldValue || isEditing) && ( - <Button - children=\\"Cancel\\" - type=\\"button\\" - size=\\"small\\" - onClick={() => { - setFieldValue(defaultFieldValue); - setIsEditing(false); - setSelectedBadgeIndex(undefined); - }} - ></Button> - )} - <Button - size=\\"small\\" - variation=\\"link\\" - color={tokens.colors.brand.primary[80]} - isDisabled={hasError} - onClick={addItem} - > - {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} - </Button> - </Flex> - )} - {!!items?.length && ( - <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> - {items.map((value, index) => { - return ( - <Badge - key={index} - style={{ - cursor: \\"pointer\\", - alignItems: \\"center\\", - marginRight: 3, - marginTop: 3, - backgroundColor: - index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", - }} - onClick={() => { - setSelectedBadgeIndex(index); - setFieldValue( - getBadgeText ? getBadgeText(items[index]) : items[index] - ); - setIsEditing(true); - }} - > - {getBadgeText ? getBadgeText(value) : value.toString()} - <Icon - style={{ - cursor: \\"pointer\\", - paddingLeft: 3, - width: 20, - height: 20, - }} - viewBox={{ width: 20, height: 20 }} - paths={[ - { - d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", - stroke: \\"black\\", - }, - ]} - ariaLabel=\\"button\\" - onClick={(event) => { - event.stopPropagation(); - removeItem(index); - }} - /> - </Badge> - ); - })} - </ScrollView> - )} - <Divider orientation=\\"horizontal\\" marginTop={5} /> - </React.Fragment> - ); -} -export default function BlogCreateForm(props) { +export default function MyPostForm(props) { const { - clearOnSuccess = true, + id, + post, onSuccess, onError, onSubmit, + onCancel, onValidate, onChange, overrides, ...rest } = props; const initialValues = { - title: undefined, - content: undefined, - switch: false, - published: undefined, - editedAt: [], + TextAreaFieldbbd63464: undefined, + caption: undefined, + username: undefined, + profile_url: undefined, + post_url: undefined, + metadata: undefined, }; - const [title, setTitle] = React.useState(initialValues.title); - const [content, setContent] = React.useState(initialValues.content); - const [switch1, setSwitch1] = React.useState(initialValues.switch); - const [published, setPublished] = React.useState(initialValues.published); - const [editedAt, setEditedAt] = React.useState(initialValues.editedAt); + const [TextAreaFieldbbd63464, setTextAreaFieldbbd63464] = React.useState( + initialValues.TextAreaFieldbbd63464 + ); + const [caption, setCaption] = React.useState(initialValues.caption); + const [username, setUsername] = React.useState(initialValues.username); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [metadata, setMetadata] = React.useState( + initialValues.metadata ? JSON.stringify(initialValues.metadata) : undefined + ); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setTitle(initialValues.title); - setContent(initialValues.content); - setSwitch1(initialValues.switch); - setPublished(initialValues.published); - setEditedAt(initialValues.editedAt); - setCurrentEditedAtValue(undefined); + const cleanValues = { ...initialValues, ...postRecord }; + setTextAreaFieldbbd63464(cleanValues.TextAreaFieldbbd63464); + setCaption(cleanValues.caption); + setUsername(cleanValues.username); + setProfile_url(cleanValues.profile_url); + setPost_url(cleanValues.post_url); + setMetadata(cleanValues.metadata); setErrors({}); }; - const [currentEditedAtValue, setCurrentEditedAtValue] = - React.useState(undefined); - const editedAtRef = React.createRef(); + const [postRecord, setPostRecord] = React.useState(post); + React.useEffect(() => { + const queryData = async () => { + const record = id ? await DataStore.query(Post, id) : post; + setPostRecord(record); + }; + queryData(); + }, [id, post]); + React.useEffect(resetStateValues, [postRecord]); const validations = { - title: [], - content: [], - switch: [], - published: [], - editedAt: [], + TextAreaFieldbbd63464: [], + caption: [], + username: [], + profile_url: [{ type: \\"URL\\" }], + post_url: [{ type: \\"URL\\" }], + metadata: [{ type: \\"JSON\\" }], }; const runValidationTasks = async ( fieldName, @@ -5452,29 +5358,6 @@ export default function BlogCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const convertTimeStampToDate = (ts) => { - if (Math.abs(Date.now() - ts) < Math.abs(Date.now() - ts * 1000)) { - return new Date(ts); - } - return new Date(ts * 1000); - }; - const convertToLocal = (date) => { - const df = new Intl.DateTimeFormat(\\"default\\", { - year: \\"numeric\\", - month: \\"2-digit\\", - day: \\"2-digit\\", - hour: \\"2-digit\\", - minute: \\"2-digit\\", - calendar: \\"iso8601\\", - numberingSystem: \\"latn\\", - hour12: false, - }); - const parts = df.formatToParts(date).reduce((acc, part) => { - acc[part.type] = part.value; - return acc; - }, {}); - return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; - }; return ( <Grid as=\\"form\\" @@ -5484,11 +5367,12 @@ export default function BlogCreateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - title, - content, - switch: switch1, - published, - editedAt, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -5513,13 +5397,14 @@ export default function BlogCreateForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save(new Blog(modelFields)); + await DataStore.save( + Post.copyOf(postRecord, (updated) => { + Object.assign(updated, modelFields); + }) + ); if (onSuccess) { onSuccess(modelFields); } - if (clearOnSuccess) { - resetStateValues(); - } } catch (err) { if (onError) { onError(modelFields, err.message); @@ -5527,185 +5412,229 @@ export default function BlogCreateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"BlogCreateForm\\")} + {...getOverrideProps(overrides, \\"MyPostForm\\")} > - <TextField - label=\\"Title\\" - isRequired={false} - isReadOnly={false} + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={resetStateValues} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={Object.values(errors).some((e) => e?.hasError)} + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> + <TextAreaField + label=\\"Label\\" + defaultValue={TextAreaFieldbbd63464} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - title: value, - content, - switch: switch1, - published, - editedAt, + TextAreaFieldbbd63464: value, + caption, + username, + profile_url, + post_url, + metadata, }; const result = onChange(modelFields); - value = result?.title ?? value; + value = result?.TextAreaFieldbbd63464 ?? value; } - if (errors.title?.hasError) { - runValidationTasks(\\"title\\", value); + if (errors.TextAreaFieldbbd63464?.hasError) { + runValidationTasks(\\"TextAreaFieldbbd63464\\", value); } - setTitle(value); + setTextAreaFieldbbd63464(value); }} - onBlur={() => runValidationTasks(\\"title\\", title)} - errorMessage={errors.title?.errorMessage} - hasError={errors.title?.hasError} - {...getOverrideProps(overrides, \\"title\\")} - ></TextField> + onBlur={() => + runValidationTasks(\\"TextAreaFieldbbd63464\\", TextAreaFieldbbd63464) + } + errorMessage={errors.TextAreaFieldbbd63464?.errorMessage} + hasError={errors.TextAreaFieldbbd63464?.hasError} + {...getOverrideProps(overrides, \\"TextAreaFieldbbd63464\\")} + ></TextAreaField> <TextField - label=\\"Content\\" + label=\\"Caption\\" isRequired={false} isReadOnly={false} + defaultValue={caption} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - title, - content: value, - switch: switch1, - published, - editedAt, + TextAreaFieldbbd63464, + caption: value, + username, + profile_url, + post_url, + metadata, }; const result = onChange(modelFields); - value = result?.content ?? value; + value = result?.caption ?? value; } - if (errors.content?.hasError) { - runValidationTasks(\\"content\\", value); + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); } - setContent(value); + setCaption(value); }} - onBlur={() => runValidationTasks(\\"content\\", content)} - errorMessage={errors.content?.errorMessage} - hasError={errors.content?.hasError} - {...getOverrideProps(overrides, \\"content\\")} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} ></TextField> - <SwitchField - label=\\"Switch\\" - defaultChecked={false} - isDisabled={false} - isChecked={switch1} + <TextField + label=\\"Username\\" + isRequired={false} + isReadOnly={false} + defaultValue={username} onChange={(e) => { - let value = e.target.checked; + let { value } = e.target; if (onChange) { const modelFields = { - title, - content, - switch: value, - published, - editedAt, + TextAreaFieldbbd63464, + caption, + username: value, + profile_url, + post_url, + metadata, }; const result = onChange(modelFields); - value = result?.switch ?? value; + value = result?.username ?? value; } - if (errors.switch?.hasError) { - runValidationTasks(\\"switch\\", value); + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); } - setSwitch1(value); + setUsername(value); }} - onBlur={() => runValidationTasks(\\"switch\\", switch1)} - errorMessage={errors.switch?.errorMessage} - hasError={errors.switch?.hasError} - {...getOverrideProps(overrides, \\"switch\\")} - ></SwitchField> + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + ></TextField> <TextField - label=\\"Published\\" + label=\\"Profile url\\" isRequired={false} isReadOnly={false} - type=\\"datetime-local\\" + defaultValue={profile_url} onChange={(e) => { let { value } = e.target; if (onChange) { const modelFields = { - title, - content, - switch: switch1, - published: value, - editedAt, + TextAreaFieldbbd63464, + caption, + username, + profile_url: value, + post_url, + metadata, }; const result = onChange(modelFields); - value = result?.published ?? value; + value = result?.profile_url ?? value; } - if (errors.published?.hasError) { - runValidationTasks(\\"published\\", value); + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); } - setPublished(new Date(value).toISOString()); + setProfile_url(value); }} - onBlur={() => runValidationTasks(\\"published\\", published)} - errorMessage={errors.published?.errorMessage} - hasError={errors.published?.hasError} - {...getOverrideProps(overrides, \\"published\\")} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} ></TextField> - <ArrayField - onChange={async (items) => { - let values = items; + <TextField + label=\\"Post url\\" + isRequired={false} + isReadOnly={false} + defaultValue={post_url} + onChange={(e) => { + let { value } = e.target; if (onChange) { const modelFields = { - title, - content, - switch: switch1, - published, - editedAt: values, + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url: value, + metadata, }; const result = onChange(modelFields); - values = result?.editedAt ?? values; + value = result?.post_url ?? value; } - setEditedAt(values); - setCurrentEditedAtValue(undefined); + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); }} - currentFieldValue={currentEditedAtValue} - label={\\"Edited at\\"} - items={editedAt} - hasError={errors.editedAt?.hasError} - setFieldValue={setCurrentEditedAtValue} - inputFieldRef={editedAtRef} - defaultFieldValue={undefined} - > - <TextField - label=\\"Edited at\\" - isRequired={false} - isReadOnly={false} - type=\\"datetime-local\\" - value={ - currentEditedAtValue && - convertToLocal(convertTimeStampToDate(currentEditedAtValue)) + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + ></TextField> + <TextAreaField + label=\\"Metadata\\" + isRequired={false} + isReadOnly={false} + defaultValue={metadata} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + TextAreaFieldbbd63464, + caption, + username, + profile_url, + post_url, + metadata: value, + }; + const result = onChange(modelFields); + value = result?.metadata ?? value; } - onChange={(e) => { - const date = new Date(e.target.value); - if (!(date instanceof Date && !isNaN(date))) { - setErrors((errors) => ({ - ...errors, - editedAt: \\"The value must be a valid date\\", - })); - return; - } - let value = Number(date); - if (errors.editedAt?.hasError) { - runValidationTasks(\\"editedAt\\", value); - } - setCurrentEditedAtValue(value); - }} - onBlur={() => runValidationTasks(\\"editedAt\\", currentEditedAtValue)} - errorMessage={errors.editedAt?.errorMessage} - hasError={errors.editedAt?.hasError} - ref={editedAtRef} - {...getOverrideProps(overrides, \\"editedAt\\")} - ></TextField> - </ArrayField> + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); + } + setMetadata(value); + }} + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + ></TextAreaField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button - children=\\"Clear\\" + children=\\"Reset\\" type=\\"reset\\" onClick={resetStateValues} - {...getOverrideProps(overrides, \\"ClearButton\\")} + {...getOverrideProps(overrides, \\"ResetButton\\")} ></Button> <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> <Button children=\\"Submit\\" type=\\"submit\\" @@ -5721,72 +5650,75 @@ export default function BlogCreateForm(props) { " `; -exports[`amplify form renderer tests datastore form tests should render a form with a javascript reserved word as the field name 2`] = ` +exports[`amplify form renderer tests datastore form tests should generate a update form 2`] = ` "import * as React from \\"react\\"; +import { Post } from \\"../models\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { GridProps, SwitchFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { GridProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type BlogCreateFormInputValues = { - title?: string; - content?: string; - switch?: boolean; - published?: string; - editedAt?: number[]; +export declare type MyPostFormInputValues = { + TextAreaFieldbbd63464?: string; + caption?: string; + username?: string; + profile_url?: string; + post_url?: string; + metadata?: string; }; -export declare type BlogCreateFormValidationValues = { - title?: ValidationFunction<string>; - content?: ValidationFunction<string>; - switch?: ValidationFunction<boolean>; - published?: ValidationFunction<string>; - editedAt?: ValidationFunction<number>; +export declare type MyPostFormValidationValues = { + TextAreaFieldbbd63464?: ValidationFunction<string>; + caption?: ValidationFunction<string>; + username?: ValidationFunction<string>; + profile_url?: ValidationFunction<string>; + post_url?: ValidationFunction<string>; + metadata?: ValidationFunction<string>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type BlogCreateFormOverridesProps = { - BlogCreateFormGrid?: FormProps<GridProps>; - title?: FormProps<TextFieldProps>; - content?: FormProps<TextFieldProps>; - switch?: FormProps<SwitchFieldProps>; - published?: FormProps<TextFieldProps>; - editedAt?: FormProps<TextFieldProps>; +export declare type MyPostFormOverridesProps = { + MyPostFormGrid?: FormProps<GridProps>; + TextAreaFieldbbd63464?: FormProps<TextAreaFieldProps>; + caption?: FormProps<TextFieldProps>; + username?: FormProps<TextFieldProps>; + profile_url?: FormProps<TextFieldProps>; + post_url?: FormProps<TextFieldProps>; + metadata?: FormProps<TextAreaFieldProps>; } & EscapeHatchProps; -export declare type BlogCreateFormProps = React.PropsWithChildren<{ - overrides?: BlogCreateFormOverridesProps | undefined | null; +export declare type MyPostFormProps = React.PropsWithChildren<{ + overrides?: MyPostFormOverridesProps | undefined | null; } & { - clearOnSuccess?: boolean; - onSubmit?: (fields: BlogCreateFormInputValues) => BlogCreateFormInputValues; - onSuccess?: (fields: BlogCreateFormInputValues) => void; - onError?: (fields: BlogCreateFormInputValues, errorMessage: string) => void; - onChange?: (fields: BlogCreateFormInputValues) => BlogCreateFormInputValues; - onValidate?: BlogCreateFormValidationValues; + id?: string; + post?: Post; + onSubmit?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onSuccess?: (fields: MyPostFormInputValues) => void; + onError?: (fields: MyPostFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: MyPostFormInputValues) => MyPostFormInputValues; + onValidate?: MyPostFormValidationValues; }>; -export default function BlogCreateForm(props: BlogCreateFormProps): React.ReactElement; +export default function MyPostForm(props: MyPostFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 1`] = ` +exports[`amplify form renderer tests datastore form tests should render a form with a javascript reserved word as the field name 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { fetchByPath, validateField } from \\"./utils\\"; -import { InputGallery } from \\"../models\\"; +import { Blog } from \\"../models\\"; import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; import { Badge, Button, - CheckboxField, Divider, Flex, Grid, Icon, - Radio, - RadioGroupField, ScrollView, + SwitchField, Text, TextField, - ToggleButton, useTheme, } from \\"@aws-amplify/ui-react\\"; import { DataStore } from \\"aws-amplify\\"; @@ -5834,48 +5766,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( <React.Fragment> - {isEditing && children} - {!isEditing ? ( - <> - <Text>{label}</Text> - <Button - onClick={() => { - setIsEditing(true); - }} - > - Add item - </Button> - </> - ) : ( - <Flex justifyContent=\\"flex-end\\"> - {(currentFieldValue || isEditing) && ( - <Button - children=\\"Cancel\\" - type=\\"button\\" - size=\\"small\\" - onClick={() => { - setFieldValue(defaultFieldValue); - setIsEditing(false); - setSelectedBadgeIndex(undefined); - }} - ></Button> - )} - <Button - size=\\"small\\" - variation=\\"link\\" - color={tokens.colors.brand.primary[80]} - isDisabled={hasError} - onClick={addItem} - > - {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} - </Button> - </Flex> - )} + <Text>{label}</Text> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { @@ -5927,68 +5820,93 @@ function ArrayField({ <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); -} -export default function InputGalleryCreateForm(props) { - const { - clearOnSuccess = true, - onSuccess, - onError, - onSubmit, - onCancel, - onValidate, - onChange, - overrides, - ...rest - } = props; - const initialValues = { - num: undefined, - rootbeer: undefined, - attend: undefined, - maybeSlide: false, - maybeCheck: undefined, - arrayTypeField: [], - timestamp: undefined, - ippy: undefined, - timeisnow: undefined, - }; - const [num, setNum] = React.useState(initialValues.num); - const [rootbeer, setRootbeer] = React.useState(initialValues.rootbeer); - const [attend, setAttend] = React.useState(initialValues.attend); - const [maybeSlide, setMaybeSlide] = React.useState(initialValues.maybeSlide); - const [maybeCheck, setMaybeCheck] = React.useState(initialValues.maybeCheck); - const [arrayTypeField, setArrayTypeField] = React.useState( - initialValues.arrayTypeField + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + <React.Fragment> + {isEditing && children} + {!isEditing ? ( + <> + <Text>{label}</Text> + <Button + onClick={() => { + setIsEditing(true); + }} + > + Add item + </Button> + </> + ) : ( + <Flex justifyContent=\\"flex-end\\"> + {(currentFieldValue || isEditing) && ( + <Button + children=\\"Cancel\\" + type=\\"button\\" + size=\\"small\\" + onClick={() => { + setFieldValue(defaultFieldValue); + setIsEditing(false); + setSelectedBadgeIndex(undefined); + }} + ></Button> + )} + <Button + size=\\"small\\" + variation=\\"link\\" + color={tokens.colors.brand.primary[80]} + isDisabled={hasError} + onClick={addItem} + > + {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} + </Button> + </Flex> + )} + </React.Fragment> ); - const [timestamp, setTimestamp] = React.useState(initialValues.timestamp); - const [ippy, setIppy] = React.useState(initialValues.ippy); - const [timeisnow, setTimeisnow] = React.useState(initialValues.timeisnow); +} +export default function BlogCreateForm(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + title: undefined, + content: undefined, + switch: false, + published: undefined, + editedAt: [], + }; + const [title, setTitle] = React.useState(initialValues.title); + const [content, setContent] = React.useState(initialValues.content); + const [switch1, setSwitch1] = React.useState(initialValues.switch); + const [published, setPublished] = React.useState(initialValues.published); + const [editedAt, setEditedAt] = React.useState(initialValues.editedAt); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setNum(initialValues.num); - setRootbeer(initialValues.rootbeer); - setAttend(initialValues.attend); - setMaybeSlide(initialValues.maybeSlide); - setMaybeCheck(initialValues.maybeCheck); - setArrayTypeField(initialValues.arrayTypeField); - setCurrentArrayTypeFieldValue(undefined); - setTimestamp(initialValues.timestamp); - setIppy(initialValues.ippy); - setTimeisnow(initialValues.timeisnow); + setTitle(initialValues.title); + setContent(initialValues.content); + setSwitch1(initialValues.switch); + setPublished(initialValues.published); + setEditedAt(initialValues.editedAt); + setCurrentEditedAtValue(undefined); setErrors({}); }; - const [currentArrayTypeFieldValue, setCurrentArrayTypeFieldValue] = + const [currentEditedAtValue, setCurrentEditedAtValue] = React.useState(undefined); - const arrayTypeFieldRef = React.createRef(); + const editedAtRef = React.createRef(); const validations = { - num: [], - rootbeer: [], - attend: [{ type: \\"Required\\" }], - maybeSlide: [], - maybeCheck: [], - arrayTypeField: [], - timestamp: [], - ippy: [{ type: \\"IpAddress\\" }], - timeisnow: [], + title: [], + content: [], + switch: [], + published: [], + editedAt: [], }; const runValidationTasks = async ( fieldName, @@ -6006,6 +5924,29 @@ export default function InputGalleryCreateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; + const convertTimeStampToDate = (ts) => { + if (Math.abs(Date.now() - ts) < Math.abs(Date.now() - ts * 1000)) { + return new Date(ts); + } + return new Date(ts * 1000); + }; + const convertToLocal = (date) => { + const df = new Intl.DateTimeFormat(\\"default\\", { + year: \\"numeric\\", + month: \\"2-digit\\", + day: \\"2-digit\\", + hour: \\"2-digit\\", + minute: \\"2-digit\\", + calendar: \\"iso8601\\", + numberingSystem: \\"latn\\", + hour12: false, + }); + const parts = df.formatToParts(date).reduce((acc, part) => { + acc[part.type] = part.value; + return acc; + }, {}); + return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; + }; return ( <Grid as=\\"form\\" @@ -6015,15 +5956,11 @@ export default function InputGalleryCreateForm(props) { onSubmit={async (event) => { event.preventDefault(); let modelFields = { - num, - rootbeer, - attend, - maybeSlide, - maybeCheck, - arrayTypeField, - timestamp, - ippy, - timeisnow, + title, + content, + switch: switch1, + published, + editedAt, }; const validationResponses = await Promise.all( Object.keys(validations).reduce((promises, fieldName) => { @@ -6048,7 +5985,7 @@ export default function InputGalleryCreateForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save(new InputGallery(modelFields)); + await DataStore.save(new Blog(modelFields)); if (onSuccess) { onSuccess(modelFields); } @@ -6062,349 +5999,174 @@ export default function InputGalleryCreateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"InputGalleryCreateForm\\")} + {...getOverrideProps(overrides, \\"BlogCreateForm\\")} > <TextField - label=\\"Num\\" + label=\\"Title\\" isRequired={false} isReadOnly={false} - type=\\"number\\" - step=\\"any\\" onChange={(e) => { - let value = parseInt(e.target.value); - if (isNaN(value)) { - setErrors((errors) => ({ - ...errors, - num: \\"Value must be a valid number\\", - })); - return; - } + let { value } = e.target; if (onChange) { const modelFields = { - num: value, - rootbeer, - attend, - maybeSlide, - maybeCheck, - arrayTypeField, - timestamp, - ippy, - timeisnow, + title: value, + content, + switch: switch1, + published, + editedAt, }; const result = onChange(modelFields); - value = result?.num ?? value; + value = result?.title ?? value; } - if (errors.num?.hasError) { - runValidationTasks(\\"num\\", value); + if (errors.title?.hasError) { + runValidationTasks(\\"title\\", value); } - setNum(value); + setTitle(value); }} - onBlur={() => runValidationTasks(\\"num\\", num)} - errorMessage={errors.num?.errorMessage} - hasError={errors.num?.hasError} - {...getOverrideProps(overrides, \\"num\\")} + onBlur={() => runValidationTasks(\\"title\\", title)} + errorMessage={errors.title?.errorMessage} + hasError={errors.title?.hasError} + {...getOverrideProps(overrides, \\"title\\")} ></TextField> <TextField - label=\\"Rootbeer\\" + label=\\"Content\\" isRequired={false} isReadOnly={false} - type=\\"number\\" - step=\\"any\\" onChange={(e) => { - let value = Number(e.target.value); - if (isNaN(value)) { - setErrors((errors) => ({ - ...errors, - rootbeer: \\"Value must be a valid number\\", - })); - return; - } + let { value } = e.target; if (onChange) { const modelFields = { - num, - rootbeer: value, - attend, - maybeSlide, - maybeCheck, - arrayTypeField, - timestamp, - ippy, - timeisnow, + title, + content: value, + switch: switch1, + published, + editedAt, }; const result = onChange(modelFields); - value = result?.rootbeer ?? value; + value = result?.content ?? value; } - if (errors.rootbeer?.hasError) { - runValidationTasks(\\"rootbeer\\", value); + if (errors.content?.hasError) { + runValidationTasks(\\"content\\", value); } - setRootbeer(value); + setContent(value); }} - onBlur={() => runValidationTasks(\\"rootbeer\\", rootbeer)} - errorMessage={errors.rootbeer?.errorMessage} - hasError={errors.rootbeer?.hasError} - {...getOverrideProps(overrides, \\"rootbeer\\")} + onBlur={() => runValidationTasks(\\"content\\", content)} + errorMessage={errors.content?.errorMessage} + hasError={errors.content?.hasError} + {...getOverrideProps(overrides, \\"content\\")} ></TextField> - <RadioGroupField - label=\\"Attend\\" - name=\\"attend\\" - isReadOnly={false} - isRequired=\\"false\\" + <SwitchField + label=\\"Switch\\" + defaultChecked={false} + isDisabled={false} + isChecked={switch1} onChange={(e) => { - let value = e.target.value === \\"true\\"; + let value = e.target.checked; if (onChange) { const modelFields = { - num, - rootbeer, - attend: value, - maybeSlide, - maybeCheck, - arrayTypeField, - timestamp, - ippy, - timeisnow, - }; - const result = onChange(modelFields); - value = result?.attend ?? value; - } - if (errors.attend?.hasError) { - runValidationTasks(\\"attend\\", value); - } - setAttend(value); - }} - onBlur={() => runValidationTasks(\\"attend\\", attend)} - errorMessage={errors.attend?.errorMessage} - hasError={errors.attend?.hasError} - {...getOverrideProps(overrides, \\"attend\\")} - > - <Radio - children=\\"Yes\\" - value=\\"true\\" - {...getOverrideProps(overrides, \\"attendRadio0\\")} - ></Radio> - <Radio - children=\\"No\\" - value=\\"false\\" - {...getOverrideProps(overrides, \\"attendRadio1\\")} - ></Radio> - </RadioGroupField> - <ToggleButton - children=\\"Maybe slide\\" - isDisabled={false} - defaultPressed={false} - isPressed={maybeSlide} - onChange={(e) => { - let value = !maybeSlide; - if (onChange) { - const modelFields = { - num, - rootbeer, - attend, - maybeSlide: value, - maybeCheck, - arrayTypeField, - timestamp, - ippy, - timeisnow, + title, + content, + switch: value, + published, + editedAt, }; const result = onChange(modelFields); - value = result?.maybeSlide ?? value; + value = result?.switch ?? value; } - if (errors.maybeSlide?.hasError) { - runValidationTasks(\\"maybeSlide\\", value); + if (errors.switch?.hasError) { + runValidationTasks(\\"switch\\", value); } - setMaybeSlide(value); + setSwitch1(value); }} - onBlur={() => runValidationTasks(\\"maybeSlide\\", maybeSlide)} - errorMessage={errors.maybeSlide?.errorMessage} - hasError={errors.maybeSlide?.hasError} - {...getOverrideProps(overrides, \\"maybeSlide\\")} - ></ToggleButton> - <CheckboxField - label=\\"Maybe check\\" - name=\\"maybeCheck\\" - value=\\"maybeCheck\\" - isDisabled={false} - defaultChecked={false} + onBlur={() => runValidationTasks(\\"switch\\", switch1)} + errorMessage={errors.switch?.errorMessage} + hasError={errors.switch?.hasError} + {...getOverrideProps(overrides, \\"switch\\")} + ></SwitchField> + <TextField + label=\\"Published\\" + isRequired={false} + isReadOnly={false} + type=\\"datetime-local\\" onChange={(e) => { - let value = e.target.checked; + let { value } = e.target; if (onChange) { const modelFields = { - num, - rootbeer, - attend, - maybeSlide, - maybeCheck: value, - arrayTypeField, - timestamp, - ippy, - timeisnow, + title, + content, + switch: switch1, + published: value, + editedAt, }; const result = onChange(modelFields); - value = result?.maybeCheck ?? value; + value = result?.published ?? value; } - if (errors.maybeCheck?.hasError) { - runValidationTasks(\\"maybeCheck\\", value); + if (errors.published?.hasError) { + runValidationTasks(\\"published\\", value); } - setMaybeCheck(value); + setPublished(new Date(value).toISOString()); }} - onBlur={() => runValidationTasks(\\"maybeCheck\\", maybeCheck)} - errorMessage={errors.maybeCheck?.errorMessage} - hasError={errors.maybeCheck?.hasError} - {...getOverrideProps(overrides, \\"maybeCheck\\")} - ></CheckboxField> + onBlur={() => runValidationTasks(\\"published\\", published)} + errorMessage={errors.published?.errorMessage} + hasError={errors.published?.hasError} + {...getOverrideProps(overrides, \\"published\\")} + ></TextField> <ArrayField onChange={async (items) => { let values = items; if (onChange) { const modelFields = { - num, - rootbeer, - attend, - maybeSlide, - maybeCheck, - arrayTypeField: values, - timestamp, - ippy, - timeisnow, + title, + content, + switch: switch1, + published, + editedAt: values, }; const result = onChange(modelFields); - values = result?.arrayTypeField ?? values; + values = result?.editedAt ?? values; } - setArrayTypeField(values); - setCurrentArrayTypeFieldValue(undefined); + setEditedAt(values); + setCurrentEditedAtValue(undefined); }} - currentFieldValue={currentArrayTypeFieldValue} - label={\\"Array type field\\"} - items={arrayTypeField} - hasError={errors.arrayTypeField?.hasError} - setFieldValue={setCurrentArrayTypeFieldValue} - inputFieldRef={arrayTypeFieldRef} + currentFieldValue={currentEditedAtValue} + label={\\"Edited at\\"} + items={editedAt} + hasError={errors.editedAt?.hasError} + setFieldValue={setCurrentEditedAtValue} + inputFieldRef={editedAtRef} defaultFieldValue={undefined} > <TextField - label=\\"Array type field\\" + label=\\"Edited at\\" isRequired={false} isReadOnly={false} - value={currentArrayTypeFieldValue} + type=\\"datetime-local\\" + value={ + currentEditedAtValue && + convertToLocal(convertTimeStampToDate(currentEditedAtValue)) + } onChange={(e) => { - let { value } = e.target; - if (errors.arrayTypeField?.hasError) { - runValidationTasks(\\"arrayTypeField\\", value); + const date = new Date(e.target.value); + if (!(date instanceof Date && !isNaN(date))) { + setErrors((errors) => ({ + ...errors, + editedAt: \\"The value must be a valid date\\", + })); + return; } - setCurrentArrayTypeFieldValue(value); + let value = Number(date); + if (errors.editedAt?.hasError) { + runValidationTasks(\\"editedAt\\", value); + } + setCurrentEditedAtValue(value); }} - onBlur={() => - runValidationTasks(\\"arrayTypeField\\", currentArrayTypeFieldValue) - } - errorMessage={errors.arrayTypeField?.errorMessage} - hasError={errors.arrayTypeField?.hasError} - ref={arrayTypeFieldRef} - {...getOverrideProps(overrides, \\"arrayTypeField\\")} + onBlur={() => runValidationTasks(\\"editedAt\\", currentEditedAtValue)} + errorMessage={errors.editedAt?.errorMessage} + hasError={errors.editedAt?.hasError} + ref={editedAtRef} + {...getOverrideProps(overrides, \\"editedAt\\")} ></TextField> </ArrayField> - <TextField - label=\\"Timestamp\\" - isRequired={false} - isReadOnly={false} - type=\\"datetime-local\\" - onChange={(e) => { - const date = new Date(e.target.value); - if (!(date instanceof Date && !isNaN(date))) { - setErrors((errors) => ({ - ...errors, - timestamp: \\"The value must be a valid date\\", - })); - return; - } - let value = Number(date); - if (onChange) { - const modelFields = { - num, - rootbeer, - attend, - maybeSlide, - maybeCheck, - arrayTypeField, - timestamp: value, - ippy, - timeisnow, - }; - const result = onChange(modelFields); - value = result?.timestamp ?? value; - } - if (errors.timestamp?.hasError) { - runValidationTasks(\\"timestamp\\", value); - } - setTimestamp(value); - }} - onBlur={() => runValidationTasks(\\"timestamp\\", timestamp)} - errorMessage={errors.timestamp?.errorMessage} - hasError={errors.timestamp?.hasError} - {...getOverrideProps(overrides, \\"timestamp\\")} - ></TextField> - <TextField - label=\\"Ippy\\" - isRequired={false} - isReadOnly={false} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - num, - rootbeer, - attend, - maybeSlide, - maybeCheck, - arrayTypeField, - timestamp, - ippy: value, - timeisnow, - }; - const result = onChange(modelFields); - value = result?.ippy ?? value; - } - if (errors.ippy?.hasError) { - runValidationTasks(\\"ippy\\", value); - } - setIppy(value); - }} - onBlur={() => runValidationTasks(\\"ippy\\", ippy)} - errorMessage={errors.ippy?.errorMessage} - hasError={errors.ippy?.hasError} - {...getOverrideProps(overrides, \\"ippy\\")} - ></TextField> - <TextField - label=\\"Timeisnow\\" - isRequired={false} - isReadOnly={false} - type=\\"time\\" - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - num, - rootbeer, - attend, - maybeSlide, - maybeCheck, - arrayTypeField, - timestamp, - ippy, - timeisnow: value, - }; - const result = onChange(modelFields); - value = result?.timeisnow ?? value; - } - if (errors.timeisnow?.hasError) { - runValidationTasks(\\"timeisnow\\", value); - } - setTimeisnow(value); - }} - onBlur={() => runValidationTasks(\\"timeisnow\\", timeisnow)} - errorMessage={errors.timeisnow?.errorMessage} - hasError={errors.timeisnow?.hasError} - {...getOverrideProps(overrides, \\"timeisnow\\")} - ></TextField> <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} @@ -6416,14 +6178,6 @@ export default function InputGalleryCreateForm(props) { {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> - <Button - children=\\"Cancel\\" - type=\\"button\\" - onClick={() => { - onCancel && onCancel(); - }} - {...getOverrideProps(overrides, \\"CancelButton\\")} - ></Button> <Button children=\\"Submit\\" type=\\"submit\\" @@ -6439,66 +6193,53 @@ export default function InputGalleryCreateForm(props) { " `; -exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 2`] = ` +exports[`amplify form renderer tests datastore form tests should render a form with a javascript reserved word as the field name 2`] = ` "import * as React from \\"react\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { CheckboxFieldProps, GridProps, RadioGroupFieldProps, TextFieldProps, ToggleButtonProps } from \\"@aws-amplify/ui-react\\"; +import { GridProps, SwitchFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type InputGalleryCreateFormInputValues = { - num?: number; - rootbeer?: number; - attend?: boolean; - maybeSlide?: boolean; - maybeCheck?: boolean; - arrayTypeField?: string[]; - timestamp?: number; - ippy?: string; - timeisnow?: string; +export declare type BlogCreateFormInputValues = { + title?: string; + content?: string; + switch?: boolean; + published?: string; + editedAt?: number[]; }; -export declare type InputGalleryCreateFormValidationValues = { - num?: ValidationFunction<number>; - rootbeer?: ValidationFunction<number>; - attend?: ValidationFunction<boolean>; - maybeSlide?: ValidationFunction<boolean>; - maybeCheck?: ValidationFunction<boolean>; - arrayTypeField?: ValidationFunction<string>; - timestamp?: ValidationFunction<number>; - ippy?: ValidationFunction<string>; - timeisnow?: ValidationFunction<string>; +export declare type BlogCreateFormValidationValues = { + title?: ValidationFunction<string>; + content?: ValidationFunction<string>; + switch?: ValidationFunction<boolean>; + published?: ValidationFunction<string>; + editedAt?: ValidationFunction<number>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type InputGalleryCreateFormOverridesProps = { - InputGalleryCreateFormGrid?: FormProps<GridProps>; - num?: FormProps<TextFieldProps>; - rootbeer?: FormProps<TextFieldProps>; - attend?: FormProps<RadioGroupFieldProps>; - maybeSlide?: FormProps<ToggleButtonProps>; - maybeCheck?: FormProps<CheckboxFieldProps>; - arrayTypeField?: FormProps<TextFieldProps>; - timestamp?: FormProps<TextFieldProps>; - ippy?: FormProps<TextFieldProps>; - timeisnow?: FormProps<TextFieldProps>; +export declare type BlogCreateFormOverridesProps = { + BlogCreateFormGrid?: FormProps<GridProps>; + title?: FormProps<TextFieldProps>; + content?: FormProps<TextFieldProps>; + switch?: FormProps<SwitchFieldProps>; + published?: FormProps<TextFieldProps>; + editedAt?: FormProps<TextFieldProps>; } & EscapeHatchProps; -export declare type InputGalleryCreateFormProps = React.PropsWithChildren<{ - overrides?: InputGalleryCreateFormOverridesProps | undefined | null; +export declare type BlogCreateFormProps = React.PropsWithChildren<{ + overrides?: BlogCreateFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: InputGalleryCreateFormInputValues) => InputGalleryCreateFormInputValues; - onSuccess?: (fields: InputGalleryCreateFormInputValues) => void; - onError?: (fields: InputGalleryCreateFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: InputGalleryCreateFormInputValues) => InputGalleryCreateFormInputValues; - onValidate?: InputGalleryCreateFormValidationValues; + onSubmit?: (fields: BlogCreateFormInputValues) => BlogCreateFormInputValues; + onSuccess?: (fields: BlogCreateFormInputValues) => void; + onError?: (fields: BlogCreateFormInputValues, errorMessage: string) => void; + onChange?: (fields: BlogCreateFormInputValues) => BlogCreateFormInputValues; + onValidate?: BlogCreateFormValidationValues; }>; -export default function InputGalleryCreateForm(props: InputGalleryCreateFormProps): React.ReactElement; +export default function BlogCreateForm(props: BlogCreateFormProps): React.ReactElement; " `; -exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 3`] = ` +exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 1`] = ` "/* eslint-disable */ import * as React from \\"react\\"; import { fetchByPath, validateField } from \\"./utils\\"; @@ -6565,48 +6306,9 @@ function ArrayField({ setIsEditing(false); } }; - if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { - return arraySection; - } - return ( + const arraySection = ( <React.Fragment> - {isEditing && children} - {!isEditing ? ( - <> - <Text>{label}</Text> - <Button - onClick={() => { - setIsEditing(true); - }} - > - Add item - </Button> - </> - ) : ( - <Flex justifyContent=\\"flex-end\\"> - {(currentFieldValue || isEditing) && ( - <Button - children=\\"Cancel\\" - type=\\"button\\" - size=\\"small\\" - onClick={() => { - setFieldValue(defaultFieldValue); - setIsEditing(false); - setSelectedBadgeIndex(undefined); - }} - ></Button> - )} - <Button - size=\\"small\\" - variation=\\"link\\" - color={tokens.colors.brand.primary[80]} - isDisabled={hasError} - onClick={addItem} - > - {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} - </Button> - </Flex> - )} + <Text>{label}</Text> {!!items?.length && ( <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> {items.map((value, index) => { @@ -6658,11 +6360,54 @@ function ArrayField({ <Divider orientation=\\"horizontal\\" marginTop={5} /> </React.Fragment> ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + <React.Fragment> + {isEditing && children} + {!isEditing ? ( + <> + <Text>{label}</Text> + <Button + onClick={() => { + setIsEditing(true); + }} + > + Add item + </Button> + </> + ) : ( + <Flex justifyContent=\\"flex-end\\"> + {(currentFieldValue || isEditing) && ( + <Button + children=\\"Cancel\\" + type=\\"button\\" + size=\\"small\\" + onClick={() => { + setFieldValue(defaultFieldValue); + setIsEditing(false); + setSelectedBadgeIndex(undefined); + }} + ></Button> + )} + <Button + size=\\"small\\" + variation=\\"link\\" + color={tokens.colors.brand.primary[80]} + isDisabled={hasError} + onClick={addItem} + > + {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} + </Button> + </Flex> + )} + </React.Fragment> + ); } -export default function InputGalleryUpdateForm(props) { +export default function InputGalleryCreateForm(props) { const { - id, - inputGallery, + clearOnSuccess = true, onSuccess, onError, onSubmit, @@ -6696,31 +6441,18 @@ export default function InputGalleryUpdateForm(props) { const [timeisnow, setTimeisnow] = React.useState(initialValues.timeisnow); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - const cleanValues = { ...initialValues, ...inputGalleryRecord }; - setNum(cleanValues.num); - setRootbeer(cleanValues.rootbeer); - setAttend(cleanValues.attend); - setMaybeSlide(cleanValues.maybeSlide); - setMaybeCheck(cleanValues.maybeCheck); - setArrayTypeField(cleanValues.arrayTypeField ?? []); + setNum(initialValues.num); + setRootbeer(initialValues.rootbeer); + setAttend(initialValues.attend); + setMaybeSlide(initialValues.maybeSlide); + setMaybeCheck(initialValues.maybeCheck); + setArrayTypeField(initialValues.arrayTypeField); setCurrentArrayTypeFieldValue(undefined); - setTimestamp(cleanValues.timestamp); - setIppy(cleanValues.ippy); - setTimeisnow(cleanValues.timeisnow); + setTimestamp(initialValues.timestamp); + setIppy(initialValues.ippy); + setTimeisnow(initialValues.timeisnow); setErrors({}); }; - const [inputGalleryRecord, setInputGalleryRecord] = - React.useState(inputGallery); - React.useEffect(() => { - const queryData = async () => { - const record = id - ? await DataStore.query(InputGallery, id) - : inputGallery; - setInputGalleryRecord(record); - }; - queryData(); - }, [id, inputGallery]); - React.useEffect(resetStateValues, [inputGalleryRecord]); const [currentArrayTypeFieldValue, setCurrentArrayTypeFieldValue] = React.useState(undefined); const arrayTypeFieldRef = React.createRef(); @@ -6751,29 +6483,6 @@ export default function InputGalleryUpdateForm(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - const convertTimeStampToDate = (ts) => { - if (Math.abs(Date.now() - ts) < Math.abs(Date.now() - ts * 1000)) { - return new Date(ts); - } - return new Date(ts * 1000); - }; - const convertToLocal = (date) => { - const df = new Intl.DateTimeFormat(\\"default\\", { - year: \\"numeric\\", - month: \\"2-digit\\", - day: \\"2-digit\\", - hour: \\"2-digit\\", - minute: \\"2-digit\\", - calendar: \\"iso8601\\", - numberingSystem: \\"latn\\", - hour12: false, - }); - const parts = df.formatToParts(date).reduce((acc, part) => { - acc[part.type] = part.value; - return acc; - }, {}); - return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; - }; return ( <Grid as=\\"form\\" @@ -6816,14 +6525,13 @@ export default function InputGalleryUpdateForm(props) { modelFields = onSubmit(modelFields); } try { - await DataStore.save( - InputGallery.copyOf(inputGalleryRecord, (updated) => { - Object.assign(updated, modelFields); - }) - ); + await DataStore.save(new InputGallery(modelFields)); if (onSuccess) { onSuccess(modelFields); } + if (clearOnSuccess) { + resetStateValues(); + } } catch (err) { if (onError) { onError(modelFields, err.message); @@ -6831,7 +6539,7 @@ export default function InputGalleryUpdateForm(props) { } }} {...rest} - {...getOverrideProps(overrides, \\"InputGalleryUpdateForm\\")} + {...getOverrideProps(overrides, \\"InputGalleryCreateForm\\")} > <TextField label=\\"Num\\" @@ -6839,7 +6547,6 @@ export default function InputGalleryUpdateForm(props) { isReadOnly={false} type=\\"number\\" step=\\"any\\" - defaultValue={num} onChange={(e) => { let value = parseInt(e.target.value); if (isNaN(value)) { @@ -6880,7 +6587,6 @@ export default function InputGalleryUpdateForm(props) { isReadOnly={false} type=\\"number\\" step=\\"any\\" - defaultValue={rootbeer} onChange={(e) => { let value = Number(e.target.value); if (isNaN(value)) { @@ -6920,7 +6626,6 @@ export default function InputGalleryUpdateForm(props) { name=\\"attend\\" isReadOnly={false} isRequired=\\"false\\" - defaultValue={attend} onChange={(e) => { let value = e.target.value === \\"true\\"; if (onChange) { @@ -6997,7 +6702,6 @@ export default function InputGalleryUpdateForm(props) { value=\\"maybeCheck\\" isDisabled={false} defaultChecked={false} - defaultValue={maybeCheck} onChange={(e) => { let value = e.target.checked; if (onChange) { @@ -7080,9 +6784,6 @@ export default function InputGalleryUpdateForm(props) { isRequired={false} isReadOnly={false} type=\\"datetime-local\\" - defaultValue={ - timestamp && convertToLocal(convertTimeStampToDate(timestamp)) - } onChange={(e) => { const date = new Date(e.target.value); if (!(date instanceof Date && !isNaN(date))) { @@ -7122,7 +6823,6 @@ export default function InputGalleryUpdateForm(props) { label=\\"Ippy\\" isRequired={false} isReadOnly={false} - defaultValue={ippy} onChange={(e) => { let { value } = e.target; if (onChange) { @@ -7155,7 +6855,6 @@ export default function InputGalleryUpdateForm(props) { isRequired={false} isReadOnly={false} type=\\"time\\" - defaultValue={timeisnow} onChange={(e) => { let { value } = e.target; if (onChange) { @@ -7188,10 +6887,10 @@ export default function InputGalleryUpdateForm(props) { {...getOverrideProps(overrides, \\"CTAFlex\\")} > <Button - children=\\"Reset\\" + children=\\"Clear\\" type=\\"reset\\" onClick={resetStateValues} - {...getOverrideProps(overrides, \\"ResetButton\\")} + {...getOverrideProps(overrides, \\"ClearButton\\")} ></Button> <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> <Button @@ -7203,97 +6902,1435 @@ export default function InputGalleryUpdateForm(props) { {...getOverrideProps(overrides, \\"CancelButton\\")} ></Button> <Button - children=\\"Submit\\" - type=\\"submit\\" - variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e?.hasError)} - {...getOverrideProps(overrides, \\"SubmitButton\\")} - ></Button> + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={Object.values(errors).some((e) => e?.hasError)} + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> + </Grid> + ); +} +" +`; + +exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 2`] = ` +"import * as React from \\"react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { CheckboxFieldProps, GridProps, RadioGroupFieldProps, TextFieldProps, ToggleButtonProps } from \\"@aws-amplify/ui-react\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; +export declare type InputGalleryCreateFormInputValues = { + num?: number; + rootbeer?: number; + attend?: boolean; + maybeSlide?: boolean; + maybeCheck?: boolean; + arrayTypeField?: string[]; + timestamp?: number; + ippy?: string; + timeisnow?: string; +}; +export declare type InputGalleryCreateFormValidationValues = { + num?: ValidationFunction<number>; + rootbeer?: ValidationFunction<number>; + attend?: ValidationFunction<boolean>; + maybeSlide?: ValidationFunction<boolean>; + maybeCheck?: ValidationFunction<boolean>; + arrayTypeField?: ValidationFunction<string>; + timestamp?: ValidationFunction<number>; + ippy?: ValidationFunction<string>; + timeisnow?: ValidationFunction<string>; +}; +export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; +export declare type InputGalleryCreateFormOverridesProps = { + InputGalleryCreateFormGrid?: FormProps<GridProps>; + num?: FormProps<TextFieldProps>; + rootbeer?: FormProps<TextFieldProps>; + attend?: FormProps<RadioGroupFieldProps>; + maybeSlide?: FormProps<ToggleButtonProps>; + maybeCheck?: FormProps<CheckboxFieldProps>; + arrayTypeField?: FormProps<TextFieldProps>; + timestamp?: FormProps<TextFieldProps>; + ippy?: FormProps<TextFieldProps>; + timeisnow?: FormProps<TextFieldProps>; +} & EscapeHatchProps; +export declare type InputGalleryCreateFormProps = React.PropsWithChildren<{ + overrides?: InputGalleryCreateFormOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: InputGalleryCreateFormInputValues) => InputGalleryCreateFormInputValues; + onSuccess?: (fields: InputGalleryCreateFormInputValues) => void; + onError?: (fields: InputGalleryCreateFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: InputGalleryCreateFormInputValues) => InputGalleryCreateFormInputValues; + onValidate?: InputGalleryCreateFormValidationValues; +}>; +export default function InputGalleryCreateForm(props: InputGalleryCreateFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 3`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { InputGallery } from \\"../models\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { + Badge, + Button, + CheckboxField, + Divider, + Flex, + Grid, + Icon, + Radio, + RadioGroupField, + ScrollView, + Text, + TextField, + ToggleButton, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { DataStore } from \\"aws-amplify\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, +}) { + const { tokens } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ( + (currentFieldValue !== undefined || + currentFieldValue !== null || + currentFieldValue !== \\"\\") && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + <React.Fragment> + <Text>{label}</Text> + {!!items?.length && ( + <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> + {items.map((value, index) => { + return ( + <Badge + key={index} + style={{ + cursor: \\"pointer\\", + alignItems: \\"center\\", + marginRight: 3, + marginTop: 3, + backgroundColor: + index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", + }} + onClick={() => { + setSelectedBadgeIndex(index); + setFieldValue( + getBadgeText ? getBadgeText(items[index]) : items[index] + ); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + <Icon + style={{ + cursor: \\"pointer\\", + paddingLeft: 3, + width: 20, + height: 20, + }} + viewBox={{ width: 20, height: 20 }} + paths={[ + { + d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", + stroke: \\"black\\", + }, + ]} + ariaLabel=\\"button\\" + onClick={(event) => { + event.stopPropagation(); + removeItem(index); + }} + /> + </Badge> + ); + })} + </ScrollView> + )} + <Divider orientation=\\"horizontal\\" marginTop={5} /> + </React.Fragment> + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + <React.Fragment> + {isEditing && children} + {!isEditing ? ( + <> + <Text>{label}</Text> + <Button + onClick={() => { + setIsEditing(true); + }} + > + Add item + </Button> + </> + ) : ( + <Flex justifyContent=\\"flex-end\\"> + {(currentFieldValue || isEditing) && ( + <Button + children=\\"Cancel\\" + type=\\"button\\" + size=\\"small\\" + onClick={() => { + setFieldValue(defaultFieldValue); + setIsEditing(false); + setSelectedBadgeIndex(undefined); + }} + ></Button> + )} + <Button + size=\\"small\\" + variation=\\"link\\" + color={tokens.colors.brand.primary[80]} + isDisabled={hasError} + onClick={addItem} + > + {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} + </Button> + </Flex> + )} + </React.Fragment> + ); +} +export default function InputGalleryUpdateForm(props) { + const { + id, + inputGallery, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + num: undefined, + rootbeer: undefined, + attend: undefined, + maybeSlide: false, + maybeCheck: undefined, + arrayTypeField: [], + timestamp: undefined, + ippy: undefined, + timeisnow: undefined, + }; + const [num, setNum] = React.useState(initialValues.num); + const [rootbeer, setRootbeer] = React.useState(initialValues.rootbeer); + const [attend, setAttend] = React.useState(initialValues.attend); + const [maybeSlide, setMaybeSlide] = React.useState(initialValues.maybeSlide); + const [maybeCheck, setMaybeCheck] = React.useState(initialValues.maybeCheck); + const [arrayTypeField, setArrayTypeField] = React.useState( + initialValues.arrayTypeField + ); + const [timestamp, setTimestamp] = React.useState(initialValues.timestamp); + const [ippy, setIppy] = React.useState(initialValues.ippy); + const [timeisnow, setTimeisnow] = React.useState(initialValues.timeisnow); + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + const cleanValues = { ...initialValues, ...inputGalleryRecord }; + setNum(cleanValues.num); + setRootbeer(cleanValues.rootbeer); + setAttend(cleanValues.attend); + setMaybeSlide(cleanValues.maybeSlide); + setMaybeCheck(cleanValues.maybeCheck); + setArrayTypeField(cleanValues.arrayTypeField ?? []); + setCurrentArrayTypeFieldValue(undefined); + setTimestamp(cleanValues.timestamp); + setIppy(cleanValues.ippy); + setTimeisnow(cleanValues.timeisnow); + setErrors({}); + }; + const [inputGalleryRecord, setInputGalleryRecord] = + React.useState(inputGallery); + React.useEffect(() => { + const queryData = async () => { + const record = id + ? await DataStore.query(InputGallery, id) + : inputGallery; + setInputGalleryRecord(record); + }; + queryData(); + }, [id, inputGallery]); + React.useEffect(resetStateValues, [inputGalleryRecord]); + const [currentArrayTypeFieldValue, setCurrentArrayTypeFieldValue] = + React.useState(undefined); + const arrayTypeFieldRef = React.createRef(); + const validations = { + num: [], + rootbeer: [], + attend: [{ type: \\"Required\\" }], + maybeSlide: [], + maybeCheck: [], + arrayTypeField: [], + timestamp: [], + ippy: [{ type: \\"IpAddress\\" }], + timeisnow: [], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + const convertTimeStampToDate = (ts) => { + if (Math.abs(Date.now() - ts) < Math.abs(Date.now() - ts * 1000)) { + return new Date(ts); + } + return new Date(ts * 1000); + }; + const convertToLocal = (date) => { + const df = new Intl.DateTimeFormat(\\"default\\", { + year: \\"numeric\\", + month: \\"2-digit\\", + day: \\"2-digit\\", + hour: \\"2-digit\\", + minute: \\"2-digit\\", + calendar: \\"iso8601\\", + numberingSystem: \\"latn\\", + hour12: false, + }); + const parts = df.formatToParts(date).reduce((acc, part) => { + acc[part.type] = part.value; + return acc; + }, {}); + return \`\${parts.year}-\${parts.month}-\${parts.day}T\${parts.hour}:\${parts.minute}\`; + }; + return ( + <Grid + as=\\"form\\" + rowGap=\\"15px\\" + columnGap=\\"15px\\" + padding=\\"20px\\" + onSubmit={async (event) => { + event.preventDefault(); + let modelFields = { + num, + rootbeer, + attend, + maybeSlide, + maybeCheck, + arrayTypeField, + timestamp, + ippy, + timeisnow, + }; + const validationResponses = await Promise.all( + 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; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + await DataStore.save( + InputGallery.copyOf(inputGalleryRecord, (updated) => { + Object.assign(updated, modelFields); + }) + ); + if (onSuccess) { + onSuccess(modelFields); + } + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...rest} + {...getOverrideProps(overrides, \\"InputGalleryUpdateForm\\")} + > + <TextField + label=\\"Num\\" + isRequired={false} + isReadOnly={false} + type=\\"number\\" + step=\\"any\\" + defaultValue={num} + onChange={(e) => { + let value = parseInt(e.target.value); + if (isNaN(value)) { + setErrors((errors) => ({ + ...errors, + num: \\"Value must be a valid number\\", + })); + return; + } + if (onChange) { + const modelFields = { + num: value, + rootbeer, + attend, + maybeSlide, + maybeCheck, + arrayTypeField, + timestamp, + ippy, + timeisnow, + }; + const result = onChange(modelFields); + value = result?.num ?? value; + } + if (errors.num?.hasError) { + runValidationTasks(\\"num\\", value); + } + setNum(value); + }} + onBlur={() => runValidationTasks(\\"num\\", num)} + errorMessage={errors.num?.errorMessage} + hasError={errors.num?.hasError} + {...getOverrideProps(overrides, \\"num\\")} + ></TextField> + <TextField + label=\\"Rootbeer\\" + isRequired={false} + isReadOnly={false} + type=\\"number\\" + step=\\"any\\" + defaultValue={rootbeer} + onChange={(e) => { + let value = Number(e.target.value); + if (isNaN(value)) { + setErrors((errors) => ({ + ...errors, + rootbeer: \\"Value must be a valid number\\", + })); + return; + } + if (onChange) { + const modelFields = { + num, + rootbeer: value, + attend, + maybeSlide, + maybeCheck, + arrayTypeField, + timestamp, + ippy, + timeisnow, + }; + const result = onChange(modelFields); + value = result?.rootbeer ?? value; + } + if (errors.rootbeer?.hasError) { + runValidationTasks(\\"rootbeer\\", value); + } + setRootbeer(value); + }} + onBlur={() => runValidationTasks(\\"rootbeer\\", rootbeer)} + errorMessage={errors.rootbeer?.errorMessage} + hasError={errors.rootbeer?.hasError} + {...getOverrideProps(overrides, \\"rootbeer\\")} + ></TextField> + <RadioGroupField + label=\\"Attend\\" + name=\\"attend\\" + isReadOnly={false} + isRequired=\\"false\\" + defaultValue={attend} + onChange={(e) => { + let value = e.target.value === \\"true\\"; + if (onChange) { + const modelFields = { + num, + rootbeer, + attend: value, + maybeSlide, + maybeCheck, + arrayTypeField, + timestamp, + ippy, + timeisnow, + }; + const result = onChange(modelFields); + value = result?.attend ?? value; + } + if (errors.attend?.hasError) { + runValidationTasks(\\"attend\\", value); + } + setAttend(value); + }} + onBlur={() => runValidationTasks(\\"attend\\", attend)} + errorMessage={errors.attend?.errorMessage} + hasError={errors.attend?.hasError} + {...getOverrideProps(overrides, \\"attend\\")} + > + <Radio + children=\\"Yes\\" + value=\\"true\\" + {...getOverrideProps(overrides, \\"attendRadio0\\")} + ></Radio> + <Radio + children=\\"No\\" + value=\\"false\\" + {...getOverrideProps(overrides, \\"attendRadio1\\")} + ></Radio> + </RadioGroupField> + <ToggleButton + children=\\"Maybe slide\\" + isDisabled={false} + defaultPressed={false} + isPressed={maybeSlide} + onChange={(e) => { + let value = !maybeSlide; + if (onChange) { + const modelFields = { + num, + rootbeer, + attend, + maybeSlide: value, + maybeCheck, + arrayTypeField, + timestamp, + ippy, + timeisnow, + }; + const result = onChange(modelFields); + value = result?.maybeSlide ?? value; + } + if (errors.maybeSlide?.hasError) { + runValidationTasks(\\"maybeSlide\\", value); + } + setMaybeSlide(value); + }} + onBlur={() => runValidationTasks(\\"maybeSlide\\", maybeSlide)} + errorMessage={errors.maybeSlide?.errorMessage} + hasError={errors.maybeSlide?.hasError} + {...getOverrideProps(overrides, \\"maybeSlide\\")} + ></ToggleButton> + <CheckboxField + label=\\"Maybe check\\" + name=\\"maybeCheck\\" + value=\\"maybeCheck\\" + isDisabled={false} + defaultChecked={false} + defaultValue={maybeCheck} + onChange={(e) => { + let value = e.target.checked; + if (onChange) { + const modelFields = { + num, + rootbeer, + attend, + maybeSlide, + maybeCheck: value, + arrayTypeField, + timestamp, + ippy, + timeisnow, + }; + const result = onChange(modelFields); + value = result?.maybeCheck ?? value; + } + if (errors.maybeCheck?.hasError) { + runValidationTasks(\\"maybeCheck\\", value); + } + setMaybeCheck(value); + }} + onBlur={() => runValidationTasks(\\"maybeCheck\\", maybeCheck)} + errorMessage={errors.maybeCheck?.errorMessage} + hasError={errors.maybeCheck?.hasError} + {...getOverrideProps(overrides, \\"maybeCheck\\")} + ></CheckboxField> + <ArrayField + onChange={async (items) => { + let values = items; + if (onChange) { + const modelFields = { + num, + rootbeer, + attend, + maybeSlide, + maybeCheck, + arrayTypeField: values, + timestamp, + ippy, + timeisnow, + }; + const result = onChange(modelFields); + values = result?.arrayTypeField ?? values; + } + setArrayTypeField(values); + setCurrentArrayTypeFieldValue(undefined); + }} + currentFieldValue={currentArrayTypeFieldValue} + label={\\"Array type field\\"} + items={arrayTypeField} + hasError={errors.arrayTypeField?.hasError} + setFieldValue={setCurrentArrayTypeFieldValue} + inputFieldRef={arrayTypeFieldRef} + defaultFieldValue={undefined} + > + <TextField + label=\\"Array type field\\" + isRequired={false} + isReadOnly={false} + value={currentArrayTypeFieldValue} + onChange={(e) => { + let { value } = e.target; + if (errors.arrayTypeField?.hasError) { + runValidationTasks(\\"arrayTypeField\\", value); + } + setCurrentArrayTypeFieldValue(value); + }} + onBlur={() => + runValidationTasks(\\"arrayTypeField\\", currentArrayTypeFieldValue) + } + errorMessage={errors.arrayTypeField?.errorMessage} + hasError={errors.arrayTypeField?.hasError} + ref={arrayTypeFieldRef} + {...getOverrideProps(overrides, \\"arrayTypeField\\")} + ></TextField> + </ArrayField> + <TextField + label=\\"Timestamp\\" + isRequired={false} + isReadOnly={false} + type=\\"datetime-local\\" + defaultValue={ + timestamp && convertToLocal(convertTimeStampToDate(timestamp)) + } + onChange={(e) => { + const date = new Date(e.target.value); + if (!(date instanceof Date && !isNaN(date))) { + setErrors((errors) => ({ + ...errors, + timestamp: \\"The value must be a valid date\\", + })); + return; + } + let value = Number(date); + if (onChange) { + const modelFields = { + num, + rootbeer, + attend, + maybeSlide, + maybeCheck, + arrayTypeField, + timestamp: value, + ippy, + timeisnow, + }; + const result = onChange(modelFields); + value = result?.timestamp ?? value; + } + if (errors.timestamp?.hasError) { + runValidationTasks(\\"timestamp\\", value); + } + setTimestamp(value); + }} + onBlur={() => runValidationTasks(\\"timestamp\\", timestamp)} + errorMessage={errors.timestamp?.errorMessage} + hasError={errors.timestamp?.hasError} + {...getOverrideProps(overrides, \\"timestamp\\")} + ></TextField> + <TextField + label=\\"Ippy\\" + isRequired={false} + isReadOnly={false} + defaultValue={ippy} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + num, + rootbeer, + attend, + maybeSlide, + maybeCheck, + arrayTypeField, + timestamp, + ippy: value, + timeisnow, + }; + const result = onChange(modelFields); + value = result?.ippy ?? value; + } + if (errors.ippy?.hasError) { + runValidationTasks(\\"ippy\\", value); + } + setIppy(value); + }} + onBlur={() => runValidationTasks(\\"ippy\\", ippy)} + errorMessage={errors.ippy?.errorMessage} + hasError={errors.ippy?.hasError} + {...getOverrideProps(overrides, \\"ippy\\")} + ></TextField> + <TextField + label=\\"Timeisnow\\" + isRequired={false} + isReadOnly={false} + type=\\"time\\" + defaultValue={timeisnow} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + num, + rootbeer, + attend, + maybeSlide, + maybeCheck, + arrayTypeField, + timestamp, + ippy, + timeisnow: value, + }; + const result = onChange(modelFields); + value = result?.timeisnow ?? value; + } + if (errors.timeisnow?.hasError) { + runValidationTasks(\\"timeisnow\\", value); + } + setTimeisnow(value); + }} + onBlur={() => runValidationTasks(\\"timeisnow\\", timeisnow)} + errorMessage={errors.timeisnow?.errorMessage} + hasError={errors.timeisnow?.hasError} + {...getOverrideProps(overrides, \\"timeisnow\\")} + ></TextField> + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Reset\\" + type=\\"reset\\" + onClick={resetStateValues} + {...getOverrideProps(overrides, \\"ResetButton\\")} + ></Button> + <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={Object.values(errors).some((e) => e?.hasError)} + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> + </Grid> + ); +} +" +`; + +exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 4`] = ` +"import * as React from \\"react\\"; +import { InputGallery } from \\"../models\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { CheckboxFieldProps, GridProps, RadioGroupFieldProps, TextFieldProps, ToggleButtonProps } from \\"@aws-amplify/ui-react\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; +export declare type InputGalleryUpdateFormInputValues = { + num?: number; + rootbeer?: number; + attend?: boolean; + maybeSlide?: boolean; + maybeCheck?: boolean; + arrayTypeField?: string[]; + timestamp?: number; + ippy?: string; + timeisnow?: string; +}; +export declare type InputGalleryUpdateFormValidationValues = { + num?: ValidationFunction<number>; + rootbeer?: ValidationFunction<number>; + attend?: ValidationFunction<boolean>; + maybeSlide?: ValidationFunction<boolean>; + maybeCheck?: ValidationFunction<boolean>; + arrayTypeField?: ValidationFunction<string>; + timestamp?: ValidationFunction<number>; + ippy?: ValidationFunction<string>; + timeisnow?: ValidationFunction<string>; +}; +export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; +export declare type InputGalleryUpdateFormOverridesProps = { + InputGalleryUpdateFormGrid?: FormProps<GridProps>; + num?: FormProps<TextFieldProps>; + rootbeer?: FormProps<TextFieldProps>; + attend?: FormProps<RadioGroupFieldProps>; + maybeSlide?: FormProps<ToggleButtonProps>; + maybeCheck?: FormProps<CheckboxFieldProps>; + arrayTypeField?: FormProps<TextFieldProps>; + timestamp?: FormProps<TextFieldProps>; + ippy?: FormProps<TextFieldProps>; + timeisnow?: FormProps<TextFieldProps>; +} & EscapeHatchProps; +export declare type InputGalleryUpdateFormProps = React.PropsWithChildren<{ + overrides?: InputGalleryUpdateFormOverridesProps | undefined | null; +} & { + id?: string; + inputGallery?: InputGallery; + onSubmit?: (fields: InputGalleryUpdateFormInputValues) => InputGalleryUpdateFormInputValues; + onSuccess?: (fields: InputGalleryUpdateFormInputValues) => void; + onError?: (fields: InputGalleryUpdateFormInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: InputGalleryUpdateFormInputValues) => InputGalleryUpdateFormInputValues; + onValidate?: InputGalleryUpdateFormValidationValues; +}>; +export default function InputGalleryUpdateForm(props: InputGalleryUpdateFormProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests datastore form tests should render form with a two inputs in row 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { Post } from \\"../models\\"; +import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; +import { + Button, + Flex, + Grid, + SelectField, + TextAreaField, + TextField, +} from \\"@aws-amplify/ui-react\\"; +import { DataStore } from \\"aws-amplify\\"; +export default function PostCreateFormRow(props) { + const { + clearOnSuccess = true, + onSuccess, + onError, + onSubmit, + onCancel, + onValidate, + onChange, + overrides, + ...rest + } = props; + const initialValues = { + username: undefined, + caption: undefined, + post_url: undefined, + profile_url: undefined, + status: undefined, + metadata: undefined, + }; + const [username, setUsername] = React.useState(initialValues.username); + const [caption, setCaption] = React.useState(initialValues.caption); + const [post_url, setPost_url] = React.useState(initialValues.post_url); + const [profile_url, setProfile_url] = React.useState( + initialValues.profile_url + ); + const [status, setStatus] = React.useState(initialValues.status); + const [metadata, setMetadata] = React.useState( + initialValues.metadata ? JSON.stringify(initialValues.metadata) : undefined + ); + const [errors, setErrors] = React.useState({}); + const resetStateValues = () => { + setUsername(initialValues.username); + setCaption(initialValues.caption); + setPost_url(initialValues.post_url); + setProfile_url(initialValues.profile_url); + setStatus(initialValues.status); + setMetadata(initialValues.metadata); + setErrors({}); + }; + const validations = { + username: [ + { + type: \\"GreaterThanChar\\", + numValues: [2], + validationMessage: \\"needs to be of length 2\\", + }, + ], + caption: [], + post_url: [{ type: \\"URL\\" }], + profile_url: [{ type: \\"URL\\" }], + status: [], + metadata: [{ type: \\"JSON\\" }], + }; + const runValidationTasks = async ( + fieldName, + currentValue, + getDisplayValue + ) => { + const value = getDisplayValue + ? getDisplayValue(currentValue) + : currentValue; + let validationResponse = validateField(value, validations[fieldName]); + const customValidator = fetchByPath(onValidate, fieldName); + if (customValidator) { + validationResponse = await customValidator(value, validationResponse); + } + setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); + return validationResponse; + }; + return ( + <Grid + as=\\"form\\" + rowGap=\\"15px\\" + columnGap=\\"15px\\" + padding=\\"20px\\" + onSubmit={async (event) => { + event.preventDefault(); + let modelFields = { + username, + caption, + post_url, + profile_url, + status, + metadata, + }; + const validationResponses = await Promise.all( + 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; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + await DataStore.save(new Post(modelFields)); + if (onSuccess) { + onSuccess(modelFields); + } + if (clearOnSuccess) { + resetStateValues(); + } + } catch (err) { + if (onError) { + onError(modelFields, err.message); + } + } + }} + {...rest} + {...getOverrideProps(overrides, \\"PostCreateFormRow\\")} + > + <Grid + columnGap=\\"inherit\\" + rowGap=\\"inherit\\" + templateColumns=\\"repeat(2, auto)\\" + {...getOverrideProps(overrides, \\"RowGrid0\\")} + > + <TextField + label=\\"Username\\" + isRequired={false} + isReadOnly={false} + placeholder=\\"john\\" + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + username: value, + caption, + post_url, + profile_url, + status, + metadata, + }; + const result = onChange(modelFields); + value = result?.username ?? value; + } + if (errors.username?.hasError) { + runValidationTasks(\\"username\\", value); + } + setUsername(value); + }} + onBlur={() => runValidationTasks(\\"username\\", username)} + errorMessage={errors.username?.errorMessage} + hasError={errors.username?.hasError} + {...getOverrideProps(overrides, \\"username\\")} + ></TextField> + <TextField + label=\\"Caption\\" + isRequired={false} + isReadOnly={false} + placeholder=\\"i love code\\" + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + username, + caption: value, + post_url, + profile_url, + status, + metadata, + }; + const result = onChange(modelFields); + value = result?.caption ?? value; + } + if (errors.caption?.hasError) { + runValidationTasks(\\"caption\\", value); + } + setCaption(value); + }} + onBlur={() => runValidationTasks(\\"caption\\", caption)} + errorMessage={errors.caption?.errorMessage} + hasError={errors.caption?.hasError} + {...getOverrideProps(overrides, \\"caption\\")} + ></TextField> + </Grid> + <TextField + label=\\"Post url\\" + descriptiveText=\\"post url to use for the component\\" + isRequired={false} + isReadOnly={false} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + username, + caption, + post_url: value, + profile_url, + status, + metadata, + }; + const result = onChange(modelFields); + value = result?.post_url ?? value; + } + if (errors.post_url?.hasError) { + runValidationTasks(\\"post_url\\", value); + } + setPost_url(value); + }} + onBlur={() => runValidationTasks(\\"post_url\\", post_url)} + errorMessage={errors.post_url?.errorMessage} + hasError={errors.post_url?.hasError} + {...getOverrideProps(overrides, \\"post_url\\")} + ></TextField> + <TextField + label=\\"Profile url\\" + descriptiveText=\\"profile image url\\" + isRequired={false} + isReadOnly={false} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + username, + caption, + post_url, + profile_url: value, + status, + metadata, + }; + const result = onChange(modelFields); + value = result?.profile_url ?? value; + } + if (errors.profile_url?.hasError) { + runValidationTasks(\\"profile_url\\", value); + } + setProfile_url(value); + }} + onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} + errorMessage={errors.profile_url?.errorMessage} + hasError={errors.profile_url?.hasError} + {...getOverrideProps(overrides, \\"profile_url\\")} + ></TextField> + <SelectField + label=\\"Label\\" + placeholder=\\"Please select an option\\" + value={status} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + username, + caption, + post_url, + profile_url, + status: value, + metadata, + }; + const result = onChange(modelFields); + value = result?.status ?? value; + } + if (errors.status?.hasError) { + runValidationTasks(\\"status\\", value); + } + setStatus(value); + }} + onBlur={() => runValidationTasks(\\"status\\", status)} + errorMessage={errors.status?.errorMessage} + hasError={errors.status?.hasError} + {...getOverrideProps(overrides, \\"status\\")} + ></SelectField> + <TextAreaField + label=\\"Metadata\\" + isRequired={false} + isReadOnly={false} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + username, + caption, + post_url, + profile_url, + status, + metadata: value, + }; + const result = onChange(modelFields); + value = result?.metadata ?? value; + } + if (errors.metadata?.hasError) { + runValidationTasks(\\"metadata\\", value); + } + setMetadata(value); + }} + onBlur={() => runValidationTasks(\\"metadata\\", metadata)} + errorMessage={errors.metadata?.errorMessage} + hasError={errors.metadata?.hasError} + {...getOverrideProps(overrides, \\"metadata\\")} + ></TextAreaField> + <Flex + justifyContent=\\"space-between\\" + {...getOverrideProps(overrides, \\"CTAFlex\\")} + > + <Button + children=\\"Clear\\" + type=\\"reset\\" + onClick={resetStateValues} + {...getOverrideProps(overrides, \\"ClearButton\\")} + ></Button> + <Flex {...getOverrideProps(overrides, \\"RightAlignCTASubFlex\\")}> + <Button + children=\\"Cancel\\" + type=\\"button\\" + onClick={() => { + onCancel && onCancel(); + }} + {...getOverrideProps(overrides, \\"CancelButton\\")} + ></Button> + <Button + children=\\"Submit\\" + type=\\"submit\\" + variation=\\"primary\\" + isDisabled={Object.values(errors).some((e) => e?.hasError)} + {...getOverrideProps(overrides, \\"SubmitButton\\")} + ></Button> + </Flex> + </Flex> + </Grid> + ); +} +" +`; + +exports[`amplify form renderer tests datastore form tests should render form with a two inputs in row 2`] = ` +"import * as React from \\"react\\"; +import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; +import { GridProps, SelectFieldProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +export declare type ValidationResponse = { + hasError: boolean; + errorMessage?: string; +}; +export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; +export declare type PostCreateFormRowInputValues = { + username?: string; + caption?: string; + post_url?: string; + profile_url?: string; + status?: string; + metadata?: string; +}; +export declare type PostCreateFormRowValidationValues = { + username?: ValidationFunction<string>; + caption?: ValidationFunction<string>; + post_url?: ValidationFunction<string>; + profile_url?: ValidationFunction<string>; + status?: ValidationFunction<string>; + metadata?: ValidationFunction<string>; +}; +export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; +export declare type PostCreateFormRowOverridesProps = { + PostCreateFormRowGrid?: FormProps<GridProps>; + RowGrid0?: FormProps<GridProps>; + username?: FormProps<TextFieldProps>; + caption?: FormProps<TextFieldProps>; + post_url?: FormProps<TextFieldProps>; + profile_url?: FormProps<TextFieldProps>; + status?: FormProps<SelectFieldProps>; + metadata?: FormProps<TextAreaFieldProps>; +} & EscapeHatchProps; +export declare type PostCreateFormRowProps = React.PropsWithChildren<{ + overrides?: PostCreateFormRowOverridesProps | undefined | null; +} & { + clearOnSuccess?: boolean; + onSubmit?: (fields: PostCreateFormRowInputValues) => PostCreateFormRowInputValues; + onSuccess?: (fields: PostCreateFormRowInputValues) => void; + onError?: (fields: PostCreateFormRowInputValues, errorMessage: string) => void; + onCancel?: () => void; + onChange?: (fields: PostCreateFormRowInputValues) => PostCreateFormRowInputValues; + onValidate?: PostCreateFormRowValidationValues; +}>; +export default function PostCreateFormRow(props: PostCreateFormRowProps): React.ReactElement; +" +`; + +exports[`amplify form renderer tests datastore form tests should use proper field overrides for belongsTo relationship 1`] = ` +"/* eslint-disable */ +import * as React from \\"react\\"; +import { fetchByPath, validateField } from \\"./utils\\"; +import { Member, Team } from \\"../models\\"; +import { + getOverrideProps, + useDataStoreBinding, +} from \\"@aws-amplify/ui-react/internal\\"; +import { + Autocomplete, + Badge, + Button, + Divider, + Flex, + Grid, + Icon, + ScrollView, + Text, + TextField, + useTheme, +} from \\"@aws-amplify/ui-react\\"; +import { DataStore } from \\"aws-amplify\\"; +function ArrayField({ + items = [], + onChange, + label, + inputFieldRef, + children, + hasError, + setFieldValue, + currentFieldValue, + defaultFieldValue, + lengthLimit, + getBadgeText, +}) { + const { tokens } = useTheme(); + const [selectedBadgeIndex, setSelectedBadgeIndex] = React.useState(); + const [isEditing, setIsEditing] = React.useState(); + React.useEffect(() => { + if (isEditing) { + inputFieldRef?.current?.focus(); + } + }, [isEditing]); + const removeItem = async (removeIndex) => { + const newItems = items.filter((value, index) => index !== removeIndex); + await onChange(newItems); + setSelectedBadgeIndex(undefined); + }; + const addItem = async () => { + if ( + (currentFieldValue !== undefined || + currentFieldValue !== null || + currentFieldValue !== \\"\\") && + !hasError + ) { + const newItems = [...items]; + if (selectedBadgeIndex !== undefined) { + newItems[selectedBadgeIndex] = currentFieldValue; + setSelectedBadgeIndex(undefined); + } else { + newItems.push(currentFieldValue); + } + await onChange(newItems); + setIsEditing(false); + } + }; + const arraySection = ( + <React.Fragment> + <Text>{label}</Text> + {!!items?.length && ( + <ScrollView height=\\"inherit\\" width=\\"inherit\\" maxHeight={\\"7rem\\"}> + {items.map((value, index) => { + return ( + <Badge + key={index} + style={{ + cursor: \\"pointer\\", + alignItems: \\"center\\", + marginRight: 3, + marginTop: 3, + backgroundColor: + index === selectedBadgeIndex ? \\"#B8CEF9\\" : \\"\\", + }} + onClick={() => { + setSelectedBadgeIndex(index); + setFieldValue( + getBadgeText ? getBadgeText(items[index]) : items[index] + ); + setIsEditing(true); + }} + > + {getBadgeText ? getBadgeText(value) : value.toString()} + <Icon + style={{ + cursor: \\"pointer\\", + paddingLeft: 3, + width: 20, + height: 20, + }} + viewBox={{ width: 20, height: 20 }} + paths={[ + { + d: \\"M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z\\", + stroke: \\"black\\", + }, + ]} + ariaLabel=\\"button\\" + onClick={(event) => { + event.stopPropagation(); + removeItem(index); + }} + /> + </Badge> + ); + })} + </ScrollView> + )} + <Divider orientation=\\"horizontal\\" marginTop={5} /> + </React.Fragment> + ); + if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { + return arraySection; + } + return ( + <React.Fragment> + {isEditing && children} + {!isEditing ? ( + <> + <Text>{label}</Text> + <Button + onClick={() => { + setIsEditing(true); + }} + > + Add item + </Button> + </> + ) : ( + <Flex justifyContent=\\"flex-end\\"> + {(currentFieldValue || isEditing) && ( + <Button + children=\\"Cancel\\" + type=\\"button\\" + size=\\"small\\" + onClick={() => { + setFieldValue(defaultFieldValue); + setIsEditing(false); + setSelectedBadgeIndex(undefined); + }} + ></Button> + )} + <Button + size=\\"small\\" + variation=\\"link\\" + color={tokens.colors.brand.primary[80]} + isDisabled={hasError} + onClick={addItem} + > + {selectedBadgeIndex !== undefined ? \\"Save\\" : \\"Add\\"} + </Button> </Flex> - </Flex> - </Grid> + )} + </React.Fragment> ); } -" -`; - -exports[`amplify form renderer tests datastore form tests should render a form with multiple date types 4`] = ` -"import * as React from \\"react\\"; -import { InputGallery } from \\"../models\\"; -import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { CheckboxFieldProps, GridProps, RadioGroupFieldProps, TextFieldProps, ToggleButtonProps } from \\"@aws-amplify/ui-react\\"; -export declare type ValidationResponse = { - hasError: boolean; - errorMessage?: string; -}; -export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type InputGalleryUpdateFormInputValues = { - num?: number; - rootbeer?: number; - attend?: boolean; - maybeSlide?: boolean; - maybeCheck?: boolean; - arrayTypeField?: string[]; - timestamp?: number; - ippy?: string; - timeisnow?: string; -}; -export declare type InputGalleryUpdateFormValidationValues = { - num?: ValidationFunction<number>; - rootbeer?: ValidationFunction<number>; - attend?: ValidationFunction<boolean>; - maybeSlide?: ValidationFunction<boolean>; - maybeCheck?: ValidationFunction<boolean>; - arrayTypeField?: ValidationFunction<string>; - timestamp?: ValidationFunction<number>; - ippy?: ValidationFunction<string>; - timeisnow?: ValidationFunction<string>; -}; -export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type InputGalleryUpdateFormOverridesProps = { - InputGalleryUpdateFormGrid?: FormProps<GridProps>; - num?: FormProps<TextFieldProps>; - rootbeer?: FormProps<TextFieldProps>; - attend?: FormProps<RadioGroupFieldProps>; - maybeSlide?: FormProps<ToggleButtonProps>; - maybeCheck?: FormProps<CheckboxFieldProps>; - arrayTypeField?: FormProps<TextFieldProps>; - timestamp?: FormProps<TextFieldProps>; - ippy?: FormProps<TextFieldProps>; - timeisnow?: FormProps<TextFieldProps>; -} & EscapeHatchProps; -export declare type InputGalleryUpdateFormProps = React.PropsWithChildren<{ - overrides?: InputGalleryUpdateFormOverridesProps | undefined | null; -} & { - id?: string; - inputGallery?: InputGallery; - onSubmit?: (fields: InputGalleryUpdateFormInputValues) => InputGalleryUpdateFormInputValues; - onSuccess?: (fields: InputGalleryUpdateFormInputValues) => void; - onError?: (fields: InputGalleryUpdateFormInputValues, errorMessage: string) => void; - onCancel?: () => void; - onChange?: (fields: InputGalleryUpdateFormInputValues) => InputGalleryUpdateFormInputValues; - onValidate?: InputGalleryUpdateFormValidationValues; -}>; -export default function InputGalleryUpdateForm(props: InputGalleryUpdateFormProps): React.ReactElement; -" -`; - -exports[`amplify form renderer tests datastore form tests should render form with a two inputs in row 1`] = ` -"/* eslint-disable */ -import * as React from \\"react\\"; -import { fetchByPath, validateField } from \\"./utils\\"; -import { Post } from \\"../models\\"; -import { getOverrideProps } from \\"@aws-amplify/ui-react/internal\\"; -import { - Button, - Flex, - Grid, - SelectField, - TextAreaField, - TextField, -} from \\"@aws-amplify/ui-react\\"; -import { DataStore } from \\"aws-amplify\\"; -export default function PostCreateFormRow(props) { +export default function MyMemberForm(props) { const { clearOnSuccess = true, onSuccess, @@ -7306,46 +8343,33 @@ export default function PostCreateFormRow(props) { ...rest } = props; const initialValues = { - username: undefined, - caption: undefined, - post_url: undefined, - profile_url: undefined, - status: undefined, - metadata: undefined, + name: undefined, + team: undefined, }; - const [username, setUsername] = React.useState(initialValues.username); - const [caption, setCaption] = React.useState(initialValues.caption); - const [post_url, setPost_url] = React.useState(initialValues.post_url); - const [profile_url, setProfile_url] = React.useState( - initialValues.profile_url - ); - const [status, setStatus] = React.useState(initialValues.status); - const [metadata, setMetadata] = React.useState( - initialValues.metadata ? JSON.stringify(initialValues.metadata) : undefined - ); + const [name, setName] = React.useState(initialValues.name); + const [team, setTeam] = React.useState(initialValues.team); const [errors, setErrors] = React.useState({}); const resetStateValues = () => { - setUsername(initialValues.username); - setCaption(initialValues.caption); - setPost_url(initialValues.post_url); - setProfile_url(initialValues.profile_url); - setStatus(initialValues.status); - setMetadata(initialValues.metadata); + setName(initialValues.name); + setTeam(initialValues.team); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(undefined); setErrors({}); }; + const [currentTeamDisplayValue, setCurrentTeamDisplayValue] = + React.useState(undefined); + const [currentTeamValue, setCurrentTeamValue] = React.useState(undefined); + const teamRef = React.createRef(); + const teamRecords = useDataStoreBinding({ + type: \\"collection\\", + model: Team, + }).items; + const getDisplayValue = { + team: (record) => record?.name, + }; const validations = { - username: [ - { - type: \\"GreaterThanChar\\", - numValues: [2], - validationMessage: \\"needs to be of length 2\\", - }, - ], - caption: [], - post_url: [{ type: \\"URL\\" }], - profile_url: [{ type: \\"URL\\" }], - status: [], - metadata: [{ type: \\"JSON\\" }], + name: [], + team: [], }; const runValidationTasks = async ( fieldName, @@ -7363,240 +8387,65 @@ export default function PostCreateFormRow(props) { setErrors((errors) => ({ ...errors, [fieldName]: validationResponse })); return validationResponse; }; - return ( - <Grid - as=\\"form\\" - rowGap=\\"15px\\" - columnGap=\\"15px\\" - padding=\\"20px\\" - onSubmit={async (event) => { - event.preventDefault(); - let modelFields = { - username, - caption, - post_url, - profile_url, - status, - metadata, - }; - const validationResponses = await Promise.all( - 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; - } - if (onSubmit) { - modelFields = onSubmit(modelFields); - } - try { - await DataStore.save(new Post(modelFields)); - if (onSuccess) { - onSuccess(modelFields); - } - if (clearOnSuccess) { - resetStateValues(); - } - } catch (err) { - if (onError) { - onError(modelFields, err.message); - } - } - }} - {...rest} - {...getOverrideProps(overrides, \\"PostCreateFormRow\\")} - > - <Grid - columnGap=\\"inherit\\" - rowGap=\\"inherit\\" - templateColumns=\\"repeat(2, auto)\\" - {...getOverrideProps(overrides, \\"RowGrid0\\")} - > - <TextField - label=\\"Username\\" - isRequired={false} - isReadOnly={false} - placeholder=\\"john\\" - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - username: value, - caption, - post_url, - profile_url, - status, - metadata, - }; - const result = onChange(modelFields); - value = result?.username ?? value; - } - if (errors.username?.hasError) { - runValidationTasks(\\"username\\", value); - } - setUsername(value); - }} - onBlur={() => runValidationTasks(\\"username\\", username)} - errorMessage={errors.username?.errorMessage} - hasError={errors.username?.hasError} - {...getOverrideProps(overrides, \\"username\\")} - ></TextField> - <TextField - label=\\"Caption\\" - isRequired={false} - isReadOnly={false} - placeholder=\\"i love code\\" - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - username, - caption: value, - post_url, - profile_url, - status, - metadata, - }; - const result = onChange(modelFields); - value = result?.caption ?? value; - } - if (errors.caption?.hasError) { - runValidationTasks(\\"caption\\", value); + return ( + <Grid + as=\\"form\\" + rowGap=\\"15px\\" + columnGap=\\"15px\\" + padding=\\"20px\\" + onSubmit={async (event) => { + event.preventDefault(); + let modelFields = { + name, + team, + }; + const validationResponses = await Promise.all( + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks( + fieldName, + item, + getDisplayValue[fieldName] + ) + ) + ); + return promises; } - setCaption(value); - }} - onBlur={() => runValidationTasks(\\"caption\\", caption)} - errorMessage={errors.caption?.errorMessage} - hasError={errors.caption?.hasError} - {...getOverrideProps(overrides, \\"caption\\")} - ></TextField> - </Grid> - <TextField - label=\\"Post url\\" - descriptiveText=\\"post url to use for the component\\" - isRequired={false} - isReadOnly={false} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - username, - caption, - post_url: value, - profile_url, - status, - metadata, - }; - const result = onChange(modelFields); - value = result?.post_url ?? value; - } - if (errors.post_url?.hasError) { - runValidationTasks(\\"post_url\\", value); - } - setPost_url(value); - }} - onBlur={() => runValidationTasks(\\"post_url\\", post_url)} - errorMessage={errors.post_url?.errorMessage} - hasError={errors.post_url?.hasError} - {...getOverrideProps(overrides, \\"post_url\\")} - ></TextField> - <TextField - label=\\"Profile url\\" - descriptiveText=\\"profile image url\\" - isRequired={false} - isReadOnly={false} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - username, - caption, - post_url, - profile_url: value, - status, - metadata, - }; - const result = onChange(modelFields); - value = result?.profile_url ?? value; - } - if (errors.profile_url?.hasError) { - runValidationTasks(\\"profile_url\\", value); - } - setProfile_url(value); - }} - onBlur={() => runValidationTasks(\\"profile_url\\", profile_url)} - errorMessage={errors.profile_url?.errorMessage} - hasError={errors.profile_url?.hasError} - {...getOverrideProps(overrides, \\"profile_url\\")} - ></TextField> - <SelectField - label=\\"Label\\" - placeholder=\\"Please select an option\\" - value={status} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - username, - caption, - post_url, - profile_url, - status: value, - metadata, - }; - const result = onChange(modelFields); - value = result?.status ?? value; - } - if (errors.status?.hasError) { - runValidationTasks(\\"status\\", value); + promises.push( + runValidationTasks( + fieldName, + modelFields[fieldName], + getDisplayValue[fieldName] + ) + ); + return promises; + }, []) + ); + if (validationResponses.some((r) => r.hasError)) { + return; + } + if (onSubmit) { + modelFields = onSubmit(modelFields); + } + try { + await DataStore.save(new Member(modelFields)); + if (onSuccess) { + onSuccess(modelFields); } - setStatus(value); - }} - onBlur={() => runValidationTasks(\\"status\\", status)} - errorMessage={errors.status?.errorMessage} - hasError={errors.status?.hasError} - {...getOverrideProps(overrides, \\"status\\")} - ></SelectField> - <TextAreaField - label=\\"Metadata\\" - isRequired={false} - isReadOnly={false} - onChange={(e) => { - let { value } = e.target; - if (onChange) { - const modelFields = { - username, - caption, - post_url, - profile_url, - status, - metadata: value, - }; - const result = onChange(modelFields); - value = result?.metadata ?? value; + if (clearOnSuccess) { + resetStateValues(); } - if (errors.metadata?.hasError) { - runValidationTasks(\\"metadata\\", value); + } catch (err) { + if (onError) { + onError(modelFields, err.message); } - setMetadata(value); - }} - onBlur={() => runValidationTasks(\\"metadata\\", metadata)} - errorMessage={errors.metadata?.errorMessage} - hasError={errors.metadata?.hasError} - {...getOverrideProps(overrides, \\"metadata\\")} - ></TextAreaField> + } + }} + {...rest} + {...getOverrideProps(overrides, \\"MyMemberForm\\")} + > <Flex justifyContent=\\"space-between\\" {...getOverrideProps(overrides, \\"CTAFlex\\")} @@ -7625,59 +8474,124 @@ export default function PostCreateFormRow(props) { ></Button> </Flex> </Flex> + <TextField + label=\\"Name\\" + isRequired={false} + isReadOnly={false} + onChange={(e) => { + let { value } = e.target; + if (onChange) { + const modelFields = { + name: value, + team, + }; + const result = onChange(modelFields); + value = result?.name ?? value; + } + if (errors.name?.hasError) { + runValidationTasks(\\"name\\", value); + } + setName(value); + }} + onBlur={() => runValidationTasks(\\"name\\", name)} + errorMessage={errors.name?.errorMessage} + hasError={errors.name?.hasError} + {...getOverrideProps(overrides, \\"name\\")} + ></TextField> + <ArrayField + lengthLimit={1} + onChange={async (items) => { + let value = items[0]; + if (onChange) { + const modelFields = { + name, + team: value, + }; + const result = onChange(modelFields); + value = result?.team ?? value; + } + setTeam(value); + setCurrentTeamValue(undefined); + setCurrentTeamDisplayValue(undefined); + }} + currentFieldValue={currentTeamValue} + label={\\"Team Label\\"} + items={team ? [team] : []} + hasError={errors.team?.hasError} + getBadgeText={getDisplayValue.team} + setFieldValue={currentTeamDisplayValue} + inputFieldRef={teamRef} + defaultFieldValue={undefined} + > + <Autocomplete + label=\\"Team Label\\" + isRequired={false} + isReadOnly={false} + value={currentTeamDisplayValue} + options={teamRecords.map((r) => ({ + id: r.id, + label: getDisplayValue.team?.(r) ?? r.id, + }))} + onSelect={({ id, label }) => { + setCurrentTeamValue(teamRecords.find((r) => r.id === id)); + setCurrentTeamDisplayValue(label); + }} + onChange={(e) => { + let { value } = e.target; + if (errors.team?.hasError) { + runValidationTasks(\\"team\\", value); + } + setCurrentTeamDisplayValue(value); + setCurrentTeamValue(undefined); + }} + onBlur={() => runValidationTasks(\\"team\\", team)} + errorMessage={errors.team?.errorMessage} + hasError={errors.team?.hasError} + ref={teamRef} + {...getOverrideProps(overrides, \\"team\\")} + ></Autocomplete> + </ArrayField> </Grid> ); } " `; -exports[`amplify form renderer tests datastore form tests should render form with a two inputs in row 2`] = ` +exports[`amplify form renderer tests datastore form tests should use proper field overrides for belongsTo relationship 2`] = ` "import * as React from \\"react\\"; +import { Team } from \\"../models\\"; import { EscapeHatchProps } from \\"@aws-amplify/ui-react/internal\\"; -import { GridProps, SelectFieldProps, TextAreaFieldProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; +import { AutocompleteProps, GridProps, TextFieldProps } from \\"@aws-amplify/ui-react\\"; export declare type ValidationResponse = { hasError: boolean; errorMessage?: string; }; export declare type ValidationFunction<T> = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise<ValidationResponse>; -export declare type PostCreateFormRowInputValues = { - username?: string; - caption?: string; - post_url?: string; - profile_url?: string; - status?: string; - metadata?: string; +export declare type MyMemberFormInputValues = { + name?: string; + team?: Team; }; -export declare type PostCreateFormRowValidationValues = { - username?: ValidationFunction<string>; - caption?: ValidationFunction<string>; - post_url?: ValidationFunction<string>; - profile_url?: ValidationFunction<string>; - status?: ValidationFunction<string>; - metadata?: ValidationFunction<string>; +export declare type MyMemberFormValidationValues = { + name?: ValidationFunction<string>; + team?: ValidationFunction<Team>; }; export declare type FormProps<T> = Partial<T> & React.DOMAttributes<HTMLDivElement>; -export declare type PostCreateFormRowOverridesProps = { - PostCreateFormRowGrid?: FormProps<GridProps>; - RowGrid0?: FormProps<GridProps>; - username?: FormProps<TextFieldProps>; - caption?: FormProps<TextFieldProps>; - post_url?: FormProps<TextFieldProps>; - profile_url?: FormProps<TextFieldProps>; - status?: FormProps<SelectFieldProps>; - metadata?: FormProps<TextAreaFieldProps>; +export declare type MyMemberFormOverridesProps = { + MyMemberFormGrid?: FormProps<GridProps>; + name?: FormProps<TextFieldProps>; + team?: FormProps<AutocompleteProps>; } & EscapeHatchProps; -export declare type PostCreateFormRowProps = React.PropsWithChildren<{ - overrides?: PostCreateFormRowOverridesProps | undefined | null; +export declare type MyMemberFormProps = React.PropsWithChildren<{ + overrides?: MyMemberFormOverridesProps | undefined | null; } & { clearOnSuccess?: boolean; - onSubmit?: (fields: PostCreateFormRowInputValues) => PostCreateFormRowInputValues; - onSuccess?: (fields: PostCreateFormRowInputValues) => void; - onError?: (fields: PostCreateFormRowInputValues, errorMessage: string) => void; + onSubmit?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onSuccess?: (fields: MyMemberFormInputValues) => void; + onError?: (fields: MyMemberFormInputValues, errorMessage: string) => void; onCancel?: () => void; - onChange?: (fields: PostCreateFormRowInputValues) => PostCreateFormRowInputValues; - onValidate?: PostCreateFormRowValidationValues; + onChange?: (fields: MyMemberFormInputValues) => MyMemberFormInputValues; + onValidate?: MyMemberFormValidationValues; }>; -export default function PostCreateFormRow(props: PostCreateFormRowProps): React.ReactElement; +export default function MyMemberForm(props: MyMemberFormProps): React.ReactElement; " `; diff --git a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts index f59daa218..c32b58d77 100644 --- a/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts +++ b/packages/codegen-ui-react/lib/__tests__/studio-ui-codegen-react-forms.test.ts @@ -58,6 +58,35 @@ describe('amplify form renderer tests', () => { expect(declaration).toMatchSnapshot(); }); + it('should generate a create form with belongsTo relationship', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/member-datastore-create', + 'datastore/project-team-model', + ); + // check nested model is imported + expect(componentText).toContain('import { Member, Team } from "../models";'); + + // check binding call is generated + expect(componentText).toContain('const teamRecords = useDataStoreBinding({'); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + + it('should use proper field overrides for belongsTo relationship', () => { + const { componentText, declaration } = generateWithAmplifyFormRenderer( + 'forms/member-datastore-create', + 'datastore/project-team-model', + ); + // Check that custom field label is working as expected + expect(componentText).toContain('Team Label'); + // Check that Autocomplete custom display value is set + expect(componentText).toContain('team: (record) => record?.name'); + + expect(componentText).toMatchSnapshot(); + expect(declaration).toMatchSnapshot(); + }); + it('should render form with a two inputs in row', () => { const { componentText, declaration } = generateWithAmplifyFormRenderer( 'forms/post-datastore-create-row', diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/all-props.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/all-props.ts index 3491d27f3..034589b36 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/all-props.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/all-props.ts @@ -17,10 +17,10 @@ import { StudioComponent, StudioComponentChild, FormMetadata, isValidVariableNam import { factory, SyntaxKind, JsxAttribute } from 'typescript'; import { buildComponentSpecificAttributes } from './static-props'; import { renderValueAttribute, renderDefaultValueAttribute, isControlledComponent } from './value-props'; -import { buildOnChangeStatement, buildOnBlurStatement, buildOnSuggestionSelect } from './event-handler-props'; +import { buildOnChangeStatement, buildOnBlurStatement, buildOnSelect } from './event-handler-props'; import { resetValuesName } from './form-state'; import { shouldWrapInArrayField } from './render-checkers'; -import { getAutocompleteSuggestionsProp } from './display-value'; +import { getAutocompleteOptionsProp } from './display-value'; export const addFormAttributes = (component: StudioComponent | StudioComponentChild, formMetadata: FormMetadata) => { const { name: componentName, componentType } = component; @@ -71,10 +71,9 @@ export const addFormAttributes = (component: StudioComponent | StudioComponentCh attributes.push(valueAttribute); } - // TODO: Allow for other relationship types once valueMappings available - if (fieldConfig.componentType === 'Autocomplete' && fieldConfig.relationship?.type === 'HAS_ONE') { - attributes.push(getAutocompleteSuggestionsProp({ fieldName: componentName, fieldConfig })); - attributes.push(buildOnSuggestionSelect({ sanitizedFieldName: renderedVariableName, fieldConfig })); + if (fieldConfig.componentType === 'Autocomplete' && fieldConfig.relationship) { + attributes.push(getAutocompleteOptionsProp({ fieldName: componentName, fieldConfig })); + attributes.push(buildOnSelect({ sanitizedFieldName: renderedVariableName, fieldConfig })); } if (formMetadata.formActionType === 'update' && !fieldConfig.isArray && !isControlledComponent(componentType)) { diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts index f45193cd7..f2c1d1cb6 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/display-value.ts @@ -145,25 +145,25 @@ export function extractModelAndKey(valueMappings?: StudioFormValueMappings): { m /** example: - suggestions={authorRecords.map(r) => ({ + options={authorRecords.map(r) => ({ id: r.id, label: getDisplayValue['primaryAuthor']?.(r) ?? r.id, }))} */ -export function getAutocompleteSuggestionsProp({ +export function getAutocompleteOptionsProp({ fieldName, fieldConfig, }: { fieldName: string; fieldConfig: FieldConfigMetadata; }): JsxAttribute { - let suggestions: Expression | undefined; + let options: Expression | undefined; const { valueMappings } = fieldConfig; const { model, key } = extractModelAndKey(valueMappings); if (model && key) { - suggestions = getModelTypeSuggestions({ + options = getModelTypeSuggestions({ modelName: model, fieldName, key, @@ -171,13 +171,13 @@ export function getAutocompleteSuggestionsProp({ }); } - if (!suggestions) { + if (!options) { throw new InvalidInputError(`Invalid value mappings on ${fieldName}`); } return factory.createJsxAttribute( - factory.createIdentifier('suggestions'), - factory.createJsxExpression(undefined, suggestions), + factory.createIdentifier('options'), + factory.createJsxExpression(undefined, options), ); } diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/event-handler-props.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/event-handler-props.ts index 6c5588554..21475a89d 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/event-handler-props.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/event-handler-props.ts @@ -273,7 +273,7 @@ export const buildOnChangeStatement = ( ); }; -// onSuggestionSelect={({ id }) => { +// onSelect={({ id }) => { // setCurrentPrimaryAuthorValue( // id // ); @@ -282,12 +282,12 @@ export const buildOnChangeStatement = ( /** example: - onSuggestionSelect={({ id, label }) => { + onSelect={({ id, label }) => { setCurrentPrimaryAuthorValue(authorRecords.find((r) => r.id === id)); setCurrentPrimaryAuthorDisplayValue(label); }} */ -export function buildOnSuggestionSelect({ +export function buildOnSelect({ sanitizedFieldName, fieldConfig, }: { @@ -362,7 +362,7 @@ export function buildOnSuggestionSelect({ } return factory.createJsxAttribute( - factory.createIdentifier('onSuggestionSelect'), + factory.createIdentifier('onSelect'), factory.createJsxExpression( undefined, factory.createArrowFunction( diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts index 5a9bbdfd5..e540ee306 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper/render-array-field.ts @@ -147,7 +147,14 @@ export const renderArrayFieldComponent = ( let itemsExpression: Expression = factory.createIdentifier(renderedFieldName); if (isLimitedToOneValue) { - itemsExpression = factory.createArrayLiteralExpression([factory.createIdentifier(renderedFieldName)], false); + // "book ? [book] : []" + itemsExpression = factory.createConditionalExpression( + factory.createIdentifier(renderedFieldName), + factory.createToken(SyntaxKind.QuestionToken), + factory.createArrayLiteralExpression([factory.createIdentifier(renderedFieldName)], false), + factory.createToken(SyntaxKind.ColonToken), + factory.createArrayLiteralExpression([], false), + ); props.push( factory.createJsxAttribute( 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 index 30815fab4..aed78abe2 100644 --- a/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts +++ b/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts @@ -18,7 +18,7 @@ import { addUseEffectWrapper } from '../generate-react-hooks'; 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'; - + const arraySection = 'arraySection'; const bodyBlock = [ factory.createVariableStatement( undefined, @@ -360,6 +360,417 @@ export const generateArrayFieldComponent = () => { ), ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier(arraySection), + undefined, + undefined, + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createPropertyAccessExpression( + factory.createIdentifier('React'), + factory.createIdentifier('Fragment'), + ) as JsxTagNamePropertyAccess, + undefined, + factory.createJsxAttributes([]), + ), + [ + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createIdentifier('Text'), + undefined, + factory.createJsxAttributes([]), + ), + [factory.createJsxExpression(undefined, factory.createIdentifier('label'))], + factory.createJsxClosingElement(factory.createIdentifier('Text')), + ), + factory.createJsxExpression( + undefined, + factory.createBinaryExpression( + factory.createPrefixUnaryExpression( + SyntaxKind.ExclamationToken, + factory.createPrefixUnaryExpression( + SyntaxKind.ExclamationToken, + factory.createPropertyAccessChain( + factory.createIdentifier('items'), + factory.createToken(SyntaxKind.QuestionDotToken), + 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')], + ), + ), + /** + setFieldValue(getBadgeText ? + getBadgeText(items[index]) + : items[index]); + */ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier('setFieldValue'), + undefined, + [ + factory.createConditionalExpression( + factory.createIdentifier('getBadgeText'), + factory.createToken(SyntaxKind.QuestionToken), + factory.createCallExpression( + factory.createIdentifier('getBadgeText'), + undefined, + [ + factory.createElementAccessExpression( + factory.createIdentifier('items'), + factory.createIdentifier('index'), + ), + ], + ), + factory.createToken(SyntaxKind.ColonToken), + factory.createElementAccessExpression( + factory.createIdentifier('items'), + factory.createIdentifier('index'), + ), + ), + ], + ), + ), + + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier('setIsEditing'), + undefined, + [factory.createTrue()], + ), + ), + ], + true, + ), + ), + ), + ), + ]), + ), + [ + // { getBadgeText ? getBadgeText(value) : value.toString() } + factory.createJsxExpression( + undefined, + factory.createConditionalExpression( + factory.createIdentifier('getBadgeText'), + factory.createToken(SyntaxKind.QuestionToken), + factory.createCallExpression( + factory.createIdentifier('getBadgeText'), + undefined, + [factory.createIdentifier('value')], + ), + factory.createToken(SyntaxKind.ColonToken), + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('value'), + factory.createIdentifier('toString'), + ), + undefined, + [], + ), + ), + ), + 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, + ), + ), + ), + ], + NodeFlags.Const, + ), + ), + /** if (lengthLimit !== undefined && items.length >= lengthLimit && !isEditing) { return arraySection; @@ -386,7 +797,7 @@ export const generateArrayFieldComponent = () => { factory.createToken(SyntaxKind.AmpersandAmpersandToken), factory.createPrefixUnaryExpression(SyntaxKind.ExclamationToken, factory.createIdentifier('isEditing')), ), - factory.createBlock([factory.createReturnStatement(factory.createIdentifier('arraySection'))], true), + factory.createBlock([factory.createReturnStatement(factory.createIdentifier(arraySection))], true), undefined, ), @@ -622,374 +1033,6 @@ export const generateArrayFieldComponent = () => { ), ), ), - - factory.createJsxExpression( - undefined, - factory.createBinaryExpression( - factory.createPrefixUnaryExpression( - SyntaxKind.ExclamationToken, - factory.createPrefixUnaryExpression( - SyntaxKind.ExclamationToken, - factory.createPropertyAccessChain( - factory.createIdentifier('items'), - factory.createToken(SyntaxKind.QuestionDotToken), - 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')], - ), - ), - /** - setFieldValue(getBadgeText ? - getBadgeText(items[index]) - : items[index]); - */ - factory.createExpressionStatement( - factory.createCallExpression( - factory.createIdentifier('setFieldValue'), - undefined, - [ - factory.createConditionalExpression( - factory.createIdentifier('getBadgeText'), - factory.createToken(SyntaxKind.QuestionToken), - factory.createCallExpression( - factory.createIdentifier('getBadgeText'), - undefined, - [ - factory.createElementAccessExpression( - factory.createIdentifier('items'), - factory.createIdentifier('index'), - ), - ], - ), - factory.createToken(SyntaxKind.ColonToken), - factory.createElementAccessExpression( - factory.createIdentifier('items'), - factory.createIdentifier('index'), - ), - ), - ], - ), - ), - - factory.createExpressionStatement( - factory.createCallExpression( - factory.createIdentifier('setIsEditing'), - undefined, - [factory.createTrue()], - ), - ), - ], - true, - ), - ), - ), - ), - ]), - ), - [ - // { getBadgeText ? getBadgeText(value) : value.toString() } - factory.createJsxExpression( - undefined, - factory.createConditionalExpression( - factory.createIdentifier('getBadgeText'), - factory.createToken(SyntaxKind.QuestionToken), - factory.createCallExpression( - factory.createIdentifier('getBadgeText'), - undefined, - [factory.createIdentifier('value')], - ), - factory.createToken(SyntaxKind.ColonToken), - factory.createCallExpression( - factory.createPropertyAccessExpression( - factory.createIdentifier('value'), - factory.createIdentifier('toString'), - ), - undefined, - [], - ), - ), - ), - 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( diff --git a/packages/codegen-ui/example-schemas/datastore/project-team-model.json b/packages/codegen-ui/example-schemas/datastore/project-team-model.json new file mode 100644 index 000000000..8e7ef77af --- /dev/null +++ b/packages/codegen-ui/example-schemas/datastore/project-team-model.json @@ -0,0 +1,145 @@ +{ + "models": { + "Member": { + "name": "Member", + "fields": { + "id": { "name": "id", "isArray": false, "type": "ID", "isRequired": true, "attributes": [] }, + "name": { "name": "name", "isArray": false, "type": "String", "isRequired": false, "attributes": [] }, + "teamID": { "name": "teamID", "isArray": false, "type": "ID", "isRequired": true, "attributes": [] }, + "team": { + "name": "team", + "isArray": false, + "type": { "model": "Team" }, + "isRequired": false, + "attributes": [], + "association": { "connectionType": "BELONGS_TO", "targetName": "teamMembersId" } + }, + "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": "Members", + "attributes": [ + { "type": "model", "properties": {} }, + { "type": "key", "properties": { "name": "byTeam", "fields": ["teamID"] } }, + { + "type": "auth", + "properties": { "rules": [{ "allow": "public", "operations": ["create", "update", "delete", "read"] }] } + } + ] + }, + "Team": { + "name": "Team", + "fields": { + "id": { "name": "id", "isArray": false, "type": "ID", "isRequired": true, "attributes": [] }, + "name": { "name": "name", "isArray": false, "type": "String", "isRequired": true, "attributes": [] }, + "Project": { + "name": "Project", + "isArray": false, + "type": { "model": "Project" }, + "isRequired": false, + "attributes": [], + "association": { "connectionType": "HAS_ONE", "associatedWith": "Team", "targetName": "teamProjectId" } + }, + "Members": { + "name": "Members", + "isArray": true, + "type": { "model": "Member" }, + "isRequired": false, + "attributes": [], + "isArrayNullable": true, + "association": { "connectionType": "HAS_MANY", "associatedWith": "teamID" } + }, + "createdAt": { + "name": "createdAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + }, + "updatedAt": { + "name": "updatedAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + }, + "teamProjectId": { + "name": "teamProjectId", + "isArray": false, + "type": "ID", + "isRequired": false, + "attributes": [] + } + }, + "syncable": true, + "pluralName": "Teams", + "attributes": [ + { "type": "model", "properties": {} }, + { + "type": "auth", + "properties": { "rules": [{ "allow": "public", "operations": ["create", "update", "delete", "read"] }] } + } + ] + }, + "Project": { + "name": "Project", + "fields": { + "id": { "name": "id", "isArray": false, "type": "ID", "isRequired": true, "attributes": [] }, + "name": { "name": "name", "isArray": false, "type": "String", "isRequired": true, "attributes": [] }, + "team": { + "name": "team", + "isArray": false, + "type": { "model": "Team" }, + "isRequired": false, + "attributes": [], + "association": { "connectionType": "BELONGS_TO", "targetName": "projectTeamId" } + }, + "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": "Projects", + "attributes": [ + { "type": "model", "properties": {} }, + { + "type": "auth", + "properties": { "rules": [{ "allow": "public", "operations": ["create", "update", "delete", "read"] }] } + } + ] + } + }, + "enums": {}, + "nonModels": {}, + "version": "f4d166560661ebce8a75b9c1b1735ed1" +} diff --git a/packages/codegen-ui/example-schemas/forms/member-datastore-create.json b/packages/codegen-ui/example-schemas/forms/member-datastore-create.json new file mode 100644 index 000000000..932419339 --- /dev/null +++ b/packages/codegen-ui/example-schemas/forms/member-datastore-create.json @@ -0,0 +1,45 @@ +{ + "name": "MyMemberForm", + "formActionType": "create", + "dataType": { + "dataSourceType": "DataStore", + "dataTypeName": "Member" + }, + "fields": { + "team": { + "inputType": { + "type": "Autocomplete", + "valueMappings": { + "values": [ + { + "value": { + "bindingProperties": { + "property": "Team", + "field": "id" + } + }, + "displayValue": { + "bindingProperties": { + "property": "Team", + "field": "name" + } + } + } + ], + "bindingProperties": { + "Team": { "type": "Data", "bindingProperties": { "model": "Team" } } + } + } + }, + "label": "Team Label" + } + }, + "sectionalElements": {}, + "style": {}, + "cta": { + "position": "top", + "clear": {}, + "cancel": {}, + "submit": {} + } +} 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 8ed1df631..1146a3527 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 @@ -69,15 +69,12 @@ function getValueMappings({ // if relationship if (field.relationship) { - // if model & HAS_ONE - if (field.relationship.type === 'HAS_ONE') { - const modelName = field.relationship.relatedModelName; - return { - // TODO: map field dynamically as part of cpk task - values: [{ value: { bindingProperties: { property: modelName, field: 'id' } } }], - bindingProperties: { [modelName]: { type: 'Data', bindingProperties: { model: modelName } } }, - }; - } + const modelName = field.relationship.relatedModelName; + return { + // TODO: map field dynamically as part of cpk task + values: [{ value: { bindingProperties: { property: modelName, field: 'id' } } }], + bindingProperties: { [modelName]: { type: 'Data', bindingProperties: { model: modelName } } }, + }; } return undefined;