Skip to content

Commit ca067cf

Browse files
feat(site): Support list(string) rich parameter field (#6653)
1 parent 090e37f commit ca067cf

File tree

5 files changed

+157
-0
lines changed

5 files changed

+157
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Story } from "@storybook/react"
2+
import { useState } from "react"
3+
import { MultiTextField, MultiTextFieldProps } from "./MultiTextField"
4+
5+
export default {
6+
title: "components/MultiTextField",
7+
component: MultiTextField,
8+
}
9+
10+
const Template: Story<MultiTextFieldProps> = (args) => {
11+
const [values, setValues] = useState(args.values ?? ["foo", "bar"])
12+
return <MultiTextField {...args} values={values} onChange={setValues} />
13+
}
14+
15+
export const Example = Template.bind({})
16+
Example.args = {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import Chip from "@material-ui/core/Chip"
2+
import { makeStyles } from "@material-ui/core/styles"
3+
import { FC } from "react"
4+
5+
export type MultiTextFieldProps = {
6+
label: string
7+
values: string[]
8+
onChange: (values: string[]) => void
9+
}
10+
11+
export const MultiTextField: FC<MultiTextFieldProps> = ({
12+
label,
13+
values,
14+
onChange,
15+
}) => {
16+
const styles = useStyles()
17+
18+
return (
19+
<label className={styles.root}>
20+
{values.map((value, index) => (
21+
<Chip
22+
key={index}
23+
label={value}
24+
size="small"
25+
onDelete={() => {
26+
onChange(values.filter((oldValue) => oldValue !== value))
27+
}}
28+
/>
29+
))}
30+
<input
31+
aria-label={label}
32+
className={styles.input}
33+
onKeyDown={(event) => {
34+
if (event.key === ",") {
35+
event.preventDefault()
36+
const newValue = event.currentTarget.value
37+
onChange([...values, newValue])
38+
event.currentTarget.value = ""
39+
return
40+
}
41+
42+
if (event.key === "Backspace" && event.currentTarget.value === "") {
43+
event.preventDefault()
44+
const lastValue = values[values.length - 1]
45+
onChange(values.slice(0, -1))
46+
event.currentTarget.value = lastValue
47+
return
48+
}
49+
}}
50+
onBlur={(event) => {
51+
if (event.currentTarget.value !== "") {
52+
const newValue = event.currentTarget.value
53+
onChange([...values, newValue])
54+
event.currentTarget.value = ""
55+
}
56+
}}
57+
/>
58+
</label>
59+
)
60+
}
61+
62+
const useStyles = makeStyles((theme) => ({
63+
root: {
64+
border: `1px solid ${theme.palette.divider}`,
65+
borderRadius: theme.shape.borderRadius,
66+
minHeight: theme.spacing(5),
67+
padding: theme.spacing(1.25, 1.75),
68+
fontSize: theme.spacing(2),
69+
display: "flex",
70+
flexWrap: "wrap",
71+
gap: theme.spacing(1),
72+
position: "relative",
73+
margin: theme.spacing(1, 0, 0.5), // Have same margin than TextField
74+
75+
"&:has(input:focus)": {
76+
borderColor: theme.palette.primary.main,
77+
borderWidth: 2,
78+
// Compensate for the border width
79+
top: -1,
80+
left: -1,
81+
},
82+
},
83+
84+
input: {
85+
flexGrow: 1,
86+
fontSize: "inherit",
87+
padding: 0,
88+
border: "none",
89+
background: "none",
90+
91+
"&:focus": {
92+
outline: "none",
93+
},
94+
},
95+
}))

site/src/components/RichParameterInput/RichParameterInput.stories.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const createTemplateVersionParameter = (
3131
validation_max: 0,
3232
validation_monotonic: "increasing",
3333
description_plaintext: "",
34+
required: true,
3435
...partial,
3536
}
3637
}
@@ -99,6 +100,17 @@ OptionsType.args = {
99100
}),
100101
}
101102

103+
export const ListStringType = Template.bind({})
104+
ListStringType.args = {
105+
initialValue: JSON.stringify(["first", "second", "third"]),
106+
id: "list_string_parameter",
107+
parameter: createTemplateVersionParameter({
108+
name: "list_string_parameter",
109+
type: "list(string)",
110+
description: "List string parameter",
111+
}),
112+
}
113+
102114
export const IconLabel = Template.bind({})
103115
IconLabel.args = {
104116
initialValue: "initial-value",

site/src/components/RichParameterInput/RichParameterInput.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { FC, useState } from "react"
88
import { TemplateVersionParameter } from "../../api/typesGenerated"
99
import { colors } from "theme/colors"
1010
import { MemoizedMarkdown } from "components/Markdown/Markdown"
11+
import { MultiTextField } from "components/MultiTextField/MultiTextField"
1112

1213
const isBoolean = (parameter: TemplateVersionParameter) => {
1314
return parameter.type === "bool"
@@ -154,6 +155,34 @@ const RichParameterField: React.FC<RichParameterInputProps> = ({
154155
)
155156
}
156157

158+
if (parameter.type === "list(string)") {
159+
let values: string[] = []
160+
161+
if (parameterValue) {
162+
try {
163+
values = JSON.parse(parameterValue) as string[]
164+
} catch (e) {
165+
console.error("Error parsing list(string) parameter", e)
166+
}
167+
}
168+
169+
return (
170+
<MultiTextField
171+
label={props.label as string}
172+
values={values}
173+
onChange={(values) => {
174+
try {
175+
const value = JSON.stringify(values)
176+
setParameterValue(value)
177+
onChange(value)
178+
} catch (e) {
179+
console.error("Error on change of list(string) parameter", e)
180+
}
181+
}}
182+
/>
183+
)
184+
}
185+
157186
// A text field can technically handle all cases!
158187
// As other cases become more prominent (like filtering for numbers),
159188
// we should break this out into more finely scoped input fields.

site/src/theme/overrides.ts

+5
Original file line numberDiff line numberDiff line change
@@ -229,5 +229,10 @@ export const getOverrides = ({
229229
borderRadius: 999,
230230
},
231231
},
232+
MuiChip: {
233+
root: {
234+
backgroundColor: colors.gray[12],
235+
},
236+
},
232237
}
233238
}

0 commit comments

Comments
 (0)