import * as stylex from "@stylexjs/stylex"; import { type Ref, memo, useCallback, useEffect, useImperativeHandle, useRef, } from "react"; import { controlColor } from "./theme.stylex"; import { font, size } from "./tokens.stylex"; /** * Properties and some behavior of this TextArea is based on the Text Field of MUI: * https://mui.com/material-ui/react-text-field/ */ export interface TextAreaProps { // Commonly used text props name?: string; placeholder?: string; disabled?: boolean; required?: boolean; maxLength?: number; minLength?: number; title?: string; autoFocus?: boolean; defaultValue?: string; /** If set, height will be 100% */ fullHeight?: boolean; /** Auto-grow is default. However, it is only best-effort (only works in browsers that support `field-sizing`). This prop turns it off completely. */ noAutoGrow?: boolean; /** `value` and `onChange` are optional because we want to be able to use the control in an uncontrolled manner as well (for example, in forms) */ value?: string; onChange?: (value: string) => void; ref?: Ref<{ focus: () => void }>; } const styles = stylex.create({ area: { resize: "vertical", borderWidth: "1px", borderStyle: "solid", borderColor: { default: controlColor.inputBorder, ":focus": controlColor.inputActiveBorder, // https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes ":user-invalid": controlColor.inputInvalidBorder, ":disabled": controlColor.inputDisabledBorder, }, borderRadius: 0, color: { default: controlColor.inputColor, ":focus": controlColor.inputActiveColor, ":disabled": controlColor.inputDisabledColor, }, padding: `${size.px2} ${size.px3}`, fontSize: size.rem4, fontWeight: 400, fontFamily: font.main, width: "100%", "::placeholder": { color: controlColor.inputPlaceholderColor, }, background: { default: controlColor.inputBackground, ":focus": controlColor.inputActiveBackground, ":disabled": controlColor.inputDisabledBackground, }, cursor: { ":disabled": "not-allowed", }, outline: { ":focus": "none", }, fieldSizing: "content", }, fullHeight: { height: "100%", }, noAutoGrow: { fieldSizing: "content", }, }); /** * @remarks Currently does not support submitting a form by pressing `Ctrl+Enter`. * TODO: Support this if the need arises: https://stackoverflow.com/q/1684196 */ export default memo(function TextArea(props: TextAreaProps) { const onChange = useCallback( (e: React.ChangeEvent) => { props.onChange?.(e.target.value); }, [props.onChange], ); const innerRef = useRef(null); useImperativeHandle( props.ref, () => ({ focus: () => innerRef.current?.focus(), }), [], ); useEffect(() => { const r = innerRef.current; if (!r) { return; } const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { const target = e.currentTarget as HTMLTextAreaElement | null; // If there are any issues, see: https://gist.github.com/KacperKozak/9736160 // When using .submit(), this error is thrown: // > A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. target?.form?.requestSubmit(); e.stopPropagation(); e.preventDefault(); } }; r.addEventListener("keydown", handler); return () => r.removeEventListener("keydown", handler); }, []); return (