|
|
|
@ -1,5 +1,9 @@
|
|
|
|
|
import clsx from 'clsx';
|
|
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { FormattedMessage } from 'react-intl';
|
|
|
|
|
|
|
|
|
|
import Stack from '../stack/stack';
|
|
|
|
|
import Text from '../text/text';
|
|
|
|
|
|
|
|
|
|
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength' | 'onChange' | 'onKeyDown' | 'onPaste' | 'required' | 'disabled' | 'rows' | 'readOnly'> {
|
|
|
|
|
/** Put the cursor into the input on mount. */
|
|
|
|
@ -28,6 +32,8 @@ interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElemen
|
|
|
|
|
isResizeable?: boolean
|
|
|
|
|
/** Textarea theme. */
|
|
|
|
|
theme?: 'default' | 'transparent'
|
|
|
|
|
/** Whether to display a character counter below the textarea. */
|
|
|
|
|
withCounter?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Textarea with custom styles. */
|
|
|
|
@ -40,8 +46,11 @@ const Textarea = React.forwardRef(({
|
|
|
|
|
maxRows = 10,
|
|
|
|
|
minRows = 1,
|
|
|
|
|
theme = 'default',
|
|
|
|
|
maxLength,
|
|
|
|
|
value,
|
|
|
|
|
...props
|
|
|
|
|
}: ITextarea, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
|
|
|
|
|
const length = value?.length || 0;
|
|
|
|
|
const [rows, setRows] = useState<number>(autoGrow ? 1 : 4);
|
|
|
|
|
|
|
|
|
|
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
|
|
@ -70,20 +79,35 @@ const Textarea = React.forwardRef(({
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<textarea
|
|
|
|
|
{...props}
|
|
|
|
|
ref={ref}
|
|
|
|
|
rows={rows}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
className={clsx('block w-full rounded-md text-gray-900 placeholder:text-gray-600 dark:text-gray-100 dark:placeholder:text-gray-600 sm:text-sm', {
|
|
|
|
|
'bg-white dark:bg-transparent shadow-sm border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
|
|
|
|
|
theme === 'default',
|
|
|
|
|
'bg-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent',
|
|
|
|
|
'font-mono': isCodeEditor,
|
|
|
|
|
'text-red-600 border-red-600': hasError,
|
|
|
|
|
'resize-none': !isResizeable,
|
|
|
|
|
})}
|
|
|
|
|
/>
|
|
|
|
|
<Stack space={1.5}>
|
|
|
|
|
<textarea
|
|
|
|
|
{...props}
|
|
|
|
|
value={value}
|
|
|
|
|
ref={ref}
|
|
|
|
|
rows={rows}
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
className={clsx('block w-full rounded-md text-gray-900 placeholder:text-gray-600 dark:text-gray-100 dark:placeholder:text-gray-600 sm:text-sm', {
|
|
|
|
|
'bg-white dark:bg-transparent shadow-sm border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
|
|
|
|
|
theme === 'default',
|
|
|
|
|
'bg-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent',
|
|
|
|
|
'font-mono': isCodeEditor,
|
|
|
|
|
'text-red-600 border-red-600': hasError,
|
|
|
|
|
'resize-none': !isResizeable,
|
|
|
|
|
})}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{maxLength && (
|
|
|
|
|
<div className='text-right rtl:text-left'>
|
|
|
|
|
<Text size='xs' theme={maxLength - length < 0 ? 'danger' : 'muted'}>
|
|
|
|
|
<FormattedMessage
|
|
|
|
|
id='textarea.counter.label'
|
|
|
|
|
defaultMessage='{count} characters remaining'
|
|
|
|
|
values={{ count: maxLength - length }}
|
|
|
|
|
/>
|
|
|
|
|
</Text>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</Stack>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|