diff --git a/site/src/components/RichParameterInput/RichParameterInput.tsx b/site/src/components/RichParameterInput/RichParameterInput.tsx index beaff8ca2772e..84fe47bbbfee3 100644 --- a/site/src/components/RichParameterInput/RichParameterInput.tsx +++ b/site/src/components/RichParameterInput/RichParameterInput.tsx @@ -19,7 +19,7 @@ import type { AutofillBuildParameter, AutofillSource, } from "utils/richParameters"; -import { MultiTextField } from "./MultiTextField"; +import { TagInput } from "../TagInput/TagInput"; const isBoolean = (parameter: TemplateVersionParameter) => { return parameter.type === "bool"; @@ -372,7 +372,7 @@ const RichParameterField: FC = ({ } return ( - = { + title: "components/TagInput", + component: TagInput, + decorators: [(Story) =>
{Story()}
], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + values: [], + }, +}; + +export const WithEmptyTags: Story = { + args: { + values: ["", "", ""], + }, +}; + +export const WithLongTags: Story = { + args: { + values: [ + "this-is-a-very-long-long-long-tag-that-might-wrap", + "another-long-tag-example", + "short", + ], + }, +}; + +export const WithManyTags: Story = { + args: { + values: [ + "tag1", + "tag2", + "tag3", + "tag4", + "tag5", + "tag6", + "tag7", + "tag8", + "tag9", + "tag10", + "tag11", + "tag12", + "tag13", + "tag14", + "tag15", + "tag16", + "tag17", + "tag18", + "tag19", + "tag20", + ], + }, +}; diff --git a/site/src/components/RichParameterInput/MultiTextField.tsx b/site/src/components/TagInput/TagInput.tsx similarity index 56% rename from site/src/components/RichParameterInput/MultiTextField.tsx rename to site/src/components/TagInput/TagInput.tsx index aed995299dbf3..40e89625502a6 100644 --- a/site/src/components/RichParameterInput/MultiTextField.tsx +++ b/site/src/components/TagInput/TagInput.tsx @@ -1,27 +1,39 @@ -import type { Interpolation, Theme } from "@emotion/react"; import Chip from "@mui/material/Chip"; import FormHelperText from "@mui/material/FormHelperText"; -import type { FC } from "react"; +import { type FC, useId, useMemo } from "react"; -export type MultiTextFieldProps = { +export type TagInputProps = { label: string; id?: string; values: string[]; onChange: (values: string[]) => void; }; -export const MultiTextField: FC = ({ +export const TagInput: FC = ({ label, id, values, onChange, }) => { + const baseId = useId(); + + const itemIds = useMemo(() => { + return Array.from( + { length: values.length }, + (_, index) => `${baseId}-item-${index}`, + ); + }, [baseId, values.length]); + return (
-
); }; - -const styles = { - root: (theme) => ({ - border: `1px solid ${theme.palette.divider}`, - borderRadius: 8, - minHeight: 48, // Chip height + paddings - padding: "10px 14px", - fontSize: 16, - display: "flex", - flexWrap: "wrap", - gap: 8, - position: "relative", - margin: "8px 0 4px", // 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", - }, - }, -} satisfies Record>; diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx index 9ec69158c4e84..dd1f52d73b07d 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx @@ -24,6 +24,7 @@ import { } from "components/Select/Select"; import { Slider } from "components/Slider/Slider"; import { Switch } from "components/Switch/Switch"; +import { TagInput } from "components/TagInput/TagInput"; import { Textarea } from "components/Textarea/Textarea"; import { Tooltip, @@ -198,21 +199,7 @@ const ParameterField: FC = ({ ); case "multi-select": { - let values: string[] = []; - - if (value) { - try { - const parsed = JSON.parse(value); - if (Array.isArray(parsed)) { - values = parsed; - } - } catch (e) { - console.error( - "Error parsing parameter value with form_type multi-select", - e, - ); - } - } + const values = parseStringArrayValue(value); // Map parameter options to MultiSelectCombobox options format const options: Option[] = parameter.options.map((opt) => ({ @@ -259,6 +246,21 @@ const ParameterField: FC = ({ ); } + case "tag-select": { + const values = parseStringArrayValue(value); + + return ( + { + onChange(JSON.stringify(values)); + }} + /> + ); + } + case "switch": return ( = ({ } }; +const parseStringArrayValue = (value: string): string[] => { + let values: string[] = []; + + if (value) { + try { + const parsed = JSON.parse(value); + if (Array.isArray(parsed)) { + values = parsed; + } + } catch (e) { + console.error("Error parsing parameter of type list(string)", e); + } + } + + return values; +}; + interface OptionDisplayProps { option: PreviewParameterOption; }