Editor
Lexical 기반 리치 텍스트 에디터 컴포넌트
개요
Foundation Editor는 Lexical 엔진을 기반으로 한 리치 텍스트 에디터입니다. 서식, 이미지, 표, 레이아웃 등 풍부한 콘텐츠 편집 기능을 제공하며, 플러그인 아키텍처를 통해 기능을 선택적으로 구성할 수 있습니다.
주요 특징
- ✅ Toolbar: 서식, 정렬, 링크, 삽입 등 편집 도구
- ✅ 서식 선택 메뉴: Paragraph, Heading, List, Code Block, Quote
- ✅ Insert 메뉴: 이미지, 표, 컬럼 레이아웃, 임베드
- ✅ 서식 적용: 글꼴, 크기, 굵기, 기울임, 밑줄, 색상 등
- ✅ 텍스트 정렬: 좌/중/우/양쪽 정렬
- ✅ 읽기 전용: readOnly 모드 지원
- ✅ 플러그인 아키텍처: 서식/콘텐츠 제한 가능
- ✅ HTML 입/출력: HTML 문자열로 초기화(
editorHtmlState) 및 변경 콜백(onHtmlChange) 지원 - ✅ 명령적 API: ref(
EditorHandle)로 외부에서 텍스트 삽입·포커스 제어 - ✅ 접근성: 키보드 네비게이션 지원
사용 예시
기본 에디터
Preview
0 characters
|0 words
읽기 전용
readOnly 설정 시 Toolbar와 Actions가 비활성화되고 콘텐츠 수정이 제한됩니다.
Preview
0 characters
|0 words
오류 상태
error 설정 시 테두리가 destructive 색상으로 변경됩니다.
Preview
0 characters
|0 words
Serialized State 관리
onSerializedChange로 EditorState를 JSON 구조로 관리합니다.
import { Editor } from "@vortex/ui-foundation"
import { useState } from "react"
function MyEditor() {
const [state, setState] = useState(undefined)
return (
<Editor
editorSerializedState={state}
onSerializedChange={setState}
/>
)
}HTML 입/출력 (editorHtmlState / onHtmlChange)
JSON 외에 HTML 문자열로도 양방향 입/출력이 가능합니다. 초기화는
editorHtmlState prop으로 마운트 시점에 1회 적용되고
($generateNodesFromDOM으로 노드 트리 변환), 변경 통보는 onHtmlChange
콜백으로 매 업데이트마다 lexical → HTML 문자열($generateHtmlFromNodes)이
전달됩니다.
onHtmlChange를 명시하지 않으면 변환을 수행하지 않으므로 추가 비용이
없습니다. 마운트 시 적용 우선순위는 editorState > editorSerializedState >
editorHtmlState 입니다. 외부에서 다시 로드하려면 key 변경으로 컴포넌트를
강제 remount 해야 합니다.
import { Editor } from "@vortex/ui-foundation"
import { useState } from "react"
function MyEditor() {
const [html, setHtml] = useState("")
return (
<Editor
editorHtmlState='<p>안녕 <strong>World</strong></p>'
onHtmlChange={setHtml}
/>
// → html === '<p class="...">안녕 <strong>World</strong></p>'
// 이 값을 그대로 contentsBody로 API에 전송 가능
)
}Ref Handle - 외부에서 텍스트 삽입
useRef<EditorHandle>(null)로 ref를 만들어 <Editor ref={...} />에 연결하면
외부에서 현재 커서(또는 선택 영역) 위치에 텍스트를 삽입할 수 있습니다.
내부적으로 lexical의 RangeSelection.insertText를 사용하므로 deprecated된
document.execCommand("insertText") 에 의존하지 않습니다.
- 선택 영역이 있으면 그 영역을 대체합니다.
- 선택 영역이 없으면 root 마지막 element에 append합니다(빈 root이면 paragraph를 새로 만들어 삽입).
readOnly상태에서는 호출이 무시됩니다.
SMS / LMS / MMS 등 다른 채널의 textarea 기반 치환 문자 삽입과 동일한 UX를 Lexical contenteditable 환경에서도 제공할 수 있습니다.
import { Editor, type EditorHandle } from "@vortex/ui-foundation"
import { useRef } from "react"
function MessageEditor() {
const editorRef = useRef<EditorHandle>(null)
const fields = ["회원명", "날짜", "주문번호"] as const
return (
<>
{fields.map((field) => (
<button
key={field}
onClick={() => editorRef.current?.insertText(`$$${field}$$`)}
>
+ {field}
</button>
))}
<Editor ref={editorRef} />
</>
)
}API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
readOnly | boolean | false | 읽기 전용 여부 |
error | boolean | string | - | 에러 상태 |
className | string | - | 추가 CSS 클래스 |
editorState | EditorState | - | Lexical EditorState (제어) |
editorSerializedState | SerializedEditorState | - | JSON 직렬화 상태 (제어) |
editorHtmlState | string | - | 마운트 시 HTML 문자열로 초기화 (LexicalComposer 마운트 시점에 1회 적용) |
초기화 우선순위: editorState > editorSerializedState > editorHtmlState
Events
| Event | Type | Description |
|---|---|---|
onChange | (editorState: EditorState) => void | EditorState 변경 핸들러 |
onSerializedChange | (state: SerializedEditorState) => void | JSON 직렬화 변경 핸들러 |
onHtmlChange | (html: string) => void | HTML 문자열 변경 핸들러 (명시 시에만 변환) |
Ref Handle (EditorHandle)
forwardRef로 받은 ref를 통해 명령적으로 에디터를 제어합니다.
| Method | Type | Description |
|---|---|---|
insertText | (text: string) => void | 현재 커서/선택 영역 위치에 텍스트 삽입. 선택 영역이 있으면 대체, 없으면 root 마지막에 append. readOnly 시 무시 |
focus | () => void | 에디터에 포커스 |
getEditor | () => LexicalEditor | null | 내부 LexicalEditor 인스턴스에 직접 접근 (고급 사용처용) |
기본 사용법
import { Editor } from "@vortex/ui-foundation"
// 기본
<Editor />
// 읽기 전용
<Editor readOnly />
// 에러 상태
<Editor error />접근성
키보드 네비게이션
- Tab: Toolbar → 에디터 영역 이동
- Tab / Shift+Tab: 에디터 내 들여쓰기/내어쓰기
- / (슬래시): 컴포넌트 피커 메뉴 호출
- Arrow Keys: 캘린더, 드롭다운 내 탐색
권장 사항
- ✅ 폼 필드에서 사용 시 iCignal Editor 권장 (label, error, required 지원)
- ✅ focus-within 링으로 포커스 상태 시각적 구분
- ❌ label 없이 단독 사용 지양
관련 컴포넌트
- Editor (iCignal): FormItem 통합 Editor
- Textarea: 단순 텍스트 입력