hover to show border, and click to edit, click outside to text
import React, { useState, useRef, useEffect, useCallback } from "react";
const EditableText = ({
initialValue = "fffff",
placeholder = "Click to edit...",
className = "",
onChange = () => {},
}) => {
const [isEditing, setIsEditing] = useState(false);
const [content, setContent] = useState(initialValue);
const editableRef = useRef(null);
// Handler for saving content
const saveContent = useCallback(() => {
if (editableRef.current) {
const newContent = editableRef.current.innerHTML;
setContent(newContent);
onChange(newContent);
}
setIsEditing(false);
}, [onChange]);
// Set up and clean up event listeners
useEffect(() => {
const handleClickOutside = (event) => {
if (editableRef.current && !editableRef.current.contains(event.target)) {
saveContent();
}
};
if (isEditing) {
document.addEventListener("mousedown", handleClickOutside);
return () =>
document.removeEventListener("mousedown", handleClickOutside);
}
}, [isEditing, saveContent]);
// Handle focus and cursor placement
useEffect(() => {
if (isEditing && editableRef.current) {
editableRef.current.focus();
// Place cursor at end
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(editableRef.current);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
}, [isEditing]);
// Handle keyboard events
const handleKeyDown = (event) => {
// Save on Enter + Ctrl
if (event.key === "Enter" && event.ctrlKey) {
event.preventDefault();
saveContent();
}
// Cancel on Escape
if (event.key === "Escape") {
setIsEditing(false);
// Revert to last saved content
if (editableRef.current) {
editableRef.current.innerHTML = content;
}
}
};
return (
<div className={`editable-container ${className}`}>
<div
ref={editableRef}
contentEditable={isEditing}
suppressContentEditableWarning={true}
onClick={() => setIsEditing(true)}
onKeyDown={handleKeyDown}
onBlur={saveContent}
style={{
padding: "8px 12px",
cursor: isEditing ? "text" : "pointer",
borderWidth: "1px",
borderStyle: "solid",
borderColor: isEditing ? "#3b82f6" : "transparent",
borderRadius: "4px",
outline: "none",
minHeight: "24px",
transition: "border-color 0.2s ease",
}}
onMouseEnter={(e) => {
if (!isEditing) {
e.target.style.borderColor = "#d1d5db";
}
}}
onMouseLeave={(e) => {
if (!isEditing) {
e.target.style.borderColor = "transparent";
}
}}
className="editable-content"
dangerouslySetInnerHTML={{ __html: content || placeholder }}
aria-label={placeholder}
/>
</div>
);
};
export default EditableText;
<EditableText
initialValue="Click to edit me"
onChange={(newContent) => console.log('Content changed:', newContent)}
className="my-custom-class"
/>
https://codesandbox.io/p/sandbox/kx8j5c?file=%2Fsrc%2FEditableText.js%3A1%2C1-110%2C1