feat: support markdown for field descriptions, and export a new ScaffolderField component
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -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,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<FieldProps>) => {
|
||||
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 } 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