From 039c0da5ae01a954006f1339c0bb4a73114fd764 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Mon, 17 Aug 2026 12:15:29 -0700 Subject: [PATCH] feat(site/src/pages/TemplateBuilder): make sidebar steps navigable (#28153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Makes the remaining `SelectionSummary` sidebar elements clickable jump targets on `/templates/new/builder`, continuing the work from #27351 (which made module rows navigable). Clickable now: | Sidebar element | Jumps to | |---|---| | `Base Template` label | `base-infra` | | Selected base-template row | `base-parameters` (falls back to `base-infra` when that step is skipped) | | `Modules` label | `module-select` | | Each module row | `module-settings` + scroll (already shipped in #27351) | | `Customizations` label | `customizations` | ## Back-stack behavior The sidebar previously colored groups purely from the current step, so jumping backward would grey out and disable steps you had already reached. This adds a `maxReachedGroup` that never shrinks on backward navigation: - Groups at or below the furthest-reached group stay `complete` (green) and clickable, like a browser back-stack. - Groups strictly above render as `upcoming` and inert (no button, no hover, not focusable). - The connecting divider color keys off `maxReachedGroup`, not the current step, so it stays green after navigating backward. Clickability is gated on `maxReachedGroup` (you can only jump to steps you have already reached). ## Changes - `SelectionSummary.tsx`: new required `maxReachedStep` and `onNavigateStep` props. Split the single `variant()` into `indicatorVariant` (label circle), `dividerVariant` (connecting line), and a `reachable()` gate. `StepIndicator` and `BaseTemplateSelection` render as ` + ); + } return (
@@ -144,19 +220,37 @@ const StepDivider: React.FC = ({ className, children }) => { type BaseTemplateSelectionProps = { template: SelectedTemplate; + onClick?: () => void; }; const BaseTemplateSelection: React.FC = ({ template, + onClick, }) => { return ( -
-
+ {onClick ? ( + + ) : ( +
+
+ +
+ {template.name}
- {template.name} -
+ )} ); }; diff --git a/site/src/pages/TemplateBuilder/TemplateBuilderPageView.tsx b/site/src/pages/TemplateBuilder/TemplateBuilderPageView.tsx index 53fbacfb47..e73d1a195d 100644 --- a/site/src/pages/TemplateBuilder/TemplateBuilderPageView.tsx +++ b/site/src/pages/TemplateBuilder/TemplateBuilderPageView.tsx @@ -5,6 +5,7 @@ import { useEffect, useReducer, useRef, + useState, } from "react"; import { useQuery } from "react-query"; @@ -105,6 +106,16 @@ export const TemplateBuilderPageView: FC = ({ const currentIndex = nearestVisible(clampedIndex, state); const currentStep = WIZARD_STEPS[currentIndex]; + // The highest sidebar group the user has reached. It never shrinks on + // backward navigation, so completed steps stay green and clickable in the + // SelectionSummary sidebar like a browser back-stack. + const [maxReachedGroup, setMaxReachedGroup] = useState<1 | 2 | 3>( + currentStep.group, + ); + if (currentStep.group > maxReachedGroup) { + setMaxReachedGroup(currentStep.group); + } + // Rewrite the URL whenever it disagrees with the resolved step. useEffect(() => { if (searchParams.get("step") === currentStep.id) { @@ -161,6 +172,22 @@ export const TemplateBuilderPageView: FC = ({ navigateToStep(nextIndex); }; + // Sidebar step labels and the base-template row call this to jump to a + // specific wizard step. Skipped steps resolve to the nearest visible one + // (so jumping to base-parameters lands on base-infra when the base has no + // parameters). + const navigateToStepId = (stepId: StepId) => { + const target = WIZARD_STEPS.findIndex((s) => s.id === stepId); + if (target < 0) { + return; + } + if (currentStep.id === "customizations" && stepId !== "customizations") { + dispatch({ type: "RESET_CUSTOMIZATIONS" }); + onClearCreateError?.(); + } + navigateToStep(nearestVisible(target, state)); + }; + const handleProvisionerStatusChange = useCallback( (value: boolean | undefined) => { dispatch({ type: "SET_HAS_PROVISIONERS", value }); @@ -302,6 +329,8 @@ export const TemplateBuilderPageView: FC = ({