PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / HelpCenter / components / ai-chat / DynamicTextarea.jsx

DynamicTextarea.jsx in Extendify 3.2.1, at src/HelpCenter/components/ai-chat/DynamicTextarea.jsx

109 lines 2.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 useEffect,
3 useLayoutEffect,
4 useRef,
5 useState,
6 } from '@wordpress/element';
7 import { AnimatePresence, motion } from 'framer-motion';
8
9 export const DynamicTextarea = ({
10 value,
11 className,
12 onChange,
13 onKeyDown,
14 disabled,
15 placeholder,
16 maxRows = 6,
17 }) => {
18 const ref = useRef(null);
19 const [height, setHeight] = useState('auto');
20 const [rowHeight, setRowHeight] = useState('auto');
21
22 useLayoutEffect(() => {
23 if (!ref.current) return;
24 const computedStyle = window.getComputedStyle(ref.current);
25 const lineHeight = parseFloat(computedStyle.lineHeight || '20px');
26 setRowHeight(lineHeight);
27 }, []);
28
29 // Dynamically resize the input by creating a temporary version and measuring the height.
30 // This is a workaround for scrollHeight not reducing when text is deleted.
31 useLayoutEffect(() => {
32 const tempTextarea = document.createElement('textarea');
33 tempTextarea.value = value || placeholder;
34 tempTextarea.rows = 1; // Start at 1
35
36 const styleProps = [
37 'paddingTop',
38 'paddingBottom',
39 'paddingLeft',
40 'paddingRight',
41 'width',
42 'fontFamily',
43 'fontSize',
44 'borderWidth',
45 ];
46
47 const styles = window.getComputedStyle(ref.current);
48
49 // apply styles to the temporary textarea
50 styleProps.forEach((prop) => {
51 tempTextarea.style[prop] = styles[prop];
52 });
53
54 Object.assign(tempTextarea.style, {
55 position: 'absolute',
56 left: '-9999px',
57 });
58
59 document.body.appendChild(tempTextarea);
60 setHeight(tempTextarea.scrollHeight < 52 ? 52 : tempTextarea.scrollHeight);
61 document.body.removeChild(tempTextarea);
62 }, [value, placeholder]);
63
64 // Focus the input.
65 useEffect(() => {
66 const input = ref.current;
67 if (!input) return;
68 if (document.activeElement === input) return;
69
70 const inputLength = input.value.length;
71 input.focus();
72 input.setSelectionRange(inputLength, inputLength); // Place cursor at the end of the input.
73 }, [value]);
74
75 return (
76 <AnimatePresence>
77 <motion.div
78 key="input"
79 animate={{ height: `${height}px` }}
80 transition={{ duration: 0.2 }}
81 style={{
82 overflow: 'hidden',
83 maxHeight: rowHeight ? `${rowHeight * maxRows + 16}px` : 'none',
84 }}
85 >
86 <label htmlFor="draft-ai-textarea" className="sr-only">
87 {placeholder}
88 </label>
89 <textarea
90 ref={ref}
91 id="draft-ai-textarea"
92 disabled={disabled}
93 className={className}
94 value={value}
95 rows={1}
96 onChange={onChange}
97 onKeyDown={onKeyDown}
98 placeholder={placeholder}
99 style={{
100 overflowY: height < rowHeight * maxRows ? 'hidden' : 'scroll',
101 maxHeight: rowHeight ? `${rowHeight * maxRows}px` : 'none',
102 boxShadow: 'none',
103 }}
104 />
105 </motion.div>
106 </AnimatePresence>
107 );
108 };
109