Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/modules/integration-picker/components/IntegrationFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
FlexJustify,
Form,
Input,
MultiSelect,
type MultiSelectOption,
Padded,
Spacer,
TextArea,
Expand Down Expand Up @@ -198,9 +200,70 @@ const FieldRenderer: React.FC<FieldRendererProps> = ({
);
}

if (field.type === 'multi-select') {
return (
<MultiSelectField field={field} fieldKey={key} errors={errors} setValue={setValue} />
);
}

return null;
};

/** Multi-select field using Malachite MultiSelect with defaultValue (uncontrolled mode) */
const MultiSelectField: React.FC<{
field: ConnectorConfigField;
fieldKey: string;
errors: FieldErrors;
setValue: UseFormSetValue<Record<string, unknown>>;
}> = ({ field, fieldKey, errors, setValue }) => {
const delimiter = field.delimiter?.trim() || ' ';
const initialValue = field.value?.toString() || '';
const defaultValues = initialValue
? initialValue
.split(delimiter)
.map((v) => v.trim())
.filter(Boolean)
: [];

const multiSelectOptions: MultiSelectOption[] = (field.options ?? []).map((opt) => ({
value: opt.value,
label: opt.label,
}));

const errorMessage = errors[fieldKey] && (
<Typography.Text color="error" style={{ fontSize: '12px' }}>
{errors[fieldKey]?.message as string}
</Typography.Text>
);

return (
<>
<MultiSelect
options={multiSelectOptions}
defaultValue={defaultValues}
onChange={(selected: string[]) => {
setValue(fieldKey, selected.join(delimiter), {
shouldValidate: true,
});
}}
placeholder={field.placeholder || 'Select...'}
size="medium"
error={!!errors[fieldKey]}
name={fieldKey}
label={field.label}
description={field.guide?.description ?? field.description}
tooltip={field.guide?.tooltip ?? field.tooltip}
required={field.required}
disabled={field.readOnly}
chips
chipsMax={4}
inlineSearch
/>
{errorMessage}
</>
);
};

const ErrorBlock = ({ error }: { error?: { message: string; provider_response: string } }) => {
if (!error) {
return null;
Expand Down
5 changes: 4 additions & 1 deletion src/modules/integration-picker/hooks/useIntegrationPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,11 @@ export const useIntegrationPicker = ({
});
}

const multiSelectKeys = new Set(
fields.filter((f) => f.type === 'multi-select').map((f) => f.key),
);
Object.keys(cleanedFormData).forEach((key) => {
if (cleanedFormData[key] === '') {
if (cleanedFormData[key] === '' && !multiSelectKeys.has(key)) {
delete cleanedFormData[key];
}
});
Expand Down
4 changes: 3 additions & 1 deletion src/modules/integration-picker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface HubData {
}

export interface ConnectorConfigField {
type?: 'text' | 'password' | 'number' | 'select' | 'text_area';
type?: 'text' | 'password' | 'number' | 'select' | 'multi-select' | 'text_area';
label: string;
key: string;
required: boolean;
Expand All @@ -43,6 +43,8 @@ export interface ConnectorConfigField {
error?: string;
};
display?: boolean;
/** Delimiter for joining multi-select values. Defaults to space. */
delimiter?: string;
}

export interface LegacyConnectorConfig {
Expand Down
Loading