diff --git a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx index 4dcfda0ccaf4d..cef7f66388b0c 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx @@ -19,7 +19,7 @@ const createTemplateVersionParameter = ( name: "first_parameter", description: "This is first parameter.", type: "string", - mutable: false, + mutable: true, default_value: "default string", icon: "/icon/folder.svg", options: [], @@ -47,6 +47,46 @@ export const Basic: Story = { }, }; +export const Optional: Story = { + args: { + value: "initial-value", + id: "project_name", + parameter: createTemplateVersionParameter({ + required: false, + name: "project_name", + description: + "Customize the name of a Google Cloud project that will be created!", + }), + }, +}; + +export const Immutable: Story = { + args: { + value: "initial-value", + id: "project_name", + parameter: createTemplateVersionParameter({ + mutable: false, + name: "project_name", + description: + "Customize the name of a Google Cloud project that will be created!", + }), + }, +}; + +export const WithError: Story = { + args: { + id: "number_parameter", + parameter: createTemplateVersionParameter({ + name: "number_parameter", + type: "number", + description: "Numeric parameter", + default_value: "", + }), + error: true, + helperText: "Number must be greater than 5", + }, +}; + export const NumberType: Story = { args: { value: "4", diff --git a/site/src/components/RichParameterInput/RichParameterInput.tsx b/site/src/components/RichParameterInput/RichParameterInput.tsx index 331c67864ad65..fe9d961d0d7c2 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.tsx @@ -10,6 +10,8 @@ import { MemoizedMarkdown } from "components/Markdown/Markdown"; import { Stack } from "components/Stack/Stack"; import { MultiTextField } from "./MultiTextField"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; +import { Pill } from "components/Pill/Pill"; +import ErrorOutline from "@mui/icons-material/ErrorOutline"; const isBoolean = (parameter: TemplateVersionParameter) => { return parameter.type === "bool"; @@ -31,7 +33,11 @@ const styles = { labelPrimary: (theme) => ({ fontSize: 16, color: theme.palette.text.primary, - fontWeight: 600, + fontWeight: 500, + display: "flex", + alignItems: "center", + flexWrap: "wrap", + gap: 8, "& p": { margin: 0, @@ -42,6 +48,11 @@ const styles = { fontSize: 14, }, }), + optionalLabel: (theme) => ({ + fontSize: 14, + color: theme.palette.text.disabled, + fontWeight: 500, + }), textField: { ".small & .MuiInputBase-root": { height: 36, @@ -102,6 +113,25 @@ const ParameterLabel: FC = ({ parameter }) => { ? parameter.display_name : parameter.name; + const labelPrimary = ( + + {displayName} + + {!parameter.required && ( + + (optional) + + )} + {!parameter.mutable && ( + + }> + Immutable + + + )} + + ); + return ( diff --git a/site/src/components/TemplateParameters/TemplateParameters.tsx b/site/src/components/TemplateParameters/TemplateParameters.tsx index 9befc7c74051f..d01f5b29bf671 100644 --- a/site/src/components/TemplateParameters/TemplateParameters.tsx +++ b/site/src/components/TemplateParameters/TemplateParameters.tsx @@ -14,9 +14,11 @@ export type TemplateParametersSectionProps = { ) => Omit; } & Pick, "classes">; -export const MutableTemplateParametersSection: FC< - TemplateParametersSectionProps -> = ({ templateParameters, getInputProps, ...formSectionProps }) => { +export const TemplateParametersSection: FC = ({ + templateParameters, + getInputProps, + ...formSectionProps +}) => { const hasMutableParameters = templateParameters.filter((p) => p.mutable).length > 0; @@ -45,40 +47,3 @@ export const MutableTemplateParametersSection: FC< ); }; - -export const ImmutableTemplateParametersSection: FC< - TemplateParametersSectionProps -> = ({ templateParameters, getInputProps, ...formSectionProps }) => { - const hasImmutableParameters = - templateParameters.filter((p) => !p.mutable).length > 0; - - return ( - <> - {hasImmutableParameters && ( - - These settings cannot be changed after creating - the workspace. - - } - > - - {templateParameters.map( - (parameter, index) => - !parameter.mutable && ( - - ), - )} - - - )} - - ); -}; diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index b6b6968e945d6..a7aabb56a57ae 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -1,5 +1,4 @@ -import { css } from "@emotion/css"; -import { useTheme, type Interpolation, type Theme } from "@emotion/react"; +import { type Interpolation, type Theme } from "@emotion/react"; import TextField from "@mui/material/TextField"; import type * as TypesGen from "api/typesGenerated"; import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete"; @@ -21,10 +20,6 @@ import { getInitialRichParameterValues, useValidationSchemaForRichParameters, } from "utils/richParameters"; -import { - ImmutableTemplateParametersSection, - MutableTemplateParametersSection, -} from "components/TemplateParameters/TemplateParameters"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Stack } from "components/Stack/Stack"; import { @@ -44,6 +39,7 @@ import { PageHeaderSubtitle, } from "components/PageHeader/PageHeader"; import { Pill } from "components/Pill/Pill"; +import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"; export const Language = { duplicationWarning: @@ -90,7 +86,6 @@ export const CreateWorkspacePageView: FC = ({ onSubmit, onCancel, }) => { - const theme = useTheme(); const [owner, setOwner] = useState(defaultOwner); const [searchParams] = useSearchParams(); const disabledParamsList = searchParams?.get("disable_params")?.split(","); @@ -222,65 +217,41 @@ export const CreateWorkspacePageView: FC = ({ - {parameters && ( - <> - { - return { - ...getFieldHelpers( - "rich_parameter_values[" + index + "].value", - ), - onChange: async (value) => { - await form.setFieldValue( - "rich_parameter_values." + index, - { - name: parameter.name, - value: value, - }, - ); - }, - disabled: - disabledParamsList?.includes( - parameter.name.toLowerCase().replace(/ /g, "_"), - ) || creatingWorkspace, - }; - }} - /> - { - return { - ...getFieldHelpers( - "rich_parameter_values[" + index + "].value", - ), - onChange: async (value) => { - await form.setFieldValue( - "rich_parameter_values." + index, - { + {parameters.length > 0 && ( + + {/* + Opted not to use FormFields in order to increase spacing. + This decision was made because rich parameter inputs are more visually dense than standard text fields. + */} +
+ {parameters.map((parameter, index) => { + const parameterField = `rich_parameter_values.${index}`; + const parameterInputName = `${parameterField}.value`; + const isDisabled = + disabledParamsList?.includes( + parameter.name.toLowerCase().replace(/ /g, "_"), + ) || creatingWorkspace; + + return ( + { + await form.setFieldValue(parameterField, { name: parameter.name, - value: value, - }, - ); - }, - disabled: - disabledParamsList?.includes( - parameter.name.toLowerCase().replace(/ /g, "_"), - ) || creatingWorkspace, - }; - }} - /> - + value, + }); + }} + key={parameter.name} + parameter={parameter} + disabled={isDisabled} + /> + ); + })} +
+
)} ; @@ -64,22 +60,6 @@ export const TemplateEmbedPageView: FC = ({ const buttonUrl = `${createWorkspaceUrl}?${createWorkspaceParams.toString()}`; const buttonMkdCode = `[![Open in Coder](${deploymentUrl}/open-in-coder.svg)](${buttonUrl})`; const clipboard = useClipboard(buttonMkdCode); - const getInputProps: TemplateParametersSectionProps["getInputProps"] = ( - parameter, - ) => { - if (!buttonValues) { - throw new Error("buttonValues is undefined"); - } - return { - value: buttonValues[`param.${parameter.name}`] ?? "", - onChange: (value) => { - setButtonValues((buttonValues) => ({ - ...buttonValues, - [`param.${parameter.name}`]: value, - })); - }, - }; - }; // template parameters is async so we need to initialize the values after it // is loaded @@ -135,16 +115,28 @@ export const TemplateEmbedPageView: FC = ({ {templateParameters.length > 0 && ( - <> - - - +
+ {templateParameters.map((parameter) => { + const parameterValue = + buttonValues[`param.${parameter.name}`] ?? ""; + + return ( + { + setButtonValues((buttonValues) => ({ + ...buttonValues, + [`param.${parameter.name}`]: value, + })); + }} + key={parameter.name} + parameter={parameter} + /> + ); + })} +
)} diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx index 12ea287acfb84..f5a03304b9eac 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx @@ -69,9 +69,6 @@ export const WorkspaceParametersForm: FC = ({ const hasNonEphemeralParameters = templateVersionRichParameters.some( (parameter) => !parameter.ephemeral, ); - const hasImmutableParameters = templateVersionRichParameters.some( - (parameter) => !parameter.mutable, - ); const disabled = workspace.outdated && @@ -97,12 +94,12 @@ export const WorkspaceParametersForm: FC = ({ {templateVersionRichParameters.map((parameter, index) => // Since we are adding the values to the form based on the index // we can't filter them to not loose the right index position - parameter.mutable && !parameter.ephemeral ? ( + !parameter.ephemeral ? ( { await form.setFieldValue( @@ -152,36 +149,7 @@ export const WorkspaceParametersForm: FC = ({ )} - {/* They are displayed here only for visibility purposes */} - {hasImmutableParameters && ( - - These settings cannot be changed after creating - the workspace. - - } - > - - {templateVersionRichParameters.map((parameter, index) => - !parameter.mutable ? ( - { - throw new Error("Immutable parameters cannot be changed"); - }} - /> - ) : null, - )} - - - )} +