Files
eene_dashboard/frontend/src/components/common/EditableText.tsx
EENE Dashboard 22366dde72 Initial commit - EENE Dashboard
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 18:07:10 +09:00

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>
);
}