diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUIForm/MUIForm.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUIForm/MUIForm.tsx new file mode 100644 index 000000000000..6943200199a6 --- /dev/null +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUIForm/MUIForm.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Box, BoxProps } from '@mui/material'; +import { ComponentProps, FC, ReactNode } from 'react'; + +interface MUIFormProps { + onSubmit?: ComponentProps<'form'>['onSubmit']; + onReset?: ComponentProps<'form'>['onReset']; + children: ReactNode; + className?: string; + id?: string; + sx?: BoxProps['sx']; +} + +const MUIForm: FC = ({ + onSubmit, + onReset, + children, + className, + id, + sx, +}) => { + return ( + + {children} + + ); +}; + +export default MUIForm; diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUIFormItem/MUIFormItem.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUIFormItem/MUIFormItem.tsx new file mode 100644 index 000000000000..a17b6ae5b1c2 --- /dev/null +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUIFormItem/MUIFormItem.tsx @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { FormControl, FormHelperText } from '@mui/material'; +import { FC, ReactNode } from 'react'; + +interface MUIFormItemProps { + label?: ReactNode; + error?: boolean; + helperText?: ReactNode; + required?: boolean; + children: ReactNode; + className?: string; + id?: string; + fullWidth?: boolean; +} + +const MUIFormItem: FC = ({ + label, + error, + helperText, + required, + children, + className, + id, + fullWidth = true, +}) => { + return ( + + {label} + {children} + {helperText && ( + {helperText} + )} + + ); +}; + +export default MUIFormItem; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/MUITextField/MUITextField.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUITextField/MUITextField.tsx similarity index 64% rename from openmetadata-ui/src/main/resources/ui/src/components/common/MUITextField/MUITextField.tsx rename to openmetadata-ui-core-components/src/main/resources/ui/src/components/MUITextField/MUITextField.tsx index e7e4f7b23bf3..02c46afa7d28 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/MUITextField/MUITextField.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/MUITextField/MUITextField.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2025 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,8 +11,7 @@ * limitations under the License. */ import { TextField, TextFieldProps } from '@mui/material'; -import { ChangeEvent, FC, memo, useCallback } from 'react'; -import { getSanitizeContent } from '../../../utils/sanitize.utils'; +import { FC, memo } from 'react'; interface MUITextFieldProps extends Omit { variant?: 'outlined' | 'filled' | 'standard'; @@ -20,29 +19,13 @@ interface MUITextFieldProps extends Omit { } const MUITextField: FC = ({ - value, - onChange, - variant, size = 'small', ...props }) => { - const handleChange = useCallback( - (e: ChangeEvent) => { - const sanitizedValue = getSanitizeContent(e.target.value); - if (onChange) { - onChange({ ...e, target: { ...e.target, value: sanitizedValue } }); - } - }, - [onChange] - ); - return ( ); diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/index.ts b/openmetadata-ui-core-components/src/main/resources/ui/src/components/index.ts index b4541b5e90b1..53b95a421de2 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/index.ts +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/index.ts @@ -1,3 +1,6 @@ // Component exports export * from "./checkbox-icons"; +export { default as MUITextField } from './MUITextField/MUITextField'; +export { default as MUIForm } from './MUIForm/MUIForm'; +export { default as MUIFormItem } from './MUIFormItem/MUIFormItem'; export { SnackbarContent } from "./SnackbarContent"; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.test.tsx index f72880541455..dfd2fedc305d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.test.tsx @@ -73,6 +73,44 @@ describe('formUtils', () => { expect(JSON.stringify(result)).not.toContain('form-item-alert'); }); + + it('Should render TEXT_MUI field type with MUITextField from core components', async () => { + const result = getField({ + name: 'testField', + label: 'Test Field', + type: FieldTypes.TEXT_MUI, + required: true, + helperText: 'This is a helper text', + helperTextType: HelperTextType.ALERT, + props: { + 'data-testid': 'test-text-field', + }, + id: 'root/testField', + formItemLayout: FormItemLayout.VERTICAL, + }); + + // Verify the result contains MUITextField + const resultString = JSON.stringify(result); + expect(resultString).toContain('testField'); + }); + + it('Should render PASSWORD_MUI field type with type password', async () => { + const result = getField({ + name: 'passwordField', + label: 'Password Field', + type: FieldTypes.PASSWORD_MUI, + required: true, + props: { + 'data-testid': 'test-password-field', + }, + id: 'root/passwordField', + formItemLayout: FormItemLayout.VERTICAL, + }); + + // Verify the result contains password type + const resultString = JSON.stringify(result); + expect(resultString).toContain('password'); + }); }); describe('createScrollToErrorHandler', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx index 9099bac3cd8a..b64012f663aa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx @@ -11,6 +11,7 @@ * limitations under the License. */ import { TooltipProps as MUITooltipProps } from '@mui/material/Tooltip'; +import { MUITextField } from '@openmetadata/ui-core-components'; import { ErrorTransformer } from '@rjsf/utils'; import { Alert, @@ -30,7 +31,7 @@ import { TooltipPlacement } from 'antd/lib/tooltip'; import { AxiosError } from 'axios'; import classNames from 'classnames'; import { compact, startCase, toString } from 'lodash'; -import { Fragment, ReactNode } from 'react'; +import { ChangeEvent, Fragment, ReactNode } from 'react'; import AsyncSelectList from '../components/common/AsyncSelectList/AsyncSelectList'; import { AsyncSelectListProps } from '../components/common/AsyncSelectList/AsyncSelectList.interface'; import TreeAsyncSelectList from '../components/common/AsyncSelectList/TreeAsyncSelectList'; @@ -50,7 +51,6 @@ import MUIFormItemLabel from '../components/common/MUIFormItemLabel'; import MUIGlossaryTagSuggestion from '../components/common/MUIGlossaryTagSuggestion/MUIGlossaryTagSuggestion'; import MUISelect from '../components/common/MUISelect/MUISelect'; import MUITagSuggestion from '../components/common/MUITagSuggestion/MUITagSuggestion'; -import MUITextField from '../components/common/MUITextField/MUITextField'; import MUIUserTeamSelect, { MUIUserTeamSelectProps, } from '../components/common/MUIUserTeamSelect/MUIUserTeamSelect'; @@ -75,6 +75,7 @@ import TagSuggestion, { TagSuggestionProps, } from '../pages/TasksPage/shared/TagSuggestion'; import { t } from './i18next/LocalUtil'; +import { getSanitizeContent } from './sanitize.utils'; import { getErrorText } from './StringsUtils'; export const getField = (field: FieldProp) => { @@ -145,11 +146,22 @@ export const getField = (field: FieldProp) => { break; case FieldTypes.TEXT_MUI: { - const { error, ...muiProps } = props; + const { error, onChange, value, ...muiProps } = props; const isRequired = fieldRules.some( (rule) => (rule as RuleObject).required ); + // Handle sanitization on change + const handleChange = (e: ChangeEvent) => { + const sanitizedValue = getSanitizeContent(e.target.value); + if (onChange) { + (onChange as (e: ChangeEvent) => void)({ + ...e, + target: { ...e.target, value: sanitizedValue }, + }); + } + }; + return ( { label={muiLabel} placeholder={placeholder} required={isRequired} + value={value} + onChange={handleChange} {...muiProps} /> @@ -168,11 +182,22 @@ export const getField = (field: FieldProp) => { } case FieldTypes.PASSWORD_MUI: { - const { error, ...muiProps } = props; + const { error, onChange, value, ...muiProps } = props; const isRequired = fieldRules.some( (rule) => (rule as RuleObject).required ); + // Handle sanitization on change + const handleChange = (e: ChangeEvent) => { + const sanitizedValue = getSanitizeContent(e.target.value); + if (onChange) { + (onChange as (e: ChangeEvent) => void)({ + ...e, + target: { ...e.target, value: sanitizedValue }, + }); + } + }; + return ( { placeholder={placeholder} required={isRequired} type="password" + value={value} + onChange={handleChange} {...muiProps} />