@hilum/ui 3.2.3 → 3.2.4
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/dist/index.d.ts +348 -32
- package/dist/index.js +2511 -676
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-dialog.tsx +19 -6
- package/src/components/callout.tsx +99 -0
- package/src/components/card.tsx +13 -0
- package/src/components/chart.tsx +398 -29
- package/src/components/color-picker.tsx +39 -45
- package/src/components/combobox.tsx +83 -64
- package/src/components/command.tsx +35 -0
- package/src/components/context-menu.tsx +49 -27
- package/src/components/data-transfer-controls.tsx +156 -0
- package/src/components/dialog.tsx +19 -6
- package/src/components/dropdown-menu.tsx +50 -28
- package/src/components/file-dropzone.tsx +171 -0
- package/src/components/help-tooltip.tsx +90 -0
- package/src/components/media-asset-card.tsx +118 -0
- package/src/components/menubar.tsx +52 -30
- package/src/components/popover.tsx +31 -17
- package/src/components/rich-text-editor.tsx +448 -0
- package/src/components/searchable-table.tsx +412 -0
- package/src/components/select.tsx +44 -28
- package/src/components/sidebar.tsx +105 -14
- package/src/components/stat-card.tsx +122 -8
- package/src/components/status-badge.tsx +144 -0
- package/src/components/titled-card.tsx +102 -0
- package/src/components/url-redirect-prompt.tsx +85 -0
- package/src/index.ts +10 -0
- package/src/lib/mobile-popper-sheet.ts +78 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import {
|
|
5
|
+
AlignLeft,
|
|
6
|
+
Bold,
|
|
7
|
+
FileText,
|
|
8
|
+
Heading1,
|
|
9
|
+
Heading2,
|
|
10
|
+
Heading3,
|
|
11
|
+
Image,
|
|
12
|
+
Italic,
|
|
13
|
+
Link,
|
|
14
|
+
List,
|
|
15
|
+
ListOrdered,
|
|
16
|
+
Minus,
|
|
17
|
+
Strikethrough,
|
|
18
|
+
Underline,
|
|
19
|
+
XCircle,
|
|
20
|
+
} from "lucide-react";
|
|
21
|
+
import { Button } from "./button";
|
|
22
|
+
import { Input } from "./input";
|
|
23
|
+
import { Separator } from "./separator";
|
|
24
|
+
import { cn } from "../lib/utils";
|
|
25
|
+
|
|
26
|
+
type RichTextImageUrlHandler = () => string | null | undefined | Promise<string | null | undefined>;
|
|
27
|
+
|
|
28
|
+
interface RichTextEditorProps {
|
|
29
|
+
value: string;
|
|
30
|
+
onChange: (html: string) => void;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
minHeight?: string | number;
|
|
33
|
+
id?: string;
|
|
34
|
+
"aria-label"?: string;
|
|
35
|
+
className?: string;
|
|
36
|
+
editorClassName?: string;
|
|
37
|
+
toolbarClassName?: string;
|
|
38
|
+
onRequestImageUrl?: RichTextImageUrlHandler;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ToolbarButtonConfig {
|
|
42
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
43
|
+
command: string;
|
|
44
|
+
title: string;
|
|
45
|
+
value?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function wrapSelectionInline(tag: string, attrs?: Record<string, string>) {
|
|
49
|
+
const selection = window.getSelection();
|
|
50
|
+
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return;
|
|
51
|
+
|
|
52
|
+
const range = selection.getRangeAt(0);
|
|
53
|
+
const wrapper = document.createElement(tag);
|
|
54
|
+
|
|
55
|
+
Object.entries(attrs ?? {}).forEach(([key, value]) => {
|
|
56
|
+
wrapper.setAttribute(key, value);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
wrapper.appendChild(range.extractContents());
|
|
60
|
+
range.insertNode(wrapper);
|
|
61
|
+
|
|
62
|
+
selection.removeAllRanges();
|
|
63
|
+
const nextRange = document.createRange();
|
|
64
|
+
nextRange.selectNodeContents(wrapper);
|
|
65
|
+
selection.addRange(nextRange);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatBlock(tag: string) {
|
|
69
|
+
const selection = window.getSelection();
|
|
70
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
71
|
+
|
|
72
|
+
const range = selection.getRangeAt(0);
|
|
73
|
+
let block = range.startContainer as HTMLElement;
|
|
74
|
+
|
|
75
|
+
if (block.nodeType === Node.TEXT_NODE) block = block.parentElement as HTMLElement;
|
|
76
|
+
while (block && block.isContentEditable !== true) {
|
|
77
|
+
const parent = block.parentElement;
|
|
78
|
+
if (!parent || parent.getAttribute("contenteditable") === "true") break;
|
|
79
|
+
block = parent;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const nextBlock = document.createElement(tag);
|
|
83
|
+
if (block && block.getAttribute("contenteditable") !== "true") {
|
|
84
|
+
nextBlock.innerHTML = block.innerHTML;
|
|
85
|
+
block.replaceWith(nextBlock);
|
|
86
|
+
} else {
|
|
87
|
+
nextBlock.appendChild(range.extractContents());
|
|
88
|
+
range.insertNode(nextBlock);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
selection.removeAllRanges();
|
|
92
|
+
const nextRange = document.createRange();
|
|
93
|
+
nextRange.selectNodeContents(nextBlock);
|
|
94
|
+
selection.addRange(nextRange);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function insertList(ordered: boolean) {
|
|
98
|
+
const selection = window.getSelection();
|
|
99
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
100
|
+
|
|
101
|
+
const range = selection.getRangeAt(0);
|
|
102
|
+
const fragment = range.extractContents();
|
|
103
|
+
const list = document.createElement(ordered ? "ol" : "ul");
|
|
104
|
+
const children = Array.from(fragment.childNodes);
|
|
105
|
+
|
|
106
|
+
if (children.length === 0) {
|
|
107
|
+
const item = document.createElement("li");
|
|
108
|
+
item.innerHTML = "\u200B";
|
|
109
|
+
list.appendChild(item);
|
|
110
|
+
} else {
|
|
111
|
+
children.forEach((child) => {
|
|
112
|
+
const item = document.createElement("li");
|
|
113
|
+
item.appendChild(child);
|
|
114
|
+
list.appendChild(item);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
range.insertNode(list);
|
|
119
|
+
selection.removeAllRanges();
|
|
120
|
+
const nextRange = document.createRange();
|
|
121
|
+
nextRange.selectNodeContents(list);
|
|
122
|
+
selection.addRange(nextRange);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function insertElement(element: HTMLElement) {
|
|
126
|
+
const selection = window.getSelection();
|
|
127
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
128
|
+
|
|
129
|
+
const range = selection.getRangeAt(0);
|
|
130
|
+
range.deleteContents();
|
|
131
|
+
range.insertNode(element);
|
|
132
|
+
|
|
133
|
+
const nextRange = document.createRange();
|
|
134
|
+
nextRange.setStartAfter(element);
|
|
135
|
+
nextRange.collapse(true);
|
|
136
|
+
selection.removeAllRanges();
|
|
137
|
+
selection.addRange(nextRange);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function removeFormat() {
|
|
141
|
+
const selection = window.getSelection();
|
|
142
|
+
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return;
|
|
143
|
+
|
|
144
|
+
const range = selection.getRangeAt(0);
|
|
145
|
+
const fragment = range.extractContents();
|
|
146
|
+
const cleaned = document.createDocumentFragment();
|
|
147
|
+
|
|
148
|
+
Array.from(fragment.childNodes).forEach((child) => {
|
|
149
|
+
cleaned.appendChild(document.createTextNode(child.textContent ?? ""));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
range.insertNode(cleaned);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function RichTextEditor({
|
|
156
|
+
value,
|
|
157
|
+
onChange,
|
|
158
|
+
placeholder = "Start writing...",
|
|
159
|
+
minHeight = "200px",
|
|
160
|
+
id,
|
|
161
|
+
"aria-label": ariaLabel,
|
|
162
|
+
className,
|
|
163
|
+
editorClassName,
|
|
164
|
+
toolbarClassName,
|
|
165
|
+
onRequestImageUrl,
|
|
166
|
+
}: RichTextEditorProps) {
|
|
167
|
+
const editorRef = React.useRef<HTMLDivElement>(null);
|
|
168
|
+
const savedRangeRef = React.useRef<Range | null>(null);
|
|
169
|
+
const [showLinkInput, setShowLinkInput] = React.useState(false);
|
|
170
|
+
const [linkUrl, setLinkUrl] = React.useState("");
|
|
171
|
+
|
|
172
|
+
React.useEffect(() => {
|
|
173
|
+
const editor = editorRef.current;
|
|
174
|
+
if (!editor || document.activeElement === editor || editor.innerHTML === value) return;
|
|
175
|
+
editor.innerHTML = value;
|
|
176
|
+
}, [value]);
|
|
177
|
+
|
|
178
|
+
const emitChange = React.useCallback(() => {
|
|
179
|
+
if (editorRef.current) onChange(editorRef.current.innerHTML);
|
|
180
|
+
}, [onChange]);
|
|
181
|
+
|
|
182
|
+
const saveSelection = React.useCallback(() => {
|
|
183
|
+
const selection = window.getSelection();
|
|
184
|
+
if (selection && selection.rangeCount > 0) {
|
|
185
|
+
savedRangeRef.current = selection.getRangeAt(0).cloneRange();
|
|
186
|
+
}
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
const restoreSelection = React.useCallback(() => {
|
|
190
|
+
const range = savedRangeRef.current;
|
|
191
|
+
const selection = window.getSelection();
|
|
192
|
+
if (!range || !selection) return;
|
|
193
|
+
selection.removeAllRanges();
|
|
194
|
+
selection.addRange(range);
|
|
195
|
+
savedRangeRef.current = null;
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
const runCommand = React.useCallback(
|
|
199
|
+
(command: string, commandValue?: string) => {
|
|
200
|
+
editorRef.current?.focus();
|
|
201
|
+
|
|
202
|
+
switch (command) {
|
|
203
|
+
case "bold":
|
|
204
|
+
wrapSelectionInline("strong");
|
|
205
|
+
break;
|
|
206
|
+
case "italic":
|
|
207
|
+
wrapSelectionInline("em");
|
|
208
|
+
break;
|
|
209
|
+
case "underline":
|
|
210
|
+
wrapSelectionInline("u");
|
|
211
|
+
break;
|
|
212
|
+
case "strikeThrough":
|
|
213
|
+
wrapSelectionInline("s");
|
|
214
|
+
break;
|
|
215
|
+
case "formatBlock":
|
|
216
|
+
if (commandValue) formatBlock(commandValue);
|
|
217
|
+
break;
|
|
218
|
+
case "insertUnorderedList":
|
|
219
|
+
insertList(false);
|
|
220
|
+
break;
|
|
221
|
+
case "insertOrderedList":
|
|
222
|
+
insertList(true);
|
|
223
|
+
break;
|
|
224
|
+
case "insertHorizontalRule":
|
|
225
|
+
insertElement(document.createElement("hr"));
|
|
226
|
+
break;
|
|
227
|
+
case "removeFormat":
|
|
228
|
+
removeFormat();
|
|
229
|
+
break;
|
|
230
|
+
default:
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
emitChange();
|
|
235
|
+
},
|
|
236
|
+
[emitChange],
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const insertLink = React.useCallback(() => {
|
|
240
|
+
const url = linkUrl.trim();
|
|
241
|
+
if (!url) return;
|
|
242
|
+
|
|
243
|
+
restoreSelection();
|
|
244
|
+
wrapSelectionInline("a", { href: url });
|
|
245
|
+
editorRef.current?.focus();
|
|
246
|
+
setLinkUrl("");
|
|
247
|
+
setShowLinkInput(false);
|
|
248
|
+
emitChange();
|
|
249
|
+
}, [emitChange, linkUrl, restoreSelection]);
|
|
250
|
+
|
|
251
|
+
const insertImage = React.useCallback(async () => {
|
|
252
|
+
const url =
|
|
253
|
+
(await onRequestImageUrl?.()) ??
|
|
254
|
+
(typeof window !== "undefined" ? window.prompt("Image URL:") : null);
|
|
255
|
+
|
|
256
|
+
if (!url) return;
|
|
257
|
+
|
|
258
|
+
const image = document.createElement("img");
|
|
259
|
+
image.src = url;
|
|
260
|
+
image.alt = "";
|
|
261
|
+
editorRef.current?.focus();
|
|
262
|
+
insertElement(image);
|
|
263
|
+
emitChange();
|
|
264
|
+
}, [emitChange, onRequestImageUrl]);
|
|
265
|
+
|
|
266
|
+
const toolbarGroups: ToolbarButtonConfig[][] = [
|
|
267
|
+
[
|
|
268
|
+
{ icon: Bold, command: "bold", title: "Bold (Ctrl+B)" },
|
|
269
|
+
{ icon: Italic, command: "italic", title: "Italic (Ctrl+I)" },
|
|
270
|
+
{ icon: Underline, command: "underline", title: "Underline (Ctrl+U)" },
|
|
271
|
+
{ icon: Strikethrough, command: "strikeThrough", title: "Strikethrough" },
|
|
272
|
+
],
|
|
273
|
+
[
|
|
274
|
+
{ icon: Heading1, command: "formatBlock", title: "Heading 1", value: "h1" },
|
|
275
|
+
{ icon: Heading2, command: "formatBlock", title: "Heading 2", value: "h2" },
|
|
276
|
+
{ icon: Heading3, command: "formatBlock", title: "Heading 3", value: "h3" },
|
|
277
|
+
],
|
|
278
|
+
[
|
|
279
|
+
{ icon: List, command: "insertUnorderedList", title: "Bullet list" },
|
|
280
|
+
{ icon: ListOrdered, command: "insertOrderedList", title: "Numbered list" },
|
|
281
|
+
{ icon: AlignLeft, command: "formatBlock", title: "Blockquote", value: "blockquote" },
|
|
282
|
+
{ icon: FileText, command: "formatBlock", title: "Code block", value: "pre" },
|
|
283
|
+
],
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
const renderToolbarButton = ({
|
|
287
|
+
icon: Icon,
|
|
288
|
+
command,
|
|
289
|
+
title,
|
|
290
|
+
value: commandValue,
|
|
291
|
+
}: ToolbarButtonConfig) => (
|
|
292
|
+
<Button
|
|
293
|
+
key={`${command}-${title}`}
|
|
294
|
+
type="button"
|
|
295
|
+
variant="ghost"
|
|
296
|
+
size="icon"
|
|
297
|
+
className="size-10 text-muted-foreground hover:text-foreground"
|
|
298
|
+
title={title}
|
|
299
|
+
aria-label={title}
|
|
300
|
+
onMouseDown={(event) => {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
runCommand(command, commandValue);
|
|
303
|
+
}}
|
|
304
|
+
>
|
|
305
|
+
<Icon className="size-4" aria-hidden="true" />
|
|
306
|
+
</Button>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<div
|
|
311
|
+
className={cn(
|
|
312
|
+
"overflow-hidden rounded-xl border border-border bg-card shadow-natural",
|
|
313
|
+
"focus-within:border-brand-primary/50 focus-within:ring-2 focus-within:ring-brand-primary/25",
|
|
314
|
+
className,
|
|
315
|
+
)}
|
|
316
|
+
data-slot="rich-text-editor"
|
|
317
|
+
>
|
|
318
|
+
<div
|
|
319
|
+
className={cn(
|
|
320
|
+
"flex min-w-0 flex-wrap items-center gap-1 border-b border-border bg-muted/40 p-1.5",
|
|
321
|
+
toolbarClassName,
|
|
322
|
+
)}
|
|
323
|
+
role="toolbar"
|
|
324
|
+
aria-label="Text formatting"
|
|
325
|
+
>
|
|
326
|
+
{toolbarGroups.map((group, index) => (
|
|
327
|
+
<React.Fragment key={`toolbar-group-${index}`}>
|
|
328
|
+
{index > 0 && <Separator orientation="vertical" className="mx-1 h-6" />}
|
|
329
|
+
{group.map(renderToolbarButton)}
|
|
330
|
+
</React.Fragment>
|
|
331
|
+
))}
|
|
332
|
+
<Separator orientation="vertical" className="mx-1 h-6" />
|
|
333
|
+
<Button
|
|
334
|
+
type="button"
|
|
335
|
+
variant="ghost"
|
|
336
|
+
size="icon"
|
|
337
|
+
className="size-10 text-muted-foreground hover:text-foreground"
|
|
338
|
+
title="Insert link"
|
|
339
|
+
aria-label="Insert link"
|
|
340
|
+
onMouseDown={(event) => {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
saveSelection();
|
|
343
|
+
setShowLinkInput((current) => !current);
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
<Link className="size-4" aria-hidden="true" />
|
|
347
|
+
</Button>
|
|
348
|
+
<Button
|
|
349
|
+
type="button"
|
|
350
|
+
variant="ghost"
|
|
351
|
+
size="icon"
|
|
352
|
+
className="size-10 text-muted-foreground hover:text-foreground"
|
|
353
|
+
title="Insert image"
|
|
354
|
+
aria-label="Insert image"
|
|
355
|
+
onMouseDown={(event) => {
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
void insertImage();
|
|
358
|
+
}}
|
|
359
|
+
>
|
|
360
|
+
<Image className="size-4" aria-hidden="true" />
|
|
361
|
+
</Button>
|
|
362
|
+
{renderToolbarButton({
|
|
363
|
+
icon: Minus,
|
|
364
|
+
command: "insertHorizontalRule",
|
|
365
|
+
title: "Horizontal rule",
|
|
366
|
+
})}
|
|
367
|
+
{renderToolbarButton({
|
|
368
|
+
icon: XCircle,
|
|
369
|
+
command: "removeFormat",
|
|
370
|
+
title: "Clear formatting",
|
|
371
|
+
})}
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
{showLinkInput && (
|
|
375
|
+
<div className="flex min-w-0 flex-col gap-2 border-b border-border bg-muted/60 p-2 sm:flex-row">
|
|
376
|
+
<Input
|
|
377
|
+
type="url"
|
|
378
|
+
value={linkUrl}
|
|
379
|
+
onChange={(event) => setLinkUrl(event.target.value)}
|
|
380
|
+
placeholder="https://..."
|
|
381
|
+
className="h-10 flex-1 bg-background"
|
|
382
|
+
onKeyDown={(event) => {
|
|
383
|
+
if (event.key === "Enter") {
|
|
384
|
+
event.preventDefault();
|
|
385
|
+
insertLink();
|
|
386
|
+
}
|
|
387
|
+
}}
|
|
388
|
+
autoFocus
|
|
389
|
+
aria-label="Link URL"
|
|
390
|
+
/>
|
|
391
|
+
<div className="grid grid-cols-2 gap-2 sm:flex sm:shrink-0">
|
|
392
|
+
<Button type="button" size="sm" className="h-10" onClick={insertLink}>
|
|
393
|
+
Insert
|
|
394
|
+
</Button>
|
|
395
|
+
<Button
|
|
396
|
+
type="button"
|
|
397
|
+
variant="outline"
|
|
398
|
+
size="sm"
|
|
399
|
+
className="h-10"
|
|
400
|
+
onClick={() => setShowLinkInput(false)}
|
|
401
|
+
>
|
|
402
|
+
Cancel
|
|
403
|
+
</Button>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
)}
|
|
407
|
+
|
|
408
|
+
<div
|
|
409
|
+
ref={editorRef}
|
|
410
|
+
id={id}
|
|
411
|
+
contentEditable
|
|
412
|
+
role="textbox"
|
|
413
|
+
aria-multiline="true"
|
|
414
|
+
aria-label={ariaLabel ?? "Content editor"}
|
|
415
|
+
className={cn(
|
|
416
|
+
"rich-text-editor-content min-w-0 px-4 py-3 text-sm leading-6 text-foreground outline-none",
|
|
417
|
+
"empty:before:pointer-events-none empty:before:text-muted-foreground empty:before:content-[attr(data-placeholder)]",
|
|
418
|
+
editorClassName,
|
|
419
|
+
)}
|
|
420
|
+
style={{ minHeight }}
|
|
421
|
+
dangerouslySetInnerHTML={{ __html: value }}
|
|
422
|
+
onInput={emitChange}
|
|
423
|
+
onBlur={emitChange}
|
|
424
|
+
data-placeholder={placeholder}
|
|
425
|
+
suppressContentEditableWarning
|
|
426
|
+
/>
|
|
427
|
+
|
|
428
|
+
<style>{`
|
|
429
|
+
.rich-text-editor-content h1 { font-size: 1.5rem; font-weight: 700; line-height: 1.2; margin: 0.75rem 0 0.5rem; text-wrap: balance; }
|
|
430
|
+
.rich-text-editor-content h2 { font-size: 1.25rem; font-weight: 650; line-height: 1.25; margin: 0.75rem 0 0.5rem; text-wrap: balance; }
|
|
431
|
+
.rich-text-editor-content h3 { font-size: 1.1rem; font-weight: 650; line-height: 1.3; margin: 0.5rem 0 0.25rem; text-wrap: balance; }
|
|
432
|
+
.rich-text-editor-content blockquote { border-left: 3px solid var(--border); color: var(--muted-foreground); margin: 0.75rem 0; padding-left: 1rem; text-wrap: pretty; }
|
|
433
|
+
.rich-text-editor-content pre { background: var(--muted); border-radius: 0.5rem; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.85rem; margin: 0.75rem 0; overflow-x: auto; padding: 0.75rem; }
|
|
434
|
+
.rich-text-editor-content a { color: var(--brand-primary); text-decoration: underline; text-underline-offset: 3px; }
|
|
435
|
+
.rich-text-editor-content img { border-radius: 0.5rem; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); height: auto; margin: 0.5rem 0; max-width: 100%; }
|
|
436
|
+
.dark .rich-text-editor-content img { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1); }
|
|
437
|
+
.rich-text-editor-content hr { border: none; border-top: 1px solid var(--border); margin: 1rem 0; }
|
|
438
|
+
.rich-text-editor-content ul, .rich-text-editor-content ol { margin: 0.5rem 0; padding-left: 1.5rem; }
|
|
439
|
+
.rich-text-editor-content li { margin: 0.25rem 0; }
|
|
440
|
+
`}</style>
|
|
441
|
+
</div>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
RichTextEditor.displayName = "RichTextEditor";
|
|
446
|
+
|
|
447
|
+
export { RichTextEditor };
|
|
448
|
+
export type { RichTextEditorProps, RichTextImageUrlHandler };
|