diff --git a/components/bluesnap/bluesnap.app.mjs b/components/bluesnap/bluesnap.app.mjs index 2dcc3eb0bae52..67f5cf5ac3f7a 100644 --- a/components/bluesnap/bluesnap.app.mjs +++ b/components/bluesnap/bluesnap.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/components/grafana/grafana.app.mjs b/components/grafana/grafana.app.mjs index 62ab563c11f68..2f53cbc58cecb 100644 --- a/components/grafana/grafana.app.mjs +++ b/components/grafana/grafana.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/components/polymer_co/polymer_co.app.mjs b/components/polymer_co/polymer_co.app.mjs index 7f014c7a7a7ec..619862a459949 100644 --- a/components/polymer_co/polymer_co.app.mjs +++ b/components/polymer_co/polymer_co.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/components/shutterstock/shutterstock.app.mjs b/components/shutterstock/shutterstock.app.mjs index cc72c74b01671..b5f44e95591fb 100644 --- a/components/shutterstock/shutterstock.app.mjs +++ b/components/shutterstock/shutterstock.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/packages/connect-react/examples/nextjs/package-lock.json b/packages/connect-react/examples/nextjs/package-lock.json index c5246f24c095f..26d7a03f4680b 100644 --- a/packages/connect-react/examples/nextjs/package-lock.json +++ b/packages/connect-react/examples/nextjs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@pipedream/connect-react": "file:../..", - "@pipedream/sdk": "^1.0.6", + "@pipedream/sdk": "^1.1.4", "next": "15.0.3", "react": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106" @@ -23,7 +23,7 @@ }, "../..": { "name": "@pipedream/connect-react", - "version": "1.0.0-preview.10", + "version": "1.0.0-preview.19", "license": "MIT", "dependencies": { "@pipedream/sdk": "workspace:^", @@ -566,9 +566,10 @@ "link": true }, "node_modules/@pipedream/sdk": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@pipedream/sdk/-/sdk-1.0.6.tgz", - "integrity": "sha512-SIhe8JOA47Uypa2OfhEpyDpyCNi2oYpavnUVVwLuqe4jLbYQEiOWjnSdJ6wfDxKwA8QT3MRbEEJ/+uVxYHMeGA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@pipedream/sdk/-/sdk-1.1.4.tgz", + "integrity": "sha512-Xjozgk/uhUyz+3h8OHXNUTaEDik1Y4kzDPmlfjeHqimwyLmY+QuRVsQCqvs4VZBSCt4fhzQt197O1aqFE+5E2g==", + "license": "SEE LICENSE IN LICENSE", "dependencies": { "@rails/actioncable": "^8.0.0", "commander": "^12.1.0", diff --git a/packages/connect-react/examples/nextjs/package.json b/packages/connect-react/examples/nextjs/package.json index a35c8049a9028..02aa363bb7538 100644 --- a/packages/connect-react/examples/nextjs/package.json +++ b/packages/connect-react/examples/nextjs/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "@pipedream/connect-react": "file:../..", - "@pipedream/sdk": "^1.0.6", + "@pipedream/sdk": "^1.1.4", "next": "15.0.3", "react": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106" diff --git a/packages/connect-react/package.json b/packages/connect-react/package.json index 85d5b14a7749d..4c3e17869e8a0 100644 --- a/packages/connect-react/package.json +++ b/packages/connect-react/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/connect-react", - "version": "1.0.0-preview.18", + "version": "1.0.0-preview.19", "description": "Pipedream Connect library for React", "files": [ "dist" diff --git a/packages/connect-react/src/components/ControlSelect.tsx b/packages/connect-react/src/components/ControlSelect.tsx index 1a191c3bcdd65..6c5ad7bda6ce5 100644 --- a/packages/connect-react/src/components/ControlSelect.tsx +++ b/packages/connect-react/src/components/ControlSelect.tsx @@ -1,20 +1,25 @@ import { useMemo } from "react"; -import Select, { Props as ReactSelectProps } from "react-select"; +import Select, { + Props as ReactSelectProps, components, +} from "react-select"; import type { CSSObjectWithLabel } from "react-select"; import CreatableSelect from "react-select/creatable"; import { useFormFieldContext } from "../hooks/form-field-context"; import { useCustomize } from "../hooks/customization-context"; import type { BaseReactSelectProps } from "../hooks/customization-context"; +import { LoadMoreButton } from "./LoadMoreButton"; // XXX T and ConfigurableProp should be related type ControlSelectProps = { isCreatable?: boolean; options: {label: string; value: T;}[]; selectProps?: ReactSelectProps; + showLoadMoreButton?: boolean; + onLoadMore?: () => void; }; export function ControlSelect({ - isCreatable, options, selectProps, + isCreatable, options, selectProps, showLoadMoreButton, onLoadMore, }: ControlSelectProps) { const formFieldCtx = useFormFieldContext(); const { @@ -24,7 +29,7 @@ export function ControlSelect({ select, theme, } = useCustomize(); - const baseSelectProps: BaseReactSelectProps = { + const baseSelectProps: BaseReactSelectProps = { styles: { container: (base): CSSObjectWithLabel => ({ ...base, @@ -69,6 +74,28 @@ export function ControlSelect({ options, ]); + const LoadMore = ({ + // eslint-disable-next-line react/prop-types + children, ...props + }) => { + return ( + + { children } +
+ +
+
+ ) + } + + const props = select.getProps("controlSelect", baseSelectProps) + if (showLoadMoreButton) { + props.components = { + // eslint-disable-next-line react/prop-types + ...props.components, + MenuList: LoadMore, + } + } const MaybeCreatableSelect = isCreatable ? CreatableSelect : Select; @@ -81,7 +108,7 @@ export function ControlSelect({ isMulti={prop.type.endsWith("[]")} isClearable={true} required={!prop.optional} - {...select.getProps("controlSelect", baseSelectProps)} + {...props} {...selectProps} onChange={(o) => { if (o) { @@ -101,7 +128,9 @@ export function ControlSelect({ } } else if (typeof o === "object" && "value" in o) { if (prop.withLabel) { - onChange({__lv: o}); + onChange({ + __lv: o, + }); } else { onChange(o.value); } diff --git a/packages/connect-react/src/components/LoadMoreButton.tsx b/packages/connect-react/src/components/LoadMoreButton.tsx new file mode 100644 index 0000000000000..95747f78a5c01 --- /dev/null +++ b/packages/connect-react/src/components/LoadMoreButton.tsx @@ -0,0 +1,33 @@ +import { useCustomize } from "../hooks/customization-context"; +import type { CSSProperties } from "react"; + +export type ButtonProps = { + onChange: () => void; +}; + +export const LoadMoreButton = (props: ButtonProps) => { + const { onChange } = props; + const { + getProps, theme, + } = useCustomize(); + + const baseStyles: CSSProperties = { + backgroundColor: theme.colors.primary, + borderRadius: theme.borderRadius, + border: "solid 1px", + borderColor: theme.colors.primary25, + color: theme.colors.primary25, + padding: "0.5rem", + fontSize: "0.8125rem", + fontWeight: "450", + gridArea: "control", + cursor: "pointer", + width: "100%", + }; + + return ( + + ); +}; diff --git a/packages/connect-react/src/components/RemoteOptionsContainer.tsx b/packages/connect-react/src/components/RemoteOptionsContainer.tsx index 50b29f96a5bd8..56dee321fab1f 100644 --- a/packages/connect-react/src/components/RemoteOptionsContainer.tsx +++ b/packages/connect-react/src/components/RemoteOptionsContainer.tsx @@ -29,6 +29,31 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP setQuery, ] = useState(""); + const [ + page, + setPage, + ] = useState(0); + + const [ + canLoadMore, + setCanLoadMore, + ] = useState(true); + + const [ + context, + setContext, + ] = useState(undefined); + + const [ + pageable, + setPageable, + ] = useState({ + page: 0, + prevContext: {}, + data: [], + values: new Set(), + }) + const configuredPropsUpTo: Record = {}; for (let i = 0; i < idx; i++) { const prop = configurableProps[i]; @@ -36,6 +61,8 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP } const componentConfigureInput: ComponentConfigureOpts = { userId, + page, + prevContext: context, componentId: component.key, propName: prop.name, configuredProps: configuredPropsUpTo, @@ -55,9 +82,18 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP setError, ] = useState<{ name: string; message: string; }>(); + const onLoadMore = () => { + setPage(pageable.page) + setContext(pageable.prevContext) + setPageable({ + ...pageable, + prevContext: {}, + }) + } + // TODO handle error! const { - isFetching, data, refetch, + isFetching, refetch, } = useQuery({ queryKey: [ "componentConfigure", @@ -67,11 +103,11 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP setError(undefined); const res = await client.componentConfigure(componentConfigureInput); - // console.log("res", res) // XXX look at errors in response here too const { options, stringOptions, errors, } = res; + if (errors?.length) { // TODO field context setError? (for validity, etc.) try { @@ -84,8 +120,9 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP } return []; } + let _options = [] if (options?.length) { - return options; + _options = options; } if (stringOptions?.length) { const options = []; @@ -95,13 +132,45 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP value: stringOption, }); } - return options; + _options = options; + } + + const newOptions = [] + const allValues = new Set(pageable.values) + for (const o of _options || []) { + const value = typeof o === "string" + ? o + : o.value + if (allValues.has(value)) { + continue + } + allValues.add(value) + newOptions.push(o) + } + let data = pageable.data + if (newOptions.length) { + data = [ + ...pageable.data, + ...newOptions, + ] + setPageable({ + page: page + 1, + prevContext: res.context, + data, + values: allValues, + }) + } else { + setCanLoadMore(false) } - return []; + return data; }, enabled: !!queryEnabled, }); + const showLoadMoreButton = () => { + return !isFetching && !error && canLoadMore + } + // TODO show error in different spot! const placeholder = error ? error.message @@ -116,7 +185,9 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP return ( ; optionalFields: ComponentProps; optionalFieldButton: ComponentProps; + loadMoreButton: ComponentProps; }; export type CustomClassNamesFn = ((opts: CustomizationOpts) => string); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca391384a140c..591259caf88fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9592,8 +9592,7 @@ importers: components/showpad: {} - components/shutterstock: - specifiers: {} + components/shutterstock: {} components/sidetracker: {} @@ -12551,8 +12550,8 @@ importers: specifier: file:../.. version: file:packages/connect-react(@types/react@18.3.12)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@pipedream/sdk': - specifier: ^1.0.6 - version: 1.0.7 + specifier: ^1.1.4 + version: 1.1.5 next: specifier: 15.0.3 version: 15.0.3(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) @@ -15224,8 +15223,8 @@ packages: resolution: {integrity: sha512-f8FXEaoBqIOQpI4vVOO8OrHRAjwQYO4pKIwMUcGRkzS5KJ6cZ/7JYYGyXEu7NcS+y1VfyegtbJWu4tWacDjQZg==} engines: {node: '>=18.0.0'} - '@pipedream/sdk@1.0.7': - resolution: {integrity: sha512-ec3XowyXvy6KPxlr0/roOgv1Y2K3PQXJKYm1dc5ezewxImMon/FdHrxLOTIaqq2CGdTG/PG60EIR1Jm9LTOQlQ==} + '@pipedream/sdk@1.1.5': + resolution: {integrity: sha512-q+dGMsNSvIcDCi6JZES7pwNBW8Ms318WMNavO71Cye0bCtloeDSj5TiFbScy64/2FHja9keKAZ9ev8yVSsM+ew==} engines: {node: '>=18.0.0'} '@pipedream/sftp@0.4.1': @@ -30625,7 +30624,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@pipedream/sdk@1.0.7': + '@pipedream/sdk@1.1.5': dependencies: '@rails/actioncable': 8.0.0 commander: 12.1.0 @@ -31013,6 +31012,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: