diff --git a/.changeset/silly-turkeys-hang.md b/.changeset/silly-turkeys-hang.md new file mode 100644 index 0000000000..e2345fb35d --- /dev/null +++ b/.changeset/silly-turkeys-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Add `ScaffolderLayouts` to `NextScaffolderPage` diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx index 5dbce3243f..57ff949c62 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -392,4 +392,47 @@ describe('Stepper', () => { await fireEvent.click(getByRole('button', { name: 'Make' })); }); }); + + describe('Scaffolder Layouts', () => { + it('should render the step in the scaffolder layout', async () => { + const ScaffolderLayout: LayoutTemplate = ({ properties }) => ( + <> +

A Scaffolder Layout

+ {properties.map((prop, i) => ( +
{prop.content}
+ ))} + + ); + + const manifest: TemplateParameterSchema = { + steps: [ + { + title: 'Step 1', + schema: { + type: 'object', + 'ui:ObjectFieldTemplate': 'Layout', + properties: { + field1: { + type: 'string', + }, + }, + }, + }, + ], + title: 'scaffolder layouts', + }; + + const { getByText, getByRole } = await renderInTestApp( + , + ); + + expect(getByText('A Scaffolder Layout')).toBeInTheDocument(); + expect(getByRole('textbox', { name: 'field1' })).toBeInTheDocument(); + }); + }); }); diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 950c9720de..c2c4f06ffd 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -28,11 +28,14 @@ import React, { useCallback, useMemo, useState, type ReactNode } from 'react'; import { NextFieldExtensionOptions } from '../../extensions'; import { TemplateParameterSchema } from '../../../types'; import { createAsyncValidators } from './createAsyncValidators'; -import type { FormProps } from '../../types'; +import type { FormProps, LayoutOptions } from '../../types'; import { ReviewState, type ReviewStateProps } from '../ReviewState'; import { useTemplateSchema } from '../../hooks/useTemplateSchema'; import { useFormDataFromQuery } from '../../hooks/useFormDataFromQuery'; import validator from '@rjsf/validator-ajv6'; +import { useFormDataFromQuery } from '../../hooks'; +import type { FormProps } from '../../types'; +import { selectedTemplateRouteRef } from '../../../routes'; const useStyles = makeStyles(theme => ({ backButton: { @@ -65,6 +68,7 @@ export type StepperProps = { createButtonText?: ReactNode; reviewButtonText?: ReactNode; }; + layouts?: LayoutOptions[]; }; // TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly @@ -76,17 +80,15 @@ const Form = withTheme(require('@rjsf/material-ui-v5').Theme); * The `Stepper` component is the Wizard that is rendered when a user selects a template * @alpha */ - export const Stepper = (stepperProps: StepperProps) => { - const { components = {}, ...props } = stepperProps; + const { layouts = [], components = {}, ...props } = stepperProps; const { ReviewStateComponent = ReviewState, createButtonText = 'Create', reviewButtonText = 'Review', } = components; - const analytics = useAnalytics(); - const { steps } = useTemplateSchema(props.manifest); + const { steps } = useTemplateSchema(props.manifest, layouts); const apiHolder = useApiHolder(); const [activeStep, setActiveStep] = useState(0); const [formState, setFormState] = useFormDataFromQuery(props.initialState); @@ -177,7 +179,7 @@ export const Stepper = (stepperProps: StepperProps) => { fields={extensions} showErrorList={false} onChange={handleChange} - {...(props.FormProps ?? {})} + {...(formProps ?? {})} >