Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
76 changes: 73 additions & 3 deletions src/lib/components/textarea/TextArea.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import {
forwardRef,
TextareaHTMLAttributes,
ForwardedRef,
useEffect,
useRef,
useImperativeHandle,
useCallback,
} from 'react';
import styled, { css } from 'styled-components';
import { spacing } from '../../spacing';
Expand All @@ -12,13 +16,19 @@ type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
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;

const TextAreaContainer = styled.textarea<{
variant: TextAreaVariant;
width?: CSSProperties['width'];
height?: CSSProperties['height'];
autoGrow?: boolean;
}>`
padding: ${spacing.r12} ${spacing.r8};
border-radius: 4px;
Expand Down Expand Up @@ -46,6 +56,12 @@ const TextAreaContainer = styled.textarea<{
height: ${props.height};
`}

${(props) =>
props.autoGrow &&
css`
overflow: hidden;
`}

&:placeholder-shown {
font-style: italic;
}
Expand Down Expand Up @@ -77,18 +93,68 @@ 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<RefType>,
) {
const internalRef = useRef<HTMLTextAreaElement>(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<HTMLTextAreaElement>) => {
if (autoGrow) {
adjustHeight();
}
if (onChange) {
onChange(event);
}
},
[autoGrow, adjustHeight, onChange],
);

if (width || height) {
return (
<TextAreaContainer
className="sc-textarea"
width={width}
height={height}
variant={variant}
autoGrow={autoGrow}
value={value}
defaultValue={defaultValue}
onChange={handleChange}
{...rest}
ref={ref}
ref={internalRef}
/>
);
}
Expand All @@ -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}
/>
);
}
Expand Down
58 changes: 56 additions & 2 deletions stories/textarea.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
};

Expand Down Expand Up @@ -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 - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: myapp
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
env:
- name: DATABASE_URL
value: "postgresql://user:password@localhost:5432/db"
- name: API_KEY
valueFrom:
secretKeyRef:
name: api-secret
key: api-key
EOF`,
width: '600px',
},
};
Loading