diff --git a/web/src/components/dynamic-form.tsx b/web/src/components/dynamic-form.tsx index 0920e2422e..107109f922 100644 --- a/web/src/components/dynamic-form.tsx +++ b/web/src/components/dynamic-form.tsx @@ -69,6 +69,7 @@ export interface FormFieldConfig { required?: boolean; placeholder?: string; options?: { label: string; value: string }[]; + allowCustomValue?: boolean; defaultValue?: any; validation?: { pattern?: RegExp; @@ -456,6 +457,7 @@ export const RenderField = ({ triggerClassName="!shrink" {...finalFieldProps} options={field.options} + allowCustomValue={field.allowCustomValue} disabled={field.disabled} /> ); diff --git a/web/src/components/originui/select-with-search.tsx b/web/src/components/originui/select-with-search.tsx index 93f62146ae..79b8ceb44a 100644 --- a/web/src/components/originui/select-with-search.tsx +++ b/web/src/components/originui/select-with-search.tsx @@ -47,6 +47,7 @@ export type SelectWithSearchFlagProps = { disabled?: boolean; placeholder?: string; emptyData?: string; + allowCustomValue?: boolean; testId?: string; optionTestIdPrefix?: string; }; @@ -88,6 +89,7 @@ export const SelectWithSearch = forwardRef< disabled = false, placeholder = t('common.selectPlaceholder'), emptyData = t('common.noDataFound'), + allowCustomValue = false, testId, optionTestIdPrefix, }, @@ -96,6 +98,7 @@ export const SelectWithSearch = forwardRef< const id = useId(); const [open, setOpen] = useState(false); const [value, setValue] = useState(''); + const [searchValue, setSearchValue] = useState(''); const selectLabel = useMemo(() => { if (options.every((x) => x.options === undefined)) { @@ -120,6 +123,9 @@ export const SelectWithSearch = forwardRef< }, [options, value]); const showSearch = useMemo(() => { + if (allowCustomValue) { + return true; + } if (Array.isArray(options) && options.length > 5) { return true; } @@ -130,7 +136,21 @@ export const SelectWithSearch = forwardRef< return optionsNum > 5; } return false; - }, [options]); + }, [allowCustomValue, options]); + + const hasCustomSearchValue = useMemo(() => { + const customValue = searchValue.trim(); + if (!allowCustomValue || !customValue) { + return false; + } + + const values = options.flatMap((option) => + option.options + ? option.options.map((item) => item.value) + : option.value, + ); + return !values.includes(customValue); + }, [allowCustomValue, options, searchValue]); const handleSelect = useCallback( (val: string) => { @@ -207,12 +227,23 @@ export const SelectWithSearch = forwardRef< )}
+ {hasCustomSearchValue && ( + + {searchValue.trim()} + + )} {options.map((group, groupIndex) => { if (group.options) { return ( diff --git a/web/src/pages/user-setting/data-source/constant/s3-constant.tsx b/web/src/pages/user-setting/data-source/constant/s3-constant.tsx index 96bcb3ea84..40223f93a4 100644 --- a/web/src/pages/user-setting/data-source/constant/s3-constant.tsx +++ b/web/src/pages/user-setting/data-source/constant/s3-constant.tsx @@ -19,6 +19,7 @@ export const S3Constant = (t: TFunction) => [ type: FormFieldType.Select, required: false, options: awsRegionOptions, + allowCustomValue: true, customValidate: (val: string, formValues: any) => { const credentials = formValues?.config?.credentials || {}; const bucketType = formValues?.config?.bucket_type || 's3';