75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
import { useState, useRef, useEffect } from 'react';
|
|
|
|
interface EditableTextProps {
|
|
value: string;
|
|
onSave: (val: string) => void;
|
|
className?: string;
|
|
multiline?: boolean;
|
|
placeholder?: string;
|
|
}
|
|
|
|
/**
|
|
* 클릭하면 편집 가능한 텍스트 컴포넌트
|
|
* - 클릭 → 입력 필드로 전환
|
|
* - Enter 또는 blur → 저장
|
|
* - Escape → 취소
|
|
*/
|
|
export function EditableText({ value, onSave, className = '', multiline = false, placeholder = '클릭하여 입력' }: EditableTextProps) {
|
|
const [editing, setEditing] = useState(false);
|
|
const [draft, setDraft] = useState(value);
|
|
const inputRef = useRef<HTMLInputElement & HTMLTextAreaElement>(null);
|
|
|
|
useEffect(() => { setDraft(value); }, [value]);
|
|
|
|
useEffect(() => {
|
|
if (editing) inputRef.current?.focus();
|
|
}, [editing]);
|
|
|
|
const commit = () => {
|
|
setEditing(false);
|
|
if (draft.trim() !== value) onSave(draft.trim());
|
|
};
|
|
|
|
const cancel = () => {
|
|
setEditing(false);
|
|
setDraft(value);
|
|
};
|
|
|
|
const sharedClass = `w-full bg-white/90 border-b-2 border-blue-400 outline-none rounded px-1 resize-none ${className}`;
|
|
|
|
if (editing) {
|
|
return multiline ? (
|
|
<textarea
|
|
ref={inputRef as any}
|
|
value={draft}
|
|
onChange={(e) => setDraft(e.target.value)}
|
|
onBlur={commit}
|
|
onKeyDown={(e) => { if (e.key === 'Escape') cancel(); }}
|
|
rows={3}
|
|
className={sharedClass}
|
|
/>
|
|
) : (
|
|
<input
|
|
ref={inputRef as any}
|
|
value={draft}
|
|
onChange={(e) => setDraft(e.target.value)}
|
|
onBlur={commit}
|
|
onKeyDown={(e) => { if (e.key === 'Enter') commit(); if (e.key === 'Escape') cancel(); }}
|
|
className={sharedClass}
|
|
placeholder={placeholder}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<span
|
|
onClick={() => setEditing(true)}
|
|
title="클릭하여 수정"
|
|
className={`cursor-text hover:bg-white/30 hover:rounded px-1 -mx-1 transition-colors group relative ${className}`}
|
|
>
|
|
{value || <span className="text-white/40 italic">{placeholder}</span>}
|
|
<span className="opacity-0 group-hover:opacity-60 ml-1 text-xs transition-opacity">✏</span>
|
|
</span>
|
|
);
|
|
}
|