Skip to content

Commit ef1e223

Browse files
Modifying string and string[] inputs if no options
1 parent b1653be commit ef1e223

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

packages/connect-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# [1.1.0] - 2025-06-04
66

77
- Adding support for 'object' prop types
8+
- Modifying string and string[] inputs to hide the dropdown in the case of no options
89

910
# [1.0.2] - 2025-04-24
1011

packages/connect-react/src/components/Control.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "@pipedream/sdk";
77
// import { ControlAny } from "./ControlAny"
88
import { ControlApp } from "./ControlApp";
9+
import { ControlArray } from "./ControlArray";
910
import { ControlBoolean } from "./ControlBoolean";
1011
import { ControlInput } from "./ControlInput";
1112
import { ControlObject } from "./ControlObject";
@@ -55,6 +56,11 @@ export function Control<T extends ConfigurableProps, U extends ConfigurableProp>
5556
}
5657

5758
if (prop.type.endsWith("[]")) {
59+
// If no options are defined, use individual inputs with "Add more" functionality
60+
if (!("options" in prop) || !prop.options) {
61+
return <ControlArray />;
62+
}
63+
// If options are defined, they would have been handled above in the options check
5864
return <ControlSelect isCreatable={true} options={[]} components={{
5965
IndicatorSeparator: () => null,
6066
}} />;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { useState, useEffect, type CSSProperties } from "react";
2+
import { useFormFieldContext } from "../hooks/form-field-context";
3+
import { useCustomize } from "../hooks/customization-context";
4+
5+
export function ControlArray() {
6+
const formFieldContextProps = useFormFieldContext();
7+
const {
8+
id, onChange, prop, value,
9+
} = formFieldContextProps;
10+
const {
11+
getProps, theme,
12+
} = useCustomize();
13+
14+
// Initialize values from the current value
15+
const initializeValues = (): string[] => {
16+
if (!value || !Array.isArray(value)) {
17+
return [""];
18+
}
19+
20+
const stringValues = value.map(v => typeof v === "string" ? v : JSON.stringify(v));
21+
return stringValues.length > 0 ? stringValues : [""];
22+
};
23+
24+
const [values, setValues] = useState<string[]>(initializeValues);
25+
26+
// Update values when value changes externally
27+
useEffect(() => {
28+
setValues(initializeValues());
29+
}, [value]);
30+
31+
const updateArray = (newValues: string[]) => {
32+
// Filter out empty values
33+
const validValues = newValues.filter(v => v.trim() !== "");
34+
35+
if (validValues.length === 0) {
36+
onChange(undefined);
37+
return;
38+
}
39+
40+
onChange(validValues);
41+
};
42+
43+
const handleValueChange = (index: number, newValue: string) => {
44+
const newValues = [...values];
45+
newValues[index] = newValue;
46+
setValues(newValues);
47+
updateArray(newValues);
48+
};
49+
50+
const addValue = () => {
51+
const newValues = [...values, ""];
52+
setValues(newValues);
53+
};
54+
55+
const removeValue = (index: number) => {
56+
const newValues = values.filter((_, i) => i !== index);
57+
setValues(newValues.length > 0 ? newValues : [""]);
58+
updateArray(newValues);
59+
};
60+
61+
const containerStyles: CSSProperties = {
62+
gridArea: "control",
63+
display: "flex",
64+
flexDirection: "column",
65+
gap: "0.5rem",
66+
};
67+
68+
const itemStyles: CSSProperties = {
69+
display: "flex",
70+
gap: "0.5rem",
71+
alignItems: "center",
72+
};
73+
74+
const inputStyles: CSSProperties = {
75+
color: theme.colors.neutral60,
76+
border: "1px solid",
77+
borderColor: theme.colors.neutral20,
78+
padding: 6,
79+
borderRadius: theme.borderRadius,
80+
boxShadow: theme.boxShadow.input,
81+
flex: 1,
82+
};
83+
84+
const buttonStyles: CSSProperties = {
85+
color: theme.colors.neutral60,
86+
display: "inline-flex",
87+
alignItems: "center",
88+
padding: `${theme.spacing.baseUnit}px ${theme.spacing.baseUnit * 1.5}px ${
89+
theme.spacing.baseUnit
90+
}px ${theme.spacing.baseUnit * 2.5}px`,
91+
border: `1px solid ${theme.colors.neutral30}`,
92+
borderRadius: theme.borderRadius,
93+
cursor: "pointer",
94+
fontSize: "0.8125rem",
95+
fontWeight: 450,
96+
gap: theme.spacing.baseUnit * 2,
97+
textWrap: "nowrap",
98+
backgroundColor: "white",
99+
};
100+
101+
const removeButtonStyles: CSSProperties = {
102+
...buttonStyles,
103+
flex: "0 0 auto",
104+
padding: "6px 8px",
105+
};
106+
107+
return (
108+
<div {...getProps("controlArray", containerStyles, formFieldContextProps)}>
109+
{values.map((value, index) => (
110+
<div key={index} style={itemStyles}>
111+
<input
112+
type="text"
113+
value={value}
114+
onChange={(e) => handleValueChange(index, e.target.value)}
115+
placeholder=""
116+
style={inputStyles}
117+
required={!prop.optional && index === 0}
118+
/>
119+
{values.length > 1 && (
120+
<button
121+
type="button"
122+
onClick={() => removeValue(index)}
123+
style={removeButtonStyles}
124+
aria-label="Remove value"
125+
>
126+
×
127+
</button>
128+
)}
129+
</div>
130+
))}
131+
{(values[values.length - 1]?.trim() || values.length > 1) && (
132+
<button
133+
type="button"
134+
onClick={addValue}
135+
style={{
136+
...buttonStyles,
137+
alignSelf: "flex-start",
138+
paddingRight: `${theme.spacing.baseUnit * 2}px`,
139+
}}
140+
>
141+
<span>+</span>
142+
<span>Add more</span>
143+
</button>
144+
)}
145+
</div>
146+
);
147+
}

packages/connect-react/src/hooks/customization-context.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type { FormFieldContext } from "./form-field-context";
2424
import { ComponentForm } from "../components/ComponentForm";
2525
import { ControlAny } from "../components/ControlAny";
2626
import { ControlApp } from "../components/ControlApp";
27+
import { ControlArray } from "../components/ControlArray";
2728
import { ControlBoolean } from "../components/ControlBoolean";
2829
import { ControlInput } from "../components/ControlInput";
2930
import { ControlObject } from "../components/ControlObject";
@@ -68,6 +69,7 @@ export type CustomizableProps = {
6869
connectButton: ComponentProps<typeof ControlApp> & FormFieldContext<ConfigurableProp>;
6970
controlAny: ComponentProps<typeof ControlAny> & FormFieldContext<ConfigurableProp>;
7071
controlApp: ComponentProps<typeof ControlApp> & FormFieldContext<ConfigurableProp>;
72+
controlArray: ComponentProps<typeof ControlArray> & FormFieldContext<ConfigurableProp>;
7173
controlBoolean: ComponentProps<typeof ControlBoolean> & FormFieldContext<ConfigurableProp>;
7274
controlInput: ComponentProps<typeof ControlInput> & FormFieldContext<ConfigurableProp>;
7375
controlObject: ComponentProps<typeof ControlObject> & FormFieldContext<ConfigurableProp>;

0 commit comments

Comments
 (0)