feat(component): add EditableCell component for inline editing of text and textarea inputs

This commit is contained in:
ryan 2025-06-19 15:32:14 +03:00
parent 007abefed2
commit f75db7e3bd

View file

@ -0,0 +1,125 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
interface EditableCellProps {
value: string;
onInput: (value: string) => void;
focusedCell: string | null;
setFocusedCell: (cellKey: string | null) => void;
cellKey: string;
inputType?: 'text' | 'textarea';
onFocus?: () => void;
onBlur?: () => void;
placeholder?: string;
className?: string;
}
export function EditableCell({
value,
onInput,
focusedCell,
setFocusedCell,
cellKey,
inputType = 'text',
onFocus,
onBlur,
placeholder = '',
className = ''
}: EditableCellProps) {
const [localValue, setLocalValue] = useState(value);
const inputRef = useRef<HTMLInputElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Update local value when prop changes
useEffect(() => {
setLocalValue(value);
}, [value]);
// Handle focus when this cell becomes focused
useEffect(() => {
if (focusedCell === cellKey) {
const element = inputType === 'textarea' ? textareaRef.current : inputRef.current;
if (element) {
element.focus();
// Select all text when focusing
element.select();
}
}
}, [focusedCell, cellKey, inputType]);
const handleInput = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const newValue = e.target.value;
console.log('Input event:', newValue);
setLocalValue(newValue);
}, []);
const commitInput = useCallback(() => {
console.log('Committing input:', localValue);
onInput(localValue);
}, [localValue, onInput]);
const handleFocus = useCallback(() => {
console.log('Focus gained for key:', cellKey);
setFocusedCell(cellKey);
onFocus?.();
}, [cellKey, setFocusedCell, onFocus]);
const handleBlur = useCallback(() => {
console.log('Focus lost');
setFocusedCell(null);
commitInput();
onBlur?.();
}, [setFocusedCell, commitInput, onBlur]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' && inputType === 'text') {
// For text inputs, commit on Enter
e.preventDefault();
commitInput();
setFocusedCell(null);
} else if (e.key === 'Escape') {
// Reset to original value on Escape
setLocalValue(value);
setFocusedCell(null);
}
}, [inputType, commitInput, setFocusedCell, value]);
const baseClassName = `
w-full px-2 py-1 border-none outline-none resize-none
focus:ring-2 focus:ring-blue-500 focus:ring-inset
${className}
`.trim();
if (inputType === 'textarea') {
return (
<div className="editable-cell">
<textarea
ref={textareaRef}
value={localValue}
onChange={handleInput}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className={`${baseClassName} min-h-[2rem] max-h-32`}
rows={1}
/>
</div>
);
}
return (
<div className="editable-cell">
<input
ref={inputRef}
type="text"
value={localValue}
onChange={handleInput}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className={baseClassName}
/>
</div>
);
}