diff --git a/site/src/components/MultiTextField/MultiTextField.stories.tsx b/site/src/components/MultiTextField/MultiTextField.stories.tsx new file mode 100644 index 0000000000000..9761e15f032da --- /dev/null +++ b/site/src/components/MultiTextField/MultiTextField.stories.tsx @@ -0,0 +1,16 @@ +import { Story } from "@storybook/react" +import { useState } from "react" +import { MultiTextField, MultiTextFieldProps } from "./MultiTextField" + +export default { + title: "components/MultiTextField", + component: MultiTextField, +} + +const Template: Story = (args) => { + const [values, setValues] = useState(args.values ?? ["foo", "bar"]) + return +} + +export const Example = Template.bind({}) +Example.args = {} diff --git a/site/src/components/MultiTextField/MultiTextField.tsx b/site/src/components/MultiTextField/MultiTextField.tsx new file mode 100644 index 0000000000000..c65528431f56c --- /dev/null +++ b/site/src/components/MultiTextField/MultiTextField.tsx @@ -0,0 +1,95 @@ +import Chip from "@material-ui/core/Chip" +import { makeStyles } from "@material-ui/core/styles" +import { FC } from "react" + +export type MultiTextFieldProps = { + label: string + values: string[] + onChange: (values: string[]) => void +} + +export const MultiTextField: FC = ({ + label, + values, + onChange, +}) => { + const styles = useStyles() + + return ( + + ) +} + +const useStyles = makeStyles((theme) => ({ + root: { + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + minHeight: theme.spacing(5), + padding: theme.spacing(1.25, 1.75), + fontSize: theme.spacing(2), + display: "flex", + flexWrap: "wrap", + gap: theme.spacing(1), + position: "relative", + margin: theme.spacing(1, 0, 0.5), // Have same margin than TextField + + "&:has(input:focus)": { + borderColor: theme.palette.primary.main, + borderWidth: 2, + // Compensate for the border width + top: -1, + left: -1, + }, + }, + + input: { + flexGrow: 1, + fontSize: "inherit", + padding: 0, + border: "none", + background: "none", + + "&:focus": { + outline: "none", + }, + }, +})) diff --git a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx index fb6530cf8017e..3bb33f45a3de4 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.stories.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.stories.tsx @@ -31,6 +31,7 @@ const createTemplateVersionParameter = ( validation_max: 0, validation_monotonic: "increasing", description_plaintext: "", + required: true, ...partial, } } @@ -99,6 +100,17 @@ OptionsType.args = { }), } +export const ListStringType = Template.bind({}) +ListStringType.args = { + initialValue: JSON.stringify(["first", "second", "third"]), + id: "list_string_parameter", + parameter: createTemplateVersionParameter({ + name: "list_string_parameter", + type: "list(string)", + description: "List string parameter", + }), +} + export const IconLabel = Template.bind({}) IconLabel.args = { initialValue: "initial-value", diff --git a/site/src/components/RichParameterInput/RichParameterInput.tsx b/site/src/components/RichParameterInput/RichParameterInput.tsx index e0fc6fef153dd..4cf8c5c82f37b 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.tsx @@ -8,6 +8,7 @@ import { FC, useState } from "react" import { TemplateVersionParameter } from "../../api/typesGenerated" import { colors } from "theme/colors" import { MemoizedMarkdown } from "components/Markdown/Markdown" +import { MultiTextField } from "components/MultiTextField/MultiTextField" const isBoolean = (parameter: TemplateVersionParameter) => { return parameter.type === "bool" @@ -154,6 +155,34 @@ const RichParameterField: React.FC = ({ ) } + if (parameter.type === "list(string)") { + let values: string[] = [] + + if (parameterValue) { + try { + values = JSON.parse(parameterValue) as string[] + } catch (e) { + console.error("Error parsing list(string) parameter", e) + } + } + + return ( + { + try { + const value = JSON.stringify(values) + setParameterValue(value) + onChange(value) + } catch (e) { + console.error("Error on change of list(string) parameter", e) + } + }} + /> + ) + } + // A text field can technically handle all cases! // As other cases become more prominent (like filtering for numbers), // we should break this out into more finely scoped input fields. diff --git a/site/src/theme/overrides.ts b/site/src/theme/overrides.ts index 65e57a2643cab..19b2947940ab2 100644 --- a/site/src/theme/overrides.ts +++ b/site/src/theme/overrides.ts @@ -229,5 +229,10 @@ export const getOverrides = ({ borderRadius: 999, }, }, + MuiChip: { + root: { + backgroundColor: colors.gray[12], + }, + }, } }