diff --git a/apps/docs/src/app/app.tsx b/apps/docs/src/app/app.tsx index c2b704b..f554d75 100644 --- a/apps/docs/src/app/app.tsx +++ b/apps/docs/src/app/app.tsx @@ -219,6 +219,15 @@ export function App() { ], }, }, + { id: 'form-password-1', + groupType: 'form', + type: 'password', + props: { + id: 'password-one', + formId: '20', + label: 'Password Field (Form Id: 20)', + }, + }, { id: 'select-form-1', groupType: 'form', @@ -256,6 +265,7 @@ export function App() { // }, // } // } + ]; const [tabs, setTabs] = useState([ diff --git a/package-lock.json b/package-lock.json index b8151f8..f9825ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@changesets/cli": "^2.27.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.21", "@mui/lab": "^5.0.0-alpha.167", "@mui/material": "^5.15.11", "react": "18.2.0", @@ -3836,6 +3837,10 @@ "resolved": "packages/components/grid", "link": true }, + "node_modules/@mui-builder/tab": { + "resolved": "packages/tab", + "link": true + }, "node_modules/@mui-builder/utils": { "resolved": "packages/utils", "link": true @@ -3880,6 +3885,31 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.15.21", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.21.tgz", + "integrity": "sha512-yqkq1MbdkmX5ZHyvZTBuAaA6RkvoqkoAgwBSx9Oh0L0jAfj9T/Ih/NhMNjkl8PWVSonjfDUkKroBnjRyo/1M9Q==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/lab": { "version": "5.0.0-alpha.167", "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.167.tgz", @@ -20911,13 +20941,18 @@ "version": "0.0.7", "dependencies": { "@mui-builder/form": "*", - "@mui-builder/grid": "*" + "@mui-builder/grid": "*", + "@mui-builder/utils": "*" }, "peerDependencies": { "react": "18.2.0", "react-dom": "18.2.0" } }, + "packages/tab": { + "name": "@mui-builder/tab", + "version": "0.0.4" + }, "packages/utils": { "name": "@mui-builder/utils", "version": "0.0.3" diff --git a/package.json b/package.json index 2aedbb3..266ee14 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@changesets/cli": "^2.27.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.21", "@mui/lab": "^5.0.0-alpha.167", "@mui/material": "^5.15.11", "react": "18.2.0", diff --git a/packages/core/src/modules/form/src/components/fields/password/password.loading.tsx b/packages/core/src/modules/form/src/components/fields/password/password.loading.tsx new file mode 100644 index 0000000..e59b140 --- /dev/null +++ b/packages/core/src/modules/form/src/components/fields/password/password.loading.tsx @@ -0,0 +1,13 @@ +import { Skeleton } from '@mui/material'; + +import { LoadingProps } from '@mui-builder/types/configs.type'; + +import usePasswordLoading from './usePassword.loading'; + +const PasswordLoading = (props: LoadingProps) => { + const { getTextLoadingProps } = usePasswordLoading(props); + + return ; +}; + +export default PasswordLoading; diff --git a/packages/core/src/modules/form/src/components/fields/password/password.tsx b/packages/core/src/modules/form/src/components/fields/password/password.tsx new file mode 100644 index 0000000..22315ca --- /dev/null +++ b/packages/core/src/modules/form/src/components/fields/password/password.tsx @@ -0,0 +1,38 @@ +import { FC } from 'react'; + +import { Visibility, VisibilityOff } from '@mui/icons-material'; +import { IconButton, InputAdornment, TextField } from '@mui/material'; + +import { PasswordProps } from './password.types'; + +import usePassword from './usePassword'; + +const Password: FC = (props) => { + const { + getFieldProps, + show, + getInputAdornmentProps, + getIconButtonProps, + showPassword, + } = usePassword(props); + + if (show) + return ( + + + {showPassword ? : } + + + ), + }} + /> + ); + + return null; +}; + +export default Password; diff --git a/packages/core/src/modules/form/src/components/fields/password/password.types.ts b/packages/core/src/modules/form/src/components/fields/password/password.types.ts new file mode 100644 index 0000000..6f94b2e --- /dev/null +++ b/packages/core/src/modules/form/src/components/fields/password/password.types.ts @@ -0,0 +1,18 @@ +import { TextFieldProps } from '@mui/material'; + +import { Api } from '@mui-builder/types/api.types'; +import { Script } from '@mui-builder/types/script.types'; + +import { Dependesies, FormId, Id } from '../../../types/public.types'; +import { Rule } from '../../../types/validation.types'; + +export type PasswordProps = TextFieldProps & { + id: Id; + formId: FormId; + script?: Script; + dependesies?: Dependesies; + propsController?: Record; + api?: Api; + rule?: Rule; + show?: boolean; +}; diff --git a/packages/core/src/modules/form/src/components/fields/password/usePassword.loading.ts b/packages/core/src/modules/form/src/components/fields/password/usePassword.loading.ts new file mode 100644 index 0000000..9c39f51 --- /dev/null +++ b/packages/core/src/modules/form/src/components/fields/password/usePassword.loading.ts @@ -0,0 +1,22 @@ +import { LoadingProps } from '@mui-builder/types/configs.type'; + +const usePasswordLoading = (props: LoadingProps) => { + const { animation = 'wave', sx, ...otherProps } = props; + + const getPasswordLoadingProps = () => ({ + sx: { + display: 'inline-block', + transform: 'unset', + width: '255px', + height: '56px', + mx: 0.5, + ...sx, + }, + animation, + ...otherProps, + }); + + return { getTextLoadingProps: getPasswordLoadingProps }; +}; + +export default usePasswordLoading; diff --git a/packages/core/src/modules/form/src/components/fields/password/usePassword.ts b/packages/core/src/modules/form/src/components/fields/password/usePassword.ts new file mode 100644 index 0000000..8377560 --- /dev/null +++ b/packages/core/src/modules/form/src/components/fields/password/usePassword.ts @@ -0,0 +1,107 @@ +import { useState } from 'react'; +import { useController, useWatch } from 'react-hook-form'; + +import { IconButtonProps, InputAdornmentProps } from '@mui/material'; + +import useQueryBuilder from '@mui-builder/utils/useQueryBuilder/useQueryBuilder'; +import UseScript from '@mui-builder/utils/useScript/useScript'; + +import axios from 'axios'; + +import { PasswordProps } from './password.types'; + +import useForms from '../../../hooks/useForms/useForms'; +import usePropsController from '../../../hooks/usePropsController/usePropsController'; +import useRule from '../../../hooks/useRule/useRule'; + +const UsePassword = (props: PasswordProps) => { + const { + formId, + script, + api, + show = true, + dependesies, + helperText, + defaultValue, + ...textFieldProps + } = props; + const { configs, queries } = api || {}; + + const { forms } = useForms(); + const formMethod = forms?.[formId]; + const { setProps, propsController } = usePropsController(); + + const newProps = propsController?.[textFieldProps?.id] || {}; + + // eye icon + const [showPassword, setShowPassword] = useState(false); + + const handleClickShowPassword = () => { + setShowPassword(!showPassword); + }; + + // Handle Script + const { scriptResult } = UseScript({ + script, + formMethod, + forms, + formId, + setProps, + }); + + // Handle Wtach Fields + useWatch({ + control: formMethod.control, + name: dependesies ?? [], + }); + + // API Call + useQueryBuilder({ + apiInstance: axios, + apiConfigs: configs ?? {}, + apiQuery: queries ?? {}, + formMethod, + formId, + forms, + }); + + // Controller + const { + field, + formState: { errors }, + } = useController({ + name: textFieldProps.id, + control: formMethod.control, + disabled: textFieldProps.disabled, + rules: useRule(textFieldProps?.rule), + defaultValue, + }); + + const error = errors?.[textFieldProps.id]; + + // Props + const getFieldProps = () => ({ + ...field, + ...textFieldProps, + helperText: error?.message ?? helperText, + error: !!error, + value: field.value ?? '', + ...scriptResult, + ...newProps, + type: showPassword ? 'text' : 'password', + }); + + const getInputAdornmentProps = (): InputAdornmentProps => ({ + position: 'end', + }); + + const getIconButtonProps = (): IconButtonProps => ({ + 'aria-label': 'toggle password visibility', + onClick: handleClickShowPassword, + edge: 'end', + }); + + return { getFieldProps, show, getInputAdornmentProps, getIconButtonProps, showPassword }; +}; + +export default UsePassword; diff --git a/packages/core/src/modules/form/src/types/public.types.ts b/packages/core/src/modules/form/src/types/public.types.ts index a985cb0..7320bab 100644 --- a/packages/core/src/modules/form/src/types/public.types.ts +++ b/packages/core/src/modules/form/src/types/public.types.ts @@ -1,5 +1,3 @@ -import { ReactNode } from 'react'; - import { SkeletonOwnProps } from '@mui/material'; import { SubmitFieldProps } from '../components/actions/submit/submit.types'; @@ -20,6 +18,7 @@ export type FormTypes = | 'action-submit' | 'auto-complete' | 'checkbox' + | 'password' | 'select'; export type Option = { diff --git a/packages/core/src/modules/form/src/utils/selector/formSelector.tsx b/packages/core/src/modules/form/src/utils/selector/formSelector.tsx index 9eb8d11..d96fbab 100644 --- a/packages/core/src/modules/form/src/utils/selector/formSelector.tsx +++ b/packages/core/src/modules/form/src/utils/selector/formSelector.tsx @@ -3,12 +3,14 @@ import { FC, Fragment, Suspense, lazy } from 'react'; import { SubmitFieldProps } from '../../components/actions/submit/submit.types'; import { AutoCompleteProps } from '../../components/fields/autoComplete/autoComplete.types'; import { CheckboxProps } from '../../components/fields/checkbox/checkbox.types'; +import { PasswordProps } from '../../components/fields/password/password.types'; import { SelectProps } from '../../components/fields/select/select.types'; import { TextProps } from '../../components/fields/text/text.types'; import { Option } from '../../types/public.types'; import { FormSelectorProps } from './formSelector.types'; import SubmitLoading from '../../components/actions/submit/submit.loading'; +import PasswordLoading from '../../components/fields/password/password.loading'; import TextLoading from '../../components/fields/text/text.loading'; const FormSelector: FC = ({ @@ -64,6 +66,20 @@ const FormSelector: FC = ({ ); + case 'password': + SelectedComponent = lazy( + () => import('../../components/fields/password/password') + ); + + return ( + } + > + + + ); + case 'select': SelectedComponent = lazy( () => import('../../components/fields/select/select') diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 95cfeb2..746702c 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -10,7 +10,7 @@ "include": [], "references": [ { - "path": "./tsconfig.lib.json" + "path": "./tsconfig.lib.json" } ], "extends": "../../tsconfig.base.json"