2.3 KB80 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {TextAreaProps} from 'isl-components/TextArea';
9
10import * as stylex from '@stylexjs/stylex';
11import {TextArea} from 'isl-components/TextArea';
12import {forwardRef, useEffect, type ForwardedRef} from 'react';
13import {notEmpty} from 'shared/utils';
14import {assert} from '../utils';
15
16const styles = stylex.create({
17 minHeight: {
18 overflow: 'hidden',
19 minHeight: '26px',
20 },
21});
22
23/**
24 * Wrap `TextArea` to auto-resize to minimum height and optionally disallow newlines.
25 * Like a `TextField` that has text wrap inside.
26 */
27export const MinHeightTextField = forwardRef(
28 (
29 props: TextAreaProps & {
30 onInput: (event: {currentTarget: HTMLTextAreaElement}) => unknown;
31 keepNewlines?: boolean;
32 xstyle?: stylex.StyleXStyles;
33 containerXstyle?: stylex.StyleXStyles;
34 },
35 ref: ForwardedRef<HTMLTextAreaElement>,
36 ) => {
37 const {onInput, keepNewlines, xstyle, ...rest} = props;
38
39 // ref could also be a callback ref; don't bother supporting that right now.
40 assert(typeof ref === 'object', 'MinHeightTextArea requires ref object');
41
42 // whenever the value is changed, recompute & apply the minimum height
43 useEffect(() => {
44 const textarea = ref?.current;
45 if (textarea) {
46 const resize = () => {
47 textarea.style.height = '';
48 const scrollheight = textarea.scrollHeight;
49 textarea.style.height = `${scrollheight}px`;
50 textarea.rows = 1;
51 };
52 resize();
53 const obs = new ResizeObserver(resize);
54 obs.observe(textarea);
55 return () => obs.unobserve(textarea);
56 }
57 }, [props.value, ref]);
58
59 return (
60 <TextArea
61 ref={ref}
62 {...rest}
63 xstyle={[styles.minHeight, xstyle].filter(notEmpty)}
64 onInput={e => {
65 const newValue = e.currentTarget?.value;
66 const result = keepNewlines
67 ? newValue
68 : // remove newlines so this acts like a textField rather than a textArea
69 newValue.replace(/(\r|\n)/g, '');
70 onInput({
71 currentTarget: {
72 value: result,
73 } as HTMLTextAreaElement,
74 });
75 }}
76 />
77 );
78 },
79);
80