Merge pull request #17952 from backstage/blam/scaffolder-md-fields
`scaffolder/next`: Provide a default `FieldTemplate` and `DescriptionFieldTemplate`
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': minor
|
||||
---
|
||||
|
||||
`scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from MUI, making it easier to write `FieldExtensions`.
|
||||
@@ -6,7 +6,6 @@
|
||||
/// <reference types="react" />
|
||||
|
||||
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<FormProps_2<any, RJSFSchema, any>>;
|
||||
// @alpha
|
||||
export const Form: (props: PropsWithChildren<FormProps_2>) => JSX.Element;
|
||||
|
||||
// @alpha
|
||||
export type FormProps = Pick<
|
||||
@@ -132,6 +131,31 @@ export type ReviewStateProps = {
|
||||
formState: JsonObject;
|
||||
};
|
||||
|
||||
// @alpha
|
||||
export const ScaffolderField: (
|
||||
props: PropsWithChildren<ScaffolderFieldProps>,
|
||||
) => 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,
|
||||
|
||||
@@ -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<T, S, F>,
|
||||
) => {
|
||||
const { id, description } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
if (description) {
|
||||
if (typeof description === 'string') {
|
||||
return (
|
||||
<MarkdownContent
|
||||
content={description}
|
||||
className={classes.markdownDescription}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography id={id} variant="subtitle2" style={{ marginTop: '5px' }}>
|
||||
{description}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -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<T, S, F>,
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
children,
|
||||
classNames,
|
||||
style,
|
||||
disabled,
|
||||
displayLabel,
|
||||
hidden,
|
||||
label,
|
||||
onDropPropertyClick,
|
||||
onKeyChange,
|
||||
readonly,
|
||||
required,
|
||||
rawErrors = [],
|
||||
errors,
|
||||
help,
|
||||
rawDescription,
|
||||
schema,
|
||||
uiSchema,
|
||||
registry,
|
||||
} = props;
|
||||
|
||||
const uiOptions = getUiOptions<T, S, F>(uiSchema);
|
||||
const WrapIfAdditionalTemplate = getTemplate<
|
||||
'WrapIfAdditionalTemplate',
|
||||
T,
|
||||
S,
|
||||
F
|
||||
>('WrapIfAdditionalTemplate', registry, uiOptions);
|
||||
|
||||
if (hidden) {
|
||||
return <div style={{ display: 'none' }}>{children}</div>;
|
||||
}
|
||||
return (
|
||||
<WrapIfAdditionalTemplate
|
||||
classNames={classNames}
|
||||
style={style}
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
label={label}
|
||||
onDropPropertyClick={onDropPropertyClick}
|
||||
onKeyChange={onKeyChange}
|
||||
readonly={readonly}
|
||||
required={required}
|
||||
schema={schema}
|
||||
uiSchema={uiSchema}
|
||||
registry={registry}
|
||||
>
|
||||
<ScaffolderField
|
||||
displayLabel={displayLabel}
|
||||
rawErrors={rawErrors}
|
||||
help={help}
|
||||
disabled={disabled}
|
||||
rawDescription={rawDescription}
|
||||
errors={errors}
|
||||
required={required}
|
||||
>
|
||||
{children}
|
||||
</ScaffolderField>
|
||||
</WrapIfAdditionalTemplate>
|
||||
);
|
||||
};
|
||||
@@ -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<FormProps>) => {
|
||||
const templates = {
|
||||
FieldTemplate,
|
||||
DescriptionFieldTemplate,
|
||||
...props.templates,
|
||||
};
|
||||
return <WrappedForm {...props} templates={templates} />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Props for the {@link ScaffolderField} component
|
||||
* @alpha
|
||||
*/
|
||||
export interface ScaffolderFieldProps {
|
||||
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<ScaffolderFieldProps>,
|
||||
) => {
|
||||
const {
|
||||
children,
|
||||
displayLabel = true,
|
||||
rawErrors = [],
|
||||
errors,
|
||||
help,
|
||||
rawDescription,
|
||||
required,
|
||||
disabled,
|
||||
} = props;
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<FormControl
|
||||
fullWidth
|
||||
error={rawErrors.length ? true : false}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
{displayLabel && rawDescription ? (
|
||||
<MarkdownContent
|
||||
content={rawDescription}
|
||||
className={classes.markdownDescription}
|
||||
/>
|
||||
) : null}
|
||||
{errors}
|
||||
{help}
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -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, type ScaffolderFieldProps } from './ScaffolderField';
|
||||
@@ -25,3 +25,4 @@ export * from './TaskSteps';
|
||||
export * from './TaskLogStream';
|
||||
export * from './TemplateCategoryPicker';
|
||||
export * from './ScaffolderPageContextMenu';
|
||||
export * from './ScaffolderField';
|
||||
|
||||
Reference in New Issue
Block a user