From 0c38c55f14bbba1e4e1318944f1bfd228d8d53fa Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 May 2023 14:54:59 +0200 Subject: [PATCH 1/6] feat: support markdown for field descriptions, and export a new ScaffolderField component Signed-off-by: blam --- .../Form/DescriptionFieldTemplate.tsx | 71 ++++++++++++ .../next/components/Form/FieldTemplate.tsx | 102 ++++++++++++++++++ .../src/next/components/Form/Form.tsx | 22 +++- .../ScaffolderField/ScaffolderField.tsx | 79 ++++++++++++++ .../next/components/ScaffolderField/index.ts | 16 +++ .../src/next/components/index.ts | 1 + 6 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx create mode 100644 plugins/scaffolder-react/src/next/components/Form/FieldTemplate.tsx create mode 100644 plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx create mode 100644 plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts diff --git a/plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx b/plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx new file mode 100644 index 0000000000..8debfb3e08 --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx @@ -0,0 +1,71 @@ +/* + * Copyright 2023 The Backstage Authors + * + * 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 React from 'react'; + +import { MarkdownContent } from '@backstage/core-components'; +import { makeStyles, Typography } from '@material-ui/core'; +import { + DescriptionFieldProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; + +const useStyles = makeStyles({ + markdownDescription: { + fontSize: '0.75rem', + margin: 0, + marginTop: '5px', + color: 'rgba(0, 0, 0, 0.54)', + '& p:first-child': { + margin: 0, + }, + }, +}); + +/** The `DescriptionField` is the template to use to render the description of a field + * @alpha + * @param props - The `DescriptionFieldProps` for this component + */ +export const DescriptionFieldTemplate = < + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>( + props: DescriptionFieldProps, +) => { + const { id, description } = props; + const classes = useStyles(); + + if (description) { + if (typeof description === 'string') { + return ( + + ); + } + + return ( + + {description} + + ); + } + + return null; +}; diff --git a/plugins/scaffolder-react/src/next/components/Form/FieldTemplate.tsx b/plugins/scaffolder-react/src/next/components/Form/FieldTemplate.tsx new file mode 100644 index 0000000000..79cac33054 --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/Form/FieldTemplate.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2023 The Backstage Authors + * + * 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 React from 'react'; + +import { + FieldTemplateProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + getTemplate, + getUiOptions, +} from '@rjsf/utils'; + +import { ScaffolderField } from '../ScaffolderField'; + +/** The `FieldTemplate` component is the template used by `SchemaField` to render any field. It renders the field + * content, (label, description, children, errors and help) inside of a `WrapIfAdditional` component. + * @alpha + * @param props - The `FieldTemplateProps` for this component + */ +export const FieldTemplate = < + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>( + props: FieldTemplateProps, +) => { + const { + id, + children, + classNames, + style, + disabled, + displayLabel, + hidden, + label, + onDropPropertyClick, + onKeyChange, + readonly, + required, + rawErrors = [], + errors, + help, + rawDescription, + schema, + uiSchema, + registry, + } = props; + + const uiOptions = getUiOptions(uiSchema); + const WrapIfAdditionalTemplate = getTemplate< + 'WrapIfAdditionalTemplate', + T, + S, + F + >('WrapIfAdditionalTemplate', registry, uiOptions); + + if (hidden) { + return
{children}
; + } + return ( + + + {children} + + + ); +}; diff --git a/plugins/scaffolder-react/src/next/components/Form/Form.tsx b/plugins/scaffolder-react/src/next/components/Form/Form.tsx index 79bcaa8475..790c9027fe 100644 --- a/plugins/scaffolder-react/src/next/components/Form/Form.tsx +++ b/plugins/scaffolder-react/src/next/components/Form/Form.tsx @@ -14,10 +14,26 @@ * limitations under the License. */ -import { withTheme } from '@rjsf/core-v5'; +import { FormProps, withTheme } from '@rjsf/core-v5'; +import React from 'react'; +import { PropsWithChildren } from 'react'; +import { FieldTemplate } from './FieldTemplate'; +import { DescriptionFieldTemplate } from './DescriptionFieldTemplate'; // TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly // which is what we're using here as the default types, it needs to depend on @rjsf/core-v5 because // of the re-writing we're doing. Once we've migrated, we can import this the exact same as before. -/** @alpha */ -export const Form = withTheme(require('@rjsf/material-ui-v5').Theme); +const WrappedForm = withTheme(require('@rjsf/material-ui-v5').Theme); + +/** + * The Form component + * @alpha + */ +export const Form = (props: PropsWithChildren) => { + const templates = { + FieldTemplate, + DescriptionFieldTemplate, + ...props.templates, + }; + return ; +}; diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx new file mode 100644 index 0000000000..f3ca775f9a --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2023 The Backstage Authors + * + * 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 React, { PropsWithChildren, ReactElement } from 'react'; + +import { MarkdownContent } from '@backstage/core-components'; +import { FormControl, makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + markdownDescription: { + fontSize: theme.typography.caption.fontSize, + margin: 0, + color: theme.palette.text.secondary, + '& :first-child': { + margin: 0, + marginTop: '3px', // to keep the standard browser padding + }, + }, +})); + +interface FieldProps { + rawDescription?: string; + errors?: ReactElement; + rawErrors?: string[]; + help?: ReactElement; + rawHelp?: string; + required?: boolean; + disabled: boolean; + displayLabel?: boolean; +} + +/** + * A component to wrap up a input field which helps with formatting and supporting markdown + * on the field types + * @alpha + */ +export const ScaffolderField = (props: PropsWithChildren) => { + const { + children, + displayLabel = true, + rawErrors = [], + errors, + help, + rawDescription, + required, + disabled, + } = props; + const classes = useStyles(); + return ( + + {children} + {displayLabel && rawDescription ? ( + + ) : null} + {errors} + {help} + + ); +}; diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts b/plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts new file mode 100644 index 0000000000..92964e748d --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 The Backstage Authors + * + * 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. + */ +export { ScaffolderField } from './ScaffolderField'; diff --git a/plugins/scaffolder-react/src/next/components/index.ts b/plugins/scaffolder-react/src/next/components/index.ts index 5bfa5ccdc8..119f983a9f 100644 --- a/plugins/scaffolder-react/src/next/components/index.ts +++ b/plugins/scaffolder-react/src/next/components/index.ts @@ -25,3 +25,4 @@ export * from './TaskSteps'; export * from './TaskLogStream'; export * from './TemplateCategoryPicker'; export * from './ScaffolderPageContextMenu'; +export * from './ScaffolderField'; From 29affaf4b94676a017455ba6049475fd63ab1fb1 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 May 2023 15:23:41 +0200 Subject: [PATCH 2/6] chore: fix api-reports Signed-off-by: blam --- plugins/scaffolder-react/alpha-api-report.md | 32 ++++++++++++++++--- .../ScaffolderField/ScaffolderField.tsx | 10 ++++-- .../next/components/ScaffolderField/index.ts | 2 +- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-react/alpha-api-report.md b/plugins/scaffolder-react/alpha-api-report.md index 319e3963f0..33137c3d46 100644 --- a/plugins/scaffolder-react/alpha-api-report.md +++ b/plugins/scaffolder-react/alpha-api-report.md @@ -6,7 +6,6 @@ /// import { ApiHolder } from '@backstage/core-plugin-api'; -import { ComponentType } from 'react'; import { CustomFieldExtensionSchema } from '@backstage/plugin-scaffolder-react'; import { Dispatch } from 'react'; import { Extension } from '@backstage/core-plugin-api'; @@ -20,8 +19,8 @@ import { JsonValue } from '@backstage/types'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; +import { ReactElement } from 'react'; import { ReactNode } from 'react'; -import { RJSFSchema } from '@rjsf/utils'; import { ScaffolderStep } from '@backstage/plugin-scaffolder-react'; import { ScaffolderTaskOutput } from '@backstage/plugin-scaffolder-react'; import { SetStateAction } from 'react'; @@ -56,8 +55,8 @@ export const extractSchemaFromStep: (inputStep: JsonObject) => { schema: JsonObject; }; -// @alpha (undocumented) -export const Form: ComponentType>; +// @alpha +export const Form: (props: PropsWithChildren) => JSX.Element; // @alpha export type FormProps = Pick< @@ -132,6 +131,31 @@ export type ReviewStateProps = { formState: JsonObject; }; +// @alpha +export const ScaffolderField: ( + props: PropsWithChildren, +) => JSX.Element; + +// @alpha +export interface ScaffolderFieldProps { + // (undocumented) + disabled: boolean; + // (undocumented) + displayLabel?: boolean; + // (undocumented) + errors?: ReactElement; + // (undocumented) + help?: ReactElement; + // (undocumented) + rawDescription?: string; + // (undocumented) + rawErrors?: string[]; + // (undocumented) + rawHelp?: string; + // (undocumented) + required?: boolean; +} + // @alpha (undocumented) export function ScaffolderPageContextMenu( props: ScaffolderPageContextMenuProps, diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx index f3ca775f9a..ea235a869e 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx @@ -30,7 +30,11 @@ const useStyles = makeStyles(theme => ({ }, })); -interface FieldProps { +/** + * Props for the {@link ScaffolderField} component + * @alpha + */ +export interface ScaffolderFieldProps { rawDescription?: string; errors?: ReactElement; rawErrors?: string[]; @@ -46,7 +50,9 @@ interface FieldProps { * on the field types * @alpha */ -export const ScaffolderField = (props: PropsWithChildren) => { +export const ScaffolderField = ( + props: PropsWithChildren, +) => { const { children, displayLabel = true, diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts b/plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts index 92964e748d..d3c2b4a25d 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts +++ b/plugins/scaffolder-react/src/next/components/ScaffolderField/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { ScaffolderField } from './ScaffolderField'; +export { ScaffolderField, type ScaffolderFieldProps } from './ScaffolderField'; From 6b571405f80640f424987019249b034fa6913ff0 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 May 2023 15:34:07 +0200 Subject: [PATCH 3/6] chore: changeset Signed-off-by: blam --- .changeset/hot-badgers-double.md | 5 +++++ .changeset/twenty-suns-breathe.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/hot-badgers-double.md create mode 100644 .changeset/twenty-suns-breathe.md diff --git a/.changeset/hot-badgers-double.md b/.changeset/hot-badgers-double.md new file mode 100644 index 0000000000..835420b91a --- /dev/null +++ b/.changeset/hot-badgers-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': minor +--- + +`scaffolder/next`: Provide some default template components to `rjsf` to allow for standardization and markdown descriptions diff --git a/.changeset/twenty-suns-breathe.md b/.changeset/twenty-suns-breathe.md new file mode 100644 index 0000000000..b87e6b9366 --- /dev/null +++ b/.changeset/twenty-suns-breathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': minor +--- + +`scaffolder/next`: provide a `ScaffolderField` component which is meant to replace soome of the `FormControl` components from mui allowing for easier to write `FieldExtensions` From 400e7abb10ae3b1a093157d1ca71684cdf9a27b7 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 May 2023 15:43:37 +0200 Subject: [PATCH 4/6] chore: fixing changeset Signed-off-by: blam --- .changeset/twenty-suns-breathe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/twenty-suns-breathe.md b/.changeset/twenty-suns-breathe.md index b87e6b9366..a47d784932 100644 --- a/.changeset/twenty-suns-breathe.md +++ b/.changeset/twenty-suns-breathe.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-react': minor --- -`scaffolder/next`: provide a `ScaffolderField` component which is meant to replace soome of the `FormControl` components from mui allowing for easier to write `FieldExtensions` +`scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from mui allowing for easier to write `FieldExtensions` From 9cd726a0e791ad1a2e3ba2e4f6b14ad833c1a700 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 30 May 2023 15:08:03 +0200 Subject: [PATCH 5/6] chore: fix typescript definitions Signed-off-by: blam --- plugins/scaffolder-react/alpha-api-report.md | 2 +- .../src/next/components/ScaffolderField/ScaffolderField.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-react/alpha-api-report.md b/plugins/scaffolder-react/alpha-api-report.md index 33137c3d46..a8bf0cea55 100644 --- a/plugins/scaffolder-react/alpha-api-report.md +++ b/plugins/scaffolder-react/alpha-api-report.md @@ -139,7 +139,7 @@ export const ScaffolderField: ( // @alpha export interface ScaffolderFieldProps { // (undocumented) - disabled: boolean; + disabled?: boolean; // (undocumented) displayLabel?: boolean; // (undocumented) diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx index ea235a869e..da233eeba0 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx @@ -41,7 +41,7 @@ export interface ScaffolderFieldProps { help?: ReactElement; rawHelp?: string; required?: boolean; - disabled: boolean; + disabled?: boolean; displayLabel?: boolean; } From 971cbdc1dadb0c874e3fa51212261aeb0cb1bdb9 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Tue, 30 May 2023 15:08:30 +0200 Subject: [PATCH 6/6] Update .changeset/twenty-suns-breathe.md Co-authored-by: Patrik Oldsberg Signed-off-by: blam --- .changeset/twenty-suns-breathe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/twenty-suns-breathe.md b/.changeset/twenty-suns-breathe.md index a47d784932..edc189a95e 100644 --- a/.changeset/twenty-suns-breathe.md +++ b/.changeset/twenty-suns-breathe.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-react': minor --- -`scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from mui allowing for easier to write `FieldExtensions` +`scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from MUI, making it easier to write `FieldExtensions`.