-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathTextArea.tsx
More file actions
126 lines (112 loc) · 4.43 KB
/
TextArea.tsx
File metadata and controls
126 lines (112 loc) · 4.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
import { hooks } from 'botframework-webchat-api';
import cx from 'classnames';
import React, {
forwardRef,
Fragment,
useCallback,
useRef,
type FormEventHandler,
type KeyboardEventHandler,
type MouseEventHandler
} from 'react';
import { boolean, custom, number, object, optional, pipe, readonly, string, type InferInput } from 'valibot';
import { reactNode, validateProps } from '@msinternal/botframework-webchat-react-valibot';
import styles from './TextArea.module.css';
const { useUIState } = hooks;
const TextAreaPropsSchema = pipe(
object({
'aria-describedby': optional(string()),
'aria-label': optional(string()),
'aria-labelledby': optional(string()),
className: optional(string()),
completion: optional(reactNode()),
'data-testid': optional(string()),
/**
* `true`, if the text area should be hidden but stay in the DOM, otherwise, `false`.
*
* Keeping the element in the DOM while making it invisible to users and PWDs is useful in these scenarios:
*
* - When the DTMF keypad is going away, we need to send focus to the text area before we unmount DTMF keypad,
* This ensures the flow of focus did not sent to document body
*/
hidden: optional(boolean()),
onClick: optional(custom<MouseEventHandler<HTMLTextAreaElement>>(value => typeof value === 'function')),
onInput: optional(custom<FormEventHandler<HTMLTextAreaElement>>(value => typeof value === 'function')),
placeholder: optional(string()),
readOnly: optional(boolean()),
startRows: optional(number()),
value: optional(string())
}),
readonly()
);
type TextAreaProps = InferInput<typeof TextAreaPropsSchema>;
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>((rawProps, ref) => {
const props = validateProps(TextAreaPropsSchema, rawProps);
const [uiState] = useUIState();
const classNames = useStyles(styles);
const isInCompositionRef = useRef<boolean>(false);
const disabled = uiState === 'disabled' || props.readOnly;
const handleCompositionEnd = useCallback(() => {
isInCompositionRef.current = false;
}, [isInCompositionRef]);
const handleCompositionStart = useCallback(() => {
isInCompositionRef.current = true;
}, [isInCompositionRef]);
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(event => {
// Shift+Enter adds a new line
// Enter requests related form submission
if (!event.shiftKey && event.key === 'Enter' && !isInCompositionRef.current) {
event.preventDefault();
if ('form' in event.target && event.target.form instanceof HTMLFormElement) {
event.target?.form?.requestSubmit();
}
}
}, []);
return (
<div
className={cx(
classNames['text-area'],
classNames['text-area--scroll'],
{ [classNames['text-area--hidden']]: props.hidden },
{ [classNames['text-area--in-completion']]: props.completion },
props.className
)}
role={props.hidden ? 'hidden' : undefined}
>
{uiState === 'blueprint' ? (
<div className={cx(classNames['text-area-doppelganger'], classNames['text-area-shared'])}> </div>
) : (
<Fragment>
<div className={cx(classNames['text-area-doppelganger'], classNames['text-area-shared'])}>
{props.completion || props.value}{' '}
</div>
<textarea
aria-describedby={props['aria-describedby']}
aria-disabled={disabled}
aria-label={props['aria-label']}
aria-labelledby={props['aria-labelledby']}
aria-placeholder={props.placeholder}
className={cx(classNames['text-area-input'], classNames['text-area-shared'])}
data-testid={props['data-testid']}
onClick={props.onClick}
onCompositionEnd={handleCompositionEnd}
onCompositionStart={handleCompositionStart}
onInput={props.onInput}
onKeyDown={handleKeyDown}
placeholder={props.placeholder}
readOnly={disabled}
ref={ref}
rows={props.startRows ?? 1}
// eslint-disable-next-line no-magic-numbers
tabIndex={props.hidden ? -1 : undefined}
value={props.value}
/>
</Fragment>
)}
</div>
);
});
TextArea.displayName = 'TextArea';
export default TextArea;
export { TextAreaPropsSchema, type TextAreaProps };