@questpie/admin 0.0.1

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.
Files changed (203) hide show
  1. package/.turbo/turbo-build.log +108 -0
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +556 -0
  4. package/STATUS.md +917 -0
  5. package/VALIDATION.md +602 -0
  6. package/components.json +24 -0
  7. package/dist/__tests__/setup.mjs +38 -0
  8. package/dist/__tests__/test-utils.mjs +45 -0
  9. package/dist/__tests__/vitest.d.mjs +3 -0
  10. package/dist/components/admin-app.mjs +69 -0
  11. package/dist/components/fields/array-field.mjs +190 -0
  12. package/dist/components/fields/checkbox-field.mjs +34 -0
  13. package/dist/components/fields/custom-field.mjs +32 -0
  14. package/dist/components/fields/date-field.mjs +41 -0
  15. package/dist/components/fields/datetime-field.mjs +42 -0
  16. package/dist/components/fields/email-field.mjs +37 -0
  17. package/dist/components/fields/embedded-collection.mjs +253 -0
  18. package/dist/components/fields/field-types.mjs +1 -0
  19. package/dist/components/fields/field-utils.mjs +10 -0
  20. package/dist/components/fields/field-wrapper.mjs +34 -0
  21. package/dist/components/fields/index.mjs +23 -0
  22. package/dist/components/fields/json-field.mjs +243 -0
  23. package/dist/components/fields/locale-badge.mjs +16 -0
  24. package/dist/components/fields/number-field.mjs +39 -0
  25. package/dist/components/fields/password-field.mjs +37 -0
  26. package/dist/components/fields/relation-field.mjs +104 -0
  27. package/dist/components/fields/relation-picker.mjs +229 -0
  28. package/dist/components/fields/relation-select.mjs +188 -0
  29. package/dist/components/fields/rich-text-editor/index.mjs +897 -0
  30. package/dist/components/fields/select-field.mjs +41 -0
  31. package/dist/components/fields/switch-field.mjs +34 -0
  32. package/dist/components/fields/text-field.mjs +38 -0
  33. package/dist/components/fields/textarea-field.mjs +38 -0
  34. package/dist/components/index.mjs +59 -0
  35. package/dist/components/primitives/checkbox-input.mjs +127 -0
  36. package/dist/components/primitives/date-input.mjs +303 -0
  37. package/dist/components/primitives/index.mjs +12 -0
  38. package/dist/components/primitives/number-input.mjs +104 -0
  39. package/dist/components/primitives/select-input.mjs +177 -0
  40. package/dist/components/primitives/tag-input.mjs +135 -0
  41. package/dist/components/primitives/text-input.mjs +39 -0
  42. package/dist/components/primitives/textarea-input.mjs +37 -0
  43. package/dist/components/primitives/toggle-input.mjs +31 -0
  44. package/dist/components/primitives/types.mjs +12 -0
  45. package/dist/components/ui/accordion.mjs +55 -0
  46. package/dist/components/ui/avatar.mjs +54 -0
  47. package/dist/components/ui/badge.mjs +34 -0
  48. package/dist/components/ui/button.mjs +48 -0
  49. package/dist/components/ui/card.mjs +58 -0
  50. package/dist/components/ui/checkbox.mjs +21 -0
  51. package/dist/components/ui/combobox.mjs +163 -0
  52. package/dist/components/ui/dialog.mjs +95 -0
  53. package/dist/components/ui/dropdown-menu.mjs +138 -0
  54. package/dist/components/ui/field.mjs +113 -0
  55. package/dist/components/ui/input-group.mjs +82 -0
  56. package/dist/components/ui/input.mjs +17 -0
  57. package/dist/components/ui/label.mjs +15 -0
  58. package/dist/components/ui/popover.mjs +56 -0
  59. package/dist/components/ui/scroll-area.mjs +38 -0
  60. package/dist/components/ui/select.mjs +100 -0
  61. package/dist/components/ui/separator.mjs +16 -0
  62. package/dist/components/ui/sheet.mjs +90 -0
  63. package/dist/components/ui/sidebar.mjs +387 -0
  64. package/dist/components/ui/skeleton.mjs +14 -0
  65. package/dist/components/ui/spinner.mjs +16 -0
  66. package/dist/components/ui/switch.mjs +22 -0
  67. package/dist/components/ui/table.mjs +68 -0
  68. package/dist/components/ui/tabs.mjs +48 -0
  69. package/dist/components/ui/textarea.mjs +15 -0
  70. package/dist/components/ui/tooltip.mjs +44 -0
  71. package/dist/config/component-registry.mjs +38 -0
  72. package/dist/config/index.mjs +129 -0
  73. package/dist/hooks/admin-provider.mjs +70 -0
  74. package/dist/hooks/index.mjs +7 -0
  75. package/dist/hooks/store.mjs +178 -0
  76. package/dist/hooks/use-auth.mjs +76 -0
  77. package/dist/hooks/use-collection-db.mjs +146 -0
  78. package/dist/hooks/use-collection.mjs +112 -0
  79. package/dist/hooks/use-global.mjs +46 -0
  80. package/dist/hooks/use-mobile.mjs +20 -0
  81. package/dist/lib/utils.mjs +10 -0
  82. package/dist/styles/index.css +336 -0
  83. package/dist/styles/index.mjs +1 -0
  84. package/dist/utils/index.mjs +9 -0
  85. package/dist/views/auth/auth-layout.mjs +52 -0
  86. package/dist/views/auth/forgot-password-form.mjs +148 -0
  87. package/dist/views/auth/index.mjs +6 -0
  88. package/dist/views/auth/login-form.mjs +156 -0
  89. package/dist/views/auth/reset-password-form.mjs +184 -0
  90. package/dist/views/collection/auto-form-fields.mjs +525 -0
  91. package/dist/views/collection/collection-form.mjs +91 -0
  92. package/dist/views/collection/collection-list.mjs +76 -0
  93. package/dist/views/collection/form-field.mjs +42 -0
  94. package/dist/views/collection/index.mjs +6 -0
  95. package/dist/views/common/index.mjs +4 -0
  96. package/dist/views/common/locale-switcher.mjs +39 -0
  97. package/dist/views/common/version-history.mjs +272 -0
  98. package/dist/views/index.mjs +9 -0
  99. package/dist/views/layout/admin-layout.mjs +40 -0
  100. package/dist/views/layout/admin-router.mjs +95 -0
  101. package/dist/views/layout/admin-sidebar.mjs +63 -0
  102. package/dist/views/layout/index.mjs +5 -0
  103. package/package.json +276 -0
  104. package/src/__tests__/setup.ts +44 -0
  105. package/src/__tests__/test-utils.tsx +49 -0
  106. package/src/__tests__/vitest.d.ts +9 -0
  107. package/src/components/admin-app.tsx +221 -0
  108. package/src/components/fields/array-field.tsx +237 -0
  109. package/src/components/fields/checkbox-field.tsx +47 -0
  110. package/src/components/fields/custom-field.tsx +50 -0
  111. package/src/components/fields/date-field.tsx +65 -0
  112. package/src/components/fields/datetime-field.tsx +67 -0
  113. package/src/components/fields/email-field.tsx +51 -0
  114. package/src/components/fields/embedded-collection.tsx +315 -0
  115. package/src/components/fields/field-types.ts +162 -0
  116. package/src/components/fields/field-utils.ts +6 -0
  117. package/src/components/fields/field-wrapper.tsx +52 -0
  118. package/src/components/fields/index.ts +66 -0
  119. package/src/components/fields/json-field.tsx +440 -0
  120. package/src/components/fields/locale-badge.tsx +15 -0
  121. package/src/components/fields/number-field.tsx +57 -0
  122. package/src/components/fields/password-field.tsx +51 -0
  123. package/src/components/fields/relation-field.tsx +243 -0
  124. package/src/components/fields/relation-picker.tsx +402 -0
  125. package/src/components/fields/relation-select.tsx +327 -0
  126. package/src/components/fields/rich-text-editor/index.tsx +1337 -0
  127. package/src/components/fields/select-field.tsx +61 -0
  128. package/src/components/fields/switch-field.tsx +47 -0
  129. package/src/components/fields/text-field.tsx +55 -0
  130. package/src/components/fields/textarea-field.tsx +55 -0
  131. package/src/components/index.ts +40 -0
  132. package/src/components/primitives/checkbox-input.tsx +193 -0
  133. package/src/components/primitives/date-input.tsx +401 -0
  134. package/src/components/primitives/index.ts +24 -0
  135. package/src/components/primitives/number-input.tsx +132 -0
  136. package/src/components/primitives/select-input.tsx +296 -0
  137. package/src/components/primitives/tag-input.tsx +200 -0
  138. package/src/components/primitives/text-input.tsx +49 -0
  139. package/src/components/primitives/textarea-input.tsx +46 -0
  140. package/src/components/primitives/toggle-input.tsx +36 -0
  141. package/src/components/primitives/types.ts +235 -0
  142. package/src/components/ui/accordion.tsx +72 -0
  143. package/src/components/ui/avatar.tsx +106 -0
  144. package/src/components/ui/badge.tsx +48 -0
  145. package/src/components/ui/button.tsx +53 -0
  146. package/src/components/ui/card.tsx +94 -0
  147. package/src/components/ui/checkbox.tsx +27 -0
  148. package/src/components/ui/combobox.tsx +290 -0
  149. package/src/components/ui/dialog.tsx +151 -0
  150. package/src/components/ui/dropdown-menu.tsx +254 -0
  151. package/src/components/ui/field.tsx +227 -0
  152. package/src/components/ui/input-group.tsx +149 -0
  153. package/src/components/ui/input.tsx +20 -0
  154. package/src/components/ui/label.tsx +18 -0
  155. package/src/components/ui/popover.tsx +88 -0
  156. package/src/components/ui/scroll-area.tsx +53 -0
  157. package/src/components/ui/select.tsx +192 -0
  158. package/src/components/ui/separator.tsx +23 -0
  159. package/src/components/ui/sheet.tsx +127 -0
  160. package/src/components/ui/sidebar.tsx +723 -0
  161. package/src/components/ui/skeleton.tsx +13 -0
  162. package/src/components/ui/spinner.tsx +10 -0
  163. package/src/components/ui/switch.tsx +32 -0
  164. package/src/components/ui/table.tsx +99 -0
  165. package/src/components/ui/tabs.tsx +82 -0
  166. package/src/components/ui/textarea.tsx +18 -0
  167. package/src/components/ui/tooltip.tsx +70 -0
  168. package/src/config/component-registry.ts +190 -0
  169. package/src/config/index.ts +1099 -0
  170. package/src/hooks/README.md +269 -0
  171. package/src/hooks/admin-provider.tsx +110 -0
  172. package/src/hooks/index.ts +41 -0
  173. package/src/hooks/store.ts +248 -0
  174. package/src/hooks/use-auth.ts +168 -0
  175. package/src/hooks/use-collection-db.ts +209 -0
  176. package/src/hooks/use-collection.ts +156 -0
  177. package/src/hooks/use-global.ts +69 -0
  178. package/src/hooks/use-mobile.ts +21 -0
  179. package/src/lib/utils.ts +6 -0
  180. package/src/styles/index.css +340 -0
  181. package/src/utils/index.ts +6 -0
  182. package/src/views/auth/auth-layout.tsx +77 -0
  183. package/src/views/auth/forgot-password-form.tsx +192 -0
  184. package/src/views/auth/index.ts +21 -0
  185. package/src/views/auth/login-form.tsx +229 -0
  186. package/src/views/auth/reset-password-form.tsx +232 -0
  187. package/src/views/collection/auto-form-fields.tsx +982 -0
  188. package/src/views/collection/collection-form.tsx +186 -0
  189. package/src/views/collection/collection-list.tsx +223 -0
  190. package/src/views/collection/form-field.tsx +52 -0
  191. package/src/views/collection/index.ts +15 -0
  192. package/src/views/common/index.ts +8 -0
  193. package/src/views/common/locale-switcher.tsx +45 -0
  194. package/src/views/common/version-history.tsx +406 -0
  195. package/src/views/index.ts +25 -0
  196. package/src/views/layout/admin-layout.tsx +117 -0
  197. package/src/views/layout/admin-router.tsx +206 -0
  198. package/src/views/layout/admin-sidebar.tsx +185 -0
  199. package/src/views/layout/index.ts +12 -0
  200. package/tsconfig.json +13 -0
  201. package/tsconfig.tsbuildinfo +1 -0
  202. package/tsdown.config.ts +13 -0
  203. package/vitest.config.ts +29 -0
@@ -0,0 +1,897 @@
1
+ import * as React$1 from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Button } from "../../ui/button";
4
+ import { Input } from "../../ui/input";
5
+ import { Label } from "../../ui/label";
6
+ import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "../../ui/popover";
7
+ import { LocaleBadge } from "../locale-badge";
8
+ import { cn } from "../../../utils";
9
+ import { Extension } from "@tiptap/core";
10
+ import { BubbleMenu, EditorContent, ReactRenderer, useEditor } from "@tiptap/react";
11
+ import StarterKit from "@tiptap/starter-kit";
12
+ import Underline from "@tiptap/extension-underline";
13
+ import Link from "@tiptap/extension-link";
14
+ import Image from "@tiptap/extension-image";
15
+ import Placeholder from "@tiptap/extension-placeholder";
16
+ import TextAlign from "@tiptap/extension-text-align";
17
+ import Table from "@tiptap/extension-table";
18
+ import TableRow from "@tiptap/extension-table-row";
19
+ import TableHeader from "@tiptap/extension-table-header";
20
+ import TableCell from "@tiptap/extension-table-cell";
21
+ import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
22
+ import CharacterCount from "@tiptap/extension-character-count";
23
+ import Suggestion from "@tiptap/suggestion";
24
+ import tippy from "tippy.js";
25
+ import { common, createLowlight } from "lowlight";
26
+
27
+ //#region src/components/fields/rich-text-editor/index.tsx
28
+ /**
29
+ * RichTextEditor Component
30
+ *
31
+ * Tiptap-based rich text editor with toolbar controls.
32
+ */
33
+ const lowlight = createLowlight(common);
34
+ const defaultFeatures = {
35
+ toolbar: true,
36
+ bubbleMenu: true,
37
+ slashCommands: true,
38
+ history: true,
39
+ heading: true,
40
+ bold: true,
41
+ italic: true,
42
+ underline: true,
43
+ strike: true,
44
+ code: true,
45
+ codeBlock: true,
46
+ blockquote: true,
47
+ bulletList: true,
48
+ orderedList: true,
49
+ horizontalRule: true,
50
+ align: true,
51
+ link: true,
52
+ image: true,
53
+ table: true,
54
+ tableControls: true,
55
+ characterCount: true
56
+ };
57
+ function ToolbarButton({ active, disabled, title, onClick, children }) {
58
+ return /* @__PURE__ */ jsx(Button, {
59
+ type: "button",
60
+ variant: "ghost",
61
+ size: "xs",
62
+ "data-active": active,
63
+ title,
64
+ disabled,
65
+ onClick,
66
+ className: "data-[active=true]:bg-muted data-[active=true]:text-foreground",
67
+ children
68
+ });
69
+ }
70
+ function ToolbarGroup({ children }) {
71
+ return /* @__PURE__ */ jsx("div", {
72
+ className: "flex items-center gap-1 border-r border-border pr-2 last:border-r-0 last:pr-0",
73
+ children
74
+ });
75
+ }
76
+ const SlashCommandList = React$1.forwardRef(function SlashCommandList$1({ items, command }, ref) {
77
+ const [selectedIndex, setSelectedIndex] = React$1.useState(0);
78
+ React$1.useEffect(() => {
79
+ setSelectedIndex(0);
80
+ }, [items]);
81
+ const selectItem = React$1.useCallback((index) => {
82
+ const item = items[index];
83
+ if (item) command(item);
84
+ }, [command, items]);
85
+ React$1.useImperativeHandle(ref, () => ({ onKeyDown: ({ event }) => {
86
+ if (items.length === 0) return false;
87
+ if (event.key === "ArrowDown") {
88
+ setSelectedIndex((prev) => (prev + 1) % items.length);
89
+ return true;
90
+ }
91
+ if (event.key === "ArrowUp") {
92
+ setSelectedIndex((prev) => (prev - 1 + items.length) % items.length);
93
+ return true;
94
+ }
95
+ if (event.key === "Enter") {
96
+ selectItem(selectedIndex);
97
+ return true;
98
+ }
99
+ return false;
100
+ } }));
101
+ return /* @__PURE__ */ jsxs("div", {
102
+ className: "qp-rich-text-editor__slash",
103
+ children: [items.length === 0 && /* @__PURE__ */ jsx("div", {
104
+ className: "qp-rich-text-editor__slash-empty",
105
+ children: "No results"
106
+ }), items.map((item, index) => /* @__PURE__ */ jsxs("button", {
107
+ type: "button",
108
+ className: cn("qp-rich-text-editor__slash-item", index === selectedIndex ? "qp-rich-text-editor__slash-item--active" : ""),
109
+ onClick: () => selectItem(index),
110
+ children: [/* @__PURE__ */ jsx("span", {
111
+ className: "qp-rich-text-editor__slash-title",
112
+ children: item.title
113
+ }), item.description && /* @__PURE__ */ jsx("span", {
114
+ className: "qp-rich-text-editor__slash-description",
115
+ children: item.description
116
+ })]
117
+ }, item.title))]
118
+ });
119
+ });
120
+ function getHeadingLevel(editor) {
121
+ if (!editor) return "paragraph";
122
+ for (let level = 1; level <= 6; level += 1) if (editor.isActive("heading", { level })) return String(level);
123
+ return "paragraph";
124
+ }
125
+ function getOutput(editor, outputFormat) {
126
+ if (outputFormat === "html") return editor.getHTML();
127
+ if (outputFormat === "markdown") {
128
+ const markdown = editor.storage?.markdown?.getMarkdown?.();
129
+ if (typeof markdown === "string") return markdown;
130
+ return editor.getHTML();
131
+ }
132
+ return editor.getJSON();
133
+ }
134
+ function isSameValue(a, b) {
135
+ if (a === b) return true;
136
+ if (!a || !b) return false;
137
+ if (typeof a === "string" && typeof b === "string") return a === b;
138
+ try {
139
+ return JSON.stringify(a) === JSON.stringify(b);
140
+ } catch {
141
+ return false;
142
+ }
143
+ }
144
+ function createSlashCommandExtension(getItems) {
145
+ return Extension.create({
146
+ name: "slashCommand",
147
+ addOptions() {
148
+ return { suggestion: {
149
+ char: "/",
150
+ startOfLine: true,
151
+ command: ({ editor, range, props }) => {
152
+ editor.chain().focus().deleteRange(range).run();
153
+ props.command(editor);
154
+ },
155
+ items: ({ query, editor }) => {
156
+ const items = getItems(editor);
157
+ if (!query) return items;
158
+ const search = query.toLowerCase();
159
+ return items.filter((item) => {
160
+ return item.title.toLowerCase().includes(search) || item.description?.toLowerCase().includes(search) || item.keywords?.some((keyword) => keyword.toLowerCase().includes(search));
161
+ });
162
+ },
163
+ render: () => {
164
+ let component = null;
165
+ let popup = null;
166
+ return {
167
+ onStart: (props) => {
168
+ component = new ReactRenderer(SlashCommandList, {
169
+ props,
170
+ editor: props.editor
171
+ });
172
+ if (!props.clientRect) return;
173
+ popup = tippy("body", {
174
+ getReferenceClientRect: props.clientRect,
175
+ appendTo: () => document.body,
176
+ content: component.element,
177
+ showOnCreate: true,
178
+ interactive: true,
179
+ trigger: "manual",
180
+ placement: "bottom-start",
181
+ theme: "qp-rich-text-editor"
182
+ });
183
+ },
184
+ onUpdate: (props) => {
185
+ component?.updateProps(props);
186
+ if (!props.clientRect) return;
187
+ popup?.[0].setProps({ getReferenceClientRect: props.clientRect });
188
+ },
189
+ onKeyDown: (props) => {
190
+ if (props.event.key === "Escape") {
191
+ popup?.[0].hide();
192
+ return true;
193
+ }
194
+ return component?.ref?.onKeyDown(props) ?? false;
195
+ },
196
+ onExit: () => {
197
+ popup?.[0].destroy();
198
+ component?.destroy();
199
+ }
200
+ };
201
+ }
202
+ } };
203
+ },
204
+ addProseMirrorPlugins() {
205
+ return [Suggestion(this.options.suggestion)];
206
+ }
207
+ });
208
+ }
209
+ function RichTextEditor({ name, value, onChange, disabled, readOnly, label, description, placeholder, required, error, localized, locale, outputFormat = "json", extensions, features, showCharacterCount, maxCharacters, enableImages, onImageUpload }) {
210
+ const [linkOpen, setLinkOpen] = React$1.useState(false);
211
+ const [linkUrl, setLinkUrl] = React$1.useState("");
212
+ const [imageOpen, setImageOpen] = React$1.useState(false);
213
+ const [imageUrl, setImageUrl] = React$1.useState("");
214
+ const [imageAlt, setImageAlt] = React$1.useState("");
215
+ const [uploadingImage, setUploadingImage] = React$1.useState(false);
216
+ const fileInputRef = React$1.useRef(null);
217
+ const resolvedFeatures = React$1.useMemo(() => ({
218
+ ...defaultFeatures,
219
+ ...features
220
+ }), [features]);
221
+ const allowImages = resolvedFeatures.image && (enableImages ?? true);
222
+ const allowLinks = resolvedFeatures.link;
223
+ const allowTables = resolvedFeatures.table;
224
+ const allowTableControls = resolvedFeatures.tableControls && allowTables;
225
+ const allowSlashCommands = resolvedFeatures.slashCommands;
226
+ const allowBubbleMenu = resolvedFeatures.bubbleMenu;
227
+ const allowToolbar = resolvedFeatures.toolbar;
228
+ const allowCharacterCount = resolvedFeatures.characterCount && (showCharacterCount || maxCharacters);
229
+ const editor = useEditor({
230
+ extensions: React$1.useMemo(() => {
231
+ const starterKitConfig = { codeBlock: false };
232
+ if (!resolvedFeatures.bold) starterKitConfig.bold = false;
233
+ if (!resolvedFeatures.italic) starterKitConfig.italic = false;
234
+ if (!resolvedFeatures.strike) starterKitConfig.strike = false;
235
+ if (!resolvedFeatures.code) starterKitConfig.code = false;
236
+ if (!resolvedFeatures.blockquote) starterKitConfig.blockquote = false;
237
+ if (!resolvedFeatures.heading) starterKitConfig.heading = false;
238
+ if (!resolvedFeatures.bulletList) starterKitConfig.bulletList = false;
239
+ if (!resolvedFeatures.orderedList) starterKitConfig.orderedList = false;
240
+ if (!resolvedFeatures.bulletList && !resolvedFeatures.orderedList) starterKitConfig.listItem = false;
241
+ if (!resolvedFeatures.horizontalRule) starterKitConfig.horizontalRule = false;
242
+ if (!resolvedFeatures.history) starterKitConfig.history = false;
243
+ const items = [StarterKit.configure(starterKitConfig), Placeholder.configure({ placeholder: placeholder || "Start writing..." })];
244
+ if (resolvedFeatures.underline) items.push(Underline);
245
+ if (allowLinks) items.push(Link.configure({
246
+ openOnClick: false,
247
+ autolink: true,
248
+ linkOnPaste: true
249
+ }));
250
+ if (resolvedFeatures.align) items.push(TextAlign.configure({ types: ["heading", "paragraph"] }));
251
+ if (allowImages) items.push(Image);
252
+ if (allowTables) items.push(Table.configure({ resizable: true }), TableRow, TableHeader, TableCell);
253
+ if (resolvedFeatures.codeBlock) items.push(CodeBlockLowlight.configure({ lowlight }));
254
+ if (allowCharacterCount) items.push(CharacterCount.configure({ limit: maxCharacters }));
255
+ if (allowSlashCommands) items.push(createSlashCommandExtension((editor$1) => {
256
+ const commands = [];
257
+ if (resolvedFeatures.heading) commands.push({
258
+ title: "Heading 1",
259
+ description: "Large section heading",
260
+ keywords: ["h1"],
261
+ command: (cmdEditor) => cmdEditor.chain().focus().toggleHeading({ level: 1 }).run()
262
+ }, {
263
+ title: "Heading 2",
264
+ description: "Medium section heading",
265
+ keywords: ["h2"],
266
+ command: (cmdEditor) => cmdEditor.chain().focus().toggleHeading({ level: 2 }).run()
267
+ }, {
268
+ title: "Heading 3",
269
+ description: "Small section heading",
270
+ keywords: ["h3"],
271
+ command: (cmdEditor) => cmdEditor.chain().focus().toggleHeading({ level: 3 }).run()
272
+ });
273
+ commands.push({
274
+ title: "Paragraph",
275
+ description: "Start with plain text",
276
+ keywords: ["text"],
277
+ command: (cmdEditor) => cmdEditor.chain().focus().setParagraph().run()
278
+ });
279
+ if (resolvedFeatures.bulletList) commands.push({
280
+ title: "Bullet list",
281
+ description: "Create a bulleted list",
282
+ keywords: ["list", "ul"],
283
+ command: (cmdEditor) => cmdEditor.chain().focus().toggleBulletList().run()
284
+ });
285
+ if (resolvedFeatures.orderedList) commands.push({
286
+ title: "Numbered list",
287
+ description: "Create an ordered list",
288
+ keywords: ["list", "ol"],
289
+ command: (cmdEditor) => cmdEditor.chain().focus().toggleOrderedList().run()
290
+ });
291
+ if (resolvedFeatures.blockquote) commands.push({
292
+ title: "Quote",
293
+ description: "Capture a quote",
294
+ keywords: ["blockquote"],
295
+ command: (cmdEditor) => cmdEditor.chain().focus().toggleBlockquote().run()
296
+ });
297
+ if (resolvedFeatures.codeBlock) commands.push({
298
+ title: "Code block",
299
+ description: "Insert code snippet",
300
+ keywords: ["code"],
301
+ command: (cmdEditor) => cmdEditor.chain().focus().toggleCodeBlock().run()
302
+ });
303
+ if (resolvedFeatures.horizontalRule) commands.push({
304
+ title: "Divider",
305
+ description: "Insert a horizontal rule",
306
+ keywords: ["hr"],
307
+ command: (cmdEditor) => cmdEditor.chain().focus().setHorizontalRule().run()
308
+ });
309
+ if (allowTables) commands.push({
310
+ title: "Table",
311
+ description: "Insert a 3x3 table",
312
+ keywords: ["grid"],
313
+ command: (cmdEditor) => cmdEditor.chain().focus().insertTable({
314
+ rows: 3,
315
+ cols: 3,
316
+ withHeaderRow: true
317
+ }).run()
318
+ });
319
+ return commands;
320
+ }));
321
+ if (extensions?.length) items.push(...extensions);
322
+ return items;
323
+ }, [
324
+ allowCharacterCount,
325
+ allowImages,
326
+ allowLinks,
327
+ allowSlashCommands,
328
+ allowTables,
329
+ extensions,
330
+ maxCharacters,
331
+ placeholder,
332
+ resolvedFeatures
333
+ ]),
334
+ content: value ?? "",
335
+ editorProps: { attributes: { class: "qp-rich-text-editor__content" } },
336
+ editable: !disabled && !readOnly,
337
+ onUpdate: ({ editor: currentEditor }) => {
338
+ if (disabled || readOnly) return;
339
+ onChange(getOutput(currentEditor, outputFormat));
340
+ }
341
+ });
342
+ const isEditable = !disabled && !readOnly;
343
+ const headingValue = getHeadingLevel(editor);
344
+ const inTable = editor?.isActive("table") ?? false;
345
+ React$1.useEffect(() => {
346
+ if (!editor) return;
347
+ editor.setEditable(isEditable);
348
+ }, [editor, isEditable]);
349
+ React$1.useEffect(() => {
350
+ if (!editor) return;
351
+ if (value === void 0) return;
352
+ if (isSameValue(value, getOutput(editor, outputFormat))) return;
353
+ editor.commands.setContent(value ?? "", false);
354
+ }, [
355
+ editor,
356
+ outputFormat,
357
+ value
358
+ ]);
359
+ React$1.useEffect(() => {
360
+ if (!linkOpen || !editor) return;
361
+ const currentLink = editor.getAttributes("link").href;
362
+ setLinkUrl(currentLink || "");
363
+ }, [editor, linkOpen]);
364
+ const handleApplyLink = React$1.useCallback(() => {
365
+ if (!editor) return;
366
+ if (!linkUrl) {
367
+ editor.chain().focus().unsetLink().run();
368
+ setLinkOpen(false);
369
+ return;
370
+ }
371
+ editor.chain().focus().setLink({
372
+ href: linkUrl,
373
+ target: "_blank",
374
+ rel: "noopener noreferrer"
375
+ }).run();
376
+ setLinkOpen(false);
377
+ }, [editor, linkUrl]);
378
+ const handleRemoveLink = React$1.useCallback(() => {
379
+ if (!editor) return;
380
+ editor.chain().focus().unsetLink().run();
381
+ setLinkOpen(false);
382
+ }, [editor]);
383
+ const handleInsertImageUrl = React$1.useCallback(() => {
384
+ if (!editor || !imageUrl) return;
385
+ editor.chain().focus().setImage({
386
+ src: imageUrl,
387
+ alt: imageAlt || void 0
388
+ }).run();
389
+ setImageUrl("");
390
+ setImageAlt("");
391
+ setImageOpen(false);
392
+ }, [
393
+ editor,
394
+ imageAlt,
395
+ imageUrl
396
+ ]);
397
+ const handleImageUpload = React$1.useCallback(async (event) => {
398
+ const file = event.target.files?.[0];
399
+ if (!file || !editor || !onImageUpload) return;
400
+ try {
401
+ setUploadingImage(true);
402
+ const url = await onImageUpload(file);
403
+ if (url) {
404
+ editor.chain().focus().setImage({
405
+ src: url,
406
+ alt: imageAlt || void 0
407
+ }).run();
408
+ setImageUrl("");
409
+ setImageAlt("");
410
+ setImageOpen(false);
411
+ }
412
+ } finally {
413
+ setUploadingImage(false);
414
+ event.target.value = "";
415
+ }
416
+ }, [
417
+ editor,
418
+ imageAlt,
419
+ onImageUpload
420
+ ]);
421
+ const characterCount = React$1.useMemo(() => {
422
+ if (!editor) return {
423
+ characters: 0,
424
+ words: 0
425
+ };
426
+ const storage = editor.storage;
427
+ if (storage?.characterCount) return {
428
+ characters: storage.characterCount.characters(),
429
+ words: storage.characterCount.words()
430
+ };
431
+ const text = editor.getText();
432
+ const words = text.trim().length ? text.trim().split(/\s+/).length : 0;
433
+ return {
434
+ characters: text.length,
435
+ words
436
+ };
437
+ }, [editor, value]);
438
+ return /* @__PURE__ */ jsxs("div", {
439
+ className: "space-y-2",
440
+ "data-disabled": disabled || readOnly,
441
+ children: [
442
+ label && /* @__PURE__ */ jsxs("div", {
443
+ className: "flex items-center gap-2",
444
+ children: [/* @__PURE__ */ jsxs(Label, {
445
+ htmlFor: name,
446
+ children: [label, required && /* @__PURE__ */ jsx("span", {
447
+ className: "text-destructive ml-1",
448
+ children: "*"
449
+ })]
450
+ }), localized && /* @__PURE__ */ jsx(LocaleBadge, { locale: locale || "i18n" })]
451
+ }),
452
+ /* @__PURE__ */ jsxs("div", {
453
+ className: cn("qp-rich-text-editor rounded-md border bg-background", disabled || readOnly ? "opacity-60" : "", error ? "border-destructive" : "border-input"),
454
+ children: [
455
+ editor && allowToolbar && /* @__PURE__ */ jsxs("div", {
456
+ className: "flex flex-wrap items-center gap-2 border-b bg-muted/40 p-2",
457
+ children: [
458
+ resolvedFeatures.history && /* @__PURE__ */ jsxs(ToolbarGroup, { children: [/* @__PURE__ */ jsx(ToolbarButton, {
459
+ disabled: !isEditable || !editor.can().undo(),
460
+ title: "Undo",
461
+ onClick: () => editor.chain().focus().undo().run(),
462
+ children: "Undo"
463
+ }), /* @__PURE__ */ jsx(ToolbarButton, {
464
+ disabled: !isEditable || !editor.can().redo(),
465
+ title: "Redo",
466
+ onClick: () => editor.chain().focus().redo().run(),
467
+ children: "Redo"
468
+ })] }),
469
+ resolvedFeatures.heading && /* @__PURE__ */ jsx(ToolbarGroup, { children: /* @__PURE__ */ jsxs("select", {
470
+ className: "h-6 rounded-sm border bg-background px-2 text-xs",
471
+ value: headingValue,
472
+ onChange: (event) => {
473
+ if (!editor) return;
474
+ const nextValue = event.target.value;
475
+ if (nextValue === "paragraph") {
476
+ editor.chain().focus().setParagraph().run();
477
+ return;
478
+ }
479
+ editor.chain().focus().toggleHeading({ level: Number(nextValue) }).run();
480
+ },
481
+ disabled: !isEditable,
482
+ children: [
483
+ /* @__PURE__ */ jsx("option", {
484
+ value: "paragraph",
485
+ children: "Paragraph"
486
+ }),
487
+ /* @__PURE__ */ jsx("option", {
488
+ value: "1",
489
+ children: "Heading 1"
490
+ }),
491
+ /* @__PURE__ */ jsx("option", {
492
+ value: "2",
493
+ children: "Heading 2"
494
+ }),
495
+ /* @__PURE__ */ jsx("option", {
496
+ value: "3",
497
+ children: "Heading 3"
498
+ }),
499
+ /* @__PURE__ */ jsx("option", {
500
+ value: "4",
501
+ children: "Heading 4"
502
+ }),
503
+ /* @__PURE__ */ jsx("option", {
504
+ value: "5",
505
+ children: "Heading 5"
506
+ }),
507
+ /* @__PURE__ */ jsx("option", {
508
+ value: "6",
509
+ children: "Heading 6"
510
+ })
511
+ ]
512
+ }) }),
513
+ /* @__PURE__ */ jsxs(ToolbarGroup, { children: [
514
+ resolvedFeatures.bold && /* @__PURE__ */ jsx(ToolbarButton, {
515
+ active: editor.isActive("bold"),
516
+ disabled: !isEditable,
517
+ title: "Bold",
518
+ onClick: () => editor.chain().focus().toggleBold().run(),
519
+ children: "Bold"
520
+ }),
521
+ resolvedFeatures.italic && /* @__PURE__ */ jsx(ToolbarButton, {
522
+ active: editor.isActive("italic"),
523
+ disabled: !isEditable,
524
+ title: "Italic",
525
+ onClick: () => editor.chain().focus().toggleItalic().run(),
526
+ children: "Italic"
527
+ }),
528
+ resolvedFeatures.underline && /* @__PURE__ */ jsx(ToolbarButton, {
529
+ active: editor.isActive("underline"),
530
+ disabled: !isEditable,
531
+ title: "Underline",
532
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
533
+ children: "Underline"
534
+ }),
535
+ resolvedFeatures.strike && /* @__PURE__ */ jsx(ToolbarButton, {
536
+ active: editor.isActive("strike"),
537
+ disabled: !isEditable,
538
+ title: "Strikethrough",
539
+ onClick: () => editor.chain().focus().toggleStrike().run(),
540
+ children: "Strike"
541
+ }),
542
+ resolvedFeatures.code && /* @__PURE__ */ jsx(ToolbarButton, {
543
+ active: editor.isActive("code"),
544
+ disabled: !isEditable,
545
+ title: "Inline code",
546
+ onClick: () => editor.chain().focus().toggleCode().run(),
547
+ children: "Code"
548
+ }),
549
+ resolvedFeatures.codeBlock && /* @__PURE__ */ jsx(ToolbarButton, {
550
+ active: editor.isActive("codeBlock"),
551
+ disabled: !isEditable,
552
+ title: "Code block",
553
+ onClick: () => editor.chain().focus().toggleCodeBlock().run(),
554
+ children: "Code Block"
555
+ })
556
+ ] }),
557
+ /* @__PURE__ */ jsxs(ToolbarGroup, { children: [
558
+ resolvedFeatures.bulletList && /* @__PURE__ */ jsx(ToolbarButton, {
559
+ active: editor.isActive("bulletList"),
560
+ disabled: !isEditable,
561
+ title: "Bullet list",
562
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
563
+ children: "Bullet List"
564
+ }),
565
+ resolvedFeatures.orderedList && /* @__PURE__ */ jsx(ToolbarButton, {
566
+ active: editor.isActive("orderedList"),
567
+ disabled: !isEditable,
568
+ title: "Numbered list",
569
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
570
+ children: "Numbered List"
571
+ }),
572
+ resolvedFeatures.blockquote && /* @__PURE__ */ jsx(ToolbarButton, {
573
+ active: editor.isActive("blockquote"),
574
+ disabled: !isEditable,
575
+ title: "Blockquote",
576
+ onClick: () => editor.chain().focus().toggleBlockquote().run(),
577
+ children: "Quote"
578
+ }),
579
+ resolvedFeatures.horizontalRule && /* @__PURE__ */ jsx(ToolbarButton, {
580
+ disabled: !isEditable,
581
+ title: "Horizontal rule",
582
+ onClick: () => editor.chain().focus().setHorizontalRule().run(),
583
+ children: "Divider"
584
+ })
585
+ ] }),
586
+ resolvedFeatures.align && /* @__PURE__ */ jsxs(ToolbarGroup, { children: [
587
+ /* @__PURE__ */ jsx(ToolbarButton, {
588
+ active: editor.isActive({ textAlign: "left" }),
589
+ disabled: !isEditable,
590
+ title: "Align left",
591
+ onClick: () => editor.chain().focus().setTextAlign("left").run(),
592
+ children: "Align Left"
593
+ }),
594
+ /* @__PURE__ */ jsx(ToolbarButton, {
595
+ active: editor.isActive({ textAlign: "center" }),
596
+ disabled: !isEditable,
597
+ title: "Align center",
598
+ onClick: () => editor.chain().focus().setTextAlign("center").run(),
599
+ children: "Align Center"
600
+ }),
601
+ /* @__PURE__ */ jsx(ToolbarButton, {
602
+ active: editor.isActive({ textAlign: "right" }),
603
+ disabled: !isEditable,
604
+ title: "Align right",
605
+ onClick: () => editor.chain().focus().setTextAlign("right").run(),
606
+ children: "Align Right"
607
+ }),
608
+ /* @__PURE__ */ jsx(ToolbarButton, {
609
+ active: editor.isActive({ textAlign: "justify" }),
610
+ disabled: !isEditable,
611
+ title: "Align justify",
612
+ onClick: () => editor.chain().focus().setTextAlign("justify").run(),
613
+ children: "Justify"
614
+ })
615
+ ] }),
616
+ /* @__PURE__ */ jsxs(ToolbarGroup, { children: [
617
+ allowLinks && /* @__PURE__ */ jsxs(Popover, {
618
+ open: linkOpen,
619
+ onOpenChange: setLinkOpen,
620
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsx(Button, {
621
+ type: "button",
622
+ variant: "ghost",
623
+ size: "xs",
624
+ disabled: !isEditable,
625
+ "data-active": editor.isActive("link"),
626
+ children: "Link"
627
+ }) }), /* @__PURE__ */ jsxs(PopoverContent, {
628
+ className: "w-72",
629
+ children: [/* @__PURE__ */ jsx(PopoverHeader, { children: /* @__PURE__ */ jsx(PopoverTitle, { children: "Insert link" }) }), /* @__PURE__ */ jsxs("div", {
630
+ className: "space-y-2",
631
+ children: [/* @__PURE__ */ jsx(Input, {
632
+ value: linkUrl,
633
+ placeholder: "https://example.com",
634
+ onChange: (event) => setLinkUrl(event.target.value),
635
+ disabled: !isEditable
636
+ }), /* @__PURE__ */ jsxs("div", {
637
+ className: "flex justify-end gap-2",
638
+ children: [/* @__PURE__ */ jsx(Button, {
639
+ type: "button",
640
+ size: "xs",
641
+ variant: "outline",
642
+ onClick: handleRemoveLink,
643
+ disabled: !isEditable || !editor.isActive("link"),
644
+ children: "Remove"
645
+ }), /* @__PURE__ */ jsx(Button, {
646
+ type: "button",
647
+ size: "xs",
648
+ onClick: handleApplyLink,
649
+ disabled: !isEditable,
650
+ children: "Apply"
651
+ })]
652
+ })]
653
+ })]
654
+ })]
655
+ }),
656
+ allowImages && /* @__PURE__ */ jsxs(Popover, {
657
+ open: imageOpen,
658
+ onOpenChange: setImageOpen,
659
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsx(Button, {
660
+ type: "button",
661
+ variant: "ghost",
662
+ size: "xs",
663
+ disabled: !isEditable,
664
+ children: "Image"
665
+ }) }), /* @__PURE__ */ jsxs(PopoverContent, {
666
+ className: "w-80",
667
+ children: [/* @__PURE__ */ jsx(PopoverHeader, { children: /* @__PURE__ */ jsx(PopoverTitle, { children: "Insert image" }) }), /* @__PURE__ */ jsxs("div", {
668
+ className: "space-y-3",
669
+ children: [/* @__PURE__ */ jsxs("div", {
670
+ className: "space-y-2",
671
+ children: [
672
+ /* @__PURE__ */ jsx(Input, {
673
+ value: imageUrl,
674
+ placeholder: "https://example.com/image.jpg",
675
+ onChange: (event) => setImageUrl(event.target.value),
676
+ disabled: !isEditable
677
+ }),
678
+ /* @__PURE__ */ jsx(Input, {
679
+ value: imageAlt,
680
+ placeholder: "Alt text (optional)",
681
+ onChange: (event) => setImageAlt(event.target.value),
682
+ disabled: !isEditable
683
+ }),
684
+ /* @__PURE__ */ jsx("div", {
685
+ className: "flex justify-end gap-2",
686
+ children: /* @__PURE__ */ jsx(Button, {
687
+ type: "button",
688
+ size: "xs",
689
+ onClick: handleInsertImageUrl,
690
+ disabled: !isEditable || !imageUrl,
691
+ children: "Insert URL"
692
+ })
693
+ })
694
+ ]
695
+ }), onImageUpload && /* @__PURE__ */ jsxs("div", {
696
+ className: "space-y-2",
697
+ children: [
698
+ /* @__PURE__ */ jsx("div", {
699
+ className: "text-xs font-medium",
700
+ children: "Upload file"
701
+ }),
702
+ /* @__PURE__ */ jsx("input", {
703
+ ref: fileInputRef,
704
+ type: "file",
705
+ accept: "image/*",
706
+ onChange: handleImageUpload,
707
+ className: "sr-only",
708
+ disabled: !isEditable || uploadingImage
709
+ }),
710
+ /* @__PURE__ */ jsx(Button, {
711
+ type: "button",
712
+ size: "xs",
713
+ variant: "outline",
714
+ onClick: () => fileInputRef.current?.click(),
715
+ disabled: !isEditable || uploadingImage,
716
+ children: uploadingImage ? "Uploading..." : "Choose file"
717
+ })
718
+ ]
719
+ })]
720
+ })]
721
+ })]
722
+ }),
723
+ allowTableControls && /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsx(Button, {
724
+ type: "button",
725
+ variant: "ghost",
726
+ size: "xs",
727
+ disabled: !isEditable,
728
+ children: "Table"
729
+ }) }), /* @__PURE__ */ jsxs(PopoverContent, {
730
+ className: "w-80",
731
+ children: [/* @__PURE__ */ jsx(PopoverHeader, { children: /* @__PURE__ */ jsx(PopoverTitle, { children: "Table tools" }) }), /* @__PURE__ */ jsxs("div", {
732
+ className: "grid grid-cols-2 gap-2",
733
+ children: [
734
+ /* @__PURE__ */ jsx(Button, {
735
+ type: "button",
736
+ size: "xs",
737
+ onClick: () => editor.chain().focus().insertTable({
738
+ rows: 3,
739
+ cols: 3,
740
+ withHeaderRow: true
741
+ }).run(),
742
+ disabled: !isEditable,
743
+ children: "Insert table"
744
+ }),
745
+ /* @__PURE__ */ jsx(Button, {
746
+ type: "button",
747
+ size: "xs",
748
+ variant: "outline",
749
+ onClick: () => editor.chain().focus().addRowBefore().run(),
750
+ disabled: !isEditable || !inTable,
751
+ children: "Add row before"
752
+ }),
753
+ /* @__PURE__ */ jsx(Button, {
754
+ type: "button",
755
+ size: "xs",
756
+ variant: "outline",
757
+ onClick: () => editor.chain().focus().addRowAfter().run(),
758
+ disabled: !isEditable || !inTable,
759
+ children: "Add row after"
760
+ }),
761
+ /* @__PURE__ */ jsx(Button, {
762
+ type: "button",
763
+ size: "xs",
764
+ variant: "outline",
765
+ onClick: () => editor.chain().focus().addColumnBefore().run(),
766
+ disabled: !isEditable || !inTable,
767
+ children: "Add column before"
768
+ }),
769
+ /* @__PURE__ */ jsx(Button, {
770
+ type: "button",
771
+ size: "xs",
772
+ variant: "outline",
773
+ onClick: () => editor.chain().focus().addColumnAfter().run(),
774
+ disabled: !isEditable || !inTable,
775
+ children: "Add column after"
776
+ }),
777
+ /* @__PURE__ */ jsx(Button, {
778
+ type: "button",
779
+ size: "xs",
780
+ variant: "outline",
781
+ onClick: () => editor.chain().focus().deleteRow().run(),
782
+ disabled: !isEditable || !inTable,
783
+ children: "Delete row"
784
+ }),
785
+ /* @__PURE__ */ jsx(Button, {
786
+ type: "button",
787
+ size: "xs",
788
+ variant: "outline",
789
+ onClick: () => editor.chain().focus().deleteColumn().run(),
790
+ disabled: !isEditable || !inTable,
791
+ children: "Delete column"
792
+ }),
793
+ /* @__PURE__ */ jsx(Button, {
794
+ type: "button",
795
+ size: "xs",
796
+ variant: "outline",
797
+ onClick: () => editor.chain().focus().toggleHeaderRow().run(),
798
+ disabled: !isEditable || !inTable,
799
+ children: "Toggle header row"
800
+ }),
801
+ /* @__PURE__ */ jsx(Button, {
802
+ type: "button",
803
+ size: "xs",
804
+ variant: "outline",
805
+ onClick: () => editor.chain().focus().toggleHeaderColumn().run(),
806
+ disabled: !isEditable || !inTable,
807
+ children: "Toggle header column"
808
+ }),
809
+ /* @__PURE__ */ jsx(Button, {
810
+ type: "button",
811
+ size: "xs",
812
+ variant: "outline",
813
+ onClick: () => editor.chain().focus().mergeCells().run(),
814
+ disabled: !isEditable || !inTable,
815
+ children: "Merge cells"
816
+ }),
817
+ /* @__PURE__ */ jsx(Button, {
818
+ type: "button",
819
+ size: "xs",
820
+ variant: "outline",
821
+ onClick: () => editor.chain().focus().splitCell().run(),
822
+ disabled: !isEditable || !inTable,
823
+ children: "Split cell"
824
+ }),
825
+ /* @__PURE__ */ jsx(Button, {
826
+ type: "button",
827
+ size: "xs",
828
+ variant: "outline",
829
+ onClick: () => editor.chain().focus().deleteTable().run(),
830
+ disabled: !isEditable || !inTable,
831
+ children: "Delete table"
832
+ })
833
+ ]
834
+ })]
835
+ })] })
836
+ ] })
837
+ ]
838
+ }),
839
+ editor && allowBubbleMenu && /* @__PURE__ */ jsxs(BubbleMenu, {
840
+ editor,
841
+ className: "flex items-center gap-1 rounded-md border bg-background p-1 shadow",
842
+ children: [
843
+ resolvedFeatures.bold && /* @__PURE__ */ jsx(ToolbarButton, {
844
+ active: editor.isActive("bold"),
845
+ disabled: !isEditable,
846
+ title: "Bold",
847
+ onClick: () => editor.chain().focus().toggleBold().run(),
848
+ children: "Bold"
849
+ }),
850
+ resolvedFeatures.italic && /* @__PURE__ */ jsx(ToolbarButton, {
851
+ active: editor.isActive("italic"),
852
+ disabled: !isEditable,
853
+ title: "Italic",
854
+ onClick: () => editor.chain().focus().toggleItalic().run(),
855
+ children: "Italic"
856
+ }),
857
+ resolvedFeatures.underline && /* @__PURE__ */ jsx(ToolbarButton, {
858
+ active: editor.isActive("underline"),
859
+ disabled: !isEditable,
860
+ title: "Underline",
861
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
862
+ children: "Underline"
863
+ })
864
+ ]
865
+ }),
866
+ /* @__PURE__ */ jsx(EditorContent, {
867
+ editor,
868
+ id: name
869
+ }),
870
+ allowCharacterCount && showCharacterCount && /* @__PURE__ */ jsxs("div", {
871
+ className: "flex items-center justify-between border-t bg-muted/30 px-2 py-1 text-xs text-muted-foreground",
872
+ children: [/* @__PURE__ */ jsxs("span", { children: [
873
+ characterCount.words,
874
+ " word",
875
+ characterCount.words === 1 ? "" : "s"
876
+ ] }), /* @__PURE__ */ jsxs("span", { children: [
877
+ characterCount.characters,
878
+ typeof maxCharacters === "number" ? ` / ${maxCharacters}` : "",
879
+ "characters"
880
+ ] })]
881
+ })
882
+ ]
883
+ }),
884
+ description && /* @__PURE__ */ jsx("p", {
885
+ className: "text-muted-foreground text-xs",
886
+ children: description
887
+ }),
888
+ error && /* @__PURE__ */ jsx("p", {
889
+ className: "text-destructive text-xs",
890
+ children: error
891
+ })
892
+ ]
893
+ });
894
+ }
895
+
896
+ //#endregion
897
+ export { RichTextEditor };