feat(component): add EditableCell component for inline editing of text and textarea inputs
This commit is contained in:
parent
007abefed2
commit
f75db7e3bd
1 changed files with 125 additions and 0 deletions
125
src/components/EditableCell.tsx
Normal file
125
src/components/EditableCell.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue