sellmate-design-system-react 9.0.0-beta.19 → 9.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -4
- package/dist/components/SButton/README.md +2 -0
- package/dist/components/SEditor/EditorBody.d.ts +40 -0
- package/dist/components/SEditor/EditorToolbar.d.ts +87 -0
- package/dist/components/SEditor/README.md +230 -0
- package/dist/components/SEditor/SEditor.d.ts +124 -0
- package/dist/components/SEditor/editor-icons.d.ts +59 -0
- package/dist/components/SEditor/editor.config.d.ts +85 -0
- package/dist/components/SEditor/index.d.ts +2 -0
- package/dist/components/SEditor/tiptap-api.d.ts +29 -0
- package/dist/components/SEditor/use-is-mobile.d.ts +14 -0
- package/dist/components/SField/README.md +2 -0
- package/dist/components/SIcon/README.md +2 -0
- package/dist/components/SInput/README.md +2 -0
- package/dist/index.cjs +1971 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1960 -72
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +245 -4
- package/dist/llms.txt +19 -6
- package/dist/styles.css +446 -0
- package/package.json +13 -1
package/AGENTS.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
| 분류 | 컴포넌트 |
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| **버튼·링크** | `SButton` `SGhostButton` `SDropdownButton` `STextLink` `SSwitch` `SToggle` |
|
|
30
|
-
| **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
30
|
+
| **입력 (폼)** | `SForm` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SChip` `SChipInput` `SBarcodeInput` `SFilePicker` |
|
|
31
31
|
| **날짜·시간** | `SCalendar` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` |
|
|
32
32
|
| **표·목록** | `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SListItem` `SExpansionList` `SDraggableList` `SDraggableItem` `STree` |
|
|
33
33
|
| **레이아웃** | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) `SSectionHeaderCard` `SCard` `SDivider` `SSplitter` `SScrollArea` `SExpansionItem` |
|
|
@@ -67,6 +67,7 @@ AI 에이전트는 코드를 생성하기 전에 이 목록을 반드시 지킨
|
|
|
67
67
|
| `<input type="file">` | `SFilePicker` |
|
|
68
68
|
| `<select>` | `SSelect` |
|
|
69
69
|
| `<textarea>` | `STextarea` |
|
|
70
|
+
| `contenteditable`, 직접 붙인 에디터 라이브러리 | `SEditor` |
|
|
70
71
|
| `<table>` | `STable`, `SKeyValueTable` |
|
|
71
72
|
| `<form>` | `SForm` |
|
|
72
73
|
| `<dialog>`, 직접 만든 오버레이 | `SModal.confirm(...)`, `SModal.create(...)`, `SPopup` |
|
|
@@ -151,7 +152,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
151
152
|
| --- | --- | --- |
|
|
152
153
|
| **셸** | 앱 전체 뼈대. 페이지가 바뀌어도 남는다 | `SLayout` `SGnb` `SPage`(제목 영역은 `header` prop) |
|
|
153
154
|
| **블록** | `SPage` 의 직계 자식. 페이지를 세로로 쌓는 단위 | `SSectionHeaderCard` `SCard` `SForm` `SSplitter` `SScrollArea` `STable` `STableBar` `SChipFilter` `SKeyValueTable` `SList` `SExpansionList` `SDraggableList` `STree` `SCallout` `STabs` `SStepper` `SPagination` `SDivider` |
|
|
154
|
-
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
|
|
155
|
+
| **요소** | 블록 **안에** 놓이는 컨트롤. 혼자 페이지에 서지 않는다 | `SButton` `SGhostButton` `SDropdownButton` `SField` `SInput` `SSearchInput` `SNumberInput` `STextarea` `SEditor` `SSelect` `SCheckbox` `SRadio` `SRadioGroup` `SRadioButton` `SSwitch` `SToggle` `SChipInput` `SBarcodeInput` `SFilePicker` `SDatePicker` `SDatePickerYearListbox` `SDatePickerMonthListbox` `SDateRangePicker` `STimePicker` `STimeRangePicker` `SCalendar` `SListItem` `SExpansionItem` `SDraggableItem` `SImage` `SLinearProgress` `SCircleProgress` |
|
|
155
156
|
| **인라인** | 텍스트 흐름·셀·라벨 안에 섞인다. 혼자 블록이 되지 않는다 | `STag` `SBadge` `SIcon` `STextLink` `SChip` |
|
|
156
157
|
| **레이어** | 문서 흐름 **밖**에 떠서 그려진다. 어느 층에서 띄우든 레이아웃에 영향이 없다 | `SModal` `SActionModal` `SConfirmModal` `SPopup` `SDrawer` `SPopover` `STooltip` `SPortal` `SToast` `SLoadingModal` `SLoadingContainer` `SGuide` |
|
|
157
158
|
|
|
@@ -472,6 +473,7 @@ Tailwind 유틸리티는 **토큰 스케일에 있는 값만** 사용한다.
|
|
|
472
473
|
| --- | --- | --- |
|
|
473
474
|
| 한 줄 텍스트를 받는다 | `SInput` | §3-7-1 |
|
|
474
475
|
| 여러 줄 텍스트를 받는다 | `STextarea` | §3-7-1 |
|
|
476
|
+
| 제목·굵게·목록·색 같은 **서식이 남아야 하는** 글을 받는다 | `SEditor` | §3-7-1 |
|
|
475
477
|
| 목록·결과를 검색어로 좁힌다 | `SSearchInput` | §3-7-1 |
|
|
476
478
|
| 숫자(수량·금액)를 받는다 | `SNumberInput` | |
|
|
477
479
|
| 바코드를 스캔해 받는다 | `SBarcodeInput` | |
|
|
@@ -1181,7 +1183,7 @@ const columns: STableColumn[] = [
|
|
|
1181
1183
|
|
|
1182
1184
|
> §3-0 라우팅에서 이 절을 가리키는 자리들이다. <!-- TODO(디자인): 전체 검수·확정 -->
|
|
1183
1185
|
|
|
1184
|
-
#### 3-7-1. SInput vs STextarea vs SSearchInput
|
|
1186
|
+
#### 3-7-1. SInput vs STextarea vs SEditor vs SSearchInput
|
|
1185
1187
|
|
|
1186
1188
|
**먼저 "그 값이 저장되는가"를 본다.** 저장되면 폼 필드(`SInput`·`STextarea`), 화면을 좁히기만 하고 사라지면 `SSearchInput` 이다.
|
|
1187
1189
|
|
|
@@ -1189,10 +1191,21 @@ const columns: STableColumn[] = [
|
|
|
1189
1191
|
| --- | --- |
|
|
1190
1192
|
| 이름·코드·전화번호·URL 처럼 형식이 정해진 값 | `SInput` |
|
|
1191
1193
|
| 메모·사유·설명처럼 길이가 예측되지 않는 문장 | `STextarea` |
|
|
1194
|
+
| 서식(제목·굵게·목록·정렬·색·링크·이미지)이 값의 일부로 저장되어야 하는 글 | `SEditor` |
|
|
1192
1195
|
| 지금 보이는 목록·결과를 좁히는 검색어 | `SSearchInput` |
|
|
1193
1196
|
|
|
1194
1197
|
폼 필드 둘은 **줄 수가 아니라 값의 성격으로** 갈린다. 값이 길어질 수 있는데 `SInput` 을 쓰면 사용자가 자기가 쓴 것을 다시 읽지 못한다 — 한 줄 안에서 좌우로 스크롤해야 하기 때문이다. 반대로 짧은 값에 `STextarea` 를 쓰면 빈 공간이 남아 입력량을 잘못 기대하게 한다.
|
|
1195
1198
|
|
|
1199
|
+
`SEditor` 는 **서식이 값의 일부일 때만** 쓴다. 값을 HTML 문자열로 주고받으므로 저장·검색·비교가 평문보다 비싸고, 화면에 다시 보여줄 때도 HTML 로 렌더해야 한다. 서식이 필요 없는 메모·사유는 `STextarea` 다 — "입력창이 커 보여서" 고르는 컴포넌트가 아니다. 반대로 공지·안내문·상품 상세처럼 **작성자가 정한 강조와 목록이 그대로 보여야 하는 글**이면 `STextarea` 로는 표현할 수 없다.
|
|
1200
|
+
|
|
1201
|
+
`SEditor` 도 `SInput`·`STextarea` 와 같은 폼 필드다 — `label`·`hint`·`rules`·`errorMessage` 를 자기 prop 으로 받고 `SForm` 제출 검증에도 들어간다. 빈 문서는 빈 문자열로 나오므로 `required` 규칙이 그대로 걸린다. 툴바 구성은 `toolbar` 로 줄이거나 늘릴 수 있고, 서식 입력이 필요 없는 자리에 굳이 놓아야 한다면 `toolbar={false}` 가 아니라 `STextarea` 를 고른다.
|
|
1202
|
+
|
|
1203
|
+
글을 선택하면 **그 위에 서식 판이 뜬다**(버블 메뉴). 툴바까지 커서를 옮기지 않고 바로 굵게·색·링크를 걸기 위한 것이라 기본으로 켜져 있고, 읽기 전용·비활성일 때는 뜨지 않는다. 판은 한 줄이라 줄바꿈하지 않으므로 **좁은 칸에 놓인 에디터라면 `bubbleMenu` 로 항목을 줄이거나 `false` 로 끈다** — 그대로 두면 필드 밖으로 넘친다. 뜨는 자리는 DS 가 잡는다, 직접 감싸거나 위치를 주지 않는다.
|
|
1204
|
+
|
|
1205
|
+
`SEditor` 는 화면에 처음 놓일 때 **에디터 엔진을 따로 불러온다** — 앱 초기 번들에는 들어가지 않는다. 그동안은 같은 크기의 빈 편집 영역이 자리를 지키므로 레이아웃은 흔들리지 않지만, **마운트하자마자 `ref.current.getHTML()` 로 값을 읽거나 툴바를 누를 수는 없다.** 열자마자 커서를 놓고 싶으면 `ref.current.focus()` 를 그냥 부르면 된다 — 준비되는 순간 대신 실행된다.
|
|
1206
|
+
|
|
1207
|
+
**이미지를 넣으려면 `onImageUpload` 를 준다** — 고른 파일을 저장하고 표시할 URL 을 돌려주는 훅이다. 저장 위치는 앱마다 다르므로 DS 가 정하지 않고, 훅이 없으면 툴바에서 이미지 항목이 빠진다. 본문에 base64 를 박는 길은 막아 두었다 — 저장 HTML 이 수 MB 로 부풀어 그대로 DB·API 에 실리기 때문이다.
|
|
1208
|
+
|
|
1196
1209
|
`SSearchInput` 은 폼 필드가 아니다 — 라벨·힌트·유효성 규칙·에러 메시지를 받지 않고, `SForm` 의 제출 검증 대상에도 들어가지 않는다. 돋보기 아이콘이 항상 앞에 붙어 "여기는 검색"임을 스스로 밝히므로 라벨을 따로 붙이지 않는다. 검색 실행은 `onSearch`(Enter) 로 받고, 값이 바뀔 때마다 좁히는 실시간 필터라면 `onValueChange` 만 쓴다. 반대로 검색어를 **저장하거나 검증해야 한다면** 그것은 폼 값이므로 `SInput` 이다.
|
|
1197
1210
|
|
|
1198
1211
|
#### 3-7-2. 하나를 고르게 하는 다섯 — SSelect vs SRadioGroup vs SRadioButton vs STabs vs SRadio
|
|
@@ -1268,7 +1281,7 @@ const [from, setFrom] = useState<string | null>(null);
|
|
|
1268
1281
|
|
|
1269
1282
|
#### 3-7-5. SField 를 직접 쓰는 경우
|
|
1270
1283
|
|
|
1271
|
-
**거의 없다.** `SInput`·`SNumberInput`·`STextarea`·`SSelect`·날짜/시간 피커는 이미 내부에서 `SField` 를 쓰고 있어서 `label`·`required`·에러 표시를 자기 prop 으로 받는다. 그 위에 `SField` 를 한 겹 더 감싸면 라벨이 두 번 나온다.
|
|
1284
|
+
**거의 없다.** `SInput`·`SNumberInput`·`STextarea`·`SEditor`·`SSelect`·날짜/시간 피커는 이미 내부에서 `SField` 를 쓰고 있어서 `label`·`required`·에러 표시를 자기 prop 으로 받는다. 그 위에 `SField` 를 한 겹 더 감싸면 라벨이 두 번 나온다.
|
|
1272
1285
|
|
|
1273
1286
|
직접 쓰는 경우는 하나뿐이다 — **디자인 시스템에 없는 컨트롤**에 다른 필드와 똑같은 라벨·필수·에러 모양을 붙일 때.
|
|
1274
1287
|
|
|
@@ -55,6 +55,7 @@ export const BUTTON_SIZES = ['xs', 'sm', 'md', 'lg'] as const;
|
|
|
55
55
|
|
|
56
56
|
- [SConfirmModal](../SConfirmModal)
|
|
57
57
|
- [SDropdownButton](../SDropdownButton)
|
|
58
|
+
- [SEditor](../SEditor)
|
|
58
59
|
- [SFooter](../SFooter)
|
|
59
60
|
- [SKeyValueTable](../SKeyValueTable)
|
|
60
61
|
- [SLoadingModal](../SLoadingModal)
|
|
@@ -72,6 +73,7 @@ graph TD;
|
|
|
72
73
|
SButton --> SIcon
|
|
73
74
|
SConfirmModal --> SButton
|
|
74
75
|
SDropdownButton --> SButton
|
|
76
|
+
SEditor --> SButton
|
|
75
77
|
SFooter --> SButton
|
|
76
78
|
SKeyValueTable --> SButton
|
|
77
79
|
SLoadingModal --> SButton
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CSSProperties, type RefObject } from 'react';
|
|
2
|
+
import { type SEditorColorOption, type SEditorToolbarItem } from './editor.config';
|
|
3
|
+
import type { SEditorHandle } from './SEditor';
|
|
4
|
+
import type { TiptapApi } from './tiptap-api';
|
|
5
|
+
export interface EditorBodyProps {
|
|
6
|
+
/** 다 불러온 tiptap — 이 컴포넌트는 준비된 뒤에만 마운트된다 */
|
|
7
|
+
api: TiptapApi;
|
|
8
|
+
value?: string;
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
/** 지금 화면에 있는 HTML. 껍데기(SEditor)가 규칙 검증·폼 제출에 쓴다 */
|
|
11
|
+
htmlRef: RefObject<string>;
|
|
12
|
+
/** 사용자가 고쳐서 값이 바뀌었다 (setHTML·clear 같은 프로그램 조작은 제외) */
|
|
13
|
+
onInput: (html: string) => void;
|
|
14
|
+
onFocusChange: (focused: boolean) => void;
|
|
15
|
+
/** 에디터 인스턴스가 생겼다 — 껍데기가 밀린 focus() 를 흘려보낸다 */
|
|
16
|
+
onReady: () => void;
|
|
17
|
+
placeholder: string;
|
|
18
|
+
typography: boolean;
|
|
19
|
+
editable: boolean;
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
minHeight?: number | string;
|
|
22
|
+
maxHeight?: number | string;
|
|
23
|
+
toolbar: SEditorToolbarItem[] | false;
|
|
24
|
+
/** 선택 영역 위에 뜨는 서식 판. `false` 면 그리지 않는다 */
|
|
25
|
+
bubbleMenu: SEditorToolbarItem[] | false;
|
|
26
|
+
fontSizes: number[];
|
|
27
|
+
colors: SEditorColorOption[];
|
|
28
|
+
highlights: SEditorColorOption[];
|
|
29
|
+
onImageUpload?: (file: File) => Promise<string>;
|
|
30
|
+
editorClass?: string;
|
|
31
|
+
editorStyle?: CSSProperties;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* SEditor 의 편집 영역 (내부 전용 — 배럴로 내보내지 않는다).
|
|
35
|
+
*
|
|
36
|
+
* tiptap 을 실제로 부리는 곳은 여기뿐이고, 그 tiptap 조차 `api` prop 으로 받는다.
|
|
37
|
+
* 이 파일이 `@tiptap/*` 을 정적으로 import 하면 그 순간 SEditor 를 쓰지 않는 소비 앱까지
|
|
38
|
+
* tiptap 이 번들에 남는다 (tiptap-api.ts 주석 참고) — 타입만 가져온다.
|
|
39
|
+
*/
|
|
40
|
+
export declare const EditorBody: import("react").ForwardRefExoticComponent<EditorBodyProps & import("react").RefAttributes<SEditorHandle>>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Editor } from '@tiptap/core';
|
|
2
|
+
import { type SEditorColorOption, type SEditorHeadingLevel, type SEditorToolbarItem } from './editor.config';
|
|
3
|
+
import type { TiptapApi } from './tiptap-api';
|
|
4
|
+
/** 툴바 막대 — 엔진을 불러오는 동안 SEditor 가 같은 높이를 잡아 두는 데도 쓴다 */
|
|
5
|
+
export declare const TOOLBAR_BAR_CLASS = "flex flex-wrap items-center gap-[2px] border-b border-solid px-[8px] py-[4px]";
|
|
6
|
+
export declare const TOOLBAR_BAR_STYLE: {
|
|
7
|
+
borderBottomColor: string;
|
|
8
|
+
};
|
|
9
|
+
/** 버튼의 눌림 표시에 필요한 것만 추린 에디터 상태 */
|
|
10
|
+
declare const readEditorState: (e: Editor) => {
|
|
11
|
+
heading: SEditorHeadingLevel | 0;
|
|
12
|
+
bold: boolean;
|
|
13
|
+
italic: boolean;
|
|
14
|
+
underline: boolean;
|
|
15
|
+
strike: boolean;
|
|
16
|
+
code: boolean;
|
|
17
|
+
superscript: boolean;
|
|
18
|
+
subscript: boolean;
|
|
19
|
+
alignLeft: boolean;
|
|
20
|
+
alignCenter: boolean;
|
|
21
|
+
alignRight: boolean;
|
|
22
|
+
alignJustify: boolean;
|
|
23
|
+
bulletList: boolean;
|
|
24
|
+
orderedList: boolean;
|
|
25
|
+
taskList: boolean;
|
|
26
|
+
blockquote: boolean;
|
|
27
|
+
codeBlock: boolean;
|
|
28
|
+
link: boolean;
|
|
29
|
+
linkHref: string;
|
|
30
|
+
color: string;
|
|
31
|
+
highlight: string;
|
|
32
|
+
fontSize: string;
|
|
33
|
+
canUndo: boolean;
|
|
34
|
+
canRedo: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type EditorToolbarState = ReturnType<typeof readEditorState>;
|
|
37
|
+
/** 에디터가 아직 없을 때의 상태 — 아무것도 켜져 있지 않고 되돌릴 것도 없다 */
|
|
38
|
+
export declare const IDLE_TOOLBAR_STATE: EditorToolbarState;
|
|
39
|
+
export interface EditorToolbarBarProps {
|
|
40
|
+
/** 눌림 표시 — 에디터가 아직 없으면 `IDLE_TOOLBAR_STATE` */
|
|
41
|
+
state: EditorToolbarState;
|
|
42
|
+
/** 없으면 버튼을 눌러도 아무 일도 하지 않는다 (그때는 disabled 로 함께 잠근다) */
|
|
43
|
+
editor: Editor | null;
|
|
44
|
+
items: SEditorToolbarItem[];
|
|
45
|
+
fontSizes: readonly number[];
|
|
46
|
+
colors: SEditorColorOption[];
|
|
47
|
+
highlights: SEditorColorOption[];
|
|
48
|
+
/** 이미지 업로드 훅. 없으면 `image` 항목을 그리지 않는다 */
|
|
49
|
+
onImageUpload?: (file: File) => Promise<string>;
|
|
50
|
+
/** 편집 불가(비활성·읽기전용·엔진 로딩 중) — 모든 버튼을 잠근다 */
|
|
51
|
+
disabled: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* `'bar'` 는 편집 영역 위에 붙는 막대, `'bubble'` 은 선택 영역 위에 뜨는 판이다.
|
|
54
|
+
* 그리는 버튼은 같고 담는 상자와 줄바꿈만 다르다.
|
|
55
|
+
*/
|
|
56
|
+
variant?: 'bar' | 'bubble';
|
|
57
|
+
}
|
|
58
|
+
interface EditorToolbarProps extends Omit<EditorToolbarBarProps, 'state'> {
|
|
59
|
+
/** 동적으로 불러온 tiptap (useEditorState) */
|
|
60
|
+
api: TiptapApi;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 툴바 막대 (내부 전용 — 배럴로 내보내지 않는다).
|
|
64
|
+
*
|
|
65
|
+
* tiptap 을 모른다 — 눌림 상태는 `state` 로 받고 명령은 `editor` 가 있을 때만 나간다.
|
|
66
|
+
* 그래서 엔진을 불러오는 동안에도 SEditor 가 같은 막대를 그려 자리를 지킬 수 있다.
|
|
67
|
+
* 버튼 개수·아이콘이 같아야 줄바꿈까지 같아, 엔진이 붙는 순간 화면이 밀리지 않는다.
|
|
68
|
+
*/
|
|
69
|
+
export declare function EditorToolbarBar({ state, editor, items, fontSizes, colors, highlights, onImageUpload, disabled, variant, }: EditorToolbarBarProps): import("react").JSX.Element;
|
|
70
|
+
/**
|
|
71
|
+
* 버블 메뉴 — 글을 선택하면 그 위에 서식 판이 뜬다.
|
|
72
|
+
*
|
|
73
|
+
* tiptap Simple Editor 에는 없는 것이다(템플릿은 막대 하나뿐이다). 막대와 같은 버튼을
|
|
74
|
+
* 같은 코드로 그리고 담는 상자만 바꾼다 — 두 곳의 눌림 표시가 어긋날 일이 없다.
|
|
75
|
+
*
|
|
76
|
+
* 판은 `appendTo` 로 소속 모달(없으면 `body`)에 붙인다. 그대로 두면 tiptap 이 편집 영역의
|
|
77
|
+
* 부모에 붙이는데, 그 부모는 `maxHeight` 를 받으면 스크롤 상자가 되어 판 위쪽이 잘린다.
|
|
78
|
+
*/
|
|
79
|
+
export declare function EditorBubbleMenu({ api, editor, ...rest }: EditorToolbarProps): import("react").JSX.Element | null;
|
|
80
|
+
/**
|
|
81
|
+
* SEditor 툴바 — 눌림 상태를 tiptap 에서 구독해 막대에 넘긴다.
|
|
82
|
+
*
|
|
83
|
+
* `useEditorState` 를 거치는 이유: tiptap v3 는 트랜잭션마다 리렌더하지 않으므로, 이걸
|
|
84
|
+
* 거치지 않으면 커서를 굵은 글자 위로 옮겨도 B 버튼이 켜지지 않는다.
|
|
85
|
+
*/
|
|
86
|
+
export declare function EditorToolbar({ api, editor, ...rest }: EditorToolbarProps): import("react").JSX.Element;
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# SEditor
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### EditorBody
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `api` | `TiptapApi` | — | 다 불러온 tiptap — 이 컴포넌트는 준비된 뒤에만 마운트된다 |
|
|
12
|
+
| `value?` | `string` | — | |
|
|
13
|
+
| `defaultValue?` | `string` | — | |
|
|
14
|
+
| `htmlRef` | `RefObject<string>` | — | 지금 화면에 있는 HTML. 껍데기(SEditor)가 규칙 검증·폼 제출에 쓴다 |
|
|
15
|
+
| `placeholder` | `string` | — | |
|
|
16
|
+
| `typography` | `boolean` | — | |
|
|
17
|
+
| `editable` | `boolean` | — | |
|
|
18
|
+
| `disabled` | `boolean` | — | |
|
|
19
|
+
| `minHeight?` | `number \| string` | — | |
|
|
20
|
+
| `maxHeight?` | `number \| string` | — | |
|
|
21
|
+
| `toolbar` | `SEditorToolbarItem[] \| false` | — | |
|
|
22
|
+
| `bubbleMenu` | `SEditorToolbarItem[] \| false` | — | 선택 영역 위에 뜨는 서식 판. `false` 면 그리지 않는다 |
|
|
23
|
+
| `fontSizes` | `number[]` | — | |
|
|
24
|
+
| `colors` | `SEditorColorOption[]` | — | |
|
|
25
|
+
| `highlights` | `SEditorColorOption[]` | — | |
|
|
26
|
+
| `editorClass?` | `string` | — | |
|
|
27
|
+
| `editorStyle?` | `CSSProperties` | — | |
|
|
28
|
+
|
|
29
|
+
#### Events
|
|
30
|
+
|
|
31
|
+
| Event | Type | Description |
|
|
32
|
+
|-------|------|-------------|
|
|
33
|
+
| `onInput` | `(html: string) => void` | 사용자가 고쳐서 값이 바뀌었다 (setHTML·clear 같은 프로그램 조작은 제외) |
|
|
34
|
+
| `onFocusChange` | `(focused: boolean) => void` | |
|
|
35
|
+
| `onReady` | `() => void` | 에디터 인스턴스가 생겼다 — 껍데기가 밀린 focus() 를 흘려보낸다 |
|
|
36
|
+
| `onImageUpload` | `(file: File) => Promise<string>` | |
|
|
37
|
+
|
|
38
|
+
### EditorToolbarBar
|
|
39
|
+
|
|
40
|
+
#### Props
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Default | Description |
|
|
43
|
+
|------|------|---------|-------------|
|
|
44
|
+
| `state` | `EditorToolbarState` | — | 눌림 표시 — 에디터가 아직 없으면 `IDLE_TOOLBAR_STATE` |
|
|
45
|
+
| `editor` | `Editor \| null` | — | 없으면 버튼을 눌러도 아무 일도 하지 않는다 (그때는 disabled 로 함께 잠근다) |
|
|
46
|
+
| `items` | `SEditorToolbarItem[]` | — | |
|
|
47
|
+
| `fontSizes` | `readonly number[]` | — | |
|
|
48
|
+
| `colors` | `SEditorColorOption[]` | — | |
|
|
49
|
+
| `highlights` | `SEditorColorOption[]` | — | |
|
|
50
|
+
| `disabled` | `boolean` | — | 편집 불가(비활성·읽기전용·엔진 로딩 중) — 모든 버튼을 잠근다 |
|
|
51
|
+
| `variant?` | `'bar' \| 'bubble'` | `'bar'` | `'bar'` 는 편집 영역 위에 붙는 막대, `'bubble'` 은 선택 영역 위에 뜨는 판이다. 그리는 버튼은 같고 담는 상자와 줄바꿈만 다르다. |
|
|
52
|
+
|
|
53
|
+
#### Events
|
|
54
|
+
|
|
55
|
+
| Event | Type | Description |
|
|
56
|
+
|-------|------|-------------|
|
|
57
|
+
| `onImageUpload` | `(file: File) => Promise<string>` | 이미지 업로드 훅. 없으면 `image` 항목을 그리지 않는다 |
|
|
58
|
+
|
|
59
|
+
### SEditor
|
|
60
|
+
|
|
61
|
+
#### Props
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Default | Description |
|
|
64
|
+
|------|------|---------|-------------|
|
|
65
|
+
| `value?` | `string` | — | 값 (제어) — HTML 문자열 |
|
|
66
|
+
| `defaultValue?` | `string` | — | 초기값 (비제어) — HTML 문자열 |
|
|
67
|
+
| `placeholder?` | `string` | `'내용을 입력해 주세요.'` | 빈 문서에 보일 안내 문구 |
|
|
68
|
+
| `minHeight?` | `number \| string` | `200` | 편집 영역 최소 높이 (숫자=px) |
|
|
69
|
+
| `maxHeight?` | `number \| string` | — | 편집 영역 최대 높이 (숫자=px). 넘으면 편집 영역 안에서만 스크롤한다 |
|
|
70
|
+
| `toolbar?` | `SEditorToolbarItem[] \| false` | `SEDITOR_DEFAULT_TOOLBAR` | 툴바 구성. `false` 면 툴바 없이 본문만 (읽기 화면·간단 메모용) |
|
|
71
|
+
| `bubbleMenu?` | `SEditorToolbarItem[] \| false` | `SEDITOR_DEFAULT_BUBBLE_MENU` | 글을 선택했을 때 그 위에 뜨는 서식 판의 구성. `false` 면 뜨지 않는다. 읽기 전용·비활성일 때는 어차피 뜨지 않는다. 좁은 칸에 놓인 에디터라면 판이 필드 밖으로 넘칠 수 있으니 항목을 줄이거나 `false` 로 끈다. |
|
|
72
|
+
| `fontSizes?` | `number[]` | `[...SEDITOR_FONT_SIZES]` | 글자 크기 드롭다운 선택지 (px) |
|
|
73
|
+
| `colors?` | `SEditorColorOption[]` | `SEDITOR_DEFAULT_COLORS` | 글자색 팔레트 |
|
|
74
|
+
| `highlights?` | `SEditorColorOption[]` | `SEDITOR_DEFAULT_HIGHLIGHTS` | 형광펜(배경색) 팔레트 |
|
|
75
|
+
| `typography?` | `boolean` | `false` | 따옴표·하이픈·화살표 자동 치환 (`"` → `“”`, `--` → `—`, `->` → `→`). 상품 코드·규격 문자열이 입력한 그대로 남아야 하는 화면이 많아 기본은 끔이다. **마운트 시점에만 반영된다** — 값이 바뀌어도 이미 만들어진 에디터에는 적용되지 않는다. |
|
|
76
|
+
| `rules?` | `Rule[]` | — | 유효성 규칙 — blur 시 자동 검증 |
|
|
77
|
+
| `status?` | `SFieldStatus` | — | 필드 상태 ('default' | 'pass' | 'error') |
|
|
78
|
+
| `focused?` | `boolean` | — | 포커스 상태 (제어/반영) |
|
|
79
|
+
| `hovered?` | `boolean` | — | 호버 상태 (제어/반영) |
|
|
80
|
+
| `name?` | `string` | — | 폼 전송용 name |
|
|
81
|
+
| `editorClass?` | `string` | — | 편집 영역 className |
|
|
82
|
+
| `editorStyle?` | `CSSProperties` | — | 편집 영역 style |
|
|
83
|
+
| `label?` | `string` | — | |
|
|
84
|
+
| `labelWidth?` | `number \| string` | — | |
|
|
85
|
+
| `icon?` | `SIconName` | — | 레이블 영역 아이콘 |
|
|
86
|
+
| `iconColor?` | `SColor` | — | |
|
|
87
|
+
| `labelTooltip?` | `string` | — | 레이블 툴팁 텍스트 |
|
|
88
|
+
| `labelTooltipProps?` | `Partial<STooltipProps>` | — | 레이블 툴팁 상세 옵션 |
|
|
89
|
+
| `addonLabel?` | `string` | — | 우측 어드온 레이블 |
|
|
90
|
+
| `addonAlign?` | `SFieldAddonAlign` | — | 어드온 정렬 |
|
|
91
|
+
| `hint?` | `string` | — | |
|
|
92
|
+
| `error?` | `boolean` | — | |
|
|
93
|
+
| `errorMessage?` | `string` | — | |
|
|
94
|
+
| `width?` | `SFieldWidth` | `'100%'` | 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이. 본문 길이에 상한이 없으므로 기본은 `"100%"`(행 전체)다. |
|
|
95
|
+
| `disabled?` | `boolean` | `false` | |
|
|
96
|
+
| `readOnly?` | `boolean` | `false` | |
|
|
97
|
+
| `className?` | `string` | — | |
|
|
98
|
+
| `style?` | `CSSProperties` | — | |
|
|
99
|
+
|
|
100
|
+
#### Events
|
|
101
|
+
|
|
102
|
+
| Event | Type | Description |
|
|
103
|
+
|-------|------|-------------|
|
|
104
|
+
| `onValueChange` | `(html: string) => void` | 값 변경 (sdUpdate) — 빈 문서면 빈 문자열을 준다 |
|
|
105
|
+
| `onImageUpload` | `(file: File) => Promise<string>` | 이미지 업로드 — 고른 파일을 저장하고 **표시할 URL 을 돌려준다.** 저장 위치는 앱마다 다르므로 DS 가 정하지 않는다. 이 훅이 없으면 툴바에서 이미지 항목이 빠진다 (본문에 base64 를 박지 않는다 — HTML 이 그대로 DB 로 실려 간다). |
|
|
106
|
+
| `onFocus` | `() => void` | 포커스 진입 |
|
|
107
|
+
| `onBlur` | `() => void` | 포커스 이탈 |
|
|
108
|
+
|
|
109
|
+
#### Methods (ref)
|
|
110
|
+
|
|
111
|
+
| Method | Type | Description |
|
|
112
|
+
|--------|------|-------------|
|
|
113
|
+
| `focus` | `() => void` | 편집 영역에 포커스 |
|
|
114
|
+
| `blur` | `() => void` | 포커스 해제 |
|
|
115
|
+
| `getHTML` | `() => string` | 현재 내용을 HTML 로 반환 (빈 문서면 빈 문자열) |
|
|
116
|
+
| `getText` | `() => string` | 현재 내용을 서식 없는 텍스트로 반환 |
|
|
117
|
+
| `setHTML` | `(html: string) => void` | 내용을 HTML 로 교체 (onValueChange 를 발생시키지 않는다) |
|
|
118
|
+
| `clear` | `() => void` | 내용을 비운다 |
|
|
119
|
+
| `editor` | `Editor \| null` | tiptap 에디터 인스턴스 — 확장 명령이 필요할 때만 쓴다 |
|
|
120
|
+
|
|
121
|
+
## Types
|
|
122
|
+
|
|
123
|
+
### TiptapApi
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
/** 동적으로 불러온 tiptap — 이 객체를 거치지 않고는 에디터 코드가 tiptap 을 만지지 않는다. */
|
|
127
|
+
export interface TiptapApi {
|
|
128
|
+
useEditor: TiptapReact['useEditor'];
|
|
129
|
+
useEditorState: TiptapReact['useEditorState'];
|
|
130
|
+
EditorContent: TiptapReact['EditorContent'];
|
|
131
|
+
/** 선택 영역 위에 뜨는 판 — 자리는 floating-ui 가 잡는다 */
|
|
132
|
+
BubbleMenu: TiptapMenus['BubbleMenu'];
|
|
133
|
+
/** tiptap 확장 구성 — 아래 createExtensions 주석 참고 */
|
|
134
|
+
createExtensions: (options: SEditorExtensionOptions) => AnyExtension[];
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### SEditorToolbarItem
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
export type SEditorToolbarItem = SEditorToolbarAction | '|';
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### SEditorColorOption
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
export interface SEditorColorOption {
|
|
148
|
+
/** 팔레트 칸의 접근성 레이블·툴팁 */
|
|
149
|
+
label: string;
|
|
150
|
+
/** 팔레트 키(`red_75` …) 또는 CSS 색상 문자열 */
|
|
151
|
+
color: SColor;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### EditorToolbarState
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
export type EditorToolbarState = ReturnType<typeof readEditorState>;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### SEditorExtensionOptions
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
export interface SEditorExtensionOptions {
|
|
165
|
+
/** 빈 문서에 보일 문구를 그때그때 읽어 오는 게터 */
|
|
166
|
+
getPlaceholder: () => string;
|
|
167
|
+
/** 따옴표·하이픈·화살표 자동 치환 (Typography) */
|
|
168
|
+
typography: boolean;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### SEditorToolbarAction
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
export type SEditorToolbarAction = (typeof SEDITOR_TOOLBAR_ITEMS)[number];
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### SEDITOR_TOOLBAR_ITEMS
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
/** 툴바에 놓을 수 있는 항목. `'|'` 는 구분선이다. */
|
|
182
|
+
export const SEDITOR_TOOLBAR_ITEMS = [
|
|
183
|
+
'heading',
|
|
184
|
+
'fontSize',
|
|
185
|
+
'bold',
|
|
186
|
+
'italic',
|
|
187
|
+
'underline',
|
|
188
|
+
'strike',
|
|
189
|
+
'code',
|
|
190
|
+
'color',
|
|
191
|
+
'highlight',
|
|
192
|
+
'superscript',
|
|
193
|
+
'subscript',
|
|
194
|
+
'alignLeft',
|
|
195
|
+
'alignCenter',
|
|
196
|
+
'alignRight',
|
|
197
|
+
'alignJustify',
|
|
198
|
+
'bulletList',
|
|
199
|
+
'orderedList',
|
|
200
|
+
'taskList',
|
|
201
|
+
'list',
|
|
202
|
+
'blockquote',
|
|
203
|
+
'codeBlock',
|
|
204
|
+
'horizontalRule',
|
|
205
|
+
'link',
|
|
206
|
+
'image',
|
|
207
|
+
'undo',
|
|
208
|
+
'redo',
|
|
209
|
+
] as const;
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Dependencies
|
|
213
|
+
|
|
214
|
+
### Depends on
|
|
215
|
+
|
|
216
|
+
- [SButton](../SButton)
|
|
217
|
+
- [SField](../SField)
|
|
218
|
+
- [SIcon](../SIcon)
|
|
219
|
+
- [SInput](../SInput)
|
|
220
|
+
|
|
221
|
+
### Graph
|
|
222
|
+
|
|
223
|
+
```mermaid
|
|
224
|
+
graph TD;
|
|
225
|
+
SEditor --> SButton
|
|
226
|
+
SEditor --> SField
|
|
227
|
+
SEditor --> SIcon
|
|
228
|
+
SEditor --> SInput
|
|
229
|
+
style SEditor fill:#f9f,stroke:#333,stroke-width:4px
|
|
230
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
import type { Editor } from '@tiptap/core';
|
|
3
|
+
import { type SColor } from '../../lib/color';
|
|
4
|
+
import { type SFieldWidth } from '../../lib/field-width';
|
|
5
|
+
import { type Rule } from '../../lib/form';
|
|
6
|
+
import { type SFieldAddonAlign, type SFieldStatus } from '../SField';
|
|
7
|
+
import { type SIconName } from '../SIcon';
|
|
8
|
+
import { type STooltipProps } from '../STooltip';
|
|
9
|
+
import { type SEditorColorOption, type SEditorToolbarItem } from './editor.config';
|
|
10
|
+
/** ref 로 노출되는 조작 메서드 */
|
|
11
|
+
export interface SEditorHandle {
|
|
12
|
+
/** 편집 영역에 포커스 */
|
|
13
|
+
focus: () => void;
|
|
14
|
+
/** 포커스 해제 */
|
|
15
|
+
blur: () => void;
|
|
16
|
+
/** 현재 내용을 HTML 로 반환 (빈 문서면 빈 문자열) */
|
|
17
|
+
getHTML: () => string;
|
|
18
|
+
/** 현재 내용을 서식 없는 텍스트로 반환 */
|
|
19
|
+
getText: () => string;
|
|
20
|
+
/** 내용을 HTML 로 교체 (onValueChange 를 발생시키지 않는다) */
|
|
21
|
+
setHTML: (html: string) => void;
|
|
22
|
+
/** 내용을 비운다 */
|
|
23
|
+
clear: () => void;
|
|
24
|
+
/** tiptap 에디터 인스턴스 — 확장 명령이 필요할 때만 쓴다 */
|
|
25
|
+
editor: Editor | null;
|
|
26
|
+
}
|
|
27
|
+
export interface SEditorProps {
|
|
28
|
+
/** 값 (제어) — HTML 문자열 */
|
|
29
|
+
value?: string;
|
|
30
|
+
/** 초기값 (비제어) — HTML 문자열 */
|
|
31
|
+
defaultValue?: string;
|
|
32
|
+
/** 값 변경 (sdUpdate) — 빈 문서면 빈 문자열을 준다 */
|
|
33
|
+
onValueChange?: (html: string) => void;
|
|
34
|
+
/** 빈 문서에 보일 안내 문구 */
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
/** 편집 영역 최소 높이 (숫자=px) */
|
|
37
|
+
minHeight?: number | string;
|
|
38
|
+
/** 편집 영역 최대 높이 (숫자=px). 넘으면 편집 영역 안에서만 스크롤한다 */
|
|
39
|
+
maxHeight?: number | string;
|
|
40
|
+
/** 툴바 구성. `false` 면 툴바 없이 본문만 (읽기 화면·간단 메모용) */
|
|
41
|
+
toolbar?: SEditorToolbarItem[] | false;
|
|
42
|
+
/**
|
|
43
|
+
* 글을 선택했을 때 그 위에 뜨는 서식 판의 구성. `false` 면 뜨지 않는다.
|
|
44
|
+
*
|
|
45
|
+
* 읽기 전용·비활성일 때는 어차피 뜨지 않는다. 좁은 칸에 놓인 에디터라면 판이 필드 밖으로
|
|
46
|
+
* 넘칠 수 있으니 항목을 줄이거나 `false` 로 끈다.
|
|
47
|
+
*/
|
|
48
|
+
bubbleMenu?: SEditorToolbarItem[] | false;
|
|
49
|
+
/** 글자 크기 드롭다운 선택지 (px) */
|
|
50
|
+
fontSizes?: number[];
|
|
51
|
+
/** 글자색 팔레트 */
|
|
52
|
+
colors?: SEditorColorOption[];
|
|
53
|
+
/** 형광펜(배경색) 팔레트 */
|
|
54
|
+
highlights?: SEditorColorOption[];
|
|
55
|
+
/**
|
|
56
|
+
* 이미지 업로드 — 고른 파일을 저장하고 **표시할 URL 을 돌려준다.**
|
|
57
|
+
* 저장 위치는 앱마다 다르므로 DS 가 정하지 않는다. 이 훅이 없으면 툴바에서
|
|
58
|
+
* 이미지 항목이 빠진다 (본문에 base64 를 박지 않는다 — HTML 이 그대로 DB 로 실려 간다).
|
|
59
|
+
*/
|
|
60
|
+
onImageUpload?: (file: File) => Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* 따옴표·하이픈·화살표 자동 치환 (`"` → `“”`, `--` → `—`, `->` → `→`).
|
|
63
|
+
* 상품 코드·규격 문자열이 입력한 그대로 남아야 하는 화면이 많아 기본은 끔이다.
|
|
64
|
+
* **마운트 시점에만 반영된다** — 값이 바뀌어도 이미 만들어진 에디터에는 적용되지 않는다.
|
|
65
|
+
*/
|
|
66
|
+
typography?: boolean;
|
|
67
|
+
/** 유효성 규칙 — blur 시 자동 검증 */
|
|
68
|
+
rules?: Rule[];
|
|
69
|
+
/** 필드 상태 ('default' | 'pass' | 'error') */
|
|
70
|
+
status?: SFieldStatus;
|
|
71
|
+
/** 포커스 상태 (제어/반영) */
|
|
72
|
+
focused?: boolean;
|
|
73
|
+
/** 호버 상태 (제어/반영) */
|
|
74
|
+
hovered?: boolean;
|
|
75
|
+
/** 폼 전송용 name */
|
|
76
|
+
name?: string;
|
|
77
|
+
/** 편집 영역 className */
|
|
78
|
+
editorClass?: string;
|
|
79
|
+
/** 편집 영역 style */
|
|
80
|
+
editorStyle?: CSSProperties;
|
|
81
|
+
/** 포커스 진입 */
|
|
82
|
+
onFocus?: () => void;
|
|
83
|
+
/** 포커스 이탈 */
|
|
84
|
+
onBlur?: () => void;
|
|
85
|
+
label?: string;
|
|
86
|
+
labelWidth?: number | string;
|
|
87
|
+
/** 레이블 영역 아이콘 */
|
|
88
|
+
icon?: SIconName;
|
|
89
|
+
iconColor?: SColor;
|
|
90
|
+
/** 레이블 툴팁 텍스트 */
|
|
91
|
+
labelTooltip?: string;
|
|
92
|
+
/** 레이블 툴팁 상세 옵션 */
|
|
93
|
+
labelTooltipProps?: Partial<STooltipProps>;
|
|
94
|
+
/** 우측 어드온 레이블 */
|
|
95
|
+
addonLabel?: string;
|
|
96
|
+
/** 어드온 정렬 */
|
|
97
|
+
addonAlign?: SFieldAddonAlign;
|
|
98
|
+
hint?: string;
|
|
99
|
+
error?: boolean;
|
|
100
|
+
errorMessage?: string;
|
|
101
|
+
/**
|
|
102
|
+
* 컨트롤 너비 — 폭 등급(`'xs' | 'sm' | 'md' | 'lg' | 'xl'`) · 숫자=px · CSS 길이.
|
|
103
|
+
* 본문 길이에 상한이 없으므로 기본은 `"100%"`(행 전체)다.
|
|
104
|
+
*/
|
|
105
|
+
width?: SFieldWidth;
|
|
106
|
+
disabled?: boolean;
|
|
107
|
+
readOnly?: boolean;
|
|
108
|
+
className?: string;
|
|
109
|
+
style?: CSSProperties;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* SEditor — tiptap 기반 리치 텍스트 에디터.
|
|
113
|
+
*
|
|
114
|
+
* 값은 **HTML 문자열**로 주고받는다. 들어온 HTML 은 tiptap 스키마가 파싱하므로 스키마에 없는
|
|
115
|
+
* 태그·속성(script·onerror …)은 그 자리에서 떨어져 나간다 — 별도 살균기를 거치지 않는다.
|
|
116
|
+
* 테두리 박스·라벨·에러 표시는 `SField`(multiline) 가 소유하고, 이 컴포넌트는 그 안에
|
|
117
|
+
* 툴바와 편집 영역만 세로로 쌓는다.
|
|
118
|
+
*
|
|
119
|
+
* tiptap 은 **마운트한 뒤에 불러온다** — 정적으로 묶으면 SEditor 를 한 번도 쓰지 않는 소비
|
|
120
|
+
* 앱까지 tiptap·prosemirror 를 번들에 지고 간다 (`tiptap-api.ts` 주석 참고). 불러오는 동안은
|
|
121
|
+
* 같은 크기의 빈 편집 영역이 자리를 지키므로 화면이 튀지 않고, 그 사이 `ref` 의 메서드는
|
|
122
|
+
* 아무 일도 하지 않는다 — `focus()` 만은 기억해 두었다가 준비되는 순간 흘려보낸다.
|
|
123
|
+
*/
|
|
124
|
+
export declare const SEditor: import("react").ForwardRefExoticComponent<SEditorProps & import("react").RefAttributes<SEditorHandle>>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { SVGProps } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* SEditor 서식 툴바 아이콘 (로컬 전용).
|
|
4
|
+
*
|
|
5
|
+
* 공용 아이콘 셋(`SIcon` / `icons.gen.tsx`)은 디자인팀 소유이고 서식(bold·italic·정렬·목록 …)
|
|
6
|
+
* 아이콘이 아직 없다. 여기 있는 것들은 그 자리를 메우는 임시 도형이며, 정식 아이콘이 셋에
|
|
7
|
+
* 들어오면 이 파일을 지우고 `SIcon` 으로 교체한다. **다른 컴포넌트에서 import 하지 않는다.**
|
|
8
|
+
*
|
|
9
|
+
* 도형은 tiptap Simple Editor 템플릿의 아이콘 셋을 그대로 옮겼다
|
|
10
|
+
* (ueberdosis/tiptap-ui-components, MIT © 2025 Tiptap — `apps/web/src/components/tiptap-icons`).
|
|
11
|
+
* 24×24 격자에 획 굵기 2 의 면(fill) 도형이고 색은 전부 `currentColor` 를 따른다 — 버튼이
|
|
12
|
+
* 토큰으로 정한 색이 그대로 내려온다. 새 아이콘을 더할 때도 이 격자·굵기를 지킨다.
|
|
13
|
+
*
|
|
14
|
+
* 템플릿에 없어 직접 그린 것은 `IconTextColor`·`IconHorizontalRule` 둘뿐이다 (아래 주석 참고).
|
|
15
|
+
*/
|
|
16
|
+
export type SEditorIconProps = {
|
|
17
|
+
size?: number | string;
|
|
18
|
+
} & SVGProps<SVGSVGElement>;
|
|
19
|
+
/** 제목 — 지금 단계의 도형을 보여 준다 (본문이면 단계 없는 H) */
|
|
20
|
+
export declare const IconHeading: ({ level, ...p }: SEditorIconProps & {
|
|
21
|
+
level?: 0 | 1 | 2 | 3;
|
|
22
|
+
}) => import("react").JSX.Element;
|
|
23
|
+
export declare const IconBold: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
24
|
+
export declare const IconItalic: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
25
|
+
export declare const IconUnderline: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
26
|
+
export declare const IconStrike: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
27
|
+
/** 인라인 코드 */
|
|
28
|
+
export declare const IconCode: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
29
|
+
export declare const IconCodeBlock: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
30
|
+
export declare const IconBlockquote: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
31
|
+
/** 형광펜 — 지금 고른 색은 툴바 버튼이 아이콘 아래 막대로 따로 보여 준다 */
|
|
32
|
+
export declare const IconHighlight: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
33
|
+
export declare const IconSuperscript: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
34
|
+
export declare const IconSubscript: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
35
|
+
export declare const IconAlignLeft: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
36
|
+
export declare const IconAlignCenter: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
37
|
+
export declare const IconAlignRight: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
38
|
+
export declare const IconAlignJustify: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
39
|
+
export declare const IconBulletList: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
40
|
+
export declare const IconOrderedList: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
41
|
+
export declare const IconTaskList: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
42
|
+
export declare const IconLink: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
43
|
+
/** 좁은 화면 툴바의 하위 뷰에서 되돌아가기 */
|
|
44
|
+
export declare const IconArrowLeft: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
45
|
+
export declare const IconUndo: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
46
|
+
export declare const IconRedo: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
47
|
+
/**
|
|
48
|
+
* 글자색 — 템플릿에 없어 직접 그렸다 (Simple Editor 에는 글자색 항목이 없다).
|
|
49
|
+
*
|
|
50
|
+
* 나머지 아이콘이 면 도형이라 A 도 같은 굵기(2)로 보이도록 획으로 긋고 끝을 둥글렸다 —
|
|
51
|
+
* 템플릿의 막대(`rx=1` 인 2px 사각형)와 같은 인상이다. 지금 고른 색은 형광펜과 마찬가지로
|
|
52
|
+
* 툴바 버튼이 아이콘 아래 막대로 보여 준다.
|
|
53
|
+
*/
|
|
54
|
+
export declare const IconTextColor: (p: SEditorIconProps) => import("react").JSX.Element;
|
|
55
|
+
/**
|
|
56
|
+
* 구분선 — 템플릿에 없어 직접 그렸다 (Simple Editor 는 구분선 노드는 있지만 툴바 항목이 없다).
|
|
57
|
+
* 막대는 템플릿의 정렬 아이콘과 같은 규격(높이 2, `rx=1`)이다.
|
|
58
|
+
*/
|
|
59
|
+
export declare const IconHorizontalRule: (p: SEditorIconProps) => import("react").JSX.Element;
|