Initial commit - EENE Dashboard
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
74
frontend/src/components/common/EditableText.tsx
Normal file
74
frontend/src/components/common/EditableText.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user