From 94c11a5d7f5102ca5aa635167b3661207223fce8 Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Sun, 6 Jul 2025 00:24:27 -0400 Subject: [PATCH 1/3] Scroll to the top of the page when navigating between template form steps Signed-off-by: Stephen Glass --- .changeset/loud-turtles-mate.md | 5 +++++ .../src/next/components/Stepper/Stepper.tsx | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 .changeset/loud-turtles-mate.md diff --git a/.changeset/loud-turtles-mate.md b/.changeset/loud-turtles-mate.md new file mode 100644 index 0000000000..533f899c92 --- /dev/null +++ b/.changeset/loud-turtles-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Scroll to the top of the page when navigating between steps in template forms. diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 6715eb0e1e..94ced3e415 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -37,6 +37,7 @@ import { merge } from 'lodash'; import { ComponentType, useCallback, + useEffect, useMemo, useState, type ReactNode, @@ -218,6 +219,13 @@ export const Stepper = (stepperProps: StepperProps) => { [validation, analytics], ); + useEffect(() => { + const main = document.querySelector('main'); + if (main) { + main.scrollTo({ top: 0, behavior: 'auto' }); + } + }, [activeStep]); + const mergedUiSchema = merge({}, propUiSchema, currentStep?.uiSchema); const [isCreating, setIsCreating] = useState(false); From 428b4349f48729097c12bebaa0bc6903e69a7ee1 Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Sun, 6 Jul 2025 00:31:17 -0400 Subject: [PATCH 2/3] Add test for form template scroll to top on step change Signed-off-by: Stephen Glass --- .../next/components/Stepper/Stepper.test.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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 af9117ccf5..b0746e0672 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -672,6 +672,40 @@ describe('Stepper', () => { ); }); + it('should scroll the first main element to top when activeStep changes', async () => { + const manifest: TemplateParameterSchema = { + steps: [ + { title: 'Step 1', schema: { properties: {} } }, + { title: 'Step 2', schema: { properties: {} } }, + ], + title: 'Scroll Test', + }; + + // Render a main element in the document for the Stepper to find + const main = document.createElement('main'); + document.body.appendChild(main); + const scrollToMock = jest.fn(); + main.scrollTo = scrollToMock; + + // Render Stepper as usual (do not pass container) + const { getByRole, unmount } = await renderInTestApp( + + + , + ); + + // Click next to change the activeStep + await act(async () => { + fireEvent.click(getByRole('button', { name: 'Next' })); + }); + + expect(scrollToMock).toHaveBeenCalledWith({ top: 0, behavior: 'auto' }); + + // Clean up + document.body.removeChild(main); + unmount(); + }); + describe('Scaffolder Layouts', () => { it('should render the step in the scaffolder layout', async () => { const ScaffolderLayout: LayoutTemplate = ({ properties }) => ( From 7f650756b15d8ee7187e095888108a0b07aa5a98 Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Sun, 6 Jul 2025 00:57:12 -0400 Subject: [PATCH 3/3] Fix scroll to on step change test Signed-off-by: Stephen Glass --- .../scaffolder-react/src/next/components/Stepper/Stepper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 94ced3e415..1f36eb58d3 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -221,7 +221,7 @@ export const Stepper = (stepperProps: StepperProps) => { useEffect(() => { const main = document.querySelector('main'); - if (main) { + if (main && typeof main.scrollTo === 'function') { main.scrollTo({ top: 0, behavior: 'auto' }); } }, [activeStep]);