diff --git a/src/lib/components/textarea/TextArea.component.tsx b/src/lib/components/textarea/TextArea.component.tsx index b1a83f0309..ee739f53b2 100644 --- a/src/lib/components/textarea/TextArea.component.tsx +++ b/src/lib/components/textarea/TextArea.component.tsx @@ -3,6 +3,10 @@ import { forwardRef, TextareaHTMLAttributes, ForwardedRef, + useEffect, + useRef, + useImperativeHandle, + useCallback, } from 'react'; import styled, { css } from 'styled-components'; import { spacing } from '../../spacing'; @@ -12,6 +16,11 @@ type Props = TextareaHTMLAttributes & { variant?: TextAreaVariant; width?: CSSProperties['width']; height?: CSSProperties['height']; + /** + * Automatically adjust height to fit content + * When enabled, the textarea will grow/shrink to show all content + */ + autoGrow?: boolean; }; type RefType = HTMLTextAreaElement | null; @@ -19,6 +28,7 @@ const TextAreaContainer = styled.textarea<{ variant: TextAreaVariant; width?: CSSProperties['width']; height?: CSSProperties['height']; + autoGrow?: boolean; }>` padding: ${spacing.r12} ${spacing.r8}; border-radius: 4px; @@ -46,6 +56,12 @@ const TextAreaContainer = styled.textarea<{ height: ${props.height}; `} + ${(props) => + props.autoGrow && + css` + overflow: hidden; + `} + &:placeholder-shown { font-style: italic; } @@ -77,9 +93,55 @@ const TextAreaContainer = styled.textarea<{ `; function TextAreaElement( - { rows = 3, cols = 20, width, height, variant = 'code', ...rest }: Props, + { + rows = 3, + cols = 20, + width, + height, + variant = 'code', + autoGrow = false, + value, + defaultValue, + onChange, + ...rest + }: Props, ref: ForwardedRef, ) { + const internalRef = useRef(null); + + // Expose the textarea element to parent components via forwarded ref + useImperativeHandle(ref, () => internalRef.current as HTMLTextAreaElement); + + // Adjust height on mount and when value changes (for controlled components) + const adjustHeight = useCallback(() => { + const textarea = internalRef.current; + if (!textarea || !autoGrow) return; + + // Reset height to auto to get the correct scrollHeight + textarea.style.height = 'auto'; + + // Set the height to match the content + const newHeight = textarea.scrollHeight; + textarea.style.height = `${newHeight}px`; + }, [autoGrow]); + + useEffect(() => { + adjustHeight(); + }, [adjustHeight, value]); + + // Handle onChange to support both controlled and uncontrolled components + const handleChange = useCallback( + (event: React.ChangeEvent) => { + if (autoGrow) { + adjustHeight(); + } + if (onChange) { + onChange(event); + } + }, + [autoGrow, adjustHeight, onChange], + ); + if (width || height) { return ( ); } @@ -99,8 +165,12 @@ function TextAreaElement( rows={rows} cols={cols} variant={variant} + autoGrow={autoGrow} + value={value} + defaultValue={defaultValue} + onChange={handleChange} {...rest} - ref={ref} + ref={internalRef} /> ); } diff --git a/stories/textarea.stories.tsx b/stories/textarea.stories.tsx index 92f017098c..43db237693 100644 --- a/stories/textarea.stories.tsx +++ b/stories/textarea.stories.tsx @@ -32,14 +32,14 @@ export const Playground = {}; export const DefaultTextArea = { args: { - value: 'Some text', + defaultValue: 'Some text', }, }; export const TextVariantTextArea = { args: { variant: 'text', - value: 'Text area with "text" variant', + defaultValue: 'Text area with "text" variant', }, }; @@ -67,3 +67,57 @@ export const RowsAndColsSet = { placeholder: 'With rows = 20 and cols = 40', }, }; + +/** + * Auto-growing textarea adjusts its height based on content + * Perfect for displaying commands or long text where you want the entire content visible + * Simply set autoGrow={true} and the textarea will grow to show all content + */ +export const AutoGrowTextArea = { + args: { + autoGrow: true, + placeholder: + 'Type or paste content here...\nThe textarea will automatically grow to fit all the content.', + defaultValue: `docker run -d \\ + --name my-container \\ + -p 8080:80 \\ + -v /host/path:/container/path \\ + -e ENV_VAR=value \\ + my-image:latest`, + width: '500px', + }, +}; + +/** + * Auto-growing textarea with long command example + * The entire command is visible without scrolling + */ +export const AutoGrowWithLongCommand = { + args: { + autoGrow: true, + variant: 'code', + defaultValue: `kubectl apply -f - <