신규 페이지: - 회계관리: 거래처, 예상비용, 청구서, 발주서 - 게시판: 공지사항, 자료실, 커뮤니티 - 고객센터: 문의/FAQ - 설정: 계정, 알림, 출퇴근, 팝업, 구독, 결제내역 - 리포트 (차트 시각화) - 개발자 테스트 URL 페이지 기능 개선: - HR 직원관리/휴가관리/카드관리 강화 - IntegratedListTemplateV2 확장 - AuthenticatedLayout 패딩 표준화 - 로그인 페이지 UI 개선 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
90 lines
2.2 KiB
TypeScript
90 lines
2.2 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* TipTap 기반 WYSIWYG 에디터
|
|
*/
|
|
|
|
import { useEditor, EditorContent } from '@tiptap/react';
|
|
import { useEffect } from 'react';
|
|
import { getEditorExtensions } from './extensions';
|
|
import { MenuBar } from './MenuBar';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface RichTextEditorProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
placeholder?: string;
|
|
onImageUpload?: (file: File) => Promise<string>;
|
|
className?: string;
|
|
disabled?: boolean;
|
|
minHeight?: string;
|
|
}
|
|
|
|
export function RichTextEditor({
|
|
value,
|
|
onChange,
|
|
placeholder = '내용을 입력해주세요',
|
|
onImageUpload,
|
|
className,
|
|
disabled = false,
|
|
minHeight = '200px',
|
|
}: RichTextEditorProps) {
|
|
const editor = useEditor({
|
|
extensions: getEditorExtensions(placeholder),
|
|
content: value,
|
|
editable: !disabled,
|
|
immediatelyRender: false, // SSR hydration mismatch 방지
|
|
onUpdate: ({ editor }) => {
|
|
onChange(editor.getHTML());
|
|
},
|
|
editorProps: {
|
|
attributes: {
|
|
class: cn(
|
|
'prose prose-sm max-w-none focus:outline-none',
|
|
'px-4 py-3',
|
|
disabled && 'opacity-50 cursor-not-allowed'
|
|
),
|
|
style: `min-height: ${minHeight}`,
|
|
},
|
|
},
|
|
});
|
|
|
|
// 외부에서 value가 변경될 때 에디터 업데이트
|
|
useEffect(() => {
|
|
if (editor && value !== editor.getHTML()) {
|
|
editor.commands.setContent(value);
|
|
}
|
|
}, [editor, value]);
|
|
|
|
// disabled 상태 변경 시 반영
|
|
useEffect(() => {
|
|
if (editor) {
|
|
editor.setEditable(!disabled);
|
|
}
|
|
}, [editor, disabled]);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'border border-gray-200 rounded-md overflow-hidden bg-white',
|
|
disabled && 'bg-gray-50',
|
|
className
|
|
)}
|
|
>
|
|
{!disabled && <MenuBar editor={editor} onImageUpload={onImageUpload} />}
|
|
<EditorContent
|
|
editor={editor}
|
|
className={cn(
|
|
'overflow-y-auto',
|
|
'[&_.is-editor-empty]:before:content-[attr(data-placeholder)]',
|
|
'[&_.is-editor-empty]:before:text-gray-400',
|
|
'[&_.is-editor-empty]:before:float-left',
|
|
'[&_.is-editor-empty]:before:pointer-events-none',
|
|
'[&_.is-editor-empty]:before:h-0'
|
|
)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default RichTextEditor; |