Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] feat(TextArea): new autoResize prop allows input to shrink vertically #6306

Merged
merged 5 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions packages/core/src/components/forms/textArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,18 @@ export interface TextAreaProps extends IntentProps, Props, React.TextareaHTMLAtt
*/
small?: boolean;

/**
* Whether the component should automatically resize as a user types in the text input.
* Please note that this will disable manual resizing.
*
* @default false
adidahiya marked this conversation as resolved.
Show resolved Hide resolved
*/
autoResize?: boolean;

/**
* Whether the text area should automatically grow vertically to accomodate content.
*
* @deprecated use the `autoResize` prop instead.
*/
growVertically?: boolean;

Expand Down Expand Up @@ -73,11 +83,14 @@ export class TextArea extends AbstractPureComponent<TextAreaProps, TextAreaState
);

private maybeSyncHeightToScrollHeight = () => {
if (this.props.growVertically && this.textareaElement != null) {
if (this.props.autoResize && this.textareaElement != null) {
adidahiya marked this conversation as resolved.
Show resolved Hide resolved
// set height to 0 to force scrollHeight to be the minimum height to fit
// the content of the textarea
this.textareaElement.style.height = "0px";

const { scrollHeight } = this.textareaElement;
if (scrollHeight > 0) {
this.setState({ height: scrollHeight });
}
this.textareaElement.style.height = scrollHeight.toString() + "px";
this.setState({ height: scrollHeight });
}
};

Expand All @@ -98,7 +111,7 @@ export class TextArea extends AbstractPureComponent<TextAreaProps, TextAreaState
}

public render() {
const { className, fill, inputRef, intent, large, small, growVertically, ...htmlProps } = this.props;
const { className, fill, inputRef, intent, large, small, autoResize, ...htmlProps } = this.props;

const rootClasses = classNames(
Classes.INPUT,
Expand All @@ -113,12 +126,13 @@ export class TextArea extends AbstractPureComponent<TextAreaProps, TextAreaState

// add explicit height style while preserving user-supplied styles if they exist
let { style = {} } = htmlProps;
if (growVertically && this.state.height != null) {
if (autoResize && this.state.height != null) {
// this style object becomes non-extensible when mounted (at least in the enzyme renderer),
// so we make a new one to add a property
style = {
...style,
height: `${this.state.height}px`,
resize: "none",
adidahiya marked this conversation as resolved.
Show resolved Hide resolved
};
}

Expand Down
51 changes: 50 additions & 1 deletion packages/core/test/forms/textAreaTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,53 @@ describe("<TextArea>", () => {
containerElement!.remove();
});

it("No manual resizes when autoResize enabled", () => {
const wrapper = mount(<TextArea autoResize={true} />, { attachTo: containerElement });
const textarea = wrapper.find("textarea");

textarea.simulate("change", { target: { scrollHeight: 500 } });

assert.notEqual(textarea.getDOMNode<HTMLElement>().style.height, "500px");
});

it("resizes with large initial input when autoResize enabled", () => {
const initialValue = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aenean finibus eget enim non accumsan.
Nunc lobortis luctus magna eleifend consectetur.
Suspendisse ut semper sem, quis efficitur felis.
Praesent suscipit nunc non semper tempor.
Sed eros sapien, semper sed imperdiet sed,
dictum eget purus. Donec porta accumsan pretium.
Fusce at felis mattis, tincidunt erat non, varius erat.`;
const wrapper = mount(<TextArea value={initialValue} autoResize={true} />, { attachTo: containerElement });
const textarea = wrapper.find("textarea");

const scrollHeightInPixels = `${textarea.getDOMNode<HTMLElement>().scrollHeight}px`;

assert.equal(textarea.getDOMNode<HTMLElement>().style.height, scrollHeightInPixels);
});

it("resizes with long text input when autoResize enabled", () => {
const initialValue = "A";
const nextValue = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aenean finibus eget enim non accumsan.
Nunc lobortis luctus magna eleifend consectetur.
Suspendisse ut semper sem, quis efficitur felis.
Praesent suscipit nunc non semper tempor.
Sed eros sapien, semper sed imperdiet sed,
dictum eget purus. Donec porta accumsan pretium.
Fusce at felis mattis, tincidunt erat non, varius erat.`;
const wrapper = mount(<TextArea value={initialValue} autoResize={true} />, { attachTo: containerElement });
const textarea = wrapper.find("textarea");

const scrollHeightInPixelsBefore = `${textarea.getDOMNode<HTMLElement>().scrollHeight}px`;
wrapper.setProps({ value: nextValue }).update();
const scrollHeightInPixelsAfter = `${textarea.getDOMNode<HTMLElement>().scrollHeight}px`;

assert.notEqual(scrollHeightInPixelsBefore, scrollHeightInPixelsAfter);
});

// coleary: growVertically deprecated as of 28/07/2023
// HACKHACK: skipped test, see https://github.com/palantir/blueprint/issues/5976
it.skip("can resize automatically", () => {
const wrapper = mount(<TextArea growVertically={true} />, { attachTo: containerElement });
Expand All @@ -59,7 +106,7 @@ describe("<TextArea>", () => {
});

it("doesn't clobber user-supplied styles", () => {
const wrapper = mount(<TextArea growVertically={true} style={{ marginTop: 10 }} />, {
const wrapper = mount(<TextArea autoResize={true} style={{ marginTop: 10 }} />, {
attachTo: containerElement,
});
const textarea = wrapper.find("textarea");
Expand All @@ -69,6 +116,7 @@ describe("<TextArea>", () => {
assert.equal(textarea.getDOMNode<HTMLElement>().style.marginTop, "10px");
});

// coleary: growVertically deprecated as of 28/07/2023
adidahiya marked this conversation as resolved.
Show resolved Hide resolved
// HACKHACK: skipped test, see https://github.com/palantir/blueprint/issues/5976
it.skip("can fit large initial content", () => {
const initialValue = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Expand Down Expand Up @@ -125,6 +173,7 @@ describe("<TextArea>", () => {
assert.instanceOf(textAreaNewRef.current, HTMLTextAreaElement);
});

// coleary: growVertically deprecated as of 28/07/2023
// HACKHACK: skipped test, see https://github.com/palantir/blueprint/issues/5976
it.skip("resizes when props change if growVertically is true", () => {
const initialText = "A";
Expand Down
10 changes: 5 additions & 5 deletions packages/docs-app/src/examples/core-examples/textAreaExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ const CONTROLLED_TEXT_TO_APPEND =
"The approach will not be easy. You are required to maneuver straight down this trench and skim the surface to this point. The target area is only two meters wide. It's a small thermal exhaust port, right below the main port. The shaft leads directly to the reactor system.";

interface TextAreaExampleState {
autoResize: boolean;
controlled: boolean;
disabled: boolean;
growVertically: boolean;
large: boolean;
readOnly: boolean;
small: boolean;
Expand All @@ -35,9 +35,9 @@ interface TextAreaExampleState {

export class TextAreaExample extends React.PureComponent<ExampleProps, TextAreaExampleState> {
public state: TextAreaExampleState = {
autoResize: false,
controlled: false,
disabled: false,
growVertically: true,
large: false,
readOnly: false,
small: false,
Expand All @@ -48,7 +48,7 @@ export class TextAreaExample extends React.PureComponent<ExampleProps, TextAreaE

private handleDisabledChange = handleBooleanChange(disabled => this.setState({ disabled }));

private handleGrowVerticallyChange = handleBooleanChange(growVertically => this.setState({ growVertically }));
private handleAutoResizeChange = handleBooleanChange(autoResize => this.setState({ autoResize }));

private handleLargeChange = handleBooleanChange(large => this.setState({ large, ...(large && { small: false }) }));

Expand Down Expand Up @@ -77,7 +77,7 @@ export class TextAreaExample extends React.PureComponent<ExampleProps, TextAreaE
}

private renderOptions() {
const { controlled, disabled, growVertically, large, readOnly, small } = this.state;
const { controlled, disabled, large, readOnly, small, autoResize } = this.state;
return (
<>
<H5>Appearance props</H5>
Expand All @@ -86,7 +86,7 @@ export class TextAreaExample extends React.PureComponent<ExampleProps, TextAreaE
<H5>Behavior props</H5>
<Switch label="Disabled" onChange={this.handleDisabledChange} checked={disabled} />
<Switch label="Read-only" onChange={this.handleReadOnlyChange} checked={readOnly} />
<Switch label="Grow vertically" onChange={this.handleGrowVerticallyChange} checked={growVertically} />
<Switch label="Auto resize" onChange={this.handleAutoResizeChange} checked={autoResize} />
<Switch label="Controlled usage" onChange={this.handleControlledChange} checked={controlled} />
<ControlGroup>
<AnchorButton
Expand Down