From cecfb52afe59e4e9599fe7a82a56dd8326983409 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Mon, 3 Aug 2026 13:13:57 +0000 Subject: [PATCH 1/4] refactor(site): demui template editor dialogs and create-template inputs Migrate remaining MUI usage in template create/editor flows to shared shadcn components (`FormField`, `Input`, `Textarea`, `Checkbox`, `RadioGroup`, `Link`). Also drops `disablePortal` on the publish-version help popover so it is no longer clipped by the dialog overflow. --- .../TemplateExampleCard.tsx | 13 +-- .../CreateTemplatePage/TemplateUpload.tsx | 9 +- .../CreateTemplatePage/VariableInput.tsx | 44 +++++----- .../TemplateScheduleAutostart.tsx | 5 +- .../TemplateVersionEditorPage/FileDialog.tsx | 40 +++++---- .../ProvisionerTagsPopover.tsx | 15 +--- .../PublishTemplateVersionDialog.tsx | 84 +++++++++++-------- .../pages/TemplatesPage/EmptyTemplates.tsx | 4 +- 8 files changed, 115 insertions(+), 99 deletions(-) diff --git a/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx b/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx index b401f71ff9514..2ad2d00a54bcb 100644 --- a/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx +++ b/site/src/modules/templates/TemplateExampleCard/TemplateExampleCard.tsx @@ -1,9 +1,9 @@ -import Link from "@mui/material/Link"; import type { FC, HTMLAttributes } from "react"; import { Link as RouterLink } from "react-router"; import type { TemplateExample } from "#/api/typesGenerated"; import { Button } from "#/components/Button/Button"; import { ExternalImage } from "#/components/ExternalImage/ExternalImage"; +import { Link } from "#/components/Link/Link"; import { Pill } from "#/components/Pill/Pill"; import { cn } from "#/utils/cn"; @@ -53,11 +53,14 @@ export const TemplateExampleCard: FC = ({ {example.description}{" "} - Read more + + Read more + diff --git a/site/src/pages/CreateTemplatePage/TemplateUpload.tsx b/site/src/pages/CreateTemplatePage/TemplateUpload.tsx index e5a9e67f2df57..1a0882c4c401a 100644 --- a/site/src/pages/CreateTemplatePage/TemplateUpload.tsx +++ b/site/src/pages/CreateTemplatePage/TemplateUpload.tsx @@ -1,7 +1,7 @@ -import Link from "@mui/material/Link"; import type { FC } from "react"; import { Link as RouterLink } from "react-router"; import { FileUpload } from "#/components/FileUpload/FileUpload"; +import { Link } from "#/components/Link/Link"; export interface TemplateUploadProps { isUploading: boolean; @@ -20,14 +20,15 @@ export const TemplateUpload: FC = ({ <> The template has to be a .tar or .zip file. You can also use our{" "} { e.stopPropagation(); }} + asChild + showExternalIcon={false} + className="p-0" > - starter templates + starter templates {" "} to get started with Coder. diff --git a/site/src/pages/CreateTemplatePage/VariableInput.tsx b/site/src/pages/CreateTemplatePage/VariableInput.tsx index 3d2e4c1070cbb..b5ed2ae05ca83 100644 --- a/site/src/pages/CreateTemplatePage/VariableInput.tsx +++ b/site/src/pages/CreateTemplatePage/VariableInput.tsx @@ -1,9 +1,8 @@ -import FormControlLabel from "@mui/material/FormControlLabel"; -import Radio from "@mui/material/Radio"; -import RadioGroup from "@mui/material/RadioGroup"; -import TextField from "@mui/material/TextField"; import type { FC } from "react"; import type { TemplateVersionVariable } from "#/api/typesGenerated"; +import { Input } from "#/components/Input/Input"; +import { Label } from "#/components/Label/Label"; +import { RadioGroup, RadioGroupItem } from "#/components/RadioGroup/RadioGroup"; const isBoolean = (variable: TemplateVersionVariable) => { return variable.type === "bool"; @@ -62,35 +61,36 @@ const VariableField: FC = ({ defaultValue, }) => { if (isBoolean(variable)) { + const trueId = `${variable.name}-true`; + const falseId = `${variable.name}-false`; + return ( { - onChange(event.target.value); - }} + disabled={disabled} + onValueChange={onChange} > - } - label="True" - /> - } - label="False" - /> +
+ + +
+
+ + +
); } return ( - = ({ ? "number" : variable.sensitive ? "password" - : "string" + : "text" } /> ); diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleAutostart.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleAutostart.tsx index fdff2130f9346..e6558478e0dcf 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleAutostart.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleAutostart.tsx @@ -1,4 +1,3 @@ -import FormHelperText from "@mui/material/FormHelperText"; import type { FC } from "react"; import { Button } from "#/components/Button/Button"; import { @@ -54,9 +53,9 @@ export const TemplateScheduleAutostart: FC = ({ ))} - +
- +
); }; diff --git a/site/src/pages/TemplateVersionEditorPage/FileDialog.tsx b/site/src/pages/TemplateVersionEditorPage/FileDialog.tsx index ef0d5ec17c944..a00fd1750e722 100644 --- a/site/src/pages/TemplateVersionEditorPage/FileDialog.tsx +++ b/site/src/pages/TemplateVersionEditorPage/FileDialog.tsx @@ -1,6 +1,6 @@ -import TextField from "@mui/material/TextField"; import { type ChangeEvent, type FC, useState } from "react"; import { ConfirmDialog } from "#/components/Dialog/ConfirmDialog/ConfirmDialog"; +import { FormField } from "#/components/FormField/FormField"; import { type FileTree, isFolder, validatePath } from "#/utils/filetree"; interface CreateFileDialogProps { @@ -63,22 +63,25 @@ export const CreateFileDialog: FC = ({ Specify the path to a file to be created. This path can contain slashes too.

- { if (event.key === "Enter") { handleConfirm(); } }} - error={Boolean(error)} - helperText={error} - name="file-path" + field={{ + name: "file-path", + id: "file-path", + value: pathValue, + onChange: handleChange, + onBlur: () => {}, + error: Boolean(error), + helperText: error, + }} + label="File Path" autoComplete="off" - id="file-path" placeholder="example.tf" - value={pathValue} - onChange={handleChange} - label="File Path" /> } @@ -184,22 +187,25 @@ export const RenameFileDialog: FC = ({ Rename {filename} to something else. This path can contain slashes too!

- { if (event.key === "Enter") { handleConfirm(); } }} - error={Boolean(error)} - helperText={error} - name="file-path" + field={{ + name: "file-path", + id: "file-path", + value: pathValue, + onChange: handleChange, + onBlur: () => {}, + error: Boolean(error), + helperText: error, + }} + label="File Path" autoComplete="off" - id="file-path" placeholder={filename} - value={pathValue} - onChange={handleChange} - label="File Path" /> } diff --git a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx index a10dca47b9812..d6925e93a323f 100644 --- a/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx +++ b/site/src/pages/TemplateVersionEditorPage/ProvisionerTagsPopover.tsx @@ -1,10 +1,9 @@ -import Link from "@mui/material/Link"; -import useTheme from "@mui/system/useTheme"; import type { FC } from "react"; import type { ProvisionerDaemon } from "#/api/typesGenerated"; import { ChevronDownIcon } from "#/components/AnimatedIcons/ChevronDown"; import { FormSection, VerticalForm } from "#/components/Form/Form"; import { TopbarButton } from "#/components/FullPageLayout/Topbar"; +import { Link } from "#/components/Link/Link"; import { Popover, PopoverContent, @@ -22,8 +21,6 @@ export const ProvisionerTagsPopover: FC = ({ tags, onTagsChange, }) => { - const theme = useTheme(); - return ( @@ -36,13 +33,7 @@ export const ProvisionerTagsPopover: FC = ({ align="end" className="w-[300px] bg-surface-secondary border-surface-quaternary" > -
+
= ({ href={docs("/admin/provisioners")} target="_blank" rel="noreferrer" + className="p-0" + showExternalIcon={false} > Learn more... diff --git a/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx b/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx index b496eee234c87..1f2473d08ae0a 100644 --- a/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx +++ b/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx @@ -1,12 +1,11 @@ -import Checkbox from "@mui/material/Checkbox"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import TextField from "@mui/material/TextField"; import { useFormik } from "formik"; import type { FC } from "react"; import * as Yup from "yup"; import { EnterpriseBadge } from "#/components/Badges/Badges"; +import { Checkbox } from "#/components/Checkbox/Checkbox"; import { ConfirmDialog } from "#/components/Dialog/ConfirmDialog/ConfirmDialog"; import { FormFields } from "#/components/Form/Form"; +import { FormField } from "#/components/FormField/FormField"; import { HelpPopover, HelpPopoverContent, @@ -16,7 +15,10 @@ import { HelpPopoverText, HelpPopoverTitle, } from "#/components/HelpPopover/HelpPopover"; +import { Label } from "#/components/Label/Label"; +import { Textarea } from "#/components/Textarea/Textarea"; import type { PublishVersionData } from "#/pages/TemplateVersionEditorPage/types"; +import { cn } from "#/utils/cn"; import { docs } from "#/utils/docs"; import { getFormHelpers } from "#/utils/formUtils"; @@ -53,6 +55,7 @@ export const PublishTemplateVersionDialog: FC< onSubmit: onConfirm, }); const getFieldHelpers = getFormHelpers(form, publishingError); + const messageField = getFieldHelpers("message"); const handleClose = () => { form.resetForm(); onClose(); @@ -76,48 +79,57 @@ export const PublishTemplateVersionDialog: FC<

You are about to publish a new version of this template.

- - - -
- { - await form.setFieldValue( - "isActiveVersion", - e.target.checked, - ); - }} - name="isActiveVersion" - /> - } +
+ +