From 82633c48316408ce21d249c7de9a12e636d6487c Mon Sep 17 00:00:00 2001 From: Matthieu Foucault Date: Wed, 20 Apr 2022 17:03:26 -0700 Subject: [PATCH 1/3] refactor: form label is displayed by fields, not widgets --- app/components/Form/ProjectForm.tsx | 9 +- .../Form/SelectProjectStatusWidget.tsx | 13 +-- app/data/jsonSchemaForm/projectSchema.ts | 1 + app/lib/theme/FieldTemplate.tsx | 55 +++++++---- app/lib/theme/ReadOnlyFieldTemplate.tsx | 67 +++++++++++++ app/lib/theme/ReadOnlyTheme.tsx | 4 +- app/lib/theme/utils/getRequiredLabel.ts | 4 - app/lib/theme/widgets/DisplayOnlyWidget.tsx | 9 +- app/lib/theme/widgets/FieldLabel.tsx | 29 +++--- app/lib/theme/widgets/MoneyWidget.tsx | 9 -- app/lib/theme/widgets/PhoneNumberWidget.tsx | 9 -- app/lib/theme/widgets/ReadOnlyMoneyWidget.tsx | 37 ++++--- app/lib/theme/widgets/ReadOnlyWidget.tsx | 11 ++- .../theme/widgets/SearchDropdownWidget.tsx | 96 ++++++++----------- app/lib/theme/widgets/SelectWidget.tsx | 7 -- app/lib/theme/widgets/TextAreaWidget.tsx | 14 +-- app/lib/theme/widgets/TextWidget.tsx | 15 +-- 17 files changed, 196 insertions(+), 193 deletions(-) create mode 100644 app/lib/theme/ReadOnlyFieldTemplate.tsx delete mode 100644 app/lib/theme/utils/getRequiredLabel.ts diff --git a/app/components/Form/ProjectForm.tsx b/app/components/Form/ProjectForm.tsx index b03b9ed517..61d02a4e05 100644 --- a/app/components/Form/ProjectForm.tsx +++ b/app/components/Form/ProjectForm.tsx @@ -38,12 +38,10 @@ export const createProjectUiSchema = ( "projectStatusId", ], proposalReference: { - "ui:placeholder": "2020-RFP-1-123-ABCD", "bcgov:size": "small", - "bcgov:help-text": "(e.g. 2020-RFP-1-ABCD-123)", + "ui:help": "(e.g. 2020-RFP-1-ABCD-123)", }, projectName: { - "ui:placeholder": "Short project name", "ui:col-md": 12, "bcgov:size": "small", }, @@ -64,16 +62,15 @@ export const createProjectUiSchema = ( "ui:widget": "SearchWidget", "ui:options": { text: `${legalName ? `${legalName} (${bcRegistryId})` : ""}`, - title: "Legal Operator Name and BC Registry ID", }, }, operatorTradeName: { "ui:col-md": 12, "ui:widget": "DisplayOnly", "bcgov:size": "small", + "ui:title": "Trade Name", "ui:options": { text: `${tradeName}`, - title: "Trade Name", }, }, fundingStreamRfpId: { @@ -82,6 +79,7 @@ export const createProjectUiSchema = ( "bcgov:size": "small", "ui:options": { text: `${rfpStream}`, + label: rfpStream ? true : false, }, }, projectStatusId: { @@ -209,6 +207,7 @@ const ProjectForm: React.FC = (props) => { }; } ); + initialSchema.required = [...initialSchema.required, "operatorTradeName"]; return initialSchema as JSONSchema7; }, [query]); diff --git a/app/components/Form/SelectProjectStatusWidget.tsx b/app/components/Form/SelectProjectStatusWidget.tsx index 5c3f598a01..d3a30b378f 100644 --- a/app/components/Form/SelectProjectStatusWidget.tsx +++ b/app/components/Form/SelectProjectStatusWidget.tsx @@ -4,16 +4,8 @@ import { graphql, useFragment } from "react-relay"; import Dropdown from "@button-inc/bcgov-theme/Dropdown"; const SelectProjectStatus: React.FunctionComponent = (props) => { - const { - id, - onChange, - placeholder, - label, - required, - uiSchema, - value, - formContext, - } = props; + const { id, onChange, placeholder, required, uiSchema, value, formContext } = + props; const { allFundingStreamRfpProjectStatuses } = useFragment( graphql` @@ -46,7 +38,6 @@ const SelectProjectStatus: React.FunctionComponent = (props) => { return (
- onChange(e.target.value || undefined)} diff --git a/app/data/jsonSchemaForm/projectSchema.ts b/app/data/jsonSchemaForm/projectSchema.ts index d01c7db551..0906bb3663 100644 --- a/app/data/jsonSchemaForm/projectSchema.ts +++ b/app/data/jsonSchemaForm/projectSchema.ts @@ -8,6 +8,7 @@ const projectSchema = { "operatorId", "fundingStreamRfpId", "totalFundingRequest", + "projectStatusId", ], properties: { proposalReference: { diff --git a/app/lib/theme/FieldTemplate.tsx b/app/lib/theme/FieldTemplate.tsx index 23667300ce..44afc82301 100644 --- a/app/lib/theme/FieldTemplate.tsx +++ b/app/lib/theme/FieldTemplate.tsx @@ -1,47 +1,60 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons"; +import { FieldTemplateProps } from "@rjsf/core"; +import FieldLabel from "./widgets/FieldLabel"; -const FieldTemplate = ({ children, errors, help, rawErrors }) => { +const FieldTemplate: React.FC = ({ + children, + errors, + help, + rawErrors, + label, + displayLabel, + required, + id, +}) => { return ( - <> -
- {children} +
+ {displayLabel && ( + + )} + {help} + {children} -
- {rawErrors && rawErrors.length > 0 ? ( - <> - - {errors} - - ) : null} -
- {help} +
+ {rawErrors && rawErrors.length > 0 ? ( + <> + + {errors} + + ) : null}
+ - +
); }; diff --git a/app/lib/theme/ReadOnlyFieldTemplate.tsx b/app/lib/theme/ReadOnlyFieldTemplate.tsx new file mode 100644 index 0000000000..1265faabbc --- /dev/null +++ b/app/lib/theme/ReadOnlyFieldTemplate.tsx @@ -0,0 +1,67 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons"; +import { FieldTemplateProps } from "@rjsf/core"; +import FieldLabel from "./widgets/FieldLabel"; + +const FieldTemplate: React.FC = ({ + children, + errors, + rawErrors, + label, + displayLabel, + required, + id, +}) => { + return ( +
+
+ {displayLabel && ( + + )} + {children} +
+ {rawErrors && rawErrors.length > 0 ? ( +
+ + {errors} +
+ ) : null} + + +
+ ); +}; + +export default FieldTemplate; diff --git a/app/lib/theme/ReadOnlyTheme.tsx b/app/lib/theme/ReadOnlyTheme.tsx index a84a1d28e5..dcfcaefd4f 100644 --- a/app/lib/theme/ReadOnlyTheme.tsx +++ b/app/lib/theme/ReadOnlyTheme.tsx @@ -1,5 +1,5 @@ import { ThemeProps } from "@rjsf/core"; -import FieldTemplate from "./FieldTemplate"; +import ReadOnlyFieldTemplate from "./ReadOnlyFieldTemplate"; import ReadOnlyObjectFieldTemplate from "./ReadOnlyObjectFieldTemplate"; import { utils } from "@rjsf/core"; import ReadOnlyWidget from "./widgets/ReadOnlyWidget"; @@ -23,7 +23,7 @@ const readOnlyTheme: ThemeProps = { SelectProjectStatusWidget: ReadOnlyWidget, }, ObjectFieldTemplate: ReadOnlyObjectFieldTemplate, - FieldTemplate: FieldTemplate, + FieldTemplate: ReadOnlyFieldTemplate, }; export default readOnlyTheme; diff --git a/app/lib/theme/utils/getRequiredLabel.ts b/app/lib/theme/utils/getRequiredLabel.ts deleted file mode 100644 index ae00ef1511..0000000000 --- a/app/lib/theme/utils/getRequiredLabel.ts +++ /dev/null @@ -1,4 +0,0 @@ -const getRequiredLabel = (label: string, required: boolean) => - label + (required ? "" : " (optional)"); - -export default getRequiredLabel; diff --git a/app/lib/theme/widgets/DisplayOnlyWidget.tsx b/app/lib/theme/widgets/DisplayOnlyWidget.tsx index c44a843c1e..f6a461a64b 100644 --- a/app/lib/theme/widgets/DisplayOnlyWidget.tsx +++ b/app/lib/theme/widgets/DisplayOnlyWidget.tsx @@ -1,12 +1,7 @@ import { WidgetProps } from "@rjsf/core"; -const DisplayOnlyWidget: React.FC = ({ options }) => { - return ( - - -

{options.text}

-
- ); +const DisplayOnlyWidget: React.FC = ({ options, id }) => { + return {options.text}; }; export default DisplayOnlyWidget; diff --git a/app/lib/theme/widgets/FieldLabel.tsx b/app/lib/theme/widgets/FieldLabel.tsx index 6aba3a2608..deeb0c826e 100644 --- a/app/lib/theme/widgets/FieldLabel.tsx +++ b/app/lib/theme/widgets/FieldLabel.tsx @@ -1,32 +1,33 @@ -import { UiSchema } from "@rjsf/core"; -import getRequiredLabel from "../utils/getRequiredLabel"; - interface Props { label: string; required: boolean; htmlFor: string; - uiSchema?: UiSchema; + tagName?: "label" | "dt"; } const FieldLabel: React.FC = ({ label, required, htmlFor, - uiSchema, + tagName = "label", }) => { - if ( - uiSchema && - uiSchema["ui:options"] && - uiSchema["ui:options"].label === false - ) { + if (!label) { return null; } + + const displayedLabel = label + (required ? "" : " (optional)") + " "; + + if (tagName === "label") + return ; + return ( <> - - {uiSchema && uiSchema["bcgov:help-text"] && ( -  {uiSchema["bcgov:help-text"]} - )} +
{displayedLabel}
+ ); }; diff --git a/app/lib/theme/widgets/MoneyWidget.tsx b/app/lib/theme/widgets/MoneyWidget.tsx index 4f4463275d..2bb1257492 100644 --- a/app/lib/theme/widgets/MoneyWidget.tsx +++ b/app/lib/theme/widgets/MoneyWidget.tsx @@ -1,25 +1,16 @@ import { WidgetProps } from "@rjsf/core"; import NumberFormat from "react-number-format"; -import FieldLabel from "./FieldLabel"; export const MoneyWidget: React.FC = ({ schema, id, disabled, label, - required, onChange, value, - uiSchema, }) => { return (
- = ({ schema, id, disabled, label, - required, onChange, value, - uiSchema, }) => { return ( <> - = ({ id, label, value }) => { +const ReadOnlyMoneyWidget: React.FC = ({ id, value }) => { return ( - <> -
{label}
-
- {value ? ( - - ) : ( - Not added - )} -
- +
+ {value ? ( + + ) : ( + Not added + )} +
); }; diff --git a/app/lib/theme/widgets/ReadOnlyWidget.tsx b/app/lib/theme/widgets/ReadOnlyWidget.tsx index e6ca612363..2698ab4bb3 100644 --- a/app/lib/theme/widgets/ReadOnlyWidget.tsx +++ b/app/lib/theme/widgets/ReadOnlyWidget.tsx @@ -1,11 +1,14 @@ import { WidgetProps } from "@rjsf/core"; -const ReadOnlyWidget: React.FC = ({ label, value, options }) => { +const ReadOnlyWidget: React.FC = ({ value, options }) => { return ( <> - {/* Some of the uiSchemas that use this widget have the label text in the label prop; others have it in options.title. Similarly, some uiSchemas have the value in the value prop; others in options.text. */} -
{options.title ?? label}
-
{options.text ?? value ?? Not added}
+ ); }; diff --git a/app/lib/theme/widgets/SearchDropdownWidget.tsx b/app/lib/theme/widgets/SearchDropdownWidget.tsx index 5812bf385b..5b21affa15 100644 --- a/app/lib/theme/widgets/SearchDropdownWidget.tsx +++ b/app/lib/theme/widgets/SearchDropdownWidget.tsx @@ -3,20 +3,10 @@ import { WidgetProps } from "@rjsf/core"; import Widgets from "@rjsf/core/dist/cjs/components/widgets"; import TextField from "@mui/material/TextField"; import Autocomplete from "@mui/material/Autocomplete"; -import FieldLabel from "./FieldLabel"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; const SearchDropdownWidget: React.FC = (props) => { - const { - id, - onChange, - schema, - placeholder, - readonly, - label, - required, - uiSchema, - } = props; + const { id, onChange, schema, placeholder, readonly } = props; const handleChange = (e: React.ChangeEvent<{}>, option: any) => { onChange(option?.value); @@ -33,52 +23,44 @@ const SearchDropdownWidget: React.FC = (props) => { if (readonly) return ; return ( - <> - - - props.value ? option.value === props.value : true - } - getOptionLabel={(option) => (option ? option.title : "")} - sx={{ - border: "2px solid #606060", - borderRadius: "0.25em", - marginTop: "0.2em", - "&.Mui-focused": { - outlineStyle: "solid", - outlineWidth: "4px", - outlineColor: "#3B99FC", - outlineOffset: "1px", - }, - }} - popupIcon={} - renderInput={(params) => { - return ( - - ); - }} - /> - + + props.value ? option.value === props.value : true + } + getOptionLabel={(option) => (option ? option.title : "")} + sx={{ + border: "2px solid #606060", + borderRadius: "0.25em", + marginTop: "0.2em", + "&.Mui-focused": { + outlineStyle: "solid", + outlineWidth: "4px", + outlineColor: "#3B99FC", + outlineOffset: "1px", + }, + }} + popupIcon={} + renderInput={(params) => { + return ( + + ); + }} + /> ); }; diff --git a/app/lib/theme/widgets/SelectWidget.tsx b/app/lib/theme/widgets/SelectWidget.tsx index a1f8006585..4d465eadb9 100644 --- a/app/lib/theme/widgets/SelectWidget.tsx +++ b/app/lib/theme/widgets/SelectWidget.tsx @@ -1,6 +1,5 @@ import { WidgetProps } from "@rjsf/core"; import Dropdown from "@button-inc/bcgov-theme/Dropdown"; -import FieldLabel from "./FieldLabel"; interface Option { type: string; @@ -28,12 +27,6 @@ const SelectWidget: React.FunctionComponent = (props) => { return (
- onChange(e.target.value || undefined)} diff --git a/app/lib/theme/widgets/TextAreaWidget.tsx b/app/lib/theme/widgets/TextAreaWidget.tsx index 86fd46837b..b6607f4130 100644 --- a/app/lib/theme/widgets/TextAreaWidget.tsx +++ b/app/lib/theme/widgets/TextAreaWidget.tsx @@ -1,6 +1,5 @@ import { WidgetProps } from "@rjsf/core"; import Textarea from "@button-inc/bcgov-theme/Textarea"; -import FieldLabel from "./FieldLabel"; const TextAreaWidget: React.FC = ({ id, @@ -9,16 +8,9 @@ const TextAreaWidget: React.FC = ({ label, value, required, - uiSchema, }) => { return ( - <> - +