addons/isl/src/CommitInfoView/MinHeightTextField.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {TextAreaProps} from 'isl-components/TextArea';
b69ab319
b69ab3110import * as stylex from '@stylexjs/stylex';
b69ab3111import {TextArea} from 'isl-components/TextArea';
b69ab3112import {forwardRef, useEffect, type ForwardedRef} from 'react';
b69ab3113import {notEmpty} from 'shared/utils';
b69ab3114import {assert} from '../utils';
b69ab3115
b69ab3116const styles = stylex.create({
b69ab3117 minHeight: {
b69ab3118 overflow: 'hidden',
b69ab3119 minHeight: '26px',
b69ab3120 },
b69ab3121});
b69ab3122
b69ab3123/**
b69ab3124 * Wrap `TextArea` to auto-resize to minimum height and optionally disallow newlines.
b69ab3125 * Like a `TextField` that has text wrap inside.
b69ab3126 */
b69ab3127export const MinHeightTextField = forwardRef(
b69ab3128 (
b69ab3129 props: TextAreaProps & {
b69ab3130 onInput: (event: {currentTarget: HTMLTextAreaElement}) => unknown;
b69ab3131 keepNewlines?: boolean;
b69ab3132 xstyle?: stylex.StyleXStyles;
b69ab3133 containerXstyle?: stylex.StyleXStyles;
b69ab3134 },
b69ab3135 ref: ForwardedRef<HTMLTextAreaElement>,
b69ab3136 ) => {
b69ab3137 const {onInput, keepNewlines, xstyle, ...rest} = props;
b69ab3138
b69ab3139 // ref could also be a callback ref; don't bother supporting that right now.
b69ab3140 assert(typeof ref === 'object', 'MinHeightTextArea requires ref object');
b69ab3141
b69ab3142 // whenever the value is changed, recompute & apply the minimum height
b69ab3143 useEffect(() => {
b69ab3144 const textarea = ref?.current;
b69ab3145 if (textarea) {
b69ab3146 const resize = () => {
b69ab3147 textarea.style.height = '';
b69ab3148 const scrollheight = textarea.scrollHeight;
b69ab3149 textarea.style.height = `${scrollheight}px`;
b69ab3150 textarea.rows = 1;
b69ab3151 };
b69ab3152 resize();
b69ab3153 const obs = new ResizeObserver(resize);
b69ab3154 obs.observe(textarea);
b69ab3155 return () => obs.unobserve(textarea);
b69ab3156 }
b69ab3157 }, [props.value, ref]);
b69ab3158
b69ab3159 return (
b69ab3160 <TextArea
b69ab3161 ref={ref}
b69ab3162 {...rest}
b69ab3163 xstyle={[styles.minHeight, xstyle].filter(notEmpty)}
b69ab3164 onInput={e => {
b69ab3165 const newValue = e.currentTarget?.value;
b69ab3166 const result = keepNewlines
b69ab3167 ? newValue
b69ab3168 : // remove newlines so this acts like a textField rather than a textArea
b69ab3169 newValue.replace(/(\r|\n)/g, '');
b69ab3170 onInput({
b69ab3171 currentTarget: {
b69ab3172 value: result,
b69ab3173 } as HTMLTextAreaElement,
b69ab3174 });
b69ab3175 }}
b69ab3176 />
b69ab3177 );
b69ab3178 },
b69ab3179);