Skip to content

Commit

Permalink
Merge pull request #6161 from Sage/FE-6001-controlled-input-scroll
Browse files Browse the repository at this point in the history
fix(textarea): stop unwanted scroll of container when expandable
  • Loading branch information
robinzigmond authored Aug 3, 2023
2 parents a5996e1 + 75d5524 commit f2899f5
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 2 deletions.
18 changes: 17 additions & 1 deletion cypress/components/textarea/textarea.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
/* eslint-disable jest/valid-expect */
import React from "react";
import { TextareaProps } from "components/textarea";
import { TextareaComponent } from "../../../src/components/textarea/textarea-test.stories";
import {
TextareaComponent,
InScrollableContainer,
} from "../../../src/components/textarea/textarea-test.stories";
import Box from "../../../src/components/box";
import * as stories from "../../../src/components/textarea/textarea.stories";
import CypressMountWithProviders from "../../support/component-helper/cypress-mount";
Expand Down Expand Up @@ -762,4 +765,17 @@ context("Tests for Textarea component", () => {
CypressMountWithProviders(<TextareaComponent />);
getElement("input").parent().should("have.css", "border-radius", "4px");
});

it("should not change the scroll position of a scrollable container when typing", () => {
CypressMountWithProviders(<InScrollableContainer />);

cy.get('[data-element="form-content"]').scrollTo("bottom");
textareaChildren()
.click({ force: true })
.type("{rightArrow}foo", { force: true });
// eslint-disable-next-line jest/valid-expect-in-promise
cy.get('[data-element="form-content"]').then(($el) =>
expect($el.scrollTop()).greaterThan(1000)
);
});
});
43 changes: 42 additions & 1 deletion src/components/textarea/textarea-test.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
/* eslint-disable react/prop-types */
import React, { useState } from "react";
import Textarea, { TextareaProps } from ".";
import Dialog from "../dialog";
import Form from "../form";
import Button from "../button";

interface TextareaTestProps extends TextareaProps {
labelHelp?: string;
}

export default {
title: "Textarea/Test",
includeStories: ["Default"],
includeStories: ["Default", "InScrollableContainer"],
parameters: {
info: { disable: true },
chromatic: {
Expand Down Expand Up @@ -155,3 +158,41 @@ export const TextareaComponent = (props: Partial<TextareaProps>) => {
<Textarea label="Textarea" value={state} onChange={setValue} {...props} />
);
};

export const InScrollableContainer = () => {
const [isOpen, setIsOpen] = useState(true);

const [value, setValue] = useState(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Faucibus in ornare quam viverra orci sagittis eu. Pellentesque nec nam aliquam sem et tortor consequat. Nibh sit amet commodo nulla. Cursus metus aliquam eleifend mi. Mi proin sed libero enim sed faucibus turpis in. Ullamcorper velit sed ullamcorper morbi tincidunt ornare massa eget. Est lorem ipsum dolor sit amet consectetur. Morbi enim nunc faucibus a pellentesque sit. Ultrices neque ornare aenean euismod elementum nisi quis eleifend quam. Dapibus ultrices in iaculis nunc sed augue lacus viverra. Feugiat vivamus at augue eget arcu dictum varius. Eget velit aliquet sagittis id consectetur purus ut faucibus. Tincidunt arcu non sodales neque sodales. Ipsum faucibus vitae aliquet nec ullamcorper sit. Faucibus a pellentesque sit amet. Amet porttitor eget dolor morbi non. Arcu non odio euismod lacinia at quis risus sed vulputate. Blandit volutpat maecenas volutpat blandit. Purus ut faucibus pulvinar elementum integer enim neque. Viverra mauris in aliquam sem fringilla ut morbi. Amet mattis vulputate enim nulla aliquet porttitor lacus luctus accumsan. Nibh mauris cursus mattis molestie a. Turpis nunc eget lorem dolor sed viverra ipsum nunc aliquet. Facilisis mauris sit amet massa vitae tortor condimentum lacinia. Consequat mauris nunc congue nisi vitae. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Eu facilisis sed odio morbi quis commodo. Ultrices vitae auctor eu augue ut lectus arcu. Ut tellus elementum sagittis vitae et leo duis ut. Sapien eget mi proin sed libero. Dictum non consectetur a erat nam at. Suspendisse interdum consectetur libero id faucibus nisl tincidunt eget nullam. Pretium fusce id velit ut tortor pretium. Donec pretium vulputate sapien nec sagittis aliquam malesuada. Semper quis lectus nulla at volutpat diam.Velit dignissim sodales ut eu sem integer. In massa tempor nec feugiat nisl pretium fusce id. Eu scelerisque felis imperdiet proin fermentum. Amet purus gravida quis blandit. Feugiat in fermentum posuere urna nec tincidunt praesent. Sit amet mauris commodo quis. Lorem sed risus ultricies tristique nulla aliquet enim tortor. Rhoncus aenean vel elit scelerisque mauris pellentesque pulvinar pellentesque. Pellentesque pulvinar pellentesque habitant morbi tristique senectus. Nibh sit amet commodo nulla facilisi nullam vehicula ipsum. Pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat. Velit laoreet id donec ultrices tincidunt arcu non sodales neque. A scelerisque purus semper eget duis. Ut faucibus pulvinar elementum integer enim neque. Integer feugiat scelerisque varius morbi enim nunc faucibus a. Amet nulla facilisi morbi tempus iaculis urna id volutpat lacus. Egestas purus viverra accumsan in nisl nisi. Sed turpis tincidunt id aliquet risus feugiat in ante. In mollis nunc sed id semper risus in hendrerit gravida. Faucibus a pellentesque sit amet porttitor eget dolor morbi. Ornare arcu dui vivamus arcu felis bibendum ut. Tempor commodo ullamcorper a lacus vestibulum sed arcu non odio. Lacinia quis vel eros donec ac odio. Amet volutpat consequat mauris nunc congue nisi vitae. Ultrices dui sapien eget mi proin sed. Adipiscing bibendum est ultricies integer quis auctor elit. Sagittis nisl rhoncus mattis rhoncus urna neque. Integer enim neque volutpat ac tincidunt. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor."
);

return (
<Dialog
open={isOpen}
onCancel={() => setIsOpen(false)}
title="Title"
subtitle="Subtitle"
size="small"
>
<Form
stickyFooter
height="300px"
leftSideButtons={
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
}
saveButton={
<Button buttonType="primary" type="submit">
Save
</Button>
}
>
<Textarea
value={value}
onChange={({ target }) => setValue(target.value)}
expandable
rows={5}
/>
</Form>
</Dialog>
);
};
16 changes: 16 additions & 0 deletions src/components/textarea/textarea.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,24 @@ export const Textarea = React.forwardRef(
textarea?.scrollHeight &&
textarea?.scrollHeight > minHeight.current
) {
// need to reset scroll position of the nearest parent which scrolls
let scrollElement: HTMLElement | null = textarea;
while (scrollElement && !scrollElement?.scrollTop) {
scrollElement = scrollElement?.parentElement || null;
}

const scrollPosition = scrollElement?.scrollTop;

textarea.style.height = "0px";
// Set the height so all content is shown
textarea.style.height = `${Math.max(
textarea.scrollHeight,
minHeight.current
)}px`;

if (scrollElement && scrollPosition) {
scrollElement.scrollTop = scrollPosition;
}
}
};

Expand Down Expand Up @@ -265,11 +277,15 @@ export const Textarea = React.forwardRef(
if (expandable) {
window.addEventListener("resize", expandTextarea);
minHeight.current = internalRef?.current?.clientHeight || 0;
// need to also run expandTextarea when the Sage UI font completes loading, to prevent strange scroll
// behaviour when it only loads after the component is rendered
document.fonts?.addEventListener("loadingdone", expandTextarea);
}

return () => {
if (expandable) {
window.removeEventListener("resize", expandTextarea);
document.fonts?.removeEventListener("loadingdone", expandTextarea);
}
};
}, [expandable]);
Expand Down
37 changes: 37 additions & 0 deletions src/components/textarea/textarea.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,43 @@ describe("componentWillUnmount", () => {
expect.any(Function)
);
});

// TODO: test should be removed once FE-5551 is implemented. This test is only for coverage,
// it still passes even when the bug is reintroduced, as the bug only happens in a real browser
// environment, not in JSDOM.
describe("when a parent container scrolls vertically", () => {
it("restores the scroll position after expanding", () => {
const wrapper = mount(
<div
id="scroll-wrapper"
style={{ height: "200px", overflowY: "scroll" }}
>
<div id="inner-wrapper" style={{ height: "1000px" }}>
<Textarea
value="foo"
name="textarea"
onChange={jest.fn()}
label="Label"
expandable
rows={10}
/>
</div>
</div>
);

const scrollWrapper = wrapper.find("#scroll-wrapper").getDOMNode();
const textarea = wrapper.find("textarea").getDOMNode();

scrollWrapper.scrollTop = 700;
jest
.spyOn(textarea, "scrollHeight", "get")
.mockImplementation(() => 500);

window.dispatchEvent(new Event("resize"));

expect(scrollWrapper.scrollTop).toBe(700);
});
});
});

describe("when textarea cannot be expanded", () => {
Expand Down

0 comments on commit f2899f5

Please sign in to comment.