Skip to content
Merged
11 changes: 6 additions & 5 deletions packages/connect-react/examples/nextjs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/connect-react/examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion packages/connect-react/package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
40 changes: 35 additions & 5 deletions packages/connect-react/src/components/ControlSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import * as React from "react";
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<T> = {
isCreatable?: boolean;
options: {label: string; value: T;}[];
selectProps?: ReactSelectProps;
showLoadMoreButton?: boolean;
onLoadMore?: () => void;
};

export function ControlSelect<T>({
isCreatable, options, selectProps,
isCreatable, options, selectProps, showLoadMoreButton, onLoadMore,
}: ControlSelectProps<T>) {
const formFieldCtx = useFormFieldContext();
const {
Expand All @@ -24,7 +30,7 @@ export function ControlSelect<T>({
select, theme,
} = useCustomize();

const baseSelectProps: BaseReactSelectProps<any, any, any> = {
const baseSelectProps: BaseReactSelectProps<never, never, never> = {
styles: {
container: (base): CSSObjectWithLabel => ({
...base,
Expand Down Expand Up @@ -69,6 +75,28 @@ export function ControlSelect<T>({
options,
]);

const LoadMore = ({
// eslint-disable-next-line react/prop-types
children, ...props
}) => {
return (
<components.MenuList {...props}>
{ children }
<div className="pt-4">
<LoadMoreButton onChange={onLoadMore}/>
</div>
</components.MenuList>
)
}

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;
Expand All @@ -81,7 +109,7 @@ export function ControlSelect<T>({
isMulti={prop.type.endsWith("[]")}
isClearable={true}
required={!prop.optional}
{...select.getProps("controlSelect", baseSelectProps)}
{...props}
{...selectProps}
onChange={(o) => {
if (o) {
Expand All @@ -101,7 +129,9 @@ export function ControlSelect<T>({
}
} else if (typeof o === "object" && "value" in o) {
if (prop.withLabel) {
onChange({__lv: o});
onChange({
__lv: o,
});
} else {
onChange(o.value);
}
Expand Down
33 changes: 33 additions & 0 deletions packages/connect-react/src/components/LoadMoreButton.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<button onClick={onChange} type="button" {...getProps("loadMoreButton", baseStyles, props)}>
Load More
</button>
);
};
83 changes: 77 additions & 6 deletions packages/connect-react/src/components/RemoteOptionsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,40 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
setQuery,
] = useState("");

const [
page,
setPage,
] = useState<number>(0);

const [
canLoadMore,
setCanLoadMore,
] = useState<boolean>(true);

const [
context,
setContext,
] = useState<never | undefined>(undefined);

const [
pageable,
setPageable,
] = useState({
page: 0,
prevContext: {},
data: [],
values: new Set(),
})

const configuredPropsUpTo: Record<string, unknown> = {};
for (let i = 0; i < idx; i++) {
const prop = configurableProps[i];
configuredPropsUpTo[prop.name] = configuredProps[prop.name];
}
const componentConfigureInput: ComponentConfigureOpts = {
userId,
page,
prevContext: context,
componentId: component.key,
propName: prop.name,
configuredProps: configuredPropsUpTo,
Expand All @@ -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",
Expand All @@ -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 {
Expand All @@ -84,8 +120,9 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP
}
return [];
}
let _options = []
if (options?.length) {
return options;
_options = options;
}
if (stringOptions?.length) {
const options = [];
Expand All @@ -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
Expand All @@ -116,7 +185,9 @@ export function RemoteOptionsContainer({ queryEnabled }: RemoteOptionsContainerP

return (
<ControlSelect
options={data || []}
showLoadMoreButton={showLoadMoreButton()}
onLoadMore={onLoadMore}
options={pageable.data}
// XXX isSearchable if pageQuery? or maybe in all cases? or maybe NOT when pageQuery
selectProps={{
isLoading: isFetching,
Expand Down
3 changes: 3 additions & 0 deletions packages/connect-react/src/hooks/customization-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ import { Errors } from "../components/Errors";
import { Field } from "../components/Field";
import { Label } from "../components/Label";
import { OptionalFieldButton } from "../components/OptionalFieldButton";
import { LoadMoreButton } from "../components/LoadMoreButton";

export const defaultComponents = {
Description,
Errors,
Label,
OptionalFieldButton,
Button: LoadMoreButton,
};

export type ReactSelectComponents = {
Expand Down Expand Up @@ -76,6 +78,7 @@ export type CustomizableProps = {
label: ComponentProps<typeof Label>;
optionalFields: ComponentProps<typeof ComponentForm>;
optionalFieldButton: ComponentProps<typeof OptionalFieldButton>;
loadMoreButton: ComponentProps<typeof LoadMoreButton>;
};

export type CustomClassNamesFn<K extends keyof CustomizableProps> = ((opts: CustomizationOpts<CustomizableProps[K]>) => string);
Expand Down
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading