79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
|
|
|
|||
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|||
|
|
import { CheckCircle2, X } from 'lucide-react';
|
|||
|
|
|
|||
|
|
interface EditableFieldProps {
|
|||
|
|
value: string | number | boolean;
|
|||
|
|
onChange: (val: string) => void;
|
|||
|
|
isEditing: boolean;
|
|||
|
|
className?: string;
|
|||
|
|
placeholder?: string;
|
|||
|
|
type?: 'text' | 'number' | 'date' | 'checkbox';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const EditableField: React.FC<EditableFieldProps> = ({ value, onChange, isEditing, className = "", placeholder, type = 'text' }) => {
|
|||
|
|
const [localValue, setLocalValue] = useState(value);
|
|||
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|||
|
|
const wasFocused = useRef(false);
|
|||
|
|
|
|||
|
|
// Синхронизируем локальное значение с внешним
|
|||
|
|
useEffect(() => {
|
|||
|
|
setLocalValue(value);
|
|||
|
|
}, [value]);
|
|||
|
|
|
|||
|
|
// Восстанавливаем фокус после обновления (setSelectionRange не поддерживается для type="number" и "date")
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (wasFocused.current && inputRef.current && isEditing) {
|
|||
|
|
const input = inputRef.current;
|
|||
|
|
input.focus();
|
|||
|
|
const inputType = (input.type || 'text').toLowerCase();
|
|||
|
|
if (inputType === 'text' || inputType === 'search' || inputType === 'url' || inputType === 'tel' || inputType === 'password') {
|
|||
|
|
const cursorPosition = input.selectionStart ?? 0;
|
|||
|
|
input.setSelectionRange(cursorPosition, cursorPosition);
|
|||
|
|
}
|
|||
|
|
wasFocused.current = false;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (type === 'checkbox') {
|
|||
|
|
const boolValue = Boolean(value);
|
|||
|
|
if (!isEditing) return boolValue ? <CheckCircle2 className="w-4 h-4 text-emerald-500" /> : <X className="w-4 h-4 text-slate-300" />;
|
|||
|
|
return (
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
checked={boolValue}
|
|||
|
|
onChange={(e) => onChange(String(e.target.checked))}
|
|||
|
|
className="w-5 h-5 accent-primary-600 cursor-pointer"
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!isEditing) {
|
|||
|
|
return <span className={`truncate ${className}`}>{value}</span>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|||
|
|
const newValue = e.target.value;
|
|||
|
|
setLocalValue(newValue);
|
|||
|
|
wasFocused.current = true;
|
|||
|
|
onChange(newValue);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleFocus = () => {
|
|||
|
|
wasFocused.current = true;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<input
|
|||
|
|
ref={inputRef}
|
|||
|
|
type={type}
|
|||
|
|
value={localValue as string | number}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
onFocus={handleFocus}
|
|||
|
|
className={`w-full bg-primary-50 border-b-2 border-primary-200 focus:border-primary-500 outline-none px-1 rounded-t transition-colors ${className}`}
|
|||
|
|
placeholder={placeholder}
|
|||
|
|
onClick={(e) => e.stopPropagation()}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
};
|