diff --git a/.changeset/late-rings-poke.md b/.changeset/late-rings-poke.md
new file mode 100644
index 0000000000..6da9e43fc0
--- /dev/null
+++ b/.changeset/late-rings-poke.md
@@ -0,0 +1,6 @@
+---
+'@twilio-paste/textarea': major
+'@twilio-paste/core': major
+---
+
+[Textarea] Update textarea to use @twilio-paste/react-autosize-textarea library and add the resize prop which allows users to resize the textarea height.
diff --git a/.changeset/slow-buttons-film.md b/.changeset/slow-buttons-film.md
new file mode 100644
index 0000000000..a64ad36720
--- /dev/null
+++ b/.changeset/slow-buttons-film.md
@@ -0,0 +1,6 @@
+---
+'@twilio-paste/core': major
+'@twilio-paste/react-textarea-autosize-library': major
+---
+
+[react-textarea-autosize-library] add react-textarea-autosize library
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index a89a9de051..cc85f6f33f 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -51,6 +51,7 @@
"/packages/paste-core/components/paragraph",
"/packages/paste-core/components/popover",
"/packages/paste-core/components/radio-group",
+ "/packages/paste-libraries/react-textarea-autosize",
"/packages/paste-libraries/reakit",
"/packages/paste-core/components/screen-reader-only",
"/packages/paste-core/components/select",
diff --git a/packages/paste-core/components/textarea/__tests__/textarea.test.tsx b/packages/paste-core/components/textarea/__tests__/textarea.test.tsx
index 347dda2ba4..2e88e8b809 100644
--- a/packages/paste-core/components/textarea/__tests__/textarea.test.tsx
+++ b/packages/paste-core/components/textarea/__tests__/textarea.test.tsx
@@ -6,7 +6,7 @@ import {HelpText} from '@twilio-paste/help-text';
// @ts-ignore typescript doesn't like js imports
import axe from '../../../../../.jest/axe-helper';
import {TextArea} from '../src';
-import {CustomizedTextarea} from '../stories/textarea.stories';
+import {CustomizedTextarea, MultipleTextareas} from '../stories/textarea.stories';
const NOOP = (): void => {};
@@ -23,6 +23,11 @@ describe('TextArea render', () => {
expect(getByRole('textbox')).not.toBeNull();
});
+ it('should render a hidden textarea', () => {
+ render();
+ expect(screen.getAllByRole('textbox', {hidden: true})).toHaveLength(2);
+ });
+
it('should render as readOnly', () => {
const {getByRole} = render();
expect(getByRole('textbox').getAttribute('aria-readOnly')).toBeTruthy();
@@ -73,6 +78,27 @@ describe('TextArea render', () => {
});
});
+describe('Multiple textareas', () => {
+ it('handles adding and removing multiple textareas', () => {
+ render();
+ const pushButton = screen.getByRole('button', {name: 'Push textarea'});
+ const popButton = screen.getByRole('button', {name: 'Pop textarea'});
+ const toggleButton = screen.getByRole('button', {name: 'Toggle textarea visibility'});
+
+ expect(screen.getAllByRole('textbox', {hidden: true})).toHaveLength(2);
+
+ fireEvent.click(pushButton);
+ fireEvent.click(pushButton);
+ expect(screen.getAllByRole('textbox', {hidden: true})).toHaveLength(6);
+
+ fireEvent.click(popButton);
+ expect(screen.getAllByRole('textbox', {hidden: true})).toHaveLength(4);
+
+ fireEvent.click(toggleButton);
+ expect(screen.queryAllByRole('textbox', {hidden: true})).toHaveLength(0);
+ });
+});
+
describe('Textarea event handlers', () => {
it('Should call the appropriate event handlers', () => {
const onChangeMock: jest.Mock = jest.fn();
diff --git a/packages/paste-core/components/textarea/package.json b/packages/paste-core/components/textarea/package.json
index 25f5c5e514..bbf092a602 100644
--- a/packages/paste-core/components/textarea/package.json
+++ b/packages/paste-core/components/textarea/package.json
@@ -24,13 +24,11 @@
"clean": "rm -rf ./dist",
"tsc": "tsc"
},
- "dependencies": {
- "react-autosize-textarea": "7.1.0"
- },
"peerDependencies": {
"@twilio-paste/box": "^6.0.0",
"@twilio-paste/design-tokens": "^7.0.0",
"@twilio-paste/input-box": "^6.0.0",
+ "@twilio-paste/react-textarea-autosize-library": "^0.0.1",
"@twilio-paste/style-props": "^5.0.0",
"@twilio-paste/styling-library": "^1.0.0",
"@twilio-paste/theme": "^7.0.0",
@@ -44,6 +42,7 @@
"@twilio-paste/box": "^6.0.0",
"@twilio-paste/design-tokens": "^7.1.1",
"@twilio-paste/input-box": "^6.0.0",
+ "@twilio-paste/react-textarea-autosize-library": "^0.0.1",
"@twilio-paste/style-props": "^5.0.0",
"@twilio-paste/styling-library": "^1.0.0",
"@twilio-paste/theme": "^7.0.0",
diff --git a/packages/paste-core/components/textarea/src/TextArea.tsx b/packages/paste-core/components/textarea/src/TextArea.tsx
index 95387535c4..64f33be29c 100644
--- a/packages/paste-core/components/textarea/src/TextArea.tsx
+++ b/packages/paste-core/components/textarea/src/TextArea.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
-import TextareaAutosize from 'react-autosize-textarea';
+import TextareaAutosize from '@twilio-paste/react-textarea-autosize-library';
import {styled, css} from '@twilio-paste/styling-library';
import {safelySpreadBoxProps, getCustomElementStyles} from '@twilio-paste/box';
import type {BoxProps} from '@twilio-paste/box';
@@ -20,11 +20,13 @@ export interface TextAreaProps extends React.TextareaHTMLAttributes(
(props) =>
@@ -40,13 +42,13 @@ const TextAreaElement = styled(TextareaAutosize)(
fontSize: 'fontSize30',
fontWeight: 'fontWeightMedium',
lineHeight: 'lineHeight20',
- maxHeight: 'size30',
+ maxHeight: props.resize === 'vertical' ? 'none' : 'size30',
outline: 'none',
paddingBottom: 'space30',
paddingLeft: 'space40',
paddingRight: 'space40',
paddingTop: 'space30',
- resize: 'vertical',
+ resize: props.resize,
width: '100%',
'&::placeholder': {
@@ -80,6 +82,7 @@ const TextArea = React.forwardRef(
insertAfter,
readOnly,
variant,
+ resize = 'none',
// size, height and width should not be passed down
size,
height,
@@ -100,7 +103,6 @@ const TextArea = React.forwardRef(
>
(
readOnly={readOnly}
ref={ref}
rows={3}
+ minRows={3}
spellCheck
+ resize={resize}
variant={variant}
>
{children}
diff --git a/packages/paste-core/components/textarea/stories/textarea.stories.tsx b/packages/paste-core/components/textarea/stories/textarea.stories.tsx
index 4781942690..53d620e92a 100644
--- a/packages/paste-core/components/textarea/stories/textarea.stories.tsx
+++ b/packages/paste-core/components/textarea/stories/textarea.stories.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import {useUID} from '@twilio-paste/uid-library';
import {action} from '@storybook/addon-actions';
import {Anchor} from '@twilio-paste/anchor';
+import {Button} from '@twilio-paste/button';
import {Box} from '@twilio-paste/box';
import {Text} from '@twilio-paste/text';
import {InformationIcon} from '@twilio-paste/icons/esm/InformationIcon';
@@ -18,23 +19,77 @@ export default {
component: TextArea,
};
-export const Textarea = (): React.ReactNode => {
+export const Textarea = (): React.ReactElement => {
const uid = useUID();
return (
<>
- Info that helps a user with this field.
+ Info that helps a user with this field.
>
);
};
+export const MultipleTextareas = (): React.ReactElement => {
+ const [shouldDisplayTextareas, setShouldDisplayTextareas] = React.useState(true);
+ const [textareas, setTextareas] = React.useState([]);
+
+ const push = (): void => {
+ setTextareas([...textareas, ]);
+ };
+
+ const pop = (): void => {
+ const updated = [...textareas];
+ updated.shift();
+ setTextareas(updated);
+ };
+
+ React.useEffect(() => {
+ push();
+ }, []);
+
+ return (
+ <>
+ {shouldDisplayTextareas && (
+
+ {textareas.map((textarea, i) => (
+ {textarea}
+ ))}
+
+ )}
+
+
+
+
+
+ >
+ );
+};
+
+MultipleTextareas.story = {
+ parameters: {
+ chromatic: {disableSnapshot: true},
+ },
+};
+
export const TextareaInverse = (): React.ReactNode => {
const uid = useUID();
return (
@@ -45,12 +100,15 @@ export const TextareaInverse = (): React.ReactNode => {
- Info that helps a user with this field.
+
+ Info that helps a user with this field.
+
);
};
@@ -59,6 +117,31 @@ TextareaInverse.story = {
name: 'Textarea - inverse',
};
+export const TextareaResizeVertical = (): React.ReactNode => {
+ const uid = useUID();
+ return (
+ <>
+
+
+ Info that helps a user with this field.
+ >
+ );
+};
+
+TextareaResizeVertical.story = {
+ name: 'Textarea - Resize Vertical',
+};
+
export const TextareaRequired = (): React.ReactNode => {
const uid = useUID();
return (
@@ -70,11 +153,12 @@ export const TextareaRequired = (): React.ReactNode => {
id={uid}
placeholder="Placeholder"
required
+ aria-describedby={`help-text-${uid}`}
onChange={action('handleFocus')}
onFocus={action('handleFocus')}
onBlur={action('handleBlur')}
/>
- Info that helps a user with this field.
+ Info that helps a user with this field.
>
);
};
@@ -94,12 +178,15 @@ export const TextareaRequiredInverse = (): React.ReactNode => {
id={uid}
placeholder="Placeholder"
required
+ aria-describedby={`help-text-${uid}`}
onChange={action('handleFocus')}
onFocus={action('handleFocus')}
onBlur={action('handleBlur')}
variant="inverse"
/>
- Info that helps a user with this field.
+
+ Info that helps a user with this field.
+
);
};
@@ -117,11 +204,14 @@ export const TextareaError = (): React.ReactNode => {
id={uid}
placeholder="Placeholder"
hasError
+ aria-describedby={`help-text-${uid}`}
onChange={action('handleFocus')}
onFocus={action('handleFocus')}
onBlur={action('handleBlur')}
/>
- Error info. Explains why the input has an error.
+
+ Error info. Explains why the input has an error.
+
>
);
};
@@ -141,12 +231,15 @@ export const TextareaErrorInverse = (): React.ReactNode => {
id={uid}
placeholder="Placeholder"
hasError
+ aria-describedby={`help-text-${uid}`}
onChange={action('handleFocus')}
onFocus={action('handleFocus')}
onBlur={action('handleBlur')}
variant="inverse"
/>
- Error info. Explains why the input has an error.
+
+ Error info. Explains why the input has an error.
+
);
};
@@ -165,13 +258,14 @@ export const TextareaDisabled = (): React.ReactNode => {
- Info that helps a user with this field.
+ Info that helps a user with this field.
>
);
};
@@ -190,6 +284,7 @@ export const TextareaDisabledInverse = (): React.ReactNode => {