|
|
|
import {
|
|
|
|
Visibility as MUIVisibilityIcon,
|
|
|
|
VisibilityOff as MUIVisibilityOffIcon,
|
|
|
|
} from '@mui/icons-material';
|
|
|
|
import {
|
|
|
|
IconButton as MUIIconButton,
|
|
|
|
IconButtonProps as MUIIconButtonProps,
|
|
|
|
OutlinedInput as MUIOutlinedInput,
|
|
|
|
outlinedInputClasses as muiOutlinedInputClasses,
|
|
|
|
OutlinedInputProps as MUIOutlinedInputProps,
|
|
|
|
} from '@mui/material';
|
|
|
|
import { cloneElement, FC, ReactElement, useMemo, useState } from 'react';
|
|
|
|
|
|
|
|
import { GREY, TEXT, UNSELECTED } from '../../lib/consts/DEFAULT_THEME';
|
|
|
|
import INPUT_TYPES from '../../lib/consts/INPUT_TYPES';
|
|
|
|
|
|
|
|
type OutlinedInputOptionalProps = {
|
|
|
|
disableAutofill?: boolean;
|
|
|
|
onPasswordVisibilityAppend?: (
|
|
|
|
inputType: string,
|
|
|
|
...restArgs: Parameters<Exclude<MUIIconButtonProps['onClick'], undefined>>
|
|
|
|
) => void;
|
|
|
|
};
|
|
|
|
|
|
|
|
type OutlinedInputProps = MUIOutlinedInputProps & OutlinedInputOptionalProps;
|
|
|
|
|
|
|
|
const OUTLINED_INPUT_DEFAULT_PROPS: Pick<
|
|
|
|
OutlinedInputOptionalProps,
|
|
|
|
'disableAutofill' | 'onPasswordVisibilityAppend'
|
|
|
|
> = {
|
|
|
|
disableAutofill: false,
|
|
|
|
onPasswordVisibilityAppend: undefined,
|
|
|
|
};
|
|
|
|
|
|
|
|
const OutlinedInput: FC<OutlinedInputProps> = (outlinedInputProps) => {
|
|
|
|
const {
|
|
|
|
disableAutofill = false,
|
|
|
|
endAdornment,
|
|
|
|
label,
|
|
|
|
onPasswordVisibilityAppend,
|
|
|
|
sx,
|
|
|
|
inputProps: { type: baseType, ...inputRestProps } = {},
|
|
|
|
// Input props that depend on other input props.
|
|
|
|
type: initialType = baseType,
|
|
|
|
|
|
|
|
...outlinedInputRestProps
|
|
|
|
} = outlinedInputProps;
|
|
|
|
|
|
|
|
const [type, setType] = useState<string>(initialType);
|
|
|
|
|
|
|
|
const passwordVisibilityButton = useMemo(() => {
|
|
|
|
const isInitialTypePassword = initialType === INPUT_TYPES.password;
|
|
|
|
const isTypePassword = type === INPUT_TYPES.password;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{isInitialTypePassword && (
|
|
|
|
<MUIIconButton
|
|
|
|
onClick={(...args) => {
|
|
|
|
const newType = isTypePassword
|
|
|
|
? INPUT_TYPES.text
|
|
|
|
: INPUT_TYPES.password;
|
|
|
|
|
|
|
|
setType(newType);
|
|
|
|
onPasswordVisibilityAppend?.call(null, newType, ...args);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{isTypePassword ? <MUIVisibilityIcon /> : <MUIVisibilityOffIcon />}
|
|
|
|
</MUIIconButton>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}, [initialType, onPasswordVisibilityAppend, type]);
|
|
|
|
|
|
|
|
const combinedSx = useMemo(
|
|
|
|
() => ({
|
|
|
|
color: GREY,
|
|
|
|
|
|
|
|
[`& .${muiOutlinedInputClasses.notchedOutline}`]: {
|
|
|
|
borderColor: UNSELECTED,
|
|
|
|
},
|
|
|
|
|
|
|
|
[`& .${muiOutlinedInputClasses.input}`]: {
|
|
|
|
color: TEXT,
|
|
|
|
},
|
|
|
|
|
|
|
|
'&:hover': {
|
|
|
|
[`& .${muiOutlinedInputClasses.notchedOutline}`]: {
|
|
|
|
borderColor: GREY,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
[`&.${muiOutlinedInputClasses.focused}`]: {
|
|
|
|
color: TEXT,
|
|
|
|
|
|
|
|
[`& .${muiOutlinedInputClasses.notchedOutline}`]: {
|
|
|
|
borderColor: GREY,
|
|
|
|
|
|
|
|
'& legend': {
|
|
|
|
paddingRight: label ? '1.2em' : 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
...sx,
|
|
|
|
}),
|
|
|
|
[label, sx],
|
|
|
|
);
|
|
|
|
|
|
|
|
const combinedEndAdornment = useMemo(() => {
|
|
|
|
let result;
|
|
|
|
|
|
|
|
if (typeof endAdornment === 'object') {
|
|
|
|
const casted = endAdornment as ReactElement;
|
|
|
|
const {
|
|
|
|
props: { children: castedChildren = [], ...castedRestProps },
|
|
|
|
} = casted;
|
|
|
|
|
|
|
|
result = cloneElement(casted, {
|
|
|
|
...castedRestProps,
|
|
|
|
children: (
|
|
|
|
<>
|
|
|
|
{passwordVisibilityButton}
|
|
|
|
{castedChildren}
|
|
|
|
</>
|
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}, [passwordVisibilityButton, endAdornment]);
|
|
|
|
|
|
|
|
const autofillLock = useMemo<
|
|
|
|
Pick<MUIOutlinedInputProps, 'onFocus' | 'readOnly'> | undefined
|
|
|
|
>(
|
|
|
|
() =>
|
|
|
|
disableAutofill
|
|
|
|
? {
|
|
|
|
onFocus: (...args) => {
|
|
|
|
const [event] = args;
|
|
|
|
|
|
|
|
event.target.readOnly = false;
|
|
|
|
|
|
|
|
outlinedInputRestProps?.onFocus?.call(null, ...args);
|
|
|
|
},
|
|
|
|
readOnly: true,
|
|
|
|
}
|
|
|
|
: undefined,
|
|
|
|
[disableAutofill, outlinedInputRestProps?.onFocus],
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<MUIOutlinedInput
|
|
|
|
endAdornment={combinedEndAdornment}
|
|
|
|
label={label}
|
|
|
|
inputProps={{ type, ...inputRestProps }}
|
|
|
|
{...outlinedInputRestProps}
|
|
|
|
{...autofillLock}
|
|
|
|
sx={combinedSx}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
OutlinedInput.defaultProps = OUTLINED_INPUT_DEFAULT_PROPS;
|
|
|
|
|
|
|
|
export type { OutlinedInputProps };
|
|
|
|
|
|
|
|
export default OutlinedInput;
|