Skip to content

Commit

Permalink
Merge pull request #6887 from Sage/FE-6726
Browse files Browse the repository at this point in the history
fix(textarea): add minheight prop to Textarea
  • Loading branch information
mihai-albu-sage committed Aug 30, 2024
2 parents 176141f + f6ee72e commit e9fc9a6
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 14 deletions.
5 changes: 5 additions & 0 deletions src/components/textarea/textarea-test.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ export default {
type: "boolean",
},
},
minHeight: {
control: {
type: "number",
},
},
},
args: {
expandable: false,
Expand Down
31 changes: 20 additions & 11 deletions src/components/textarea/textarea.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Input from "../../__internal__/input/input.component";
import { InputBehaviour } from "../../__internal__/input-behaviour";
import InputIconToggle from "../../__internal__/input-icon-toggle";
import guid from "../../__internal__/utils/helpers/guid";
import StyledTextarea, { MIN_HEIGHT } from "./textarea.style";
import StyledTextarea, { DEFAULT_MIN_HEIGHT } from "./textarea.style";
import { TooltipProvider } from "../../__internal__/tooltip-provider";
import useInputAccessibility from "../../hooks/__internal__/useInputAccessibility";
import NewValidationContext from "../carbon-provider/__internal__/new-validation.context";
Expand Down Expand Up @@ -129,6 +129,8 @@ export interface TextareaProps
borderRadius?: BorderRadiusType | BorderRadiusType[];
/** Hides the borders for the component. Please note that validation and focus styling will still be applied */
hideBorders?: boolean;
/** Specify the minimum height */
minHeight?: number;
}

let deprecateUncontrolledWarnTriggered = false;
Expand Down Expand Up @@ -177,11 +179,15 @@ export const Textarea = React.forwardRef(
hideBorders = false,
required,
isOptional,
minHeight = DEFAULT_MIN_HEIGHT,
...rest
}: TextareaProps,
ref: React.ForwardedRef<HTMLTextAreaElement>
) => {
const { validationRedesignOptIn } = useContext(NewValidationContext);
const [textareaMinHeight, setTextareaMinHeight] = useState(
DEFAULT_MIN_HEIGHT
);
const computeLabelPropValues = <T,>(prop: T): undefined | T =>
validationRedesignOptIn ? undefined : prop;

Expand Down Expand Up @@ -243,14 +249,12 @@ export const Textarea = React.forwardRef(
warnBorderRadiusArrayTooLarge = true;
}

const minHeight = useRef(MIN_HEIGHT);

const expandTextarea = () => {
const expandTextarea = useCallback(() => {
const textarea = internalRef.current;

if (
textarea?.scrollHeight &&
textarea?.scrollHeight > minHeight.current
textarea?.scrollHeight > textareaMinHeight
) {
// need to reset scroll position of the nearest parent which scrolls
let scrollElement: HTMLElement | null = textarea;
Expand All @@ -264,14 +268,14 @@ export const Textarea = React.forwardRef(
// Set the height so all content is shown
textarea.style.height = `${Math.max(
textarea.scrollHeight,
minHeight.current
textareaMinHeight
)}px`;

if (scrollElement && scrollPosition) {
scrollElement.scrollTop = scrollPosition;
}
}
};
}, [textareaMinHeight]);

const {
labelId,
Expand All @@ -296,9 +300,13 @@ export const Textarea = React.forwardRef(

useEffect(() => {
if (rows) {
minHeight.current = internalRef?.current?.scrollHeight || 0;
setTextareaMinHeight(internalRef?.current?.scrollHeight || 0);
} else {
setTextareaMinHeight(
minHeight > DEFAULT_MIN_HEIGHT ? minHeight : DEFAULT_MIN_HEIGHT
);
}
}, [rows]);
}, [minHeight, rows]);

useEffect(() => {
if (expandable) {
Expand All @@ -309,7 +317,7 @@ export const Textarea = React.forwardRef(
useEffect(() => {
if (expandable) {
window.addEventListener("resize", expandTextarea);
minHeight.current = internalRef?.current?.clientHeight || 0;
setTextareaMinHeight(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);
Expand All @@ -321,7 +329,7 @@ export const Textarea = React.forwardRef(
document.fonts?.removeEventListener("loadingdone", expandTextarea);
}
};
}, [expandable]);
}, [expandTextarea, expandable]);

const hasIconInside = !!(inputIcon || (validationId && !validationOnLabel));

Expand Down Expand Up @@ -400,6 +408,7 @@ export const Textarea = React.forwardRef(
data-role={dataRole}
data-element={dataElement}
hasIcon={hasIconInside}
minHeight={textareaMinHeight}
{...marginProps}
>
<FormField
Expand Down
7 changes: 4 additions & 3 deletions src/components/textarea/textarea.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import StyledInput from "../../__internal__/input/input.style";
import { StyledLabelContainer } from "../../__internal__/label/label.style";
import InputIconToggleStyle from "../../__internal__/input-icon-toggle/input-icon-toggle.style";
import BaseTheme from "../../style/themes/base";
import { TextareaProps } from "./textarea.component";

export const MIN_HEIGHT = 64;
export const DEFAULT_MIN_HEIGHT = 64;

export interface StyledTextAreaProps {
export interface StyledTextAreaProps extends Pick<TextareaProps, "minHeight"> {
/** When true, label is placed in line an input */
labelInline?: boolean;
/** When true, adjusts padding for icon */
Expand All @@ -21,7 +22,7 @@ const StyledTextarea = styled.div<StyledTextAreaProps>`
${StyledInput} {
box-sizing: border-box;
resize: none;
min-height: ${MIN_HEIGHT}px;
${({ minHeight }) => `min-height: ${minHeight || DEFAULT_MIN_HEIGHT}px;`}
padding: var(--spacing150) var(--spacing200);
${({ hasIcon }) => hasIcon && "padding-right: var(--spacing500)"}
Expand Down
16 changes: 16 additions & 0 deletions src/components/textarea/textarea.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ test.each([
}
);

test("should have default min-height of 64px if no minHeight is specified", () => {
render(<Textarea value="Initial content" />);

const textarea = screen.getByRole("textbox");

expect(textarea).toHaveStyle({ "min-height": `64px` });
});

test("should apply the corect min-height if minHeight is specified", () => {
render(<Textarea minHeight={200} value="Initial content" />);

const textarea = screen.getByRole("textbox");

expect(textarea).toHaveStyle({ "min-height": "200px" });
});

test.each([
"enter",
"done",
Expand Down

0 comments on commit e9fc9a6

Please sign in to comment.