1 import type { ForwardRefRenderFunction } from 'react';
2 import { type FC, forwardRef, useMemo, useState } from 'react';
4 import { type FieldProps } from 'formik';
6 import { InputFieldTwo, TextAreaTwo } from '@proton/components';
7 import { type InputFieldProps } from '@proton/components/components/v2/field/InputField';
8 import { pipe } from '@proton/pass/utils/fp/pipe';
9 import { isEmptyString } from '@proton/pass/utils/string/is-empty-string';
10 import clsx from '@proton/utils/clsx';
12 import { useFieldControl } from '../../../hooks/useFieldControl';
13 import { useMaxLengthLimiter } from '../../../hooks/useMaxLengthLimiter';
14 import { usePasteLengthLimiter } from '../../../hooks/usePasteLengthLimiter';
15 import { FieldBox, type FieldBoxProps } from './Layout/FieldBox';
17 export type BaseTextAreaFieldProps = FieldProps & InputFieldProps<typeof TextAreaTwo>;
19 const DEFAULT_MIN_ROWS = 1;
20 const DEFAULT_MAX_ROWS = 5;
22 const BaseTextAreaFieldRender: ForwardRefRenderFunction<HTMLTextAreaElement, BaseTextAreaFieldProps> = (
23 { className, form, field, labelContainerClassName, meta, onKeyDown, onPaste, ...props },
26 const { error } = useFieldControl({ form, field, meta });
27 const pasteLengthLimiter = usePasteLengthLimiter();
28 const maxLengthLimiter = useMaxLengthLimiter();
30 const { value } = field;
31 const { minRows, rows } = props;
33 /* the nested `useAutoGrow` doesn't support initial row resolution
34 * on initial mount - we resort to a dynamic `minRows` prop */
35 const dynamicMinRows = useMemo(
36 () => Math.max(minRows ?? DEFAULT_MIN_ROWS, Math.min(value.split('\n').length, rows ?? DEFAULT_MAX_ROWS)),
37 [minRows, rows, value]
46 assistContainerClassName="empty:hidden"
48 'border-none flex p-0 resize-none',
49 props.disabled ? 'color-disabled' : 'color-norm',
53 labelContainerClassName={clsx(
54 'm-0 text-normal text-sm',
55 error ? 'color-danger' : 'color-weak',
56 labelContainerClassName
58 rows={DEFAULT_MAX_ROWS}
61 minRows={dynamicMinRows}
62 onPaste={props.maxLength ? pasteLengthLimiter(props.maxLength, onPaste) : onPaste}
63 onKeyDown={props.maxLength ? maxLengthLimiter(props.maxLength, onKeyDown) : onKeyDown}
68 export const BaseTextAreaField = forwardRef(BaseTextAreaFieldRender);
70 export const BaseMaskedTextAreaField: FC<BaseTextAreaFieldProps> = ({ form, field, ...rest }) => {
71 const { value } = field;
72 const [masked, setMasked] = useState<boolean>(true);
73 const isEmpty = isEmptyString(value);
77 className={clsx(!isEmpty && masked && 'text-monospace')}
78 onFocus={() => setMasked(false)}
79 onBlur={pipe(field.onBlur, () => setMasked(true))}
82 rows={masked ? DEFAULT_MIN_ROWS : rest.rows}
83 value={!masked || isEmpty ? value : '•'.repeat(16)}
89 export type TextAreaFieldProps = FieldBoxProps & BaseTextAreaFieldProps;
91 export const TextAreaField: FC<TextAreaFieldProps> = ({
93 actionsContainerClassName,
98 <FieldBox actions={actions} actionsContainerClassName={actionsContainerClassName} className={className} icon={icon}>
99 <BaseTextAreaField {...props} />