Skip to content

Commit 9c33a97

Browse files
Adding support for object prop types and improving string inputs that have no options (#16974)
* Adding support for object prop type * Changelog and version rev * Modifying string and string[] inputs if no options * Update CHANGELOG.md * Update CHANGELOG.md * Addressing PR feedback * Linting * Update pnpm-lock.yaml * Linting and PR feedback * Update pnpm-lock.yaml * Update ControlObject.tsx
1 parent 9548295 commit 9c33a97

File tree

9 files changed

+521
-18
lines changed

9 files changed

+521
-18
lines changed

packages/connect-react/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
# Changelog
44

5+
# [1.1.0] - 2025-06-04
6+
7+
- Adding support for 'object' prop types
8+
- Modifying string and string[] inputs to hide the dropdown in the case of no options
9+
- Added basic styling to hyperlinks in prop descriptions
10+
511
# [1.0.2] - 2025-04-24
612

713
- Updating README to remove note about this package being in early preview

packages/connect-react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/connect-react",
3-
"version": "1.0.2",
3+
"version": "1.1.0",
44
"description": "Pipedream Connect library for React",
55
"files": [
66
"dist"

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ 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";
12+
import { ControlObject } from "./ControlObject";
1113
import { ControlSelect } from "./ControlSelect";
1214
import { RemoteOptionsContainer } from "./RemoteOptionsContainer";
1315

@@ -54,6 +56,11 @@ export function Control<T extends ConfigurableProps, U extends ConfigurableProp>
5456
}
5557

5658
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
5764
return <ControlSelect isCreatable={true} options={[]} components={{
5865
IndicatorSeparator: () => null,
5966
}} />;
@@ -73,6 +80,8 @@ export function Control<T extends ConfigurableProps, U extends ConfigurableProp>
7380
case "integer":
7481
// XXX split into ControlString, ControlInteger, etc? but want to share autoComplet="off", etc functionality in base one
7582
return <ControlInput />;
83+
case "object":
84+
return <ControlObject />;
7685
default:
7786
// TODO "not supported prop type should bubble up"
7887
throw new Error("Unsupported property type: " + prop.type);
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import {
2+
useState, useEffect,
3+
} from "react";
4+
import { useFormFieldContext } from "../hooks/form-field-context";
5+
import { useCustomize } from "../hooks/customization-context";
6+
import {
7+
getInputStyles, getButtonStyles, getRemoveButtonStyles, getContainerStyles, getItemStyles,
8+
} from "../styles/control-styles";
9+
10+
export function ControlArray() {
11+
const formFieldContextProps = useFormFieldContext();
12+
const {
13+
onChange, prop, value,
14+
} = formFieldContextProps;
15+
const {
16+
getProps, theme,
17+
} = useCustomize();
18+
19+
// Initialize values from the current value
20+
const initializeValues = (): string[] => {
21+
if (!value || !Array.isArray(value)) {
22+
return [
23+
"",
24+
];
25+
}
26+
27+
const stringValues = value.map((v) => typeof v === "string"
28+
? v
29+
: JSON.stringify(v));
30+
return stringValues.length > 0
31+
? stringValues
32+
: [
33+
"",
34+
];
35+
};
36+
37+
const [
38+
values,
39+
setValues,
40+
] = useState<string[]>(initializeValues);
41+
42+
// Update values when value changes externally
43+
useEffect(() => {
44+
setValues(initializeValues());
45+
}, [
46+
value,
47+
]);
48+
49+
const updateArray = (newValues: string[]) => {
50+
// Filter out empty values
51+
const validValues = newValues.filter((v) => v.trim() !== "");
52+
53+
if (validValues.length === 0) {
54+
onChange(undefined);
55+
return;
56+
}
57+
58+
onChange(validValues as string[]);
59+
};
60+
61+
const handleValueChange = (index: number, newValue: string) => {
62+
const newValues = [
63+
...values,
64+
];
65+
newValues[index] = newValue;
66+
setValues(newValues);
67+
updateArray(newValues);
68+
};
69+
70+
const addValue = () => {
71+
const newValues = [
72+
...values,
73+
"",
74+
];
75+
setValues(newValues);
76+
};
77+
78+
const removeValue = (index: number) => {
79+
const newValues = values.filter((_, i) => i !== index);
80+
setValues(newValues.length > 0
81+
? newValues
82+
: [
83+
"",
84+
]);
85+
updateArray(newValues);
86+
};
87+
88+
const containerStyles = getContainerStyles();
89+
const itemStyles = getItemStyles();
90+
const inputStyles = getInputStyles(theme);
91+
const buttonStyles = getButtonStyles(theme);
92+
const removeButtonStyles = getRemoveButtonStyles(theme);
93+
94+
// Show "Add more" button if the last input has content or if there are multiple inputs
95+
const shouldShowAddMoreButton = values[values.length - 1]?.trim() || values.length > 1;
96+
97+
return (
98+
<div {...getProps("controlArray", containerStyles, formFieldContextProps)}>
99+
{values.map((value, index) => (
100+
<div key={index} style={itemStyles}>
101+
<input
102+
type="text"
103+
value={value}
104+
onChange={(e) => handleValueChange(index, e.target.value)}
105+
placeholder=""
106+
style={inputStyles}
107+
required={!prop.optional && index === 0}
108+
/>
109+
{values.length > 1 && (
110+
<button
111+
type="button"
112+
onClick={() => removeValue(index)}
113+
style={removeButtonStyles}
114+
aria-label="Remove value"
115+
>
116+
×
117+
</button>
118+
)}
119+
</div>
120+
))}
121+
{shouldShowAddMoreButton && (
122+
<button
123+
type="button"
124+
onClick={addValue}
125+
style={{
126+
...buttonStyles,
127+
alignSelf: "flex-start",
128+
paddingRight: `${theme.spacing.baseUnit * 2}px`,
129+
}}
130+
>
131+
<span>+</span>
132+
<span>Add more</span>
133+
</button>
134+
)}
135+
</div>
136+
);
137+
}

0 commit comments

Comments
 (0)