diff --git a/.rat-excludes b/.rat-excludes index b652dffe0dc..91b98685a66 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -452,6 +452,8 @@ BoolField.test.tsx.snap CheckBoxGroupField.test.tsx.snap # packages/form-code-generator-patternfly-theme/tests/__snapshots__/DateField.test.tsx.snap DateField.test.tsx.snap +# packages/form-code-generator-patternfly-theme/tests/__snapshots__/ListField.test.tsx.snap +ListField.test.tsx.snap # packages/form-code-generator-patternfly-theme/tests/__snapshots__/NestField.test.tsx.snap NestField.test.tsx.snap # packages/form-code-generator-patternfly-theme/tests/__snapshots__/NumField.test.tsx.snap diff --git a/packages/form-code-generator-patternfly-theme/src/api/types.ts b/packages/form-code-generator-patternfly-theme/src/api/types.ts index 468283af5a6..a8a859e3401 100644 --- a/packages/form-code-generator-patternfly-theme/src/api/types.ts +++ b/packages/form-code-generator-patternfly-theme/src/api/types.ts @@ -20,11 +20,9 @@ export interface FormElement { reactImports: string[]; pfImports: string[]; - + pfIconImports?: string[]; requiredCode?: string[]; - ref: InputReference; - stateCode: string; jsxCode: string; isReadonly: boolean; @@ -33,6 +31,7 @@ export interface FormElement { abstract class AbstractFormElement implements FormElement { jsxCode: string; pfImports: string[]; + pfIconImports?: string[]; reactImports: string[]; requiredCode?: string[]; ref: InputReference; diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/AutoField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/AutoField.tsx index 91e12513ac3..2226b7b74fb 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/AutoField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/AutoField.tsx @@ -21,6 +21,7 @@ import { createAutoField } from "uniforms/cjs/createAutoField"; import TextField from "./TextField"; import BoolField from "./BoolField"; +import ListField from "./ListField"; import NumField from "./NumField"; import NestField from "./NestField"; import DateField from "./DateField"; @@ -40,10 +41,8 @@ const AutoField = createAutoField((props) => { } switch (props.fieldType) { - /* - TODO: implement array support case Array: - return ListField;*/ + return ListField; case Boolean: return BoolField; case Date: diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/AutoForm.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/AutoForm.tsx index ffb59cc8fec..272d042a2fd 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/AutoForm.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/AutoForm.tsx @@ -45,11 +45,13 @@ const AutoForm: React.FC = (props) => { const inputs: FormElement[] = renderFormInputs(props.schema); let pfImports: string[] = []; + let pfIconImports: string[] = []; let reactImports: string[] = ["useCallback", "useEffect"]; let staticCodeArray: string[] = []; inputs.forEach((input) => { pfImports = union(pfImports, input.pfImports); + pfIconImports = union(pfIconImports, input.pfIconImports); reactImports = union(reactImports, input.reactImports); staticCodeArray = union(staticCodeArray, input.requiredCode); }); @@ -61,8 +63,9 @@ const AutoForm: React.FC = (props) => { const staticCodeStr: string = staticCodeArray.map((id) => JSON.stringify(getStaticCodeBlock(id))).join("\n"); const formTemplate = ` -import React, { ${reactImports.join(", ")} } from "react"; - import { ${pfImports.join(", ")} } from "@patternfly/react-core"; +import React, { ${reactImports.join(", ")} } from "react"; +import { ${pfImports.join(", ")} } from "@patternfly/react-core"; +${pfIconImports.length > 0 ? `import { ${pfIconImports.join(", ")} } from "@patternfly/react-icons";` : ""} const ${formName}: React.FC = ( props:any ) => { const [formApi, setFormApi] = useState(); diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/BoolField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/BoolField.tsx index 89418cba5b1..1e41c1081be 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/BoolField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/BoolField.tsx @@ -24,6 +24,7 @@ import { FormInput, InputReference } from "../api"; import { getInputReference, getStateCodeFromRef, renderField } from "./utils/Utils"; import { BOOLEAN } from "./utils/dataTypes"; +import { getListItemName, getListItemOnChange, getListItemValue, ListItemProps } from "./rendering/ListItemField"; export type BoolFieldProps = HTMLFieldProps< boolean, @@ -31,6 +32,7 @@ export type BoolFieldProps = HTMLFieldProps< { name: string; label: string; + itemProps?: ListItemProps; } >; @@ -41,12 +43,12 @@ const Bool: React.FC = (props: BoolFieldProps) => { const jsxCode = ` `; diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/CheckBoxGroupField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/CheckBoxGroupField.tsx index 8068d37260a..8bc9347a97c 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/CheckBoxGroupField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/CheckBoxGroupField.tsx @@ -24,6 +24,7 @@ import { FormInput, InputReference } from "../api"; import { useAddFormElementToContext } from "./CodeGenContext"; import { CHECKBOX_GROUP_FUNCTIONS } from "./staticCode/staticCodeBlocks"; import { ARRAY } from "./utils/dataTypes"; +import { getListItemName, getListItemOnChange, getListItemValue, ListItemProps } from "./rendering/ListItemField"; export type CheckBoxGroupProps = HTMLFieldProps< string[], @@ -34,6 +35,7 @@ export type CheckBoxGroupProps = HTMLFieldProps< allowedValues?: string[]; required: boolean; transform?(value: string): string; + itemProps: ListItemProps; } >; @@ -42,14 +44,17 @@ const CheckBoxGroup: React.FC = (props: CheckBoxGroupProps) const jsxCode = props.allowedValues ?.map((value) => { - return ` handleCheckboxGroupChange('${value}', ${ref.stateName}, ${ref.stateSetter})} - value={'${value}'}/>`; + return ` `handleCheckboxGroupChange(${internalValue}, ${ref.stateName}, ${ref.stateSetter})`, overrideNewValue: `'${value}'` }) : `() => handleCheckboxGroupChange('${value}', ${ref.stateName}, ${ref.stateSetter})`}} + value={${props.itemProps?.isListItem ? getListItemValue({ itemProps: props.itemProps, name: props.name }) : `'${value}'`}} +/>`; }) .join("\n"); diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/DateField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/DateField.tsx index 98cc654f2a5..e84502c8db3 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/DateField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/DateField.tsx @@ -25,6 +25,7 @@ import { buildDefaultInputElement, getInputReference, renderField } from "./util import { useAddFormElementToContext } from "./CodeGenContext"; import { DATE_FUNCTIONS, TIME_FUNCTIONS } from "./staticCode/staticCodeBlocks"; import { DATE } from "./utils/dataTypes"; +import { getListItemName, getListItemOnChange, getListItemValue, ListItemProps } from "./rendering/ListItemField"; export type DateFieldProps = HTMLFieldProps< Date, @@ -35,6 +36,7 @@ export type DateFieldProps = HTMLFieldProps< required: boolean; max?: Date; min?: Date; + itemProps: ListItemProps; } >; @@ -52,19 +54,34 @@ const Date: React.FC = (props: DateFieldProps) => { onDateChange(newDate, ${ref.stateSetter}, ${ref.stateName})} - value={parseDate(${ref.stateName})} + name={${props.itemProps?.isListItem ? getListItemName({ itemProps: props.itemProps, name: props.name }) : `'${props.name}'`}} + onChange={${ + props.itemProps?.isListItem + ? getListItemOnChange({ + itemProps: props.itemProps, + name: props.name, + callback: (value) => `onDateChange(${value}, ${ref.stateSetter}, ${ref.stateName})`, + }) + : `newDate => onDateChange(newDate, ${ref.stateSetter}, ${ref.stateName})` + }} + value={${props.itemProps?.isListItem ? getListItemValue({ itemProps: props.itemProps, name: props.name, callback: (value: string) => `parseDate(${value})` }) : `parseDate(${ref.stateName})`}} /> onTimeChange(time, ${ref.stateSetter}, ${ - ref.stateName - }, hours, minutes)} + name={${props.itemProps?.isListItem ? getListItemName({ itemProps: props.itemProps, name: props.name }) : `'${props.name}'`}} + onChange={${ + props.itemProps?.isListItem + ? getListItemOnChange({ + itemProps: props.itemProps, + name: props.name, + callback: (_) => `onTimeChange(time, ${ref.stateSetter}, ${ref.stateName}, hours, minutes)`, + overrideParam: "(time, hours?, minutes?)", + }) + : `(time, hours?, minutes?) => onTimeChange(time, ${ref.stateSetter}, ${ref.stateName}, hours, minutes)` + }} style={{ width: '120px' }} - time={parseTime(${ref.stateName})} + time={${props.itemProps?.isListItem ? getListItemValue({ itemProps: props.itemProps, name: props.name, callback: (value: string) => `parseTime(${value})` }) : `parseTime(${ref.stateName})`}} /> diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/ListField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/ListField.tsx new file mode 100644 index 00000000000..54885e15895 --- /dev/null +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/ListField.tsx @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 React, { useContext } from "react"; +import { connectField, context, HTMLFieldProps, joinName } from "uniforms/cjs"; +import { getInputReference, getStateCode, renderField } from "./utils/Utils"; +import { codeGenContext } from "./CodeGenContext"; +import { FormInput, InputReference } from "../api"; +import { ARRAY } from "./utils/dataTypes"; +import { renderListItemFragmentWithContext } from "./rendering/RenderingUtils"; +import { ListItemProps } from "./rendering/ListItemField"; + +export type ListFieldProps = HTMLFieldProps< + unknown[], + HTMLDivElement, + { + itemProps?: ListItemProps; + maxCount?: number; + minCount?: number; + } +>; + +const List: React.FC = (props: ListFieldProps) => { + const ref: InputReference = getInputReference(props.name, ARRAY); + + const uniformsContext = useContext(context); + const codegenCtx = useContext(codeGenContext); + + const listItem = renderListItemFragmentWithContext( + uniformsContext, + "$", + { + isListItem: true, + indexVariableName: "itemIndex", + listName: props.name, + listStateName: ref.stateName, + listStateSetter: ref.stateSetter, + }, + props.disabled + ); + + const getNewItemProps = () => { + const typeName = listItem?.ref.dataType.name; + if (typeName?.endsWith("[]")) { + return listItem?.ref.dataType.defaultValue ?? []; + } + switch (typeName) { + case "string": + return listItem?.ref.dataType.defaultValue ?? ""; + case "number": + return listItem?.ref.dataType.defaultValue ?? null; + case "boolean": + return listItem?.ref.dataType.defaultValue ?? false; + case "object": + return listItem?.ref.dataType.defaultValue ?? {}; + default: // any + return listItem?.ref.dataType.defaultValue; + } + }; + + const jsxCode = `
+ + + {'${props.label}' && ( + + )} + + + + + + +
+ {${ref.stateName}?.map((_, itemIndex) => + (
+
${listItem?.jsxCode}
+
+ +
+
) + )} +
+
`; + + const element: FormInput = { + ref, + pfImports: [...new Set(["Split", "SplitItem", "Button", ...(listItem?.pfImports ?? [])])], + pfIconImports: [...new Set(["PlusCircleIcon", "MinusCircleIcon", ...(listItem?.pfIconImports ?? [])])], + reactImports: [...new Set([...(listItem?.reactImports ?? [])])], + jsxCode, + stateCode: getStateCode(ref.stateName, ref.stateSetter, ref.dataType.name, "[]"), + isReadonly: props.disabled, + }; + + codegenCtx?.rendered.push(element); + + return renderField(element); +}; + +export default connectField(List); diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/NestField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/NestField.tsx index 8d707958823..4965c4b1a51 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/NestField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/NestField.tsx @@ -25,8 +25,9 @@ import { InputReference, InputsContainer } from "../api"; import { codeGenContext } from "./CodeGenContext"; import { union } from "lodash"; import { OBJECT } from "./utils/dataTypes"; +import { ListItemProps } from "./rendering/ListItemField"; -export type NestFieldProps = HTMLFieldProps; +export type NestFieldProps = HTMLFieldProps; const Nest: React.FunctionComponent = ({ id, @@ -49,6 +50,7 @@ const Nest: React.FunctionComponent = ({ const nestedJsx: string[] = []; let pfImports: string[] = ["Card", "CardBody"]; + let pfIconImports: string[] = []; let reactImports: string[] = []; let requiredCode: string[] = []; @@ -65,6 +67,7 @@ const Nest: React.FunctionComponent = ({ nestedRefs.push(...nestedContainer.childRefs); } pfImports = union(pfImports, renderedInput.pfImports); + pfIconImports = union(pfIconImports, renderedInput.pfIconImports); reactImports = union(reactImports, renderedInput.reactImports); if (renderedInput.requiredCode) { requiredCode = union(requiredCode, renderedInput.requiredCode); @@ -75,7 +78,7 @@ const Nest: React.FunctionComponent = ({ }); } - const bodyLabel = label ? `` : ""; + const bodyLabel = label && !itemProps?.isListItem ? `` : ""; const stateCode = nestedStates.join("\n"); const jsxCode = ` @@ -86,6 +89,7 @@ const Nest: React.FunctionComponent = ({ const rendered: InputsContainer = { pfImports, + pfIconImports, reactImports, requiredCode: requiredCode, stateCode, diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/NumField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/NumField.tsx index dc0de287bc9..abfbee312dc 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/NumField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/NumField.tsx @@ -24,6 +24,7 @@ import { buildDefaultInputElement, getInputReference, renderField } from "./util import { useAddFormElementToContext } from "./CodeGenContext"; import { FormInput, InputReference } from "../api"; import { NUMBER } from "./utils/dataTypes"; +import { getListItemName, getListItemOnChange, getListItemValue, ListItemProps } from "./rendering/ListItemField"; export type NumFieldProps = HTMLFieldProps< string, @@ -34,6 +35,7 @@ export type NumFieldProps = HTMLFieldProps< decimal?: boolean; min?: string; max?: string; + itemProps?: ListItemProps; } >; @@ -45,13 +47,13 @@ const Num: React.FC = (props: NumFieldProps) => { const inputJsxCode = ` ${ref.stateSetter}(Number(newValue))} + value={${props.itemProps?.isListItem ? getListItemValue({ itemProps: props.itemProps, name: props.name }) : ref.stateName}} + onChange={${props.itemProps?.isListItem ? getListItemOnChange({ itemProps: props.itemProps, name: props.name, callback: (value: string) => `Number(${value})` }) : `(newValue) => ${ref.stateSetter}(Number(newValue))`}} />`; const element: FormInput = buildDefaultInputElement({ diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/RadioField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/RadioField.tsx index c5363bf4d77..cdaaba713fa 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/RadioField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/RadioField.tsx @@ -23,6 +23,7 @@ import { useAddFormElementToContext } from "./CodeGenContext"; import { FormInput, InputReference } from "../api"; import { buildDefaultInputElement, getInputReference, renderField } from "./utils/Utils"; import { STRING } from "./utils/dataTypes"; +import { getListItemName, getListItemOnChange, getListItemValue, ListItemProps } from "./rendering/ListItemField"; export type RadioFieldProps = HTMLFieldProps< string, @@ -34,6 +35,7 @@ export type RadioFieldProps = HTMLFieldProps< allowedValues: string[]; required: boolean; disabled: boolean; + itemProps?: ListItemProps; } >; @@ -46,12 +48,12 @@ const Radio = (props: RadioFieldProps) => { const radio = ` ${ref.stateSetter}('${item}')} + onChange={${props.itemProps?.isListItem ? getListItemOnChange({ itemProps: props.itemProps, name: props.name, overrideNewValue: item }) : `() => ${ref.stateSetter}('${item}')`}} />`; radios.push(radio); }); diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/SelectField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/SelectField.tsx index 7dcc01c535a..212b91466ad 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/SelectField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/SelectField.tsx @@ -24,6 +24,7 @@ import { FormInput, InputReference } from "../api"; import { getInputReference, getStateCode, getStateCodeFromRef, NS_SEPARATOR, renderField } from "./utils/Utils"; import { MULTIPLE_SELECT_FUNCTIONS, SELECT_FUNCTIONS } from "./staticCode/staticCodeBlocks"; import { ARRAY, STRING } from "./utils/dataTypes"; +import { getListItemName, getListItemOnChange, getListItemValue, ListItemProps } from "./rendering/ListItemField"; export type SelectInputProps = HTMLFieldProps< string | string[], @@ -31,6 +32,7 @@ export type SelectInputProps = HTMLFieldProps< { allowedValues?: string[]; transform?(value: string): string; + itemProps?: ListItemProps; } >; @@ -75,17 +77,15 @@ const Select: React.FC = (props: SelectInputProps) => { isRequired={${props.required}} >`; diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/TextField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/TextField.tsx index 9f26e130bfe..92303b9cd01 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/TextField.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/TextField.tsx @@ -25,6 +25,7 @@ import { FormInput, InputReference } from "../api"; import { buildDefaultInputElement, getInputReference, renderField } from "./utils/Utils"; import { DATE_FUNCTIONS } from "./staticCode/staticCodeBlocks"; import { DATE, STRING } from "./utils/dataTypes"; +import { getListItemName, getListItemOnChange, getListItemValue, ListItemProps } from "./rendering/ListItemField"; export type TextFieldProps = HTMLFieldProps< string, @@ -32,6 +33,7 @@ export type TextFieldProps = HTMLFieldProps< { label: string; required: boolean; + itemProps?: ListItemProps; } >; @@ -44,9 +46,9 @@ const Text: React.FC = (props: TextFieldProps) => { const inputJsxCode = ` onDateChange(newDate, ${ref.stateSetter}, ${ref.stateName})} - value={parseDate(${ref.stateName})} + name={${props.itemProps?.isListItem ? getListItemName({ itemProps: props.itemProps, name: props.name }) : `'${props.name}'`}} + onChange={${props.itemProps?.isListItem ? getListItemOnChange({ itemProps: props.itemProps, name: props.name, callback: (value: string) => `onDateChange(${value}, ${ref.stateSetter}, ${ref.stateName})` }) : `newDate => onDateChange(newDate, ${ref.stateSetter}, ${ref.stateName})`}} + value={${props.itemProps?.isListItem ? `parseDate(${getListItemValue({ itemProps: props.itemProps, name: props.name })})` : `parseDate(${ref.stateName})`}} />`; return buildDefaultInputElement({ pfImports: ["DatePicker"], @@ -64,13 +66,13 @@ const Text: React.FC = (props: TextFieldProps) => { const getTextInputElement = (): FormInput => { const inputJsxCode = ``; return buildDefaultInputElement({ diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/index.ts b/packages/form-code-generator-patternfly-theme/src/uniforms/index.ts index 041c9a78b3d..82287b4e235 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/index.ts +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/index.ts @@ -23,6 +23,7 @@ export { default as AutoFields } from "./AutoFields"; export { default as BoolField } from "./BoolField"; export { default as CheckBoxGroupField } from "./CheckBoxGroupField"; export { default as DateField } from "./DateField"; +export { default as ListField } from "./ListField"; export { default as NestField } from "./NestField"; export { default as NumField } from "./NumField"; export { default as RadioField } from "./RadioField"; diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/ListItemField.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/ListItemField.tsx new file mode 100644 index 00000000000..ba170e07acf --- /dev/null +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/ListItemField.tsx @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 { Context } from "uniforms"; +import * as React from "react"; +import { CodeGenContext, CodeGenContextProvider } from "../CodeGenContext"; +import AutoField from "../AutoField"; + +export interface ListItemProps { + isListItem: boolean; + indexVariableName: string; + listName: string; + listStateName: string; + listStateSetter: string; +} + +/** + * The list item can be nested or not (be part of an object). + * For non-nested items the `itemName` will have value "$", for nested items it will have its property name + */ +function getItemNameAndWithIsNested(name: string) { + const itemName = name.split(".").pop() ?? "$"; + const isNested = itemName !== "$"; + return { itemName, isNested }; +} + +/** + * This function can either return: + * `listName.$` + * `listName.${index}.itemName` + */ +export const getListItemName = ({ itemProps, name }: { itemProps: ListItemProps; name: string }) => { + const { itemName, isNested } = getItemNameAndWithIsNested(name); + return `\`${itemProps?.listName}${isNested ? `.$\{${itemProps?.indexVariableName}}.${itemName}` : `.$\{${itemProps?.indexVariableName}}`}\``; +}; + +/** + * This function can either return: + * `listStateName[index]` + * `listStateName[index].itemName.` + */ +export const getListItemValue = ({ + itemProps, + name, + callback, +}: { + itemProps: ListItemProps; + name: string; + callback?: (value: string) => string; +}) => { + const { itemName, isNested } = getItemNameAndWithIsNested(name); + const property = `${itemProps?.listStateName}[${itemProps?.indexVariableName}]${isNested ? `.${itemName}` : ""}`; + return `${callback ? callback(property) : property}`; +}; + +/** + * This function can either return: + * `newValue => listStateSetter(s => + * const newState = [...s]; + * const newState[index] = newValue; + * return newState; + * );` + * `newValue => listStateSetter(s => + * const newState = [...s]; + * const newState[index].itemName = newValue; + * return newState; + * );` + */ +export const getListItemOnChange = ({ + itemProps, + name, + callback, + overrideNewValue, + overrideParam, +}: { + itemProps: ListItemProps; + name: string; + callback?: (value: string) => string; + overrideParam?: string; + overrideNewValue?: string; +}) => { + const { itemName, isNested } = getItemNameAndWithIsNested(name); + return ` + ${overrideParam ? overrideParam : "newValue"} => { + ${itemProps?.listStateSetter}(s => { + const newState = [...s]; + newState[${itemProps?.indexVariableName}]${isNested ? `.${itemName}` : ""} = ${callback ? callback(overrideNewValue ? overrideNewValue : "newValue") : overrideNewValue ? overrideNewValue : "newValue"}; + return newState; + }) + }`; +}; + +export interface Props { + codegenCtx: CodeGenContext; + uniformsContext: Context; + fieldName: any; + itemProps: ListItemProps; + disabled?: boolean; +} + +export const ListItemField: React.FC = ({ codegenCtx, uniformsContext, fieldName, itemProps, disabled }) => { + return ( + + + + ); +}; + +export default ListItemField; diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/NestedFieldInput.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/NestedFieldInput.tsx index 72c49ce3b23..0b161f36856 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/NestedFieldInput.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/NestedFieldInput.tsx @@ -26,14 +26,14 @@ export interface Props { codegenCtx: CodeGenContext; uniformsContext: Context; field: any; - itempProps: any; + itemProps: any; disabled?: boolean; } -export const NestedFieldInput: React.FC = ({ codegenCtx, uniformsContext, field, itempProps, disabled }) => { +export const NestedFieldInput: React.FC = ({ codegenCtx, uniformsContext, field, itemProps, disabled }) => { return ( - + ); }; diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/RenderingUtils.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/RenderingUtils.tsx index dd2d4d0318d..6574eda24a6 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/RenderingUtils.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/rendering/RenderingUtils.tsx @@ -24,6 +24,7 @@ import { FormElement, FormInput } from "../../api"; import FormInputs from "./FormInputs"; import { CodeGenContext } from "../CodeGenContext"; import NestedFieldInput from "./NestedFieldInput"; +import ListItemField, { ListItemProps } from "./ListItemField"; export const renderFormInputs = (schema: Bridge): FormElement[] => { const codegenCtx: CodeGenContext = { @@ -43,7 +44,7 @@ export const renderFormInputs = (schema: Bridge): FormElement[] => { export const renderNestedInputFragmentWithContext = ( uniformsContext: any, field: any, - itempProps: any, + itemProps: any, disabled?: boolean ): FormInput | undefined => { const codegenCtx: CodeGenContext = { @@ -55,7 +56,30 @@ export const renderNestedInputFragmentWithContext = ( codegenCtx, uniformsContext, field, - itempProps, + itemProps, + disabled, + }) + ); + + return codegenCtx.rendered.length === 1 ? codegenCtx.rendered[0] : undefined; +}; + +export const renderListItemFragmentWithContext = ( + uniformsContext: any, + fieldName: string, + itemProps: ListItemProps, + disabled?: boolean +): FormInput | undefined => { + const codegenCtx: CodeGenContext = { + rendered: [], + }; + + ReactDOMServer.renderToString( + React.createElement(ListItemField, { + codegenCtx, + uniformsContext, + fieldName, + itemProps, disabled, }) ); diff --git a/packages/form-code-generator-patternfly-theme/src/uniforms/utils/Utils.tsx b/packages/form-code-generator-patternfly-theme/src/uniforms/utils/Utils.tsx index 70f765ea518..fde13d3620e 100644 --- a/packages/form-code-generator-patternfly-theme/src/uniforms/utils/Utils.tsx +++ b/packages/form-code-generator-patternfly-theme/src/uniforms/utils/Utils.tsx @@ -50,6 +50,7 @@ export const getStateCode = ( type DefaultInputProps = { pfImports: string[]; + pfIconImports?: string[]; inputJsxCode: string; ref: InputReference; requiredCode?: string[]; @@ -65,6 +66,7 @@ type WrapperProps = { export const buildDefaultInputElement = ({ pfImports, + pfIconImports, inputJsxCode, ref, wrapper, @@ -86,6 +88,7 @@ export const buildDefaultInputElement = ({ return { ref, pfImports, + pfIconImports, reactImports: ["useState"], requiredCode: requiredCode, jsxCode, diff --git a/packages/form-code-generator-patternfly-theme/tests/AutoField.test.tsx b/packages/form-code-generator-patternfly-theme/tests/AutoField.test.tsx index 8db2f75e8ed..63f64a00da9 100644 --- a/packages/form-code-generator-patternfly-theme/tests/AutoField.test.tsx +++ b/packages/form-code-generator-patternfly-theme/tests/AutoField.test.tsx @@ -114,6 +114,17 @@ describe(" tests", () => { expect(formElement.requiredCode).toContain(TIME_FUNCTIONS); }); + it(" - rendering", () => { + const { formElement } = doRenderField("friends"); + + expect(formElement.pfImports).toContain("FormGroup"); + expect(formElement.pfImports).toContain("TextInput"); + expect(formElement.pfIconImports).toContain("PlusCircleIcon"); + expect(formElement.pfIconImports).toContain("MinusCircleIcon"); + + expect(formElement.jsxCode).not.toBeNull(); + }); + it(" - integer rendering", () => { const { formElement } = doRenderField("age"); @@ -182,11 +193,4 @@ describe(" tests", () => { expect(formElement.requiredCode).toHaveLength(1); expect(formElement.requiredCode).toContain(DATE_FUNCTIONS); }); - - it(" - rendering", () => { - const { formElement } = doRenderField("friends"); - - expect(formElement.pfImports).toContain("FormGroup"); - expect(formElement.pfImports).toContain("Alert"); - }); }); diff --git a/packages/form-code-generator-patternfly-theme/tests/CheckBoxGroupField.test.tsx b/packages/form-code-generator-patternfly-theme/tests/CheckBoxGroupField.test.tsx index 8eef0b7317e..a4376b771a5 100644 --- a/packages/form-code-generator-patternfly-theme/tests/CheckBoxGroupField.test.tsx +++ b/packages/form-code-generator-patternfly-theme/tests/CheckBoxGroupField.test.tsx @@ -66,14 +66,18 @@ describe(" tests", () => { expect(formElement.jsxCode).toContain("isDisabled={false}"); props.allowedValues.forEach((value) => { - const checkbox = ` handleCheckboxGroupChange('${value}', ${formElement.ref.stateName}, ${formElement.ref.stateSetter})}` ); - expect(formElement.jsxCode).toContain(`value={'${value}'}/>`); + expect(formElement.jsxCode).toContain(`value={'${value}'}`); }); expect(formElement.stateCode).not.toBeNull(); }); diff --git a/packages/form-code-generator-patternfly-theme/tests/ListField.test.tsx b/packages/form-code-generator-patternfly-theme/tests/ListField.test.tsx new file mode 100644 index 00000000000..77c815488ef --- /dev/null +++ b/packages/form-code-generator-patternfly-theme/tests/ListField.test.tsx @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 * as React from "react"; +import { render } from "@testing-library/react"; +import createSchema from "./_createSchema"; +import AutoForm, { AutoFormProps } from "../src/uniforms/AutoForm"; +import { renderField } from "./_render"; +import { ListField } from "../src/uniforms"; +import { InputsContainer } from "../src/api"; + +describe(" tests", () => { + it("", () => { + const { container, formElement } = renderField( + ListField, + { + id: "id", + label: "Friends", + name: "friends", + disabled: false, + }, + { + friends: { type: Array }, + "friends.$": Object, + "friends.$.name": { type: String }, + "friends.$.age": { type: Number }, + "friends.$.country": { type: String, allowedValues: ["US", "Brazil"] }, + "friends.$.married": { type: Boolean }, + "friends.$.know": { + type: Array, + allowedValues: ["Java", "Node", "Docker"], + uniforms: { + checkboxes: true, + }, + }, + "friends.$.know.$": String, + "friends.$.areas": { + type: String, + allowedValues: ["Developer", "HR", "UX"], + }, + "friends.$.birthday": { type: Date }, + } + ); + + expect(container).toMatchSnapshot(); + + const inputContainer = formElement as InputsContainer; + expect(inputContainer.pfImports).toStrictEqual([ + "Split", + "SplitItem", + "Button", + "Card", + "CardBody", + "TextInput", + "FormGroup", + "SelectOption", + "SelectOptionObject", + "Select", + "SelectVariant", + "Checkbox", + "DatePicker", + "Flex", + "FlexItem", + "InputGroup", + "TimePicker", + ]); + expect(inputContainer.pfIconImports).toStrictEqual(["PlusCircleIcon", "MinusCircleIcon"]); + }); +}); diff --git a/packages/form-code-generator-patternfly-theme/tests/NestField.test.tsx b/packages/form-code-generator-patternfly-theme/tests/NestField.test.tsx index c728553612a..458f1f7f3c4 100644 --- a/packages/form-code-generator-patternfly-theme/tests/NestField.test.tsx +++ b/packages/form-code-generator-patternfly-theme/tests/NestField.test.tsx @@ -30,6 +30,11 @@ const schema = { type: String, allowedValues: ["Developer", "HR", "UX"], }, + "candidate.skills": { + type: Array, + }, + "candidate.skills.$": Object, + "candidate.skills.$.name": { type: String }, }; describe(" tests", () => { @@ -57,8 +62,12 @@ describe(" tests", () => { "SelectOptionObject", "Select", "SelectVariant", + "Split", + "SplitItem", + "Button", ]); - expect(inputContainer.childRefs).toHaveLength(3); + expect(inputContainer.pfIconImports).toStrictEqual(["PlusCircleIcon", "MinusCircleIcon"]); + expect(inputContainer.childRefs).toHaveLength(4); expect(inputContainer.childRefs[0].binding).toEqual("candidate.name"); expect(inputContainer.childRefs[0].stateName).toEqual("candidate__name"); @@ -71,5 +80,9 @@ describe(" tests", () => { expect(inputContainer.childRefs[2].binding).toEqual("candidate.role"); expect(inputContainer.childRefs[2].stateName).toEqual("candidate__role"); expect(inputContainer.childRefs[2].stateSetter).toEqual("set__candidate__role"); + + expect(inputContainer.childRefs[3].binding).toEqual("candidate.skills"); + expect(inputContainer.childRefs[3].stateName).toEqual("candidate__skills"); + expect(inputContainer.childRefs[3].stateSetter).toEqual("set__candidate__skills"); }); }); diff --git a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/AutoForm.test.tsx.snap b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/AutoForm.test.tsx.snap index 96df0733236..78e7cd9c490 100644 --- a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/AutoForm.test.tsx.snap +++ b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/AutoForm.test.tsx.snap @@ -19,8 +19,11 @@ import { FlexItem, InputGroup, TimePicker, - Alert, + Split, + SplitItem, + Button, } from '@patternfly/react-core'; +import { PlusCircleIcon, MinusCircleIcon } from '@patternfly/react-icons'; const Form__HRInterview: React.FC<any> = (props: any) => { const [formApi, setFormApi] = useState<any>(); const [personalData__name, set__personalData__name] = useState<string>(''); @@ -48,7 +51,7 @@ const Form__HRInterview: React.FC<any> = (props: any) => { const [interview__hire, set__interview__hire] = useState<boolean>(false); const [interview__hiringDate, set__interview__hiringDate] = useState<string>(); - const [friends, set__friends] = useState<any[]>(); + const [friends, set__friends] = useState<string[]>([]); /* Utility function that fills the form with the data received from the kogito runtime */ const setFormData = (data) => { if (!data) { @@ -69,7 +72,7 @@ const Form__HRInterview: React.FC<any> = (props: any) => { set__interview__rating(data?.interview?.rating ?? ''); set__interview__hire(data?.interview?.hire ?? false); set__interview__hiringDate(data?.interview?.hiringDate); - set__friends(data?.friends); + set__friends(data?.friends ?? []); }; /* Utility function to generate the expected form output as a json object */ const getFormData = useCallback(() => { @@ -300,15 +303,15 @@ const Form__HRInterview: React.FC<any> = (props: any) => { isOpen={interview__position__expanded} selections={interview__position} onToggle={(isOpen) => set__interview__position__expanded(isOpen)} - onSelect={(event, value, isPlaceHolder) => { + onSelect={(event, value, isPlaceHolder) => handleSelect( value, isPlaceHolder, interview__position, set__interview__position, set__interview__position__expanded - ); - }} + ) + } value={interview__position}> <SelectOption key={'Developer'} value={'Developer'}> Developer @@ -336,14 +339,14 @@ const Form__HRInterview: React.FC<any> = (props: any) => { onToggle={(isOpen) => set__interview__otherPositions__expanded(isOpen) } - onSelect={(event, value, isPlaceHolder) => { + onSelect={(event, value, isPlaceHolder) => handleMultipleSelect( value, isPlaceHolder, interview__otherPositions, set__interview__otherPositions - ); - }} + ) + } value={interview__otherPositions}> <SelectOption key={'Developer'} value={'Developer'}> Developer @@ -367,7 +370,7 @@ const Form__HRInterview: React.FC<any> = (props: any) => { aria-label={'interview.skills'} label={'Java'} isDisabled={false} - isChecked={interview__skills.indexOf('Java') != -1} + isChecked={interview__skills.indexOf('Java') !== -1} onChange={() => handleCheckboxGroupChange( 'Java', @@ -384,7 +387,7 @@ const Form__HRInterview: React.FC<any> = (props: any) => { aria-label={'interview.skills'} label={'React'} isDisabled={false} - isChecked={interview__skills.indexOf('React') != -1} + isChecked={interview__skills.indexOf('React') !== -1} onChange={() => handleCheckboxGroupChange( 'React', @@ -401,7 +404,7 @@ const Form__HRInterview: React.FC<any> = (props: any) => { aria-label={'interview.skills'} label={'TypeScript'} isDisabled={false} - isChecked={interview__skills.indexOf('TypeScript') != -1} + isChecked={interview__skills.indexOf('TypeScript') !== -1} onChange={() => handleCheckboxGroupChange( 'TypeScript', @@ -418,7 +421,7 @@ const Form__HRInterview: React.FC<any> = (props: any) => { aria-label={'interview.skills'} label={'Quarkus'} isDisabled={false} - isChecked={interview__skills.indexOf('Quarkus') != -1} + isChecked={interview__skills.indexOf('Quarkus') !== -1} onChange={() => handleCheckboxGroupChange( 'Quarkus', @@ -571,21 +574,102 @@ const Form__HRInterview: React.FC<any> = (props: any) => { </FormGroup> </CardBody> </Card> - <FormGroup - fieldId={'uniforms-0000-000x'} - label={'Friends'} - isRequired={true}> - <Alert variant='warning' title='Unsupported field type: Array'> - Cannot find form control for property <code>friends</code> with type{' '} - <code>Array</code>:<br /> - Some complex property types, such as <code> - Array&lt;object&gt; - </code>{' '} - aren't yet supported, however, you can still write your own component - into the form and use the already existing states{' '} - <code>const [ friends, set__friends ]</code>. - </Alert> - </FormGroup> + <div> + <Split hasGutter> + <SplitItem> + {'Friends' && ( + <label className={'pf-c-form__label'}> + <span className={'pf-c-form__label-text'}>Friends</span> + </label> + )} + </SplitItem> + <SplitItem isFilled /> + <SplitItem> + <Button + name='$' + variant='plain' + style={{ paddingLeft: '0', paddingRight: '0' }} + disabled={false} + onClick={() => { + !false && set__friends((friends ?? []).concat([undefined])); + }}> + <PlusCircleIcon color='#0088ce' /> + </Button> + </SplitItem> + </Split> + <div> + {friends?.map((_, itemIndex) => ( + <div + key={itemIndex} + style={{ + marginBottom: '1rem', + display: 'flex', + justifyContent: 'space-between', + }}> + <div style={{ width: '100%', marginRight: '10px' }}> + <Card> + <CardBody className='pf-c-form'> + <FormGroup + fieldId={'uniforms-0000-0010'} + label={'Name'} + isRequired={true}> + <TextInput + name={\`friends.\${itemIndex}.name\`} + id={'uniforms-0000-0010'} + isDisabled={false} + placeholder={''} + type={'text'} + value={friends[itemIndex].name} + onChange={(newValue) => { + set__friends((s) => { + const newState = [...s]; + newState[itemIndex].name = newValue; + return newState; + }); + }} + /> + </FormGroup> + <FormGroup + fieldId={'uniforms-0000-0012'} + label={'Age'} + isRequired={true}> + <TextInput + type={'number'} + name={\`friends.\${itemIndex}.age\`} + isDisabled={false} + id={'uniforms-0000-0012'} + placeholder={''} + step={0.01} + value={friends[itemIndex].age} + onChange={(newValue) => { + set__friends((s) => { + const newState = [...s]; + newState[itemIndex].age = Number(newValue); + return newState; + }); + }} + /> + </FormGroup> + </CardBody> + </Card> + </div> + <div> + <Button + disabled={false} + variant='plain' + style={{ paddingLeft: '0', paddingRight: '0' }} + onClick={() => { + const value = [...friends]; + value.splice(itemIndex, 1); + !false && set__friends(value); + }}> + <MinusCircleIcon color='#cc0000' /> + </Button> + </div> + </div> + ))} + </div> + </div> </div> ); }; diff --git a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/CheckBoxGroupField.test.tsx.snap b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/CheckBoxGroupField.test.tsx.snap index 31a949fe3cd..dad60b85dff 100644 --- a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/CheckBoxGroupField.test.tsx.snap +++ b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/CheckBoxGroupField.test.tsx.snap @@ -2,6 +2,6 @@ exports[` tests - rendering 1`] = `
- {"ref":{"binding":"roles","stateName":"roles","stateSetter":"set__roles","dataType":{"name":"string[]","defaultValue":"[]"}},"pfImports":["Checkbox","FormGroup"],"reactImports":["useState"],"requiredCode":["checkbox_group_functions"],"jsxCode":"<FormGroup\\n fieldId={'id'}\\n label={'Roles'}\\n isRequired={true}\\n >\\n <Checkbox key={'id-Developer'} id={'id-Developer'} name={'roles'} aria-label={'roles'}\\n label={'Developer'} \\n isDisabled={false} \\n isChecked={roles.indexOf('Developer') != -1}\\n onChange={() => handleCheckboxGroupChange('Developer', roles, set__roles)}\\n value={'Developer'}/>\\n<Checkbox key={'id-HR'} id={'id-HR'} name={'roles'} aria-label={'roles'}\\n label={'HR'} \\n isDisabled={false} \\n isChecked={roles.indexOf('HR') != -1}\\n onChange={() => handleCheckboxGroupChange('HR', roles, set__roles)}\\n value={'HR'}/>\\n<Checkbox key={'id-UX'} id={'id-UX'} name={'roles'} aria-label={'roles'}\\n label={'UX'} \\n isDisabled={false} \\n isChecked={roles.indexOf('UX') != -1}\\n onChange={() => handleCheckboxGroupChange('UX', roles, set__roles)}\\n value={'UX'}/>\\n </FormGroup>","stateCode":"const [ roles, set__roles ] = useState<string[]>([]);","isReadonly":false} + {"ref":{"binding":"roles","stateName":"roles","stateSetter":"set__roles","dataType":{"name":"string[]","defaultValue":"[]"}},"pfImports":["Checkbox","FormGroup"],"reactImports":["useState"],"requiredCode":["checkbox_group_functions"],"jsxCode":"<FormGroup\\n fieldId={'id'}\\n label={'Roles'}\\n isRequired={true}\\n >\\n <Checkbox\\n key={'id-Developer'}\\n id={'id-Developer'}\\n name={'roles'}\\n aria-label={'roles'}\\n label={'Developer'} \\n isDisabled={false} \\n isChecked={roles.indexOf('Developer') !== -1}\\n onChange={() => handleCheckboxGroupChange('Developer', roles, set__roles)}\\n value={'Developer'}\\n/>\\n<Checkbox\\n key={'id-HR'}\\n id={'id-HR'}\\n name={'roles'}\\n aria-label={'roles'}\\n label={'HR'} \\n isDisabled={false} \\n isChecked={roles.indexOf('HR') !== -1}\\n onChange={() => handleCheckboxGroupChange('HR', roles, set__roles)}\\n value={'HR'}\\n/>\\n<Checkbox\\n key={'id-UX'}\\n id={'id-UX'}\\n name={'roles'}\\n aria-label={'roles'}\\n label={'UX'} \\n isDisabled={false} \\n isChecked={roles.indexOf('UX') !== -1}\\n onChange={() => handleCheckboxGroupChange('UX', roles, set__roles)}\\n value={'UX'}\\n/>\\n </FormGroup>","stateCode":"const [ roles, set__roles ] = useState<string[]>([]);","isReadonly":false}
`; diff --git a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/ListField.test.tsx.snap b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/ListField.test.tsx.snap new file mode 100644 index 00000000000..69e0ab1e604 --- /dev/null +++ b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/ListField.test.tsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` tests 1`] = ` +
+ {"ref":{"binding":"friends","stateName":"friends","stateSetter":"set__friends","dataType":{"name":"string[]","defaultValue":"[]"}},"pfImports":["Split","SplitItem","Button","Card","CardBody","TextInput","FormGroup","SelectOption","SelectOptionObject","Select","SelectVariant","Checkbox","DatePicker","Flex","FlexItem","InputGroup","TimePicker"],"pfIconImports":["PlusCircleIcon","MinusCircleIcon"],"reactImports":["useState"],"jsxCode":"<div>\\n <Split hasGutter>\\n <SplitItem>\\n {'Friends' && (\\n <label className={\\"pf-c-form__label\\"}>\\n <span className={\\"pf-c-form__label-text\\"}>\\n Friends\\n </span>\\n </label>\\n )}\\n </SplitItem>\\n <SplitItem isFilled />\\n <SplitItem>\\n <Button\\n name='$'\\n variant='plain'\\n style={{ paddingLeft: '0', paddingRight: '0' }}\\n disabled={false}\\n onClick={() => {\\n !false && set__friends((friends ?? []).concat([undefined]));\\n }}\\n >\\n <PlusCircleIcon color='#0088ce' />\\n </Button>\\n </SplitItem>\\n </Split>\\n <div>\\n {friends?.map((_, itemIndex) =>\\n (<div\\n key={itemIndex}\\n style={{\\n marginBottom: '1rem',\\n display: 'flex',\\n justifyContent: 'space-between',\\n }}\\n >\\n <div style={{ width: '100%', marginRight: '10px' }}><Card>\\n <CardBody className=\\"pf-c-form\\">\\n \\n <FormGroup\\n fieldId={'uniforms-0000-0003'}\\n label={'Name'}\\n isRequired={true}\\n >\\n <TextInput\\n name={\`friends.\${itemIndex}.name\`}\\n id={'uniforms-0000-0003'}\\n isDisabled={false}\\n placeholder={''}\\n type={'text'}\\n value={friends[itemIndex].name}\\n onChange={\\n newValue => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].name = newValue;\\n return newState;\\n })\\n }}\\n />\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-0005'}\\n label={'Age'}\\n isRequired={true}\\n >\\n <TextInput\\n type={'number'}\\n name={\`friends.\${itemIndex}.age\`}\\n isDisabled={false}\\n id={'uniforms-0000-0005'}\\n placeholder={''}\\n step={0.01} \\n value={friends[itemIndex].age}\\n onChange={\\n newValue => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].age = Number(newValue);\\n return newState;\\n })\\n }}\\n />\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-0007'}\\n label={'Country'}\\n isRequired={true}\\n ><Select\\n id={'uniforms-0000-0007'}\\n name={\`friends.\${itemIndex}.country\`}\\n variant={SelectVariant.single}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={friends__$__country__expanded}\\n selections={friends__$__country}\\n onToggle={(isOpen) => set__friends__$__country__expanded(isOpen)}\\n onSelect={\\n (event, value, isPlaceHolder) => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].country = handleSelect(value, isPlaceHolder, friends__$__country, set__friends__$__country, set__friends__$__country__expanded);\\n return newState;\\n })\\n }}\\n value={friends[itemIndex].country}\\n >\\n <SelectOption key={'US'} value={'US'}>US</SelectOption>\\n<SelectOption key={'Brazil'} value={'Brazil'}>Brazil</SelectOption>\\n </Select></FormGroup>\\n<FormGroup fieldId='uniforms-0000-0009'>\\n <Checkbox\\n isChecked={friends[itemIndex].married}\\n isDisabled={false}\\n id={'uniforms-0000-0009'}\\n name={\`friends.\${itemIndex}.married\`}\\n label={'Married'}\\n onChange={\\n newValue => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].married = newValue;\\n return newState;\\n })\\n }}\\n />\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-000b'}\\n label={'Know'}\\n isRequired={true}\\n >\\n <Checkbox\\n key={'uniforms-0000-000b-Java'}\\n id={'uniforms-0000-000b-Java'}\\n name={\`friends.\${itemIndex}.know\`}\\n aria-label={'friends.$.know'}\\n label={'Java'} \\n isDisabled={false} \\n isChecked={friends__$__know.indexOf('Java') !== -1}\\n onChange={\\n newValue => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].know = handleCheckboxGroupChange('Java', friends__$__know, set__friends__$__know);\\n return newState;\\n })\\n }}\\n value={friends[itemIndex].know}\\n/>\\n<Checkbox\\n key={'uniforms-0000-000b-Node'}\\n id={'uniforms-0000-000b-Node'}\\n name={\`friends.\${itemIndex}.know\`}\\n aria-label={'friends.$.know'}\\n label={'Node'} \\n isDisabled={false} \\n isChecked={friends__$__know.indexOf('Node') !== -1}\\n onChange={\\n newValue => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].know = handleCheckboxGroupChange('Node', friends__$__know, set__friends__$__know);\\n return newState;\\n })\\n }}\\n value={friends[itemIndex].know}\\n/>\\n<Checkbox\\n key={'uniforms-0000-000b-Docker'}\\n id={'uniforms-0000-000b-Docker'}\\n name={\`friends.\${itemIndex}.know\`}\\n aria-label={'friends.$.know'}\\n label={'Docker'} \\n isDisabled={false} \\n isChecked={friends__$__know.indexOf('Docker') !== -1}\\n onChange={\\n newValue => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].know = handleCheckboxGroupChange('Docker', friends__$__know, set__friends__$__know);\\n return newState;\\n })\\n }}\\n value={friends[itemIndex].know}\\n/>\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-000d'}\\n label={'Areas'}\\n isRequired={true}\\n ><Select\\n id={'uniforms-0000-000d'}\\n name={\`friends.\${itemIndex}.areas\`}\\n variant={SelectVariant.single}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={friends__$__areas__expanded}\\n selections={friends__$__areas}\\n onToggle={(isOpen) => set__friends__$__areas__expanded(isOpen)}\\n onSelect={\\n (event, value, isPlaceHolder) => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].areas = handleSelect(value, isPlaceHolder, friends__$__areas, set__friends__$__areas, set__friends__$__areas__expanded);\\n return newState;\\n })\\n }}\\n value={friends[itemIndex].areas}\\n >\\n <SelectOption key={'Developer'} value={'Developer'}>Developer</SelectOption>\\n<SelectOption key={'HR'} value={'HR'}>HR</SelectOption>\\n<SelectOption key={'UX'} value={'UX'}>UX</SelectOption>\\n </Select></FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-000f'}\\n label={'Birthday'}\\n isRequired={true}\\n >\\n <Flex\\n direction={{ default: 'column' }}\\n id={'uniforms-0000-000f'}\\n >\\n <FlexItem>\\n <InputGroup style={{ background: 'transparent' }}>\\n <DatePicker\\n id={'date-picker-uniforms-0000-000f'}\\n isDisabled={false}\\n name={\`friends.\${itemIndex}.birthday\`}\\n onChange={\\n newValue => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].birthday = onDateChange(newValue, set__friends__$__birthday, friends__$__birthday);\\n return newState;\\n })\\n }}\\n value={parseDate(friends[itemIndex].birthday)}\\n />\\n <TimePicker\\n id={'time-picker-uniforms-0000-000f'}\\n isDisabled={false}\\n name={\`friends.\${itemIndex}.birthday\`}\\n onChange={\\n (time, hours?, minutes?) => {\\n set__friends(s => {\\n const newState = [...s];\\n newState[itemIndex].birthday = onTimeChange(time, set__friends__$__birthday, friends__$__birthday, hours, minutes);\\n return newState;\\n })\\n }}\\n style={{ width: '120px' }}\\n time={parseTime(friends[itemIndex].birthday)}\\n />\\n </InputGroup>\\n </FlexItem>\\n </Flex>\\n </FormGroup>\\n </CardBody></Card></div>\\n <div>\\n <Button\\n disabled={false}\\n variant='plain'\\n style={{ paddingLeft: '0', paddingRight: '0' }}\\n onClick={() => {\\n const value = [...friends]\\n value.splice(itemIndex, 1);\\n !false && set__friends(value);\\n }}\\n >\\n <MinusCircleIcon color='#cc0000' />\\n </Button>\\n </div>\\n </div>)\\n )}\\n </div>\\n </div>","stateCode":"const [ friends, set__friends ] = useState<string[]>([]);","isReadonly":false} +
+`; diff --git a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/NestField.test.tsx.snap b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/NestField.test.tsx.snap index ee9d764a0d4..5c38b7d6e60 100644 --- a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/NestField.test.tsx.snap +++ b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/NestField.test.tsx.snap @@ -2,6 +2,6 @@ exports[` tests - rendering 1`] = `
- {"pfImports":["Card","CardBody","TextInput","FormGroup","SelectOption","SelectOptionObject","Select","SelectVariant"],"reactImports":["useState"],"requiredCode":["select_functions"],"stateCode":"const [ candidate__name, set__candidate__name ] = useState<string>(\\"\\");\\nconst [ candidate__age, set__candidate__age ] = useState<number>();\\nconst [ candidate__role, set__candidate__role ] = useState<string>(\\"\\");\\nconst [ candidate__role__expanded, set__candidate__role__expanded ] = useState<boolean>(false);","jsxCode":"<Card>\\n <CardBody className=\\"pf-c-form\\">\\n <label><b>Candidate</b></label>\\n <FormGroup\\n fieldId={'uniforms-0000-0001'}\\n label={'Name'}\\n isRequired={true}\\n >\\n <TextInput\\n name={'candidate.name'}\\n id={'uniforms-0000-0001'}\\n isDisabled={false}\\n placeholder={''}\\n type={'text'}\\n value={candidate__name}\\n onChange={set__candidate__name}\\n />\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-0003'}\\n label={'Age'}\\n isRequired={true}\\n >\\n <TextInput\\n type={'number'}\\n name={'candidate.age'}\\n isDisabled={false}\\n id={'uniforms-0000-0003'}\\n placeholder={''}\\n step={1} \\n value={candidate__age}\\n onChange={(newValue) => set__candidate__age(Number(newValue))}\\n />\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-0005'}\\n label={'Role'}\\n isRequired={true}\\n ><Select\\n id={'uniforms-0000-0005'}\\n name={'candidate.role'}\\n variant={SelectVariant.single}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={candidate__role__expanded}\\n selections={candidate__role}\\n onToggle={(isOpen) => set__candidate__role__expanded(isOpen)}\\n onSelect={(event, value, isPlaceHolder) => {\\n handleSelect(value, isPlaceHolder, candidate__role, set__candidate__role, set__candidate__role__expanded)\\n }}\\n value={candidate__role}\\n >\\n <SelectOption key={'Developer'} value={'Developer'}>Developer</SelectOption>\\n<SelectOption key={'HR'} value={'HR'}>HR</SelectOption>\\n<SelectOption key={'UX'} value={'UX'}>UX</SelectOption>\\n </Select></FormGroup>\\n </CardBody></Card>","ref":{"binding":"candidate","stateName":"candidate","stateSetter":"set__candidate","dataType":{"name":"any"}},"childRefs":[{"binding":"candidate.name","stateName":"candidate__name","stateSetter":"set__candidate__name","dataType":{"name":"string","defaultValue":"\\"\\""}},{"binding":"candidate.age","stateName":"candidate__age","stateSetter":"set__candidate__age","dataType":{"name":"number"}},{"binding":"candidate.role","stateName":"candidate__role","stateSetter":"set__candidate__role","dataType":{"name":"string","defaultValue":"\\"\\""}}],"isReadonly":false} + {"pfImports":["Card","CardBody","TextInput","FormGroup","SelectOption","SelectOptionObject","Select","SelectVariant","Split","SplitItem","Button"],"pfIconImports":["PlusCircleIcon","MinusCircleIcon"],"reactImports":["useState"],"requiredCode":["select_functions"],"stateCode":"const [ candidate__name, set__candidate__name ] = useState<string>(\\"\\");\\nconst [ candidate__age, set__candidate__age ] = useState<number>();\\nconst [ candidate__role, set__candidate__role ] = useState<string>(\\"\\");\\nconst [ candidate__role__expanded, set__candidate__role__expanded ] = useState<boolean>(false);\\nconst [ candidate__skills, set__candidate__skills ] = useState<string[]>([]);","jsxCode":"<Card>\\n <CardBody className=\\"pf-c-form\\">\\n <label><b>Candidate</b></label>\\n <FormGroup\\n fieldId={'uniforms-0000-0001'}\\n label={'Name'}\\n isRequired={true}\\n >\\n <TextInput\\n name={'candidate.name'}\\n id={'uniforms-0000-0001'}\\n isDisabled={false}\\n placeholder={''}\\n type={'text'}\\n value={candidate__name}\\n onChange={set__candidate__name}\\n />\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-0003'}\\n label={'Age'}\\n isRequired={true}\\n >\\n <TextInput\\n type={'number'}\\n name={'candidate.age'}\\n isDisabled={false}\\n id={'uniforms-0000-0003'}\\n placeholder={''}\\n step={1} \\n value={candidate__age}\\n onChange={(newValue) => set__candidate__age(Number(newValue))}\\n />\\n </FormGroup>\\n<FormGroup\\n fieldId={'uniforms-0000-0005'}\\n label={'Role'}\\n isRequired={true}\\n ><Select\\n id={'uniforms-0000-0005'}\\n name={'candidate.role'}\\n variant={SelectVariant.single}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={candidate__role__expanded}\\n selections={candidate__role}\\n onToggle={(isOpen) => set__candidate__role__expanded(isOpen)}\\n onSelect={(event, value, isPlaceHolder) => handleSelect(value, isPlaceHolder, candidate__role, set__candidate__role, set__candidate__role__expanded)}\\n value={candidate__role}\\n >\\n <SelectOption key={'Developer'} value={'Developer'}>Developer</SelectOption>\\n<SelectOption key={'HR'} value={'HR'}>HR</SelectOption>\\n<SelectOption key={'UX'} value={'UX'}>UX</SelectOption>\\n </Select></FormGroup>\\n<div>\\n <Split hasGutter>\\n <SplitItem>\\n {'Skills' && (\\n <label className={\\"pf-c-form__label\\"}>\\n <span className={\\"pf-c-form__label-text\\"}>\\n Skills\\n </span>\\n </label>\\n )}\\n </SplitItem>\\n <SplitItem isFilled />\\n <SplitItem>\\n <Button\\n name='$'\\n variant='plain'\\n style={{ paddingLeft: '0', paddingRight: '0' }}\\n disabled={false}\\n onClick={() => {\\n !false && set__candidate__skills((candidate__skills ?? []).concat([undefined]));\\n }}\\n >\\n <PlusCircleIcon color='#0088ce' />\\n </Button>\\n </SplitItem>\\n </Split>\\n <div>\\n {candidate__skills?.map((_, itemIndex) =>\\n (<div\\n key={itemIndex}\\n style={{\\n marginBottom: '1rem',\\n display: 'flex',\\n justifyContent: 'space-between',\\n }}\\n >\\n <div style={{ width: '100%', marginRight: '10px' }}><Card>\\n <CardBody className=\\"pf-c-form\\">\\n \\n <FormGroup\\n fieldId={'uniforms-0000-000a'}\\n label={'Name'}\\n isRequired={true}\\n >\\n <TextInput\\n name={\`candidate.skills.\${itemIndex}.name\`}\\n id={'uniforms-0000-000a'}\\n isDisabled={false}\\n placeholder={''}\\n type={'text'}\\n value={candidate__skills[itemIndex].name}\\n onChange={\\n newValue => {\\n set__candidate__skills(s => {\\n const newState = [...s];\\n newState[itemIndex].name = newValue;\\n return newState;\\n })\\n }}\\n />\\n </FormGroup>\\n </CardBody></Card></div>\\n <div>\\n <Button\\n disabled={false}\\n variant='plain'\\n style={{ paddingLeft: '0', paddingRight: '0' }}\\n onClick={() => {\\n const value = [...candidate__skills]\\n value.splice(itemIndex, 1);\\n !false && set__candidate__skills(value);\\n }}\\n >\\n <MinusCircleIcon color='#cc0000' />\\n </Button>\\n </div>\\n </div>)\\n )}\\n </div>\\n </div>\\n </CardBody></Card>","ref":{"binding":"candidate","stateName":"candidate","stateSetter":"set__candidate","dataType":{"name":"any"}},"childRefs":[{"binding":"candidate.name","stateName":"candidate__name","stateSetter":"set__candidate__name","dataType":{"name":"string","defaultValue":"\\"\\""}},{"binding":"candidate.age","stateName":"candidate__age","stateSetter":"set__candidate__age","dataType":{"name":"number"}},{"binding":"candidate.role","stateName":"candidate__role","stateSetter":"set__candidate__role","dataType":{"name":"string","defaultValue":"\\"\\""}},{"binding":"candidate.skills","stateName":"candidate__skills","stateSetter":"set__candidate__skills","dataType":{"name":"string[]","defaultValue":"[]"}}],"isReadonly":false}
`; diff --git a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/SelectField.test.tsx.snap b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/SelectField.test.tsx.snap index 506093d2ca8..24b0b19b086 100644 --- a/packages/form-code-generator-patternfly-theme/tests/__snapshots__/SelectField.test.tsx.snap +++ b/packages/form-code-generator-patternfly-theme/tests/__snapshots__/SelectField.test.tsx.snap @@ -2,12 +2,12 @@ exports[` tests - multiple value rendering 1`] = `
- {"ref":{"binding":"otherPositions","stateName":"otherPositions","stateSetter":"set__otherPositions","dataType":{"name":"string[]","defaultValue":"[]"}},"pfImports":["SelectOption","SelectOptionObject","Select","SelectVariant","FormGroup"],"reactImports":["useState"],"requiredCode":["multiple_select_functions"],"jsxCode":"<FormGroup\\n fieldId={'id'}\\n label={'OtherPositions'}\\n isRequired={true}\\n ><Select\\n id={'id'}\\n name={'otherPositions'}\\n variant={SelectVariant.typeaheadMulti}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={otherPositions__expanded}\\n selections={otherPositions}\\n onToggle={(isOpen) => set__otherPositions__expanded(isOpen)}\\n onSelect={(event, value, isPlaceHolder) => {\\n handleMultipleSelect(value, isPlaceHolder, otherPositions, set__otherPositions)\\n }}\\n value={otherPositions}\\n >\\n <SelectOption key={'Developer'} value={'Developer'}>Developer</SelectOption>\\n<SelectOption key={'HR'} value={'HR'}>HR</SelectOption>\\n<SelectOption key={'UX'} value={'UX'}>UX</SelectOption>\\n </Select></FormGroup>","stateCode":"const [ otherPositions, set__otherPositions ] = useState<string[]>([]);\\nconst [ otherPositions__expanded, set__otherPositions__expanded ] = useState<boolean>(false);","isReadonly":false} + {"ref":{"binding":"otherPositions","stateName":"otherPositions","stateSetter":"set__otherPositions","dataType":{"name":"string[]","defaultValue":"[]"}},"pfImports":["SelectOption","SelectOptionObject","Select","SelectVariant","FormGroup"],"reactImports":["useState"],"requiredCode":["multiple_select_functions"],"jsxCode":"<FormGroup\\n fieldId={'id'}\\n label={'OtherPositions'}\\n isRequired={true}\\n ><Select\\n id={'id'}\\n name={'otherPositions'}\\n variant={SelectVariant.typeaheadMulti}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={otherPositions__expanded}\\n selections={otherPositions}\\n onToggle={(isOpen) => set__otherPositions__expanded(isOpen)}\\n onSelect={(event, value, isPlaceHolder) => handleMultipleSelect(value, isPlaceHolder, otherPositions, set__otherPositions)}\\n value={otherPositions}\\n >\\n <SelectOption key={'Developer'} value={'Developer'}>Developer</SelectOption>\\n<SelectOption key={'HR'} value={'HR'}>HR</SelectOption>\\n<SelectOption key={'UX'} value={'UX'}>UX</SelectOption>\\n </Select></FormGroup>","stateCode":"const [ otherPositions, set__otherPositions ] = useState<string[]>([]);\\nconst [ otherPositions__expanded, set__otherPositions__expanded ] = useState<boolean>(false);","isReadonly":false}
`; exports[` tests - single value rendering 1`] = `
- {"ref":{"binding":"role","stateName":"role","stateSetter":"set__role","dataType":{"name":"string","defaultValue":"\\"\\""}},"pfImports":["SelectOption","SelectOptionObject","Select","SelectVariant","FormGroup"],"reactImports":["useState"],"requiredCode":["select_functions"],"jsxCode":"<FormGroup\\n fieldId={'id'}\\n label={'Role'}\\n isRequired={true}\\n ><Select\\n id={'id'}\\n name={'role'}\\n variant={SelectVariant.single}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={role__expanded}\\n selections={role}\\n onToggle={(isOpen) => set__role__expanded(isOpen)}\\n onSelect={(event, value, isPlaceHolder) => {\\n handleSelect(value, isPlaceHolder, role, set__role, set__role__expanded)\\n }}\\n value={role}\\n >\\n <SelectOption key={'Developer'} value={'Developer'}>Developer</SelectOption>\\n<SelectOption key={'HR'} value={'HR'}>HR</SelectOption>\\n<SelectOption key={'UX'} value={'UX'}>UX</SelectOption>\\n </Select></FormGroup>","stateCode":"const [ role, set__role ] = useState<string>(\\"\\");\\nconst [ role__expanded, set__role__expanded ] = useState<boolean>(false);","isReadonly":false} + {"ref":{"binding":"role","stateName":"role","stateSetter":"set__role","dataType":{"name":"string","defaultValue":"\\"\\""}},"pfImports":["SelectOption","SelectOptionObject","Select","SelectVariant","FormGroup"],"reactImports":["useState"],"requiredCode":["select_functions"],"jsxCode":"<FormGroup\\n fieldId={'id'}\\n label={'Role'}\\n isRequired={true}\\n ><Select\\n id={'id'}\\n name={'role'}\\n variant={SelectVariant.single}\\n isDisabled={false}\\n placeholderText={''}\\n isOpen={role__expanded}\\n selections={role}\\n onToggle={(isOpen) => set__role__expanded(isOpen)}\\n onSelect={(event, value, isPlaceHolder) => handleSelect(value, isPlaceHolder, role, set__role, set__role__expanded)}\\n value={role}\\n >\\n <SelectOption key={'Developer'} value={'Developer'}>Developer</SelectOption>\\n<SelectOption key={'HR'} value={'HR'}>HR</SelectOption>\\n<SelectOption key={'UX'} value={'UX'}>UX</SelectOption>\\n </Select></FormGroup>","stateCode":"const [ role, set__role ] = useState<string>(\\"\\");\\nconst [ role__expanded, set__role__expanded ] = useState<boolean>(false);","isReadonly":false}
`; diff --git a/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/envelope/components/ReactFormRenderer/ReactFormRenderer.tsx b/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/envelope/components/ReactFormRenderer/ReactFormRenderer.tsx index fa6dbf35b0c..8eee4681aa9 100644 --- a/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/envelope/components/ReactFormRenderer/ReactFormRenderer.tsx +++ b/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/envelope/components/ReactFormRenderer/ReactFormRenderer.tsx @@ -21,7 +21,8 @@ import React, { useEffect } from "react"; import { v4 as uuidv4 } from "uuid"; import { transform } from "@babel/standalone"; import ReactDOM from "react-dom"; -import * as Patternfly from "@patternfly/react-core/dist/js"; +import * as PatternflyReact from "@patternfly/react-core/dist/js"; +import * as PatternflyReactIcons from "@patternfly/react-icons/dist/js"; import { FormResources } from "../../../api"; import { sourceHandler } from "../../../utils"; import ResourcesContainer from "../ResourcesContainer/ResourcesContainer"; @@ -37,6 +38,7 @@ interface ReactFormRendererProps { declare global { interface Window { PatternFlyReact: any; + PatternflyReactIcons: any; PatternFly: any; } } @@ -57,7 +59,8 @@ const ReactFormRenderer: React.FC = ({ source, resources window.React = React; window.ReactDOM = ReactDOM; - window.PatternFlyReact = Patternfly; + window.PatternFlyReact = PatternflyReact; + window.PatternflyReactIcons = PatternflyReactIcons; const container = document.getElementById("formContainer"); if (!container) { @@ -70,11 +73,13 @@ const ReactFormRenderer: React.FC = ({ source, resources container.appendChild(formContainer); - const { reactElements, patternflyElements, formName, trimmedSource } = sourceHandler(source)!; + const { reactElements, patternflyElements, patternflyIconElements, formName, trimmedSource } = + sourceHandler(source)!; const content = ` const {${reactElements}} = React; const {${patternflyElements}} = PatternFlyReact; + ${patternflyIconElements !== undefined ? `const {${patternflyIconElements}} = PatternflyReactIcons;` : ""} ${trimmedSource} const target = document.getElementById("${containerId}"); const element = window.React.createElement(${formName}, {}); diff --git a/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/utils/utils.tsx b/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/utils/utils.tsx index f3e63b9c148..664a5614199 100644 --- a/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/utils/utils.tsx +++ b/packages/runtime-tools-shared-enveloped-components/src/formDisplayer/utils/utils.tsx @@ -19,25 +19,52 @@ export const sourceHandler = ( source: string -): { reactElements: string; patternflyElements: string; formName: string; trimmedSource: string } | undefined => { - const reactReg = /import React, {[^}]*}.*(?=["']react['"]).*/gim; - const patternflyReg = /import {[^}]*}.*(?=['"]@patternfly\/react-core['"]).*/gim; - const regexvalueReact = new RegExp(reactReg); - const reactImport = regexvalueReact.exec(source); - const reg = /\{([^)]+)\}/; - if (!reactImport) { +): + | { + reactElements: string; + patternflyElements: string; + patternflyIconElements: string | undefined; + formName: string; + trimmedSource: string; + } + | undefined => { + const importsReg = /\{([^)]+)\}/; + const reactImportsRegExp = /import React, {[^}]*}.*(?=["']react['"]).*/gim; + + const reactImports = new RegExp(reactImportsRegExp).exec(source); + if (!reactImports) { return; } - const reactElements = reg.exec(reactImport[0])?.[1]; - const regexvaluePat = new RegExp(patternflyReg); - const patternflyImport = regexvaluePat.exec(source); - if (!patternflyImport) { + const reactElements = importsReg.exec(reactImports[0])?.[1]; + + const patternflyImportsRegExp = /import {[^}]*}.*(?=['"]@patternfly\/react-core['"]).*/gim; + const patternflyImports = new RegExp(patternflyImportsRegExp).exec(source); + if (!patternflyImports) { return; } - const patternflyElements = reg.exec(patternflyImport[0])?.[1]; - const trimmedSource = source.split(reactReg).join("").trim().split(patternflyReg).join("").trim(); + const patternflyElements = importsReg.exec(patternflyImports[0])?.[1]; + + const patternflyIconImportsRegExp = /import {[^}]*}.*(?=['"]@patternfly\/react-icons['"]).*/gim; + const patternflyIconImports = new RegExp(patternflyIconImportsRegExp).exec(source); + const patternflyIconElements = importsReg.exec(patternflyIconImports?.[0] ?? "")?.[1]; + const trimmedSource = source + .split(reactImportsRegExp) + .join("") + .trim() + .split(patternflyImportsRegExp) + .join("") + .trim() + .split(patternflyIconImportsRegExp) + .join("") + .trim(); const formName = trimmedSource.split(": React.FC")[0].split("const ")[1]; - return { reactElements: reactElements!, patternflyElements: patternflyElements!, formName, trimmedSource }; + return { + reactElements: reactElements!, + patternflyElements: patternflyElements!, + patternflyIconElements: patternflyIconElements, + formName, + trimmedSource, + }; };