From 11c52e90f6bbfdf279824e4bea28215402e29714 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:15:08 +0800 Subject: [PATCH] refactor(web/select): base selects to dify-ui (#35720) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 31 +- .../src/select/__tests__/index.spec.tsx | 6 + packages/dify-ui/src/select/index.tsx | 2 +- .../__tests__/dropdown-callbacks.spec.tsx | 8 +- .../app-sidebar/dataset-info/dropdown.tsx | 39 +- .../base/__tests__/base-field.spec.tsx | 60 +- .../base/form/components/base/base-field.tsx | 185 +++- .../field/__tests__/custom-select.spec.tsx | 49 - .../field/__tests__/select.spec.tsx | 28 +- .../form/components/field/custom-select.tsx | 41 - .../__tests__/index.spec.tsx | 14 +- .../__tests__/trigger.spec.tsx | 18 +- .../field/input-type-select/index.tsx | 61 +- .../field/input-type-select/trigger.tsx | 36 +- .../base/form/components/field/select.tsx | 84 +- .../base/form/form-scenarios/base/types.ts | 2 +- web/app/components/base/form/index.tsx | 2 - web/app/components/base/infotip/index.tsx | 11 +- .../base/portal-to-follow-elem/index.tsx | 2 +- .../base/select/__tests__/custom.spec.tsx | 124 --- .../base/select/__tests__/index.spec.tsx | 957 ------------------ .../select/__tests__/locale-signin.spec.tsx | 116 --- .../base/select/__tests__/pure.spec.tsx | 197 ---- web/app/components/base/select/custom.tsx | 171 ---- .../components/base/select/index.stories.tsx | 572 ----------- web/app/components/base/select/index.tsx | 441 -------- .../components/base/select/locale-signin.tsx | 64 -- web/app/components/base/select/locale.tsx | 64 -- web/app/components/base/select/pure.tsx | 207 ---- .../__tests__/usage-priority-section.spec.tsx | 22 +- .../usage-priority-section.tsx | 39 +- .../workflow/block-selector/main.tsx | 31 +- .../variable/var-reference-picker.trigger.tsx | 2 + .../components/parameter-table.tsx | 2 +- web/app/signin/__tests__/_header.spec.tsx | 57 ++ .../__tests__/_locale-menu.spec.tsx} | 34 +- web/app/signin/_header.tsx | 7 +- web/app/signin/_locale-menu.tsx | 73 ++ web/docs/overlay-migration.md | 1 - web/eslint.constants.mjs | 12 - 40 files changed, 626 insertions(+), 3246 deletions(-) delete mode 100644 web/app/components/base/form/components/field/__tests__/custom-select.spec.tsx delete mode 100644 web/app/components/base/form/components/field/custom-select.tsx delete mode 100644 web/app/components/base/select/__tests__/custom.spec.tsx delete mode 100644 web/app/components/base/select/__tests__/index.spec.tsx delete mode 100644 web/app/components/base/select/__tests__/locale-signin.spec.tsx delete mode 100644 web/app/components/base/select/__tests__/pure.spec.tsx delete mode 100644 web/app/components/base/select/custom.tsx delete mode 100644 web/app/components/base/select/index.stories.tsx delete mode 100644 web/app/components/base/select/index.tsx delete mode 100644 web/app/components/base/select/locale-signin.tsx delete mode 100644 web/app/components/base/select/locale.tsx delete mode 100644 web/app/components/base/select/pure.tsx create mode 100644 web/app/signin/__tests__/_header.spec.tsx rename web/app/{components/base/select/__tests__/locale.spec.tsx => signin/__tests__/_locale-menu.spec.tsx} (74%) create mode 100644 web/app/signin/_locale-menu.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 183560f81b..7b24f216aa 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -202,11 +202,6 @@ "count": 1 } }, - "web/app/components/app-sidebar/dataset-info/dropdown.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/app-sidebar/index.tsx": { "ts/no-explicit-any": { "count": 1 @@ -1158,7 +1153,7 @@ }, "web/app/components/base/form/components/base/base-field.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 }, "ts/no-explicit-any": { "count": 3 @@ -1909,25 +1904,6 @@ "count": 1 } }, - "web/app/components/base/select/index.stories.tsx": { - "no-console": { - "count": 4 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, - "web/app/components/base/select/index.tsx": { - "react/set-state-in-effect": { - "count": 2 - }, - "style/multiline-ternary": { - "count": 2 - }, - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/base/sort/index.tsx": { "ts/no-explicit-any": { "count": 2 @@ -5432,11 +5408,6 @@ "count": 1 } }, - "web/app/signin/_header.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/signin/components/mail-and-password-auth.tsx": { "ts/no-explicit-any": { "count": 1 diff --git a/packages/dify-ui/src/select/__tests__/index.spec.tsx b/packages/dify-ui/src/select/__tests__/index.spec.tsx index f2f3221eda..ac8be5917e 100644 --- a/packages/dify-ui/src/select/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/select/__tests__/index.spec.tsx @@ -170,6 +170,12 @@ describe('Select wrappers', () => { expect(screen.getByRole('combobox', { name: 'city select' }).element().querySelector('.i-ri-arrow-down-s-line')).toBeInTheDocument() }) + + it('should include open state feedback classes', async () => { + const screen = await renderOpenSelect() + + expect(screen.getByRole('combobox', { name: 'city select' }).element().className).toContain('data-open:bg-state-base-hover-alt') + }) }) describe('SelectContent', () => { diff --git a/packages/dify-ui/src/select/index.tsx b/packages/dify-ui/src/select/index.tsx index 017093c584..2f2f91d9c6 100644 --- a/packages/dify-ui/src/select/index.tsx +++ b/packages/dify-ui/src/select/index.tsx @@ -21,7 +21,7 @@ export const SelectGroup = BaseSelect.Group const selectTriggerVariants = cva( [ 'group flex w-full items-center border-0 bg-components-input-bg-normal text-left text-components-input-text-filled outline-hidden', - 'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt', + 'hover:bg-state-base-hover-alt focus-visible:bg-state-base-hover-alt data-open:bg-state-base-hover-alt', 'data-placeholder:text-components-input-text-placeholder', 'data-readonly:cursor-default data-readonly:bg-transparent data-readonly:hover:bg-transparent', 'data-disabled:cursor-not-allowed data-disabled:bg-components-input-bg-disabled data-disabled:text-components-input-text-filled-disabled data-disabled:hover:bg-components-input-bg-disabled', diff --git a/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx b/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx index ceb8302ee6..05a06f2f77 100644 --- a/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx +++ b/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx @@ -113,7 +113,9 @@ vi.mock('@/service/datasets', () => ({ })) vi.mock('@langgenius/dify-ui/toast', () => ({ - toast: (...args: unknown[]) => mockToast(...args), + toast: { + error: (...args: unknown[]) => mockToast(...args), + }, })) vi.mock('@/app/components/datasets/rename-modal', () => ({ @@ -220,7 +222,7 @@ describe('Dropdown callback coverage', () => { await user.click(screen.getByText('datasetPipeline.operations.exportPipeline')) await waitFor(() => { - expect(mockToast).toHaveBeenCalledWith('app.exportFailed', { type: 'error' }) + expect(mockToast).toHaveBeenCalledWith('app.exportFailed') }) }) @@ -257,7 +259,7 @@ describe('Dropdown callback coverage', () => { await user.click(screen.getByText('common.operation.delete')) await waitFor(() => { - expect(mockToast).toHaveBeenCalledWith('check failed', { type: 'error' }) + expect(mockToast).toHaveBeenCalledWith('check failed') }) expect(screen.queryByText('dataset.deleteDatasetConfirmTitle')).not.toBeInTheDocument() }) diff --git a/web/app/components/app-sidebar/dataset-info/dropdown.tsx b/web/app/components/app-sidebar/dataset-info/dropdown.tsx index 8f3a25738a..fa5a40f8a4 100644 --- a/web/app/components/app-sidebar/dataset-info/dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-info/dropdown.tsx @@ -34,6 +34,25 @@ type DropDownProps = { expand: boolean } +type JsonErrorResponse = { + json: () => Promise<{ message?: string }> +} + +const isJsonErrorResponse = (error: unknown): error is JsonErrorResponse => { + return typeof error === 'object' + && error !== null + && 'json' in error + && typeof error.json === 'function' +} + +const getErrorMessage = async (error: unknown) => { + if (!isJsonErrorResponse(error)) + return 'Unknown error' + + const res = await error.json() + return res?.message || 'Unknown error' +} + const DropDown = ({ expand, }: DropDownProps) => { @@ -78,7 +97,7 @@ const DropDown = ({ downloadBlob({ data: file, fileName: `${name}.pipeline` }) } catch { - toast(t('exportFailed', { ns: 'app' }), { type: 'error' }) + toast.error(t('exportFailed', { ns: 'app' })) } }, [dataset, exportPipelineConfig, t]) @@ -89,9 +108,8 @@ const DropDown = ({ setConfirmMessage(isUsedByApp ? t('datasetUsedByApp', { ns: 'dataset' })! : t('deleteDatasetConfirmContent', { ns: 'dataset' })!) setShowConfirmDelete(true) } - catch (e: any) { - const res = await e.json() - toast(res?.message || 'Unknown error', { type: 'error' }) + catch (e: unknown) { + toast.error(await getErrorMessage(e)) } }, [dataset.id, t]) @@ -112,10 +130,15 @@ const DropDown = ({ open={open} onOpenChange={setOpen} > - }> - - - + + )} + > + { expect(screen.queryByText('Beta')).not.toBeInTheDocument() }) + it('should not render current select value when it is filtered out by show_on conditions', () => { + renderBaseField({ + formSchema: { + type: FormTypeEnum.select, + name: 'mode', + label: 'Mode', + required: false, + options: [ + { label: 'Alpha', value: 'alpha' }, + { label: 'Beta', value: 'beta', show_on: [{ variable: 'enabled', value: 'yes' }] }, + ], + }, + defaultValues: { mode: 'beta', enabled: 'no' }, + }) + + expect(screen.getByRole('combobox', { name: 'Mode' })).not.toHaveTextContent('beta') + expect(screen.getByRole('combobox', { name: 'Mode' })).toHaveTextContent('common.placeholder.input') + }) + it('should render dynamic select loading state', () => { mockDynamicOptions.mockReturnValue({ data: undefined, @@ -238,6 +258,7 @@ describe('BaseField', () => { }) it('should render dynamic options and allow selecting one', async () => { + const user = userEvent.setup() mockDynamicOptions.mockReturnValue({ data: { options: [ @@ -258,13 +279,42 @@ describe('BaseField', () => { defaultValues: { plugin_option: '' }, }) - await act(async () => { - fireEvent.click(screen.getByText('common.placeholder.input')) + await user.click(screen.getByRole('combobox', { name: 'Plugin option' })) + await user.click(screen.getByRole('option', { name: 'Option A' })) + expect(screen.getByRole('combobox', { name: 'Plugin option' })).toHaveTextContent('Option A') + }) + + it('should preserve multiple dynamic select values', async () => { + const user = userEvent.setup() + mockDynamicOptions.mockReturnValue({ + data: { + options: [ + { label: { en_US: 'Option A', zh_Hans: '选项A' }, value: 'a' }, + { label: { en_US: 'Option B', zh_Hans: '选项B' }, value: 'b' }, + ], + }, + isLoading: false, + error: null, }) - await act(async () => { - fireEvent.click(screen.getByText('Option A')) + + renderBaseField({ + formSchema: { + type: FormTypeEnum.dynamicSelect, + name: 'plugin_options', + label: 'Plugin options', + required: false, + multiple: true, + }, + defaultValues: { plugin_options: ['a'] }, + showCurrentValue: true, }) - expect(screen.getByText('Option A')).toBeInTheDocument() + + expect(screen.getByRole('combobox', { name: 'Plugin options' })).toHaveTextContent('common.dynamicSelect.selected') + + await user.click(screen.getByRole('combobox', { name: 'Plugin options' })) + await user.click(screen.getByRole('option', { name: 'Option B' })) + + expect(screen.getByTestId('field-value')).toHaveTextContent('a,b') }) it('should update boolean field when users choose false', async () => { diff --git a/web/app/components/base/form/components/base/base-field.tsx b/web/app/components/base/form/components/base/base-field.tsx index b1e17bdefc..a256bccd8b 100644 --- a/web/app/components/base/form/components/base/base-field.tsx +++ b/web/app/components/base/form/components/base/base-field.tsx @@ -1,6 +1,15 @@ import type { AnyFieldApi } from '@tanstack/react-form' import type { FieldState, FormSchema, TypeWithI18N } from '@/app/components/base/form/types' import { cn } from '@langgenius/dify-ui/cn' +import { + Select, + SelectContent, + SelectItem, + SelectItemIndicator, + SelectItemText, + SelectTrigger, + SelectValue, +} from '@langgenius/dify-ui/select' import { useStore } from '@tanstack/react-form' import { isValidElement, @@ -14,7 +23,6 @@ import { FormItemValidateStatusEnum, FormTypeEnum } from '@/app/components/base/ import Input from '@/app/components/base/input' import Radio from '@/app/components/base/radio' import RadioE from '@/app/components/base/radio/ui' -import PureSelect from '@/app/components/base/select/pure' import Tooltip from '@/app/components/base/tooltip' import { useRenderI18nObject } from '@/hooks/use-i18n' import { useTriggerPluginDynamicOptions } from '@/service/use-triggers' @@ -43,6 +51,19 @@ const getTranslatedContent = ({ content, render }: { return '' } +type SelectOption = { + label: string + value: string +} + +const getSingleSelectValue = (value: unknown, options: SelectOption[]) => { + return options.find(option => option.value === value)?.value ?? null +} + +const getSingleSelectLabel = (value: unknown, options: SelectOption[], placeholder: string | undefined) => { + return options.find(option => option.value === value)?.label ?? placeholder +} + const VALIDATE_STATUS_STYLE_MAP: Record = { [FormItemValidateStatusEnum.Error]: { componentClassName: 'border-components-input-border-destructive focus:border-components-input-border-destructive', @@ -121,7 +142,7 @@ const BaseField = ({ if (!results[1]) results[1] = t('placeholder.input', { ns: 'common' }) return results - }, [label, placeholder, tooltip, description, help, renderI18nObject]) + }, [label, placeholder, tooltip, description, help, renderI18nObject, t]) const watchedVariables = useMemo(() => { const variables = new Set() @@ -184,6 +205,13 @@ const BaseField = ({ field.handleChange(value) onChange?.(field.name, value) }, [field, onChange]) + const dynamicPlaceholder = isDynamicOptionsLoading + ? t('dynamicSelect.loading', { ns: 'common' }) + : translatedPlaceholder + const dynamicNoticeTitle = dynamicOptionsError + ? t('dynamicSelect.error', { ns: 'common' }) + : (!dynamicOptions.length ? t('dynamicSelect.noData', { ns: 'common' }) : null) + const dynamicNoticeClassName = dynamicOptionsError ? 'text-text-destructive-secondary' : undefined return ( <> @@ -223,19 +251,58 @@ const BaseField = ({ ) } { - formItemType === FormTypeEnum.select && !multiple && ( - handleChange(v)} - disabled={disabled} - placeholder={translatedPlaceholder} - options={memorizedOptions} - triggerPopupSameWidth - popupProps={{ - className: 'max-h-[320px] overflow-y-auto', - }} - /> - ) + formItemType === FormTypeEnum.select && (multiple + ? ( + + ) + : ( + + )) } { formItemType === FormTypeEnum.checkbox /* && multiple */ && ( @@ -249,24 +316,76 @@ const BaseField = ({ ) } { - formItemType === FormTypeEnum.dynamicSelect && ( - - ) + formItemType === FormTypeEnum.dynamicSelect && (multiple + ? ( + + ) + : ( + + )) } { formItemType === FormTypeEnum.radio && ( diff --git a/web/app/components/base/form/components/field/__tests__/custom-select.spec.tsx b/web/app/components/base/form/components/field/__tests__/custom-select.spec.tsx deleted file mode 100644 index 5470df58a3..0000000000 --- a/web/app/components/base/form/components/field/__tests__/custom-select.spec.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { fireEvent, render, screen } from '@testing-library/react' -import CustomSelectField from '../custom-select' - -const mockField = { - name: 'custom-select-field', - state: { - value: 'small', - }, - handleChange: vi.fn(), -} - -vi.mock('../../..', () => ({ - useFieldContext: () => mockField, -})) - -describe('CustomSelectField', () => { - beforeEach(() => { - vi.clearAllMocks() - mockField.state.value = 'small' - }) - - it('should render select placeholder or selected value', () => { - render( - , - ) - expect(screen.getByText('Small')).toBeInTheDocument() - }) - - it('should update value when users select another option', () => { - render( - , - ) - fireEvent.click(screen.getByText('Small')) - fireEvent.click(screen.getByText('Large')) - expect(mockField.handleChange).toHaveBeenCalledWith('large') - }) -}) diff --git a/web/app/components/base/form/components/field/__tests__/select.spec.tsx b/web/app/components/base/form/components/field/__tests__/select.spec.tsx index 0bf6b4e022..45cc87d157 100644 --- a/web/app/components/base/form/components/field/__tests__/select.spec.tsx +++ b/web/app/components/base/form/components/field/__tests__/select.spec.tsx @@ -1,4 +1,5 @@ -import { fireEvent, render, screen } from '@testing-library/react' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import SelectField from '../select' const mockField = { @@ -29,10 +30,27 @@ describe('SelectField', () => { ]} />, ) - expect(screen.getByText('Alpha')).toBeInTheDocument() + expect(screen.getByRole('combobox', { name: 'Mode' })).toHaveTextContent('Alpha') }) - it('should update value when users select another option', () => { + it('should render the option label when selected value is an empty string', () => { + mockField.state.value = '' + + render( + , + ) + + expect(screen.getByRole('combobox', { name: 'Mode' })).toHaveTextContent('No default selected') + }) + + it('should update value when users select another option', async () => { + const user = userEvent.setup() render( { ]} />, ) - fireEvent.click(screen.getByText('Alpha')) - fireEvent.click(screen.getByText('Beta')) + await user.click(screen.getByRole('combobox', { name: 'Mode' })) + await user.click(screen.getByRole('option', { name: 'Beta' })) expect(mockField.handleChange).toHaveBeenCalledWith('beta') }) }) diff --git a/web/app/components/base/form/components/field/custom-select.tsx b/web/app/components/base/form/components/field/custom-select.tsx deleted file mode 100644 index 5808d4004d..0000000000 --- a/web/app/components/base/form/components/field/custom-select.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { CustomSelectProps, Option } from '../../../select/custom' -import type { LabelProps } from '../label' -import { cn } from '@langgenius/dify-ui/cn' -import { useFieldContext } from '../..' -import CustomSelect from '../../../select/custom' -import Label from '../label' - -type CustomSelectFieldProps = { - label: string - labelOptions?: Omit - options: T[] - className?: string -} & Omit, 'options' | 'value' | 'onChange'> - -const CustomSelectField = ({ - label, - labelOptions, - options, - className, - ...selectProps -}: CustomSelectFieldProps) => { - const field = useFieldContext() - - return ( -
-
- ) -} - -export default CustomSelectField diff --git a/web/app/components/base/form/components/field/input-type-select/__tests__/index.spec.tsx b/web/app/components/base/form/components/field/input-type-select/__tests__/index.spec.tsx index bb7ae80a34..f666315ddf 100644 --- a/web/app/components/base/form/components/field/input-type-select/__tests__/index.spec.tsx +++ b/web/app/components/base/form/components/field/input-type-select/__tests__/index.spec.tsx @@ -1,4 +1,5 @@ -import { fireEvent, render, screen } from '@testing-library/react' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import InputTypeSelectField from '../index' const mockField = { @@ -20,17 +21,20 @@ describe('InputTypeSelectField', () => { }) it('should render label and selected option', () => { - render() + const { container } = render() expect(screen.getByText('Input type')).toBeInTheDocument() expect(screen.getByText('appDebug.variableConfig.text-input')).toBeInTheDocument() + expect(container.querySelector('[role="combobox"] span > div')).not.toBeInTheDocument() + expect(container.querySelector('[role="combobox"] > span > span')).toHaveClass('flex', 'min-w-0', 'items-center', 'gap-x-0.5') }) - it('should update value when users choose another input type', () => { + it('should update value when users choose another input type', async () => { + const user = userEvent.setup() render() - fireEvent.click(screen.getByText('appDebug.variableConfig.text-input')) - fireEvent.click(screen.getByText('appDebug.variableConfig.number')) + await user.click(screen.getByRole('combobox', { name: 'Input type' })) + await user.click(screen.getByRole('option', { name: /appDebug.variableConfig.number/ })) expect(mockField.handleChange).toHaveBeenCalledWith('number') }) diff --git a/web/app/components/base/form/components/field/input-type-select/__tests__/trigger.spec.tsx b/web/app/components/base/form/components/field/input-type-select/__tests__/trigger.spec.tsx index 0957ac41c1..a7a1f2a294 100644 --- a/web/app/components/base/form/components/field/input-type-select/__tests__/trigger.spec.tsx +++ b/web/app/components/base/form/components/field/input-type-select/__tests__/trigger.spec.tsx @@ -5,7 +5,7 @@ const MockIcon = () => describe('InputTypeSelect Trigger', () => { it('should show placeholder text when no option is selected', () => { - render() + render() expect(screen.getByText('common.placeholder.select')).toBeInTheDocument() }) @@ -18,11 +18,25 @@ describe('InputTypeSelect Trigger', () => { Icon: MockIcon, type: 'string', }} - open={false} />, ) expect(screen.getByText('Text Input')).toBeInTheDocument() expect(screen.getByText('string')).toBeInTheDocument() }) + + it('should keep selected option parts in one inline flex row', () => { + render( + , + ) + + expect(screen.getByText('Text Input').parentElement).toHaveClass('flex', 'min-w-0', 'items-center', 'gap-x-0.5') + }) }) diff --git a/web/app/components/base/form/components/field/input-type-select/index.tsx b/web/app/components/base/form/components/field/input-type-select/index.tsx index 5e150240f6..37f9a510d4 100644 --- a/web/app/components/base/form/components/field/input-type-select/index.tsx +++ b/web/app/components/base/form/components/field/input-type-select/index.tsx @@ -1,10 +1,13 @@ -import type { CustomSelectProps } from '../../../../select/custom' import type { LabelProps } from '../../label' import type { FileTypeSelectOption, InputType } from './types' import { cn } from '@langgenius/dify-ui/cn' -import { useCallback } from 'react' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from '@langgenius/dify-ui/select' import { useFieldContext } from '../../..' -import CustomSelect from '../../../../select/custom' import Label from '../../label' import { useInputTypeOptions } from './hooks' import Option from './option' @@ -15,24 +18,19 @@ type InputTypeSelectFieldProps = { labelOptions?: Omit supportFile: boolean className?: string -} & Omit, 'options' | 'value' | 'onChange' | 'CustomTrigger' | 'CustomOption'> + disabled?: boolean +} const InputTypeSelectField = ({ label, labelOptions, supportFile, className, - ...customSelectProps + disabled, }: InputTypeSelectFieldProps) => { const field = useFieldContext() const inputTypeOptions = useInputTypeOptions(supportFile) - - const renderTrigger = useCallback((option: FileTypeSelectOption | undefined, open: boolean) => { - return - }, []) - const renderOption = useCallback((option: FileTypeSelectOption) => { - return