Files
sam-react-prod/src/components/board/RichTextEditor/index.tsx
byeongcheolryu c6b605200d feat: 신규 페이지 구현 및 HR/설정 기능 개선
신규 페이지:
- 회계관리: 거래처, 예상비용, 청구서, 발주서
- 게시판: 공지사항, 자료실, 커뮤니티
- 고객센터: 문의/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>
2025-12-19 19:12:34 +09:00

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;