@crystallize/design-system 1.3.2 → 1.4.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 (246) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/TableComponent-I2YOOYOU.css +281 -0
  3. package/dist/TableComponent-QINOO453.mjs +1377 -0
  4. package/dist/arrow-clockwise-Z2G6UEGP.svg +1 -0
  5. package/dist/arrow-counterclockwise-2O5EYVJT.svg +1 -0
  6. package/dist/bg-color-HB2WDYGO.svg +1 -0
  7. package/dist/camera-CR7D2PNH.svg +1 -0
  8. package/dist/caret-right-fill-FFBNEXVX.svg +1 -0
  9. package/dist/chat-square-quote-CI6PUJHH.svg +1 -0
  10. package/dist/chevron-down-3FRWSIKS.svg +1 -0
  11. package/dist/chunk-VUXQZRSP.mjs +737 -0
  12. package/dist/clipboard-OSEFDF25.svg +1 -0
  13. package/dist/close-FH57ZMJF.svg +1 -0
  14. package/dist/code-SEVR6TIQ.svg +1 -0
  15. package/dist/copy-DMGDODUL.svg +1 -0
  16. package/dist/diagram-2-CEJUD2B4.svg +1 -0
  17. package/dist/download-JXUGIUCX.svg +1 -0
  18. package/dist/draggable-block-menu-KKHDNKJA.svg +1 -0
  19. package/dist/dropdown-more-BHZ5COKX.svg +1 -0
  20. package/dist/file-image-TIQPFJX4.svg +1 -0
  21. package/dist/filetype-gif-OG2BEYYK.svg +1 -0
  22. package/dist/font-color-J4GA3ZJO.svg +1 -0
  23. package/dist/font-family-ZU5N6TTE.svg +1 -0
  24. package/dist/gear-ICMT4NTP.svg +1 -0
  25. package/dist/horizontal-rule-N6RD2V7H.svg +1 -0
  26. package/dist/indent-MJ6JIMCK.svg +1 -0
  27. package/dist/index.css +2711 -315
  28. package/dist/index.d.ts +145 -40
  29. package/dist/index.js +10376 -1481
  30. package/dist/index.mjs +7609 -746
  31. package/dist/journal-code-XUT44HDV.svg +1 -0
  32. package/dist/justify-J7X5JEEX.svg +1 -0
  33. package/dist/link-W52N4JKZ.svg +1 -0
  34. package/dist/list-ol-2ZEUN4Z7.svg +1 -0
  35. package/dist/list-ul-DVKNUP47.svg +1 -0
  36. package/dist/lock-WCYOZOHW.svg +1 -0
  37. package/dist/lock-fill-JZSKOSHK.svg +1 -0
  38. package/dist/markdown-4BGQNLLT.svg +1 -0
  39. package/dist/mic-H5FNOMM7.svg +1 -0
  40. package/dist/outdent-2LUMUMIP.svg +1 -0
  41. package/dist/paint-bucket-VCISMZTH.svg +1 -0
  42. package/dist/palette-SWGFPRWZ.svg +1 -0
  43. package/dist/pencil-fill-STFSC26F.svg +1 -0
  44. package/dist/plug-HGGGEVS3.svg +1 -0
  45. package/dist/plug-fill-OTG3U4TN.svg +1 -0
  46. package/dist/plus-CQISIKEC.svg +1 -0
  47. package/dist/plus-slash-minus-N22JU4TI.svg +1 -0
  48. package/dist/prettier-WUJ7B5NV.svg +1 -0
  49. package/dist/prettier-error-DYJSLYDP.svg +1 -0
  50. package/dist/square-check-UTG6FU6D.svg +1 -0
  51. package/dist/success-YVXUMPEZ.svg +1 -0
  52. package/dist/table-BR6DI4ZQ.svg +1 -0
  53. package/dist/text-center-UQI6PAEF.svg +1 -0
  54. package/dist/text-left-KT2B6TR3.svg +1 -0
  55. package/dist/text-paragraph-MFTUIIQG.svg +1 -0
  56. package/dist/text-right-SKELPISG.svg +1 -0
  57. package/dist/trash-UOM6D7TD.svg +1 -0
  58. package/dist/type-bold-PY7COC3N.svg +1 -0
  59. package/dist/type-h1-6KJP7YOM.svg +1 -0
  60. package/dist/type-h2-VHI2USC3.svg +1 -0
  61. package/dist/type-h3-JIU77CHO.svg +1 -0
  62. package/dist/type-h4-P5EHKDAL.svg +1 -0
  63. package/dist/type-h5-CS2KYVRG.svg +1 -0
  64. package/dist/type-h6-J2O74LJZ.svg +1 -0
  65. package/dist/type-italic-3DSFOSG2.svg +1 -0
  66. package/dist/type-strikethrough-E2KKQFSX.svg +1 -0
  67. package/dist/type-subscript-BMPTRIBU.svg +1 -0
  68. package/dist/type-superscript-EDF6EPAA.svg +1 -0
  69. package/dist/type-underline-CBFA5VLF.svg +1 -0
  70. package/dist/upload-Q6KICGZW.svg +1 -0
  71. package/dist/user-EOI2NEFZ.svg +1 -0
  72. package/package.json +30 -6
  73. package/src/dialog/dialog.tsx +1 -0
  74. package/src/icon-button/icon-button.css +16 -14
  75. package/src/index.ts +4 -4
  76. package/src/input/input.css +1 -1
  77. package/src/input-with-label/input-with-label.css +1 -1
  78. package/src/rich-text-editor/appSettings.ts +28 -0
  79. package/src/rich-text-editor/context/SettingsContext.tsx +71 -0
  80. package/src/rich-text-editor/context/SharedAutocompleteContext.tsx +60 -0
  81. package/src/rich-text-editor/context/SharedHistoryContext.tsx +25 -0
  82. package/src/rich-text-editor/hooks/useReport.ts +64 -0
  83. package/src/rich-text-editor/images/cat-typing.gif +0 -0
  84. package/src/rich-text-editor/images/emoji/1F600.png +0 -0
  85. package/src/rich-text-editor/images/emoji/1F641.png +0 -0
  86. package/src/rich-text-editor/images/emoji/1F642.png +0 -0
  87. package/src/rich-text-editor/images/emoji/2764.png +0 -0
  88. package/src/rich-text-editor/images/emoji/LICENSE.md +5 -0
  89. package/src/rich-text-editor/images/icons/LICENSE.md +5 -0
  90. package/src/rich-text-editor/images/icons/arrow-clockwise.svg +1 -0
  91. package/src/rich-text-editor/images/icons/arrow-counterclockwise.svg +1 -0
  92. package/src/rich-text-editor/images/icons/bg-color.svg +1 -0
  93. package/src/rich-text-editor/images/icons/camera.svg +1 -0
  94. package/src/rich-text-editor/images/icons/card-checklist.svg +1 -0
  95. package/src/rich-text-editor/images/icons/caret-right-fill.svg +1 -0
  96. package/src/rich-text-editor/images/icons/chat-left-text.svg +1 -0
  97. package/src/rich-text-editor/images/icons/chat-right-dots.svg +1 -0
  98. package/src/rich-text-editor/images/icons/chat-right-text.svg +1 -0
  99. package/src/rich-text-editor/images/icons/chat-right.svg +1 -0
  100. package/src/rich-text-editor/images/icons/chat-square-quote.svg +1 -0
  101. package/src/rich-text-editor/images/icons/chevron-down.svg +1 -0
  102. package/src/rich-text-editor/images/icons/clipboard.svg +1 -0
  103. package/src/rich-text-editor/images/icons/close.svg +1 -0
  104. package/src/rich-text-editor/images/icons/code.svg +1 -0
  105. package/src/rich-text-editor/images/icons/comments.svg +1 -0
  106. package/src/rich-text-editor/images/icons/copy.svg +1 -0
  107. package/src/rich-text-editor/images/icons/diagram-2.svg +1 -0
  108. package/src/rich-text-editor/images/icons/download.svg +1 -0
  109. package/src/rich-text-editor/images/icons/draggable-block-menu.svg +1 -0
  110. package/src/rich-text-editor/images/icons/dropdown-more.svg +1 -0
  111. package/src/rich-text-editor/images/icons/figma.svg +1 -0
  112. package/src/rich-text-editor/images/icons/file-image.svg +1 -0
  113. package/src/rich-text-editor/images/icons/filetype-gif.svg +1 -0
  114. package/src/rich-text-editor/images/icons/font-color.svg +1 -0
  115. package/src/rich-text-editor/images/icons/font-family.svg +1 -0
  116. package/src/rich-text-editor/images/icons/gear.svg +1 -0
  117. package/src/rich-text-editor/images/icons/horizontal-rule.svg +1 -0
  118. package/src/rich-text-editor/images/icons/indent.svg +1 -0
  119. package/src/rich-text-editor/images/icons/journal-code.svg +1 -0
  120. package/src/rich-text-editor/images/icons/journal-text.svg +1 -0
  121. package/src/rich-text-editor/images/icons/justify.svg +1 -0
  122. package/src/rich-text-editor/images/icons/link.svg +1 -0
  123. package/src/rich-text-editor/images/icons/list-ol.svg +1 -0
  124. package/src/rich-text-editor/images/icons/list-ul.svg +1 -0
  125. package/src/rich-text-editor/images/icons/lock-fill.svg +1 -0
  126. package/src/rich-text-editor/images/icons/lock.svg +1 -0
  127. package/src/rich-text-editor/images/icons/markdown.svg +1 -0
  128. package/src/rich-text-editor/images/icons/mic.svg +1 -0
  129. package/src/rich-text-editor/images/icons/outdent.svg +1 -0
  130. package/src/rich-text-editor/images/icons/paint-bucket.svg +1 -0
  131. package/src/rich-text-editor/images/icons/palette.svg +1 -0
  132. package/src/rich-text-editor/images/icons/pencil-fill.svg +1 -0
  133. package/src/rich-text-editor/images/icons/plug-fill.svg +1 -0
  134. package/src/rich-text-editor/images/icons/plug.svg +1 -0
  135. package/src/rich-text-editor/images/icons/plus-slash-minus.svg +1 -0
  136. package/src/rich-text-editor/images/icons/plus.svg +1 -0
  137. package/src/rich-text-editor/images/icons/prettier-error.svg +1 -0
  138. package/src/rich-text-editor/images/icons/prettier.svg +1 -0
  139. package/src/rich-text-editor/images/icons/send.svg +1 -0
  140. package/src/rich-text-editor/images/icons/square-check.svg +1 -0
  141. package/src/rich-text-editor/images/icons/sticky.svg +1 -0
  142. package/src/rich-text-editor/images/icons/success.svg +1 -0
  143. package/src/rich-text-editor/images/icons/table.svg +1 -0
  144. package/src/rich-text-editor/images/icons/text-center.svg +1 -0
  145. package/src/rich-text-editor/images/icons/text-left.svg +1 -0
  146. package/src/rich-text-editor/images/icons/text-paragraph.svg +1 -0
  147. package/src/rich-text-editor/images/icons/text-right.svg +1 -0
  148. package/src/rich-text-editor/images/icons/trash.svg +1 -0
  149. package/src/rich-text-editor/images/icons/trash3.svg +1 -0
  150. package/src/rich-text-editor/images/icons/tweet.svg +1 -0
  151. package/src/rich-text-editor/images/icons/type-bold.svg +1 -0
  152. package/src/rich-text-editor/images/icons/type-h1.svg +1 -0
  153. package/src/rich-text-editor/images/icons/type-h2.svg +1 -0
  154. package/src/rich-text-editor/images/icons/type-h3.svg +1 -0
  155. package/src/rich-text-editor/images/icons/type-h4.svg +1 -0
  156. package/src/rich-text-editor/images/icons/type-h5.svg +1 -0
  157. package/src/rich-text-editor/images/icons/type-h6.svg +1 -0
  158. package/src/rich-text-editor/images/icons/type-italic.svg +1 -0
  159. package/src/rich-text-editor/images/icons/type-strikethrough.svg +1 -0
  160. package/src/rich-text-editor/images/icons/type-subscript.svg +1 -0
  161. package/src/rich-text-editor/images/icons/type-superscript.svg +1 -0
  162. package/src/rich-text-editor/images/icons/type-underline.svg +1 -0
  163. package/src/rich-text-editor/images/icons/upload.svg +1 -0
  164. package/src/rich-text-editor/images/icons/user.svg +1 -0
  165. package/src/rich-text-editor/images/icons/youtube.svg +1 -0
  166. package/src/rich-text-editor/images/image/LICENSE.md +5 -0
  167. package/src/rich-text-editor/images/landscape.jpg +0 -0
  168. package/src/rich-text-editor/images/logo.svg +1 -0
  169. package/src/rich-text-editor/images/yellow-flower-small.jpg +0 -0
  170. package/src/rich-text-editor/images/yellow-flower.jpg +0 -0
  171. package/src/rich-text-editor/index.ts +1 -0
  172. package/src/rich-text-editor/model/crystallize-rich-text-types/code.ts +39 -0
  173. package/src/rich-text-editor/model/crystallize-rich-text-types/headings.ts +12 -0
  174. package/src/rich-text-editor/model/crystallize-rich-text-types/index.ts +69 -0
  175. package/src/rich-text-editor/model/crystallize-rich-text-types/link.ts +9 -0
  176. package/src/rich-text-editor/model/crystallize-rich-text-types/table.ts +16 -0
  177. package/src/rich-text-editor/model/crystallize-to-lexical.ts +186 -0
  178. package/src/rich-text-editor/model/lexical-to-crystallize.ts +232 -0
  179. package/src/rich-text-editor/nodes/AutocompleteNode.tsx +96 -0
  180. package/src/rich-text-editor/nodes/BaseNodes.ts +45 -0
  181. package/src/rich-text-editor/nodes/KeywordNode.ts +73 -0
  182. package/src/rich-text-editor/nodes/TableCellNodes.ts +31 -0
  183. package/src/rich-text-editor/nodes/TableComponent.tsx +1547 -0
  184. package/src/rich-text-editor/nodes/TableNode.tsx +398 -0
  185. package/src/rich-text-editor/plugins/ActionsPlugin/index.tsx +83 -0
  186. package/src/rich-text-editor/plugins/AutoLinkPlugin/index.tsx +47 -0
  187. package/src/rich-text-editor/plugins/AutocompletePlugin/index.tsx +2536 -0
  188. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +60 -0
  189. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +14 -0
  190. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +140 -0
  191. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/index.css +46 -0
  192. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/index.tsx +155 -0
  193. package/src/rich-text-editor/plugins/CodeHighlightPlugin/index.ts +21 -0
  194. package/src/rich-text-editor/plugins/ComponentPickerPlugin/index.tsx +320 -0
  195. package/src/rich-text-editor/plugins/DragDropPastePlugin/index.ts +40 -0
  196. package/src/rich-text-editor/plugins/DraggableBlockPlugin/index.css +36 -0
  197. package/src/rich-text-editor/plugins/DraggableBlockPlugin/index.tsx +368 -0
  198. package/src/rich-text-editor/plugins/FloatingLinkEditorPlugin/index.css +40 -0
  199. package/src/rich-text-editor/plugins/FloatingLinkEditorPlugin/index.tsx +305 -0
  200. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.css +128 -0
  201. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +351 -0
  202. package/src/rich-text-editor/plugins/LinkPlugin/index.tsx +16 -0
  203. package/src/rich-text-editor/plugins/ListMaxIndentLevelPlugin/index.ts +86 -0
  204. package/src/rich-text-editor/plugins/MarkdownShortcutPlugin/index.tsx +16 -0
  205. package/src/rich-text-editor/plugins/MarkdownTransformers/index.ts +195 -0
  206. package/src/rich-text-editor/plugins/MaxLengthPlugin/index.tsx +49 -0
  207. package/src/rich-text-editor/plugins/SpeechToTextPlugin/index.ts +113 -0
  208. package/src/rich-text-editor/plugins/TabFocusPlugin/index.tsx +65 -0
  209. package/src/rich-text-editor/plugins/TableActionMenuPlugin/index.tsx +481 -0
  210. package/src/rich-text-editor/plugins/TableCellResizer/index.css +12 -0
  211. package/src/rich-text-editor/plugins/TableCellResizer/index.tsx +386 -0
  212. package/src/rich-text-editor/plugins/TablePlugin.tsx +190 -0
  213. package/src/rich-text-editor/plugins/ToolbarPlugin/index.tsx +726 -0
  214. package/src/rich-text-editor/plugins/TreeViewPlugin/index.tsx +25 -0
  215. package/src/rich-text-editor/plugins/TypingPerfPlugin/index.ts +117 -0
  216. package/src/rich-text-editor/rich-text-editor.css +1396 -0
  217. package/src/rich-text-editor/rich-text-editor.stories.tsx +385 -0
  218. package/src/rich-text-editor/rich-text-editor.tsx +228 -0
  219. package/src/rich-text-editor/tests/rich-text-editor-basic-rendering.test.tsx +47 -0
  220. package/src/rich-text-editor/tests/rich-text-editor-code.test.tsx +39 -0
  221. package/src/rich-text-editor/tests/rich-text-editor-model-basics.test.tsx +56 -0
  222. package/src/rich-text-editor/tests/rich-text-editor-model-conversions.test.tsx +195 -0
  223. package/src/rich-text-editor/tests/rich-text-editor-onchange.test.tsx +37 -0
  224. package/src/rich-text-editor/tests/rich-text-editor-quote.test.tsx +36 -0
  225. package/src/rich-text-editor/tests/rich-text-editor-text-formats.test.tsx +135 -0
  226. package/src/rich-text-editor/tests/rich-text-editor-typing.test.tsx +73 -0
  227. package/src/rich-text-editor/tests/utils.ts +23 -0
  228. package/src/rich-text-editor/themes/PlaygroundEditorTheme.css +433 -0
  229. package/src/rich-text-editor/themes/PlaygroundEditorTheme.ts +113 -0
  230. package/src/rich-text-editor/types.ts +5 -0
  231. package/src/rich-text-editor/ui/ContentEditable.css +13 -0
  232. package/src/rich-text-editor/ui/ContentEditable.tsx +15 -0
  233. package/src/rich-text-editor/ui/LinkPreview.css +57 -0
  234. package/src/rich-text-editor/ui/LinkPreview.tsx +169 -0
  235. package/src/rich-text-editor/utils/environment.ts +1 -0
  236. package/src/rich-text-editor/utils/getDOMRangeRect.ts +42 -0
  237. package/src/rich-text-editor/utils/getSelectedNode.ts +27 -0
  238. package/src/rich-text-editor/utils/guard.ts +10 -0
  239. package/src/rich-text-editor/utils/isMobileWidth.ts +7 -0
  240. package/src/rich-text-editor/utils/joinClasses.ts +13 -0
  241. package/src/rich-text-editor/utils/point.ts +55 -0
  242. package/src/rich-text-editor/utils/rect.ts +158 -0
  243. package/src/rich-text-editor/utils/setFloatingElemPosition.ts +46 -0
  244. package/src/rich-text-editor/utils/swipe.ts +127 -0
  245. package/src/rich-text-editor/utils/url.ts +33 -0
  246. package/src/Tokens.stories.tsx +0 -18
@@ -0,0 +1,726 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import { useCallback, useEffect, useState } from 'react';
10
+ import {
11
+ $createParagraphNode,
12
+ $getNodeByKey,
13
+ $getRoot,
14
+ $getSelection,
15
+ $isRangeSelection,
16
+ $isRootOrShadowRoot,
17
+ $isTextNode,
18
+ CAN_REDO_COMMAND,
19
+ CAN_UNDO_COMMAND,
20
+ COMMAND_PRIORITY_CRITICAL,
21
+ DEPRECATED_$isGridSelection,
22
+ FORMAT_TEXT_COMMAND,
23
+ REDO_COMMAND,
24
+ SELECTION_CHANGE_COMMAND,
25
+ UNDO_COMMAND,
26
+ type LexicalEditor,
27
+ type NodeKey,
28
+ } from 'lexical';
29
+ import {
30
+ $createCodeNode,
31
+ $isCodeNode,
32
+ CODE_LANGUAGE_FRIENDLY_NAME_MAP,
33
+ CODE_LANGUAGE_MAP,
34
+ getLanguageFriendlyName,
35
+ } from '@lexical/code';
36
+ import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
37
+ import {
38
+ $isListNode,
39
+ INSERT_ORDERED_LIST_COMMAND,
40
+ INSERT_UNORDERED_LIST_COMMAND,
41
+ ListNode,
42
+ REMOVE_LIST_COMMAND,
43
+ } from '@lexical/list';
44
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
45
+ import { $isDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
46
+ import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
47
+ import { $createHeadingNode, $createQuoteNode, $isHeadingNode, HeadingTagType } from '@lexical/rich-text';
48
+ import { $selectAll, $setBlocksType_experimental } from '@lexical/selection';
49
+ import {
50
+ $findMatchingParent,
51
+ $getNearestBlockElementAncestorOrThrow,
52
+ $getNearestNodeOfType,
53
+ mergeRegister,
54
+ } from '@lexical/utils';
55
+
56
+ import { Button } from '../../../button';
57
+ import { Dialog } from '../../../dialog';
58
+ import { DropdownMenu } from '../../../dropdown-menu';
59
+ import { IconButton } from '../../../icon-button';
60
+ import { Icon } from '../../../iconography';
61
+ import type { CrystallizeRichTextActionMenuItem } from '../../types';
62
+ import { IS_APPLE } from '../../utils/environment';
63
+ import { getSelectedNode } from '../../utils/getSelectedNode';
64
+ import { sanitizeUrl } from '../../utils/url';
65
+ import { InsertNewTableDialog } from '../TablePlugin';
66
+ import ActionsPlugin from './../ActionsPlugin';
67
+
68
+ const blockTypeToBlockName = {
69
+ bullet: 'Bulleted List',
70
+ check: 'Check List',
71
+ code: 'Code Block',
72
+ h1: 'Heading 1',
73
+ h2: 'Heading 2',
74
+ h3: 'Heading 3',
75
+ h4: 'Heading 4',
76
+ h5: 'Heading 5',
77
+ h6: 'Heading 6',
78
+ number: 'Numbered List',
79
+ paragraph: 'Normal',
80
+ quote: 'Quote',
81
+ };
82
+
83
+ function getCodeLanguageOptions(): [string, string][] {
84
+ const options: [string, string][] = [];
85
+
86
+ for (const [lang, friendlyName] of Object.entries(CODE_LANGUAGE_FRIENDLY_NAME_MAP)) {
87
+ options.push([lang, friendlyName]);
88
+ }
89
+
90
+ return options;
91
+ }
92
+
93
+ const CODE_LANGUAGE_OPTIONS = getCodeLanguageOptions();
94
+
95
+ function dropDownActiveClass(active: boolean) {
96
+ if (active) return 'active dropdown-item-active';
97
+ else return '';
98
+ }
99
+
100
+ function BlockFormatDropDown({
101
+ editor,
102
+ blockType,
103
+ disabled = false,
104
+ }: {
105
+ blockType: keyof typeof blockTypeToBlockName;
106
+ editor: LexicalEditor;
107
+ disabled?: boolean;
108
+ }): JSX.Element {
109
+ const formatParagraph = () => {
110
+ if (blockType !== 'paragraph') {
111
+ editor.update(() => {
112
+ const selection = $getSelection();
113
+ if ($isRangeSelection(selection) || DEPRECATED_$isGridSelection(selection))
114
+ $setBlocksType_experimental(selection, () => $createParagraphNode());
115
+ });
116
+ }
117
+ };
118
+
119
+ const formatHeading = (headingSize: HeadingTagType) => {
120
+ if (blockType !== headingSize) {
121
+ editor.update(() => {
122
+ const selection = $getSelection();
123
+ if ($isRangeSelection(selection) || DEPRECATED_$isGridSelection(selection)) {
124
+ $setBlocksType_experimental(selection, () => $createHeadingNode(headingSize));
125
+ }
126
+ });
127
+ }
128
+ };
129
+
130
+ const formatBulletList = () => {
131
+ if (blockType !== 'bullet') {
132
+ editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
133
+ } else {
134
+ editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
135
+ }
136
+ };
137
+
138
+ const formatNumberedList = () => {
139
+ if (blockType !== 'number') {
140
+ editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
141
+ } else {
142
+ editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
143
+ }
144
+ };
145
+
146
+ const formatQuote = () => {
147
+ if (blockType !== 'quote') {
148
+ editor.update(() => {
149
+ const selection = $getSelection();
150
+ if ($isRangeSelection(selection) || DEPRECATED_$isGridSelection(selection)) {
151
+ $setBlocksType_experimental(selection, () => $createQuoteNode());
152
+ } else {
153
+ /**
154
+ * Will select the entire editor, in case it is not selected. This is added
155
+ * to get the unit tests working
156
+ */
157
+ $setBlocksType_experimental($getRoot().select(), () => $createQuoteNode());
158
+ }
159
+ });
160
+ }
161
+ };
162
+
163
+ const formatCode = () => {
164
+ if (blockType !== 'code') {
165
+ editor.update(() => {
166
+ let selection = $getSelection();
167
+
168
+ if ($isRangeSelection(selection) || DEPRECATED_$isGridSelection(selection)) {
169
+ if (selection.isCollapsed()) {
170
+ $setBlocksType_experimental(selection, () => $createCodeNode());
171
+ } else {
172
+ const textContent = selection.getTextContent();
173
+ const codeNode = $createCodeNode();
174
+ selection.insertNodes([codeNode]);
175
+ selection = $getSelection();
176
+ if ($isRangeSelection(selection)) selection.insertRawText(textContent);
177
+ }
178
+ } else {
179
+ /**
180
+ * Will select the entire editor, in case it is not selected. This is added
181
+ * to get the unit tests working
182
+ */
183
+ $setBlocksType_experimental($getRoot().select(), () => $createCodeNode());
184
+ }
185
+ });
186
+ }
187
+ };
188
+
189
+ return (
190
+ <DropdownMenu.Root
191
+ disabled={disabled}
192
+ style={{ zIndex: 1 }}
193
+ content={
194
+ <>
195
+ <DropdownMenu.Item onClick={formatParagraph}>
196
+ <i
197
+ className={`icon paragraph border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
198
+ blockType === 'paragraph' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
199
+ }`}
200
+ />
201
+
202
+ <i className="icon paragraph" />
203
+ <span className={`${blockType === 'paragraph' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
204
+ Normal
205
+ </span>
206
+ </DropdownMenu.Item>
207
+ <DropdownMenu.Item onClick={() => formatHeading('h1')}>
208
+ <i
209
+ className={`icon h1 border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
210
+ blockType === 'h1' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
211
+ }`}
212
+ />
213
+ <span className={`${blockType === 'h1' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
214
+ Heading 1
215
+ </span>
216
+ </DropdownMenu.Item>
217
+ <DropdownMenu.Item onClick={() => formatHeading('h2')}>
218
+ <i
219
+ className={`icon h2 border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
220
+ blockType === 'h2' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
221
+ }`}
222
+ />
223
+ <span className={`${blockType === 'h2' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
224
+ Heading 2
225
+ </span>
226
+ </DropdownMenu.Item>
227
+ <DropdownMenu.Item onClick={() => formatHeading('h3')}>
228
+ <i
229
+ className={`icon h3 border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
230
+ blockType === 'h3' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
231
+ }`}
232
+ />
233
+ <span className={`${blockType === 'h3' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
234
+ Heading 3
235
+ </span>
236
+ </DropdownMenu.Item>
237
+ <DropdownMenu.Item onClick={formatBulletList}>
238
+ <i
239
+ className={`icon bullet-list border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
240
+ blockType === 'bullet' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
241
+ }`}
242
+ />
243
+ <span className={`${blockType === 'bullet' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
244
+ Bullet List
245
+ </span>
246
+ </DropdownMenu.Item>
247
+ <DropdownMenu.Item onClick={formatNumberedList}>
248
+ <i
249
+ className={`icon numbered-list border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
250
+ blockType === 'number' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
251
+ }`}
252
+ />
253
+ <span className={`${blockType === 'number' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
254
+ Numbered List
255
+ </span>
256
+ </DropdownMenu.Item>
257
+ <DropdownMenu.Item onClick={formatQuote} data-testid="toggle-block-format-quote">
258
+ <i
259
+ className={`icon quote border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
260
+ blockType === 'quote' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
261
+ }`}
262
+ />
263
+ <span className={`${blockType === 'quote' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
264
+ Quote
265
+ </span>
266
+ </DropdownMenu.Item>
267
+ <DropdownMenu.Item onClick={formatCode} data-testid="toggle-block-format-code">
268
+ <i
269
+ className={`icon code border w-6 h-6 rounded-md bg-no-repeat bg-center bg-[length:18px_18px] ${
270
+ blockType === 'code' ? 'opacity-100 bg-purple-50-900' : 'opacity-60'
271
+ }`}
272
+ />
273
+ <span className={`${blockType === 'code' ? 'font-bold' : 'font-normal'} text-sm px-3 min-w-[150px]`}>
274
+ Code block
275
+ </span>
276
+ </DropdownMenu.Item>
277
+ </>
278
+ }
279
+ >
280
+ <Button
281
+ style={{ backgroundColor: 'transparent', padding: '0 8px' }}
282
+ aria-label="Formatting options for text style"
283
+ data-testid="toggle-block-format"
284
+ >
285
+ <i className={`icon ${blockType} border bg-no-repeat bg-center bg-[length:18px_18px] w-6 h-6`} />
286
+ <Icon.Arrow />
287
+ </Button>
288
+ </DropdownMenu.Root>
289
+ );
290
+ }
291
+
292
+ function Divider(): JSX.Element {
293
+ return <div className="divider" />;
294
+ }
295
+
296
+ export default function ToolbarPlugin({
297
+ actionsMenuPrepend,
298
+ actionsMenuAppend,
299
+ }: {
300
+ actionsMenuPrepend?: CrystallizeRichTextActionMenuItem[];
301
+ actionsMenuAppend?: CrystallizeRichTextActionMenuItem[];
302
+ }): JSX.Element {
303
+ const [editor] = useLexicalComposerContext();
304
+ const [activeEditor, setActiveEditor] = useState(editor);
305
+ const [blockType, setBlockType] = useState<keyof typeof blockTypeToBlockName>('paragraph');
306
+ const [selectedElementKey, setSelectedElementKey] = useState<NodeKey | null>(null);
307
+ const [isLink, setIsLink] = useState(false);
308
+ const [isBold, setIsBold] = useState(false);
309
+ const [isItalic, setIsItalic] = useState(false);
310
+ const [isUnderline, setIsUnderline] = useState(false);
311
+ const [isStrikethrough, setIsStrikethrough] = useState(false);
312
+ const [isSubscript, setIsSubscript] = useState(false);
313
+ const [isSuperscript, setIsSuperscript] = useState(false);
314
+ const [isCode, setIsCode] = useState(false);
315
+ const [canUndo, setCanUndo] = useState(false);
316
+ const [canRedo, setCanRedo] = useState(false);
317
+
318
+ const [codeLanguage, setCodeLanguage] = useState<string>('');
319
+ const [isEditable, setIsEditable] = useState(() => editor.isEditable());
320
+
321
+ const updateToolbar = useCallback(() => {
322
+ const selection = $getSelection();
323
+ if ($isRangeSelection(selection)) {
324
+ const anchorNode = selection.anchor.getNode();
325
+ let element =
326
+ anchorNode.getKey() === 'root'
327
+ ? anchorNode
328
+ : $findMatchingParent(anchorNode, e => {
329
+ const parent = e.getParent();
330
+ return parent !== null && $isRootOrShadowRoot(parent);
331
+ });
332
+
333
+ if (element === null) {
334
+ element = anchorNode.getTopLevelElementOrThrow();
335
+ }
336
+
337
+ const elementKey = element.getKey();
338
+ const elementDOM = activeEditor.getElementByKey(elementKey);
339
+
340
+ // Update text format
341
+ setIsBold(selection.hasFormat('bold'));
342
+ setIsItalic(selection.hasFormat('italic'));
343
+ setIsUnderline(selection.hasFormat('underline'));
344
+ setIsStrikethrough(selection.hasFormat('strikethrough'));
345
+ setIsSubscript(selection.hasFormat('subscript'));
346
+ setIsSuperscript(selection.hasFormat('superscript'));
347
+ setIsCode(selection.hasFormat('code'));
348
+
349
+ // Update links
350
+ const node = getSelectedNode(selection);
351
+ const parent = node.getParent();
352
+ if ($isLinkNode(parent) || $isLinkNode(node)) {
353
+ setIsLink(true);
354
+ } else {
355
+ setIsLink(false);
356
+ }
357
+
358
+ if (elementDOM !== null) {
359
+ setSelectedElementKey(elementKey);
360
+ if ($isListNode(element)) {
361
+ const parentList = $getNearestNodeOfType<ListNode>(anchorNode, ListNode);
362
+ const type = parentList ? parentList.getListType() : element.getListType();
363
+ setBlockType(type);
364
+ } else {
365
+ const type = $isHeadingNode(element) ? element.getTag() : element.getType();
366
+ if (type in blockTypeToBlockName) {
367
+ setBlockType(type as keyof typeof blockTypeToBlockName);
368
+ }
369
+ if ($isCodeNode(element)) {
370
+ const language = element.getLanguage() as keyof typeof CODE_LANGUAGE_MAP;
371
+ setCodeLanguage(language ? CODE_LANGUAGE_MAP[language] || language : '');
372
+ return;
373
+ }
374
+ }
375
+ }
376
+ }
377
+ }, [activeEditor]);
378
+
379
+ useEffect(() => {
380
+ return editor.registerCommand(
381
+ SELECTION_CHANGE_COMMAND,
382
+ (_payload, newEditor) => {
383
+ updateToolbar();
384
+ setActiveEditor(newEditor);
385
+ return false;
386
+ },
387
+ COMMAND_PRIORITY_CRITICAL,
388
+ );
389
+ }, [editor, updateToolbar]);
390
+
391
+ useEffect(() => {
392
+ return mergeRegister(
393
+ editor.registerEditableListener(editable => {
394
+ setIsEditable(editable);
395
+ }),
396
+ activeEditor.registerUpdateListener(({ editorState }) => {
397
+ editorState.read(() => {
398
+ updateToolbar();
399
+ });
400
+ }),
401
+ activeEditor.registerCommand<boolean>(
402
+ CAN_UNDO_COMMAND,
403
+ payload => {
404
+ setCanUndo(payload);
405
+ return false;
406
+ },
407
+ COMMAND_PRIORITY_CRITICAL,
408
+ ),
409
+ activeEditor.registerCommand<boolean>(
410
+ CAN_REDO_COMMAND,
411
+ payload => {
412
+ setCanRedo(payload);
413
+ return false;
414
+ },
415
+ COMMAND_PRIORITY_CRITICAL,
416
+ ),
417
+ );
418
+ }, [activeEditor, editor, updateToolbar]);
419
+
420
+ const clearFormatting = useCallback(() => {
421
+ activeEditor.update(() => {
422
+ const selection = $getSelection();
423
+ if ($isRangeSelection(selection)) {
424
+ $selectAll(selection);
425
+ selection.getNodes().forEach(node => {
426
+ if ($isTextNode(node)) {
427
+ node.setFormat(0);
428
+ node.setStyle('');
429
+ $getNearestBlockElementAncestorOrThrow(node).setFormat('');
430
+ }
431
+ if ($isDecoratorBlockNode(node)) {
432
+ node.setFormat('');
433
+ }
434
+ });
435
+ }
436
+ });
437
+ }, [activeEditor]);
438
+
439
+ const insertLink = useCallback(() => {
440
+ if (!isLink) {
441
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl('https://'));
442
+ } else {
443
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
444
+ }
445
+ }, [editor, isLink]);
446
+
447
+ const onCodeLanguageSelect = useCallback(
448
+ (value: string) => {
449
+ activeEditor.update(() => {
450
+ if (selectedElementKey !== null) {
451
+ const node = $getNodeByKey(selectedElementKey);
452
+ if ($isCodeNode(node)) {
453
+ node.setLanguage(value);
454
+ }
455
+ }
456
+ });
457
+ },
458
+ [activeEditor, selectedElementKey],
459
+ );
460
+
461
+ return (
462
+ <div className="toolbar">
463
+ <div className="flex">
464
+ <IconButton
465
+ disabled={!canUndo || !isEditable}
466
+ onClick={() => {
467
+ activeEditor.dispatchCommand(UNDO_COMMAND, undefined);
468
+ }}
469
+ title={IS_APPLE ? 'Undo (⌘Z)' : 'Undo (Ctrl+Z)'}
470
+ type="button"
471
+ aria-label="Undo"
472
+ >
473
+ <i
474
+ className={`format icon undo border w-4 h-6 bg-no-repeat bg-center bg-[length:17px_17px] ${
475
+ canUndo ? 'opacity-100' : 'opacity-30'
476
+ }
477
+ `}
478
+ />
479
+ </IconButton>
480
+ <IconButton
481
+ disabled={!canRedo || !isEditable}
482
+ onClick={() => {
483
+ activeEditor.dispatchCommand(REDO_COMMAND, undefined);
484
+ }}
485
+ title={IS_APPLE ? 'Redo (⌘Y)' : 'Redo (Ctrl+Y)'}
486
+ type="button"
487
+ aria-label="Redo"
488
+ >
489
+ <i
490
+ className={`format icon redo border w-4 h-6 bg-no-repeat bg-center bg-[length:17px_17px] ${
491
+ canRedo ? 'opacity-100' : 'opacity-30'
492
+ }`}
493
+ />
494
+ </IconButton>
495
+ <Divider />
496
+ {blockType in blockTypeToBlockName && activeEditor === editor && (
497
+ <>
498
+ <BlockFormatDropDown disabled={!isEditable} blockType={blockType} editor={editor} />
499
+ <Divider />
500
+ </>
501
+ )}
502
+ {blockType === 'code' ? (
503
+ <>
504
+ <DropdownMenu.Root
505
+ disabled={!isEditable}
506
+ style={{ zIndex: 1 }}
507
+ content={
508
+ <>
509
+ {CODE_LANGUAGE_OPTIONS.map(([value, name]) => {
510
+ return (
511
+ <DropdownMenu.Item
512
+ className={`item ${dropDownActiveClass(value === codeLanguage)}`}
513
+ onClick={() => onCodeLanguageSelect(value)}
514
+ key={value}
515
+ >
516
+ <span
517
+ className={`text min-w-[200px] block text-sm px-3 ${
518
+ dropDownActiveClass(value === codeLanguage)
519
+ ? 'font-bold opacity-100'
520
+ : 'font-normal opacity-80'
521
+ }`}
522
+ >
523
+ {name}
524
+ </span>
525
+ </DropdownMenu.Item>
526
+ );
527
+ })}
528
+ </>
529
+ }
530
+ >
531
+ <Button aria-label="Select language" append={<Icon.Arrow />}>
532
+ <span className="font-medium text-sm">{getLanguageFriendlyName(codeLanguage)}</span>
533
+ </Button>
534
+ </DropdownMenu.Root>
535
+ </>
536
+ ) : (
537
+ <Dialog>
538
+ <div className="flex gap-1">
539
+ <IconButton
540
+ disabled={!isEditable}
541
+ title={IS_APPLE ? 'Bold (⌘B)' : 'Bold (Ctrl+B)'}
542
+ className={`${isBold ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'}`}
543
+ type="button"
544
+ aria-label={`Format text as bold. Shortcut: ${IS_APPLE ? '⌘B' : 'Ctrl+B'}`}
545
+ data-testid="toggle-format-bold"
546
+ onClick={() => {
547
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
548
+ }}
549
+ >
550
+ <i className={`format icon bold border w-full h-full bg-no-repeat bg-center bg-[length:18px_18px]`} />
551
+ </IconButton>
552
+ <IconButton
553
+ className={`${isItalic ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'}`}
554
+ disabled={!isEditable}
555
+ onClick={() => {
556
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
557
+ }}
558
+ title={IS_APPLE ? 'Italic (⌘I)' : 'Italic (Ctrl+I)'}
559
+ type="button"
560
+ aria-label={`Format text as italics. Shortcut: ${IS_APPLE ? '⌘I' : 'Ctrl+I'}`}
561
+ data-testid="toggle-format-emphasized"
562
+ >
563
+ <i className={`format icon italic border w-full h-full bg-no-repeat bg-center bg-[length:18px_18px]`} />
564
+ </IconButton>
565
+
566
+ <IconButton
567
+ className={`${isUnderline ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'}`}
568
+ disabled={!isEditable}
569
+ onClick={() => {
570
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
571
+ }}
572
+ title={IS_APPLE ? 'Underline (⌘U)' : 'Underline (Ctrl+U)'}
573
+ type="button"
574
+ aria-label={`Format text to underlined. Shortcut: ${IS_APPLE ? '⌘U' : 'Ctrl+U'}`}
575
+ data-testid="toggle-format-underlined"
576
+ >
577
+ <i
578
+ className={`format icon underline border w-full h-full bg-no-repeat bg-center bg-[length:18px_18px]`}
579
+ />
580
+ </IconButton>
581
+
582
+ <IconButton
583
+ className={`${isCode ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'}`}
584
+ disabled={!isEditable}
585
+ onClick={() => {
586
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code');
587
+ }}
588
+ title="Insert code block"
589
+ type="button"
590
+ aria-label="Insert code block"
591
+ >
592
+ <i className={`format icon code border w-full h-full bg-no-repeat bg-center bg-[length:18px_18px]`} />
593
+ </IconButton>
594
+
595
+ <IconButton
596
+ className={`${isLink ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'}`}
597
+ disabled={!isEditable}
598
+ onClick={insertLink}
599
+ aria-label="Insert link"
600
+ title="Insert link"
601
+ type="button"
602
+ >
603
+ <i className={`format icon link border w-full h-full bg-no-repeat bg-center bg-[length:18px_18px]`} />
604
+ </IconButton>
605
+
606
+ <DropdownMenu.Root
607
+ disabled={!isEditable}
608
+ style={{ zIndex: 1 }}
609
+ content={
610
+ <>
611
+ <DropdownMenu.Item
612
+ onClick={() => {
613
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
614
+ }}
615
+ title="Strikethrough"
616
+ aria-label="Format text with a strikethrough"
617
+ >
618
+ <i
619
+ className={`icon w-6 h-6 strikethrough border bg-no-repeat bg-center bg-[length:16px_16px] rounded-sm ${
620
+ isStrikethrough ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'
621
+ }`}
622
+ />
623
+ <span className={`px-3 text-sm font-sans ${isStrikethrough ? 'font-medium' : 'font-normal'}`}>
624
+ Strikethrough
625
+ </span>
626
+ </DropdownMenu.Item>
627
+ <DropdownMenu.Item
628
+ onClick={() => {
629
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript');
630
+ }}
631
+ title="Subscript"
632
+ aria-label="Format text with a subscript"
633
+ >
634
+ <i
635
+ className={`icon w-6 h-6 subscript border bg-no-repeat bg-center bg-[length:16px_16px] rounded-sm ${
636
+ isSubscript ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'
637
+ }`}
638
+ />
639
+ <span className={`px-3 text-sm font-sans ${isSubscript ? 'font-medium' : 'font-normal'}`}>
640
+ Subscript
641
+ </span>
642
+ </DropdownMenu.Item>
643
+ <DropdownMenu.Item
644
+ onClick={() => {
645
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript');
646
+ }}
647
+ title="Superscript"
648
+ aria-label="Format text with a superscript"
649
+ >
650
+ <i
651
+ className={`icon w-6 h-6 superscript border bg-no-repeat bg-center bg-[length:16px_16px] rounded-sm ${
652
+ isSuperscript ? 'opacity-100 !bg-purple-50-900' : 'opacity-60'
653
+ }`}
654
+ />
655
+ <span className={`px-3 text-sm font-sans ${isSuperscript ? 'bg-purple-50-900' : 'font-normal'}`}>
656
+ Superscript
657
+ </span>
658
+ </DropdownMenu.Item>
659
+ <DropdownMenu.Item
660
+ onClick={clearFormatting}
661
+ className="item"
662
+ title="Clear text formatting"
663
+ aria-label="Clear all text formatting"
664
+ >
665
+ <i className="icon w-6 h-6 clear border bg-no-repeat bg-center bg-[length:16px_16px] opacity-60" />
666
+ <span className="px-3 text-sm text-pink-600-300 font-sans font-normal">Clear Formatting</span>
667
+ </DropdownMenu.Item>
668
+ </>
669
+ }
670
+ >
671
+ <Button
672
+ style={{ backgroundColor: 'transparent', padding: '0 8px' }}
673
+ aria-label="Formatting options for additional text styles"
674
+ >
675
+ <i className={`icon dropdown-more border bg-no-repeat bg-center bg-[length:18px_18px] w-6 h-6`} />
676
+ <Icon.Arrow />
677
+ </Button>
678
+ </DropdownMenu.Root>
679
+ <Divider />
680
+ <DropdownMenu.Root
681
+ style={{ zIndex: 1 }}
682
+ disabled={!isEditable}
683
+ content={
684
+ <>
685
+ <DropdownMenu.Item
686
+ onClick={() => {
687
+ activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined);
688
+ }}
689
+ >
690
+ <div className="flex items-center font-sans font-normal">
691
+ <i className="icon w-5 h-5 horizontal-rule border bg-no-repeat bg-center bg-[length:16px_16px] opacity-60" />
692
+ <span className="px-3 text-sm">Horizontal rule</span>
693
+ </div>
694
+ </DropdownMenu.Item>
695
+ <DropdownMenu.Item>
696
+ <Dialog.Trigger asChild>
697
+ <div className="flex items-center font-sans font-normal">
698
+ <i className="icon w-5 h-5 table border bg-no-repeat bg-center bg-[length:16px_16px] opacity-60" />
699
+ <span className="px-3 text-sm">Table</span>
700
+ </div>
701
+ </Dialog.Trigger>
702
+ </DropdownMenu.Item>
703
+ </>
704
+ }
705
+ >
706
+ <IconButton>
707
+ <i className="icon plus border w-full h-full bg-no-repeat bg-center bg-[length:20px_20px] " />
708
+ </IconButton>
709
+ </DropdownMenu.Root>
710
+ <Dialog.Content>
711
+ <Dialog.Title>Insert table</Dialog.Title>
712
+ <Dialog.Description>
713
+ Define your starting point of a table, you can add and remove columns and rows after creation.
714
+ </Dialog.Description>
715
+ <div className="items-center justify-between">
716
+ <InsertNewTableDialog activeEditor={activeEditor} />
717
+ </div>
718
+ </Dialog.Content>
719
+ </div>
720
+ </Dialog>
721
+ )}
722
+ </div>
723
+ <ActionsPlugin prepend={actionsMenuPrepend} append={actionsMenuAppend} />
724
+ </div>
725
+ );
726
+ }