feat(site/src/pages/TemplateBuilder): add alternatives box to base template step (#26878)

Adds an "Alternatives to create a template" section below the navigation
controls on the base infrastructure selection step of the Template
Builder wizard. The box is only shown on the first step and contains
four outline buttons linking to other template creation paths:

- **Start from scratch** -> Coder docs tutorial (external)
- **Upload an existing template** -> `/starter-templates` (internal)
- **Browse community templates** -> Registry templates (external)
- **Use template agent skill** -> Registry skills (external)

External links open in a new tab with an external-link icon.

Closes [DEVEX-564](https://linear.app/codercom/issue/DEVEX-564)

> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.

<details>
<summary>Implementation plan</summary>

### 1. `TemplateAlternatives.tsx` (new)
- Presentational component rendering a bordered container with label and
four `Button` components (variant `outline`, size `sm`)
- External links use `ExternalLinkIcon` from lucide-react and open in
`_blank`
- Internal link uses React Router `Link`

### 2. `TemplateBuilderPageView.tsx` (modified)
- Import and render `<TemplateAlternatives />` below navigation
controls, conditionally when `currentStep.id === "base-infra"`

### 3. `TemplateAlternatives.stories.tsx` (new)
- Default Storybook story for the component

</details>
This commit is contained in:
Jeremy Ruppel
2026-06-30 16:02:06 +00:00
committed by GitHub
parent 2fd5ae4323
commit a153b77fa5
3 changed files with 74 additions and 0 deletions
@@ -0,0 +1,12 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { TemplateAlternatives } from "./TemplateAlternatives";
const meta: Meta<typeof TemplateAlternatives> = {
title: "pages/TemplateBuilder/TemplateAlternatives",
component: TemplateAlternatives,
};
export default meta;
type Story = StoryObj<typeof TemplateAlternatives>;
export const Default: Story = {};
@@ -0,0 +1,59 @@
import { ExternalLinkIcon } from "lucide-react";
import type { FC } from "react";
import { Link } from "react-router";
import { Button } from "#/components/Button/Button";
interface AlternativeLink {
label: string;
href: string;
external: boolean;
}
const alternatives: readonly AlternativeLink[] = [
{
label: "Start from scratch",
href: "https://coder.com/docs/tutorials/template-from-scratch",
external: true,
},
{
label: "Upload an existing template",
href: "/starter-templates",
external: false,
},
{
label: "Browse community templates",
href: "https://registry.coder.com/templates",
external: true,
},
{
label: "Use template agent skill",
href: "https://registry.coder.com/skills",
external: true,
},
];
export const TemplateAlternatives: FC = () => {
return (
<div className="p-6 border border-solid rounded-lg mt-6">
<p className="text-sm text-content-secondary mb-4">
Alternatives to create a template:
</p>
<div className="flex flex-wrap gap-2">
{alternatives.map((alt) =>
alt.external ? (
<Button key={alt.label} variant="outline" size="sm" asChild>
<a href={alt.href} target="_blank" rel="noreferrer">
{alt.label}
<ExternalLinkIcon />
</a>
</Button>
) : (
<Button key={alt.label} variant="outline" size="sm" asChild>
<Link to={alt.href}>{alt.label}</Link>
</Button>
),
)}
</div>
</div>
);
};
@@ -34,6 +34,7 @@ import {
type StepId,
WIZARD_STEPS,
} from "./steps";
import { TemplateAlternatives } from "./TemplateAlternatives";
import { TemplateCustomizationsStep } from "./TemplateCustomizationsStep";
import {
initialWizardState,
@@ -152,6 +153,8 @@ export const TemplateBuilderPageView: FC<TemplateBuilderPageViewProps> = ({
{isLastStep ? "Create Template" : "Continue"}
</Button>
</div>
{currentStep.id === "base-infra" && <TemplateAlternatives />}
</div>
{/* Sidebar */}