Skip to content

Commit

Permalink
Feat(form)/password 49 (#50)
Browse files Browse the repository at this point in the history
* feat(form): password field component

* feat: visibilty icon in password input

---------

Co-authored-by: mrbadri <mrbadri.dev@gmail.com>
  • Loading branch information
erfanmoghadasi and mrbadri authored Jul 8, 2024
1 parent 1bebcad commit 03b1e98
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 4 deletions.
10 changes: 10 additions & 0 deletions apps/docs/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -256,6 +265,7 @@ export function App() {
// },
// }
// }

];

const [tabs, setTabs] = useState<TabData[]>([
Expand Down
37 changes: 36 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <Skeleton {...getTextLoadingProps()} />;
};

export default PasswordLoading;
Original file line number Diff line number Diff line change
@@ -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<PasswordProps> = (props) => {
const {
getFieldProps,
show,
getInputAdornmentProps,
getIconButtonProps,
showPassword,
} = usePassword(props);

if (show)
return (
<TextField
{...getFieldProps()}
InputProps={{
endAdornment: (
<InputAdornment {...getInputAdornmentProps()}>
<IconButton {...getIconButtonProps()}>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
);

return null;
};

export default Password;
Original file line number Diff line number Diff line change
@@ -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<string, any>;
api?: Api;
rule?: Rule;
show?: boolean;
};
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 1 addition & 2 deletions packages/core/src/modules/form/src/types/public.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ReactNode } from 'react';

import { SkeletonOwnProps } from '@mui/material';

import { SubmitFieldProps } from '../components/actions/submit/submit.types';
Expand All @@ -20,6 +18,7 @@ export type FormTypes =
| 'action-submit'
| 'auto-complete'
| 'checkbox'
| 'password'
| 'select';

export type Option = {
Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/modules/form/src/utils/selector/formSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<FormSelectorProps> = ({
Expand Down Expand Up @@ -64,6 +66,20 @@ const FormSelector: FC<FormSelectorProps> = ({
</Suspense>
);

case 'password':
SelectedComponent = lazy(
() => import('../../components/fields/password/password')
);

return (
<Suspense
key={fieldProps.id}
fallback={<PasswordLoading {...loading} />}
>
<SelectedComponent {...(fieldProps as PasswordProps)} />
</Suspense>
);

case 'select':
SelectedComponent = lazy(
() => import('../../components/fields/select/select')
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
"path": "./tsconfig.lib.json"
}
],
"extends": "../../tsconfig.base.json"
Expand Down

0 comments on commit 03b1e98

Please sign in to comment.