|
import React, { useState, useRef, ChangeEvent, MouseEvent } from 'react'; |
|
|
|
/** |
|
* Configuration for buttons to be displayed |
|
* @interface ButtonConfig |
|
*/ |
|
interface ButtonConfig { |
|
/** Text to be displayed and inserted */ |
|
text: string; |
|
/** Tailwind CSS background color class */ |
|
color: string; |
|
} |
|
|
|
/** |
|
* TextAreaButtonInteraction Component |
|
* Provides an interactive textarea with buttons that insert text |
|
* while maintaining intelligent spacing and cursor position |
|
* |
|
* @component |
|
* @returns {React.ReactElement} Rendered React component |
|
*/ |
|
const TextAreaButtonInteraction: React.FC = () => { |
|
/** |
|
* State to manage textarea content |
|
* @type {[string, React.Dispatch<React.SetStateAction<string>>]} |
|
*/ |
|
const [text, setText] = useState<string>(''); |
|
|
|
/** |
|
* Ref to access textarea DOM element directly |
|
* @type {React.RefObject<HTMLTextAreaElement>} |
|
*/ |
|
const textareaRef = useRef<HTMLTextAreaElement>(null); |
|
|
|
/** |
|
* Ref to track and preserve cursor position |
|
* @type {React.MutableRefObject<number>} |
|
*/ |
|
const cursorPositionRef = useRef<number>(0); |
|
|
|
/** |
|
* Handles text change in the textarea |
|
* Updates text state and current cursor position |
|
* |
|
* @param {ChangeEvent<HTMLTextAreaElement>} e - Textarea change event |
|
* @returns {void} |
|
*/ |
|
const handleTextChange = (e: ChangeEvent<HTMLTextAreaElement>): void => { |
|
setText(e.target.value); |
|
cursorPositionRef.current = e.target.selectionStart; |
|
}; |
|
|
|
/** |
|
* Tracks cursor position when clicking in the textarea |
|
* |
|
* @param {MouseEvent<HTMLTextAreaElement>} e - Textarea click event |
|
* @returns {void} |
|
*/ |
|
const handleCursorClick = (e: MouseEvent<HTMLTextAreaElement>): void => { |
|
if (e.target instanceof HTMLTextAreaElement) { |
|
cursorPositionRef.current = e.target.selectionStart; |
|
} |
|
}; |
|
|
|
/** |
|
* Inserts button text into textarea with intelligent spacing |
|
* |
|
* @param {string} buttonText - Text to be inserted |
|
* @returns {void} |
|
*/ |
|
const handleButtonClick = (buttonText: string): void => { |
|
const textarea = textareaRef.current; |
|
|
|
if (textarea) { |
|
// Preserve the exact cursor position |
|
const cursorPosition = cursorPositionRef.current; |
|
|
|
// Check characters before and after the cursor |
|
const charBefore = text.charAt(cursorPosition - 1) || ''; |
|
const charAfter = text.charAt(cursorPosition) || ''; |
|
|
|
// Determine space requirements |
|
let finalButtonText = buttonText; |
|
if (charBefore !== '' && charBefore !== ' ') { |
|
// Add space at the start if previous character exists and is not a space |
|
finalButtonText = ' ' + finalButtonText; |
|
} |
|
|
|
if (charAfter !== '' && charAfter !== ' ') { |
|
// Add space at the end if next character exists and is not a space |
|
finalButtonText = finalButtonText + ' '; |
|
} |
|
|
|
// Create new text by inserting button text at cursor position |
|
const newText = |
|
text.slice(0, cursorPosition) + |
|
finalButtonText + |
|
text.slice(cursorPosition); |
|
|
|
// Update text state |
|
setText(newText); |
|
|
|
// Set cursor back to its original position |
|
requestAnimationFrame(() => { |
|
const newCursorPosition = cursorPosition + finalButtonText.length; |
|
textarea.setSelectionRange(newCursorPosition, newCursorPosition); |
|
}); |
|
} |
|
}; |
|
|
|
/** |
|
* Button configurations for dynamic rendering |
|
* @type {ButtonConfig[]} |
|
*/ |
|
const buttons: ButtonConfig[] = [ |
|
{ text: 'Hello', color: 'bg-blue-500' }, |
|
{ text: 'World', color: 'bg-green-500' }, |
|
{ text: 'React', color: 'bg-purple-500' }, |
|
{ text: 'Coding', color: 'bg-red-500' } |
|
]; |
|
|
|
/** |
|
* Renders the TextAreaButtonInteraction component |
|
* @returns {React.ReactElement} |
|
*/ |
|
return ( |
|
<div className="p-4 max-w-md mx-auto"> |
|
<textarea |
|
ref={textareaRef} |
|
value={text} |
|
onChange={handleTextChange} |
|
onClick={handleCursorClick} |
|
className="w-full h-40 border p-2 mb-4" |
|
placeholder="Click buttons to insert text..." |
|
/> |
|
|
|
<div className="flex flex-wrap gap-2"> |
|
{buttons.map((button, index) => ( |
|
<button |
|
key={index} |
|
onClick={() => handleButtonClick(button.text)} |
|
className={`${button.color} text-white px-3 py-1 rounded`} |
|
> |
|
{button.text} |
|
</button> |
|
))} |
|
</div> |
|
</div> |
|
); |
|
}; |
|
|
|
export default TextAreaButtonInteraction; |