@gcforms/editor 0.0.1 → 1.0.0

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.
@@ -0,0 +1,78 @@
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 { TOGGLE_LINK_COMMAND } from "@lexical/link";
10
+ import { HeadingTagType } from "@lexical/rich-text";
11
+ import { COMMAND_PRIORITY_NORMAL, KEY_MODIFIER_COMMAND } from "lexical";
12
+ import { Dispatch, useEffect } from "react";
13
+
14
+ import { useToolbarState } from "../../context/ToolbarContext";
15
+ import {
16
+ formatBulletList,
17
+ formatHeading,
18
+ formatNumberedList,
19
+ formatParagraph,
20
+ } from "../ToolbarPlugin/utils";
21
+ import {
22
+ isFormatBulletList,
23
+ isFormatHeading,
24
+ isFormatNumberedList,
25
+ isFormatParagraph,
26
+ isInsertLink,
27
+ } from "./shortcuts";
28
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
29
+
30
+ export default function ShortcutsPlugin({
31
+ setIsLinkEditMode,
32
+ }: {
33
+ setIsLinkEditMode: Dispatch<boolean>;
34
+ }): null {
35
+ const [editor] = useLexicalComposerContext();
36
+ const { toolbarState } = useToolbarState();
37
+
38
+ useEffect(() => {
39
+ const keyboardShortcutsHandler = (payload: KeyboardEvent) => {
40
+ const event: KeyboardEvent = payload;
41
+
42
+ if (isFormatParagraph(event)) {
43
+ event.preventDefault();
44
+ formatParagraph(editor);
45
+ } else if (isFormatHeading(event)) {
46
+ event.preventDefault();
47
+ if (!["2", "3"].includes(event.code[event.code.length - 1])) {
48
+ return false;
49
+ }
50
+ const { code } = event;
51
+ const headingSize = `h${code[code.length - 1]}` as HeadingTagType;
52
+ formatHeading(editor, toolbarState.blockType, headingSize);
53
+ } else if (isFormatBulletList(event)) {
54
+ event.preventDefault();
55
+ formatBulletList(editor, toolbarState.blockType);
56
+ } else if (isFormatNumberedList(event)) {
57
+ event.preventDefault();
58
+ formatNumberedList(editor, toolbarState.blockType);
59
+ } else if (isInsertLink(event)) {
60
+ event.preventDefault();
61
+ const url = toolbarState.isLink ? null : "";
62
+ setIsLinkEditMode(!toolbarState.isLink);
63
+
64
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
65
+ }
66
+
67
+ return false;
68
+ };
69
+
70
+ return editor.registerCommand(
71
+ KEY_MODIFIER_COMMAND,
72
+ keyboardShortcutsHandler,
73
+ COMMAND_PRIORITY_NORMAL
74
+ );
75
+ }, [editor, toolbarState.isLink, toolbarState.blockType, setIsLinkEditMode]);
76
+
77
+ return null;
78
+ }
@@ -0,0 +1,76 @@
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 { IS_APPLE } from "@lexical/utils";
10
+
11
+ //disable eslint sorting rule for quick reference to shortcuts
12
+ export const SHORTCUTS = Object.freeze({
13
+ // (Ctrl|⌘) + (Alt|Option) + <key> shortcuts
14
+ NORMAL: IS_APPLE ? "⌘+Opt+0" : "Ctrl+Alt+0",
15
+ HEADING2: IS_APPLE ? "⌘+Opt+2" : "Ctrl+Alt+2",
16
+ HEADING3: IS_APPLE ? "⌘+Opt+3" : "Ctrl+Alt+3",
17
+ BULLET_LIST: IS_APPLE ? "⌘+Opt+4" : "Ctrl+Alt+4",
18
+ NUMBERED_LIST: IS_APPLE ? "⌘+Opt+5" : "Ctrl+Alt+5",
19
+
20
+ // (Ctrl|⌘) + Shift + <key> shortcuts
21
+ //
22
+
23
+ // (Ctrl|⌘) + <key> shortcuts
24
+ BOLD: IS_APPLE ? "⌘+B" : "Ctrl+B",
25
+ ITALIC: IS_APPLE ? "⌘+I" : "Ctrl+I",
26
+ INSERT_LINK: IS_APPLE ? "⌘+K" : "Ctrl+K",
27
+ });
28
+
29
+ export function controlOrMeta(metaKey: boolean, ctrlKey: boolean): boolean {
30
+ return IS_APPLE ? metaKey : ctrlKey;
31
+ }
32
+
33
+ export function isFormatParagraph(event: KeyboardEvent): boolean {
34
+ const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
35
+
36
+ return (
37
+ (code === "Numpad0" || code === "Digit0") &&
38
+ !shiftKey &&
39
+ altKey &&
40
+ controlOrMeta(metaKey, ctrlKey)
41
+ );
42
+ }
43
+
44
+ export function isFormatHeading(event: KeyboardEvent): boolean {
45
+ const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
46
+ const keyNumber = code[code.length - 1];
47
+
48
+ return (
49
+ ["1", "2", "3"].includes(keyNumber) && !shiftKey && altKey && controlOrMeta(metaKey, ctrlKey)
50
+ );
51
+ }
52
+
53
+ export function isFormatBulletList(event: KeyboardEvent): boolean {
54
+ const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
55
+ return (
56
+ (code === "Numpad4" || code === "Digit4") &&
57
+ !shiftKey &&
58
+ altKey &&
59
+ controlOrMeta(metaKey, ctrlKey)
60
+ );
61
+ }
62
+
63
+ export function isFormatNumberedList(event: KeyboardEvent): boolean {
64
+ const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
65
+ return (
66
+ (code === "Numpad5" || code === "Digit5") &&
67
+ !shiftKey &&
68
+ altKey &&
69
+ controlOrMeta(metaKey, ctrlKey)
70
+ );
71
+ }
72
+
73
+ export function isInsertLink(event: KeyboardEvent): boolean {
74
+ const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
75
+ return code === "KeyK" && !shiftKey && !altKey && controlOrMeta(metaKey, ctrlKey);
76
+ }
@@ -1,35 +1,26 @@
1
1
  "use client";
2
2
  import React, { useState, useCallback, useEffect, useRef, KeyboardEvent } from "react";
3
- import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
4
- import { $isHeadingNode, $createHeadingNode } from "@lexical/rich-text";
5
- import { mergeRegister, $getNearestNodeOfType } from "@lexical/utils";
3
+ import { $isHeadingNode } from "@lexical/rich-text";
4
+ import { mergeRegister, $getNearestNodeOfType, $findMatchingParent } from "@lexical/utils";
6
5
 
7
6
  import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
8
7
 
9
- // @TODO: pass in this dependency or otherwise incorporate into package
10
- import { useTranslation } from "@i18n/client";
11
-
12
- import {
13
- $isListNode,
14
- INSERT_ORDERED_LIST_COMMAND,
15
- INSERT_UNORDERED_LIST_COMMAND,
16
- REMOVE_LIST_COMMAND,
17
- ListNode,
18
- } from "@lexical/list";
8
+ import { $isListNode, ListNode } from "@lexical/list";
19
9
 
20
10
  import {
21
11
  FORMAT_TEXT_COMMAND,
22
12
  $getSelection,
23
13
  $isRangeSelection,
24
14
  SELECTION_CHANGE_COMMAND,
25
- $createParagraphNode,
15
+ $isRootOrShadowRoot,
16
+ COMMAND_PRIORITY_CRITICAL,
17
+ CAN_UNDO_COMMAND,
18
+ CAN_REDO_COMMAND,
26
19
  } from "lexical";
27
20
 
28
- import { $wrapNodes } from "@lexical/selection";
29
- import { sanitizeUrl } from "../../utils/url";
30
21
  import { useEditorFocus } from "../../hooks/useEditorFocus";
31
22
  import { getSelectedNode } from "../../utils/getSelectedNode";
32
- import { ToolTip } from "../../ToolTip";
23
+ import { ToolTip } from "../../ui/ToolTip";
33
24
  import { H2Icon } from "../../icons/H2Icon";
34
25
  import { H3Icon } from "../../icons/H3Icon";
35
26
  import { BoldIcon } from "../../icons/BoldIcon";
@@ -37,43 +28,38 @@ import { ItalicIcon } from "../../icons/ItalicIcon";
37
28
  import { BulletListIcon } from "../../icons/BulletListIcon";
38
29
  import { NumberedListIcon } from "../../icons/NumberedListIcon";
39
30
  import { LinkIcon } from "../../icons/LinkIcon";
31
+ import { useTranslation } from "../../hooks/useTranslation";
32
+ import { formatBulletList, formatHeading, formatNumberedList } from "./utils";
33
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
40
34
  import "./styles.css";
41
-
42
- const blockTypeToBlockName = {
43
- bullet: "Bulleted List",
44
- check: "Check List",
45
- code: "Code Block",
46
- h1: "Heading 1",
47
- h2: "Heading 2",
48
- h3: "Heading 3",
49
- h4: "Heading 4",
50
- h5: "Heading 5",
51
- h6: "Heading 6",
52
- number: "Numbered List",
53
- paragraph: "Normal",
54
- quote: "Quote",
55
- };
56
-
57
- const LowPriority = 1;
58
- type HeadingTagType = "h2" | "h3" | "h4" | "h5";
59
-
60
- export default function ToolbarPlugin({ editorId }: { editorId: string }) {
35
+ import { SHORTCUTS } from "../ShortcutsPlugin/shortcuts";
36
+ import { blockTypeToBlockName, useToolbarState } from "../../context/ToolbarContext";
37
+
38
+ export default function ToolbarPlugin({
39
+ editorId,
40
+ setIsLinkEditMode,
41
+ }: {
42
+ editorId: string;
43
+ setIsLinkEditMode: (isLinkEditMode: boolean) => void;
44
+ }) {
61
45
  const [editor] = useLexicalComposerContext();
62
- const [isBold, setIsBold] = useState(false);
63
- const [isItalic, setIsItalic] = useState(false);
64
- const [isLink, setIsLink] = useState(false);
46
+
47
+ const { toolbarState, updateToolbarState } = useToolbarState();
65
48
  const [, setSelectedElementKey] = useState("");
66
- const [blockType, setBlockType] = useState("paragraph");
49
+ const [isEditable, setIsEditable] = useState(() => editor.isEditable());
50
+ const [activeEditor, setActiveEditor] = useState(editor);
67
51
 
68
- const [isEditable] = useState(() => editor.isEditable());
52
+ const { t } = useTranslation();
69
53
 
70
54
  const insertLink = useCallback(() => {
71
- if (!isLink) {
72
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl("https://"));
55
+ if (!toolbarState.isLink) {
56
+ setIsLinkEditMode(true);
57
+ activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, "");
73
58
  } else {
74
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
59
+ setIsLinkEditMode(false);
60
+ activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
75
61
  }
76
- }, [editor, isLink]);
62
+ }, [activeEditor, setIsLinkEditMode, toolbarState.isLink]);
77
63
 
78
64
  const [items] = useState([
79
65
  { id: 1, txt: "heading2" },
@@ -117,117 +103,101 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
117
103
  [items, setCurrentFocusIndex, setToolbarInit, toolbarInit]
118
104
  );
119
105
 
120
- const formatHeading = (level: HeadingTagType) => {
121
- if (blockType === level) {
122
- formatParagraph();
123
- }
124
-
125
- if (blockType !== level) {
126
- editor.update(() => {
127
- const selection = $getSelection();
128
- if ($isRangeSelection(selection)) {
129
- $wrapNodes(selection, () => $createHeadingNode(level));
130
- }
131
- });
132
- }
133
- };
134
-
135
- const formatParagraph = () => {
136
- if (blockType !== "paragraph") {
137
- editor.update(() => {
138
- const selection = $getSelection();
139
-
140
- if ($isRangeSelection(selection)) {
141
- $wrapNodes(selection, () => $createParagraphNode());
142
- }
143
- });
144
- }
145
- };
146
-
147
- const formatBulletList = (evt: React.MouseEvent<HTMLButtonElement>) => {
148
- evt.preventDefault();
149
- if (blockType !== "bullet") {
150
- editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
151
- return;
152
- }
153
- editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
154
- };
155
-
156
- const formatNumberedList = (evt: React.MouseEvent<HTMLButtonElement>) => {
157
- evt.preventDefault();
158
- if (blockType !== "number") {
159
- editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
160
- return;
161
- }
162
- editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
163
- };
164
-
165
- const updateToolbar = useCallback(() => {
106
+ const $updateToolbar = useCallback(() => {
166
107
  const selection = $getSelection();
167
108
 
168
109
  if ($isRangeSelection(selection)) {
169
110
  const anchorNode = selection.anchor.getNode();
170
- const element =
171
- anchorNode.getKey() === "root" ? anchorNode : anchorNode.getTopLevelElementOrThrow();
172
- const elementKey = element.getKey();
173
- const elementDOM = editor.getElementByKey(elementKey);
174
- if (elementDOM !== null) {
175
- setSelectedElementKey(elementKey);
176
-
177
- const type = $isHeadingNode(element) ? element.getTag() : element.getType();
178
- setBlockType(type);
111
+ let element =
112
+ anchorNode.getKey() === "root"
113
+ ? anchorNode
114
+ : $findMatchingParent(anchorNode, (e) => {
115
+ const parent = e.getParent();
116
+ return parent !== null && $isRootOrShadowRoot(parent);
117
+ });
118
+
119
+ if (element === null) {
120
+ element = anchorNode.getTopLevelElementOrThrow();
179
121
  }
180
122
 
181
- // Update text format
182
- setIsBold(selection.hasFormat("bold"));
183
- setIsItalic(selection.hasFormat("italic"));
123
+ const elementKey = element.getKey();
124
+ const elementDOM = editor.getElementByKey(elementKey);
184
125
 
185
- // Get current node and parent
126
+ // Update links
186
127
  const node = getSelectedNode(selection);
187
128
  const parent = node.getParent();
188
-
189
- // Update links
190
- if ($isLinkNode(parent) || $isLinkNode(node)) {
191
- setIsLink(true);
192
- } else {
193
- setIsLink(false);
194
- }
129
+ const isLink = $isLinkNode(parent) || $isLinkNode(node);
130
+ updateToolbarState("isLink", isLink);
195
131
 
196
132
  if (elementDOM !== null) {
197
133
  setSelectedElementKey(elementKey);
198
134
  if ($isListNode(element)) {
199
135
  const parentList = $getNearestNodeOfType<ListNode>(anchorNode, ListNode);
200
136
  const type = parentList ? parentList.getListType() : element.getListType();
201
- setBlockType(type);
137
+
138
+ updateToolbarState("blockType", type);
202
139
  } else {
203
140
  const type = $isHeadingNode(element) ? element.getTag() : element.getType();
204
141
  if (type in blockTypeToBlockName) {
205
- setBlockType(type as keyof typeof blockTypeToBlockName);
142
+ updateToolbarState("blockType", type as keyof typeof blockTypeToBlockName);
206
143
  }
207
144
  }
208
145
  }
209
146
  }
210
- }, [editor]);
147
+ if ($isRangeSelection(selection)) {
148
+ // Update text format
149
+ updateToolbarState("isBold", selection.hasFormat("bold"));
150
+ updateToolbarState("isItalic", selection.hasFormat("italic"));
151
+ }
152
+ }, [editor, updateToolbarState]);
153
+
154
+ useEffect(() => {
155
+ return editor.registerCommand(
156
+ SELECTION_CHANGE_COMMAND,
157
+ (_payload, newEditor) => {
158
+ setActiveEditor(newEditor);
159
+ $updateToolbar();
160
+ return false;
161
+ },
162
+ COMMAND_PRIORITY_CRITICAL
163
+ );
164
+ }, [editor, $updateToolbar, setActiveEditor]);
165
+
166
+ useEffect(() => {
167
+ activeEditor.getEditorState().read(() => {
168
+ $updateToolbar();
169
+ });
170
+ }, [activeEditor, $updateToolbar]);
211
171
 
212
172
  useEffect(() => {
213
173
  return mergeRegister(
214
- editor.registerUpdateListener(({ editorState }) => {
174
+ editor.registerEditableListener((editable) => {
175
+ setIsEditable(editable);
176
+ }),
177
+ activeEditor.registerUpdateListener(({ editorState }) => {
215
178
  editorState.read(() => {
216
- updateToolbar();
179
+ $updateToolbar();
217
180
  });
218
181
  }),
219
- editor.registerCommand(
220
- SELECTION_CHANGE_COMMAND,
221
- () => {
222
- updateToolbar();
182
+ activeEditor.registerCommand<boolean>(
183
+ CAN_UNDO_COMMAND,
184
+ (payload) => {
185
+ updateToolbarState("canUndo", payload);
186
+ return false;
187
+ },
188
+ COMMAND_PRIORITY_CRITICAL
189
+ ),
190
+ activeEditor.registerCommand<boolean>(
191
+ CAN_REDO_COMMAND,
192
+ (payload) => {
193
+ updateToolbarState("canRedo", payload);
223
194
  return false;
224
195
  },
225
- LowPriority
196
+ COMMAND_PRIORITY_CRITICAL
226
197
  )
227
198
  );
228
- }, [editor, updateToolbar]);
199
+ }, [$updateToolbar, activeEditor, editor, updateToolbarState]);
229
200
 
230
- const { t } = useTranslation("form-builder");
231
201
  const editorHasFocus = useEditorFocus();
232
202
 
233
203
  return (
@@ -240,7 +210,7 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
240
210
  onKeyDown={handleNav}
241
211
  data-testid="toolbar"
242
212
  >
243
- <ToolTip text={t("tooltipFormatH2")}>
213
+ <ToolTip text={t("tooltipFormatH2") + ` (${SHORTCUTS.HEADING2})`}>
244
214
  <button
245
215
  tabIndex={currentFocusIndex == 0 ? 0 : -1}
246
216
  ref={(el) => {
@@ -250,20 +220,20 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
250
220
  }
251
221
  }}
252
222
  onClick={() => {
253
- formatHeading("h2");
223
+ formatHeading(editor, toolbarState.blockType, "h2");
254
224
  }}
255
225
  className={
256
- "toolbar-item spaced " + (blockType === "h2" && editorHasFocus ? "active" : "")
226
+ "toolbar-item " + (toolbarState.blockType === "h2" && editorHasFocus ? "active" : "")
257
227
  }
258
228
  aria-label={t("formatH2")}
259
- aria-pressed={blockType === "h2"}
229
+ aria-pressed={toolbarState.blockType === "h2"}
260
230
  data-testid={`h2-button`}
261
231
  >
262
232
  <H2Icon />
263
233
  </button>
264
234
  </ToolTip>
265
235
 
266
- <ToolTip text={t("tooltipFormatH3")}>
236
+ <ToolTip text={t("tooltipFormatH3") + ` (${SHORTCUTS.HEADING3})`}>
267
237
  <button
268
238
  tabIndex={currentFocusIndex == 1 ? 0 : -1}
269
239
  ref={(el) => {
@@ -273,20 +243,20 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
273
243
  }
274
244
  }}
275
245
  onClick={() => {
276
- formatHeading("h3");
246
+ formatHeading(editor, toolbarState.blockType, "h3");
277
247
  }}
278
248
  className={
279
- "peer toolbar-item spaced " + (blockType === "h3" && editorHasFocus ? "active" : "")
249
+ "toolbar-item " + (toolbarState.blockType === "h3" && editorHasFocus ? "active" : "")
280
250
  }
281
251
  aria-label={t("formatH3")}
282
- aria-pressed={blockType === "h3"}
252
+ aria-pressed={toolbarState.blockType === "h3"}
283
253
  data-testid={`h3-button`}
284
254
  >
285
255
  <H3Icon />
286
256
  </button>
287
257
  </ToolTip>
288
258
 
289
- <ToolTip text={t("tooltipFormatBold")}>
259
+ <ToolTip text={t("tooltipFormatBold") + ` (${SHORTCUTS.BOLD})`}>
290
260
  <button
291
261
  tabIndex={currentFocusIndex == 2 ? 0 : -1}
292
262
  ref={(el) => {
@@ -298,16 +268,16 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
298
268
  onClick={() => {
299
269
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
300
270
  }}
301
- className={"peer toolbar-item " + (isBold && editorHasFocus ? "active" : "")}
271
+ className={"toolbar-item " + (toolbarState.isBold && editorHasFocus ? "active" : "")}
302
272
  aria-label={t("formatBold")}
303
- aria-pressed={isBold}
273
+ aria-pressed={toolbarState.isBold}
304
274
  data-testid={`bold-button`}
305
275
  >
306
276
  <BoldIcon />
307
277
  </button>
308
278
  </ToolTip>
309
279
 
310
- <ToolTip text={t("tooltipFormatItalic")}>
280
+ <ToolTip text={t("tooltipFormatItalic") + ` (${SHORTCUTS.ITALIC})`}>
311
281
  <button
312
282
  tabIndex={currentFocusIndex == 3 ? 0 : -1}
313
283
  ref={(el) => {
@@ -319,16 +289,16 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
319
289
  onClick={() => {
320
290
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
321
291
  }}
322
- className={"peer toolbar-item " + (isItalic && editorHasFocus ? "active" : "")}
292
+ className={"toolbar-item " + (toolbarState.isItalic && editorHasFocus ? "active" : "")}
323
293
  aria-label={t("formatItalic")}
324
- aria-pressed={isItalic}
294
+ aria-pressed={toolbarState.isItalic}
325
295
  data-testid={`italic-button`}
326
296
  >
327
297
  <ItalicIcon />
328
298
  </button>
329
299
  </ToolTip>
330
300
 
331
- <ToolTip text={t("tooltipFormatBulletList")}>
301
+ <ToolTip text={t("tooltipFormatBulletList") + ` (${SHORTCUTS.BULLET_LIST})`}>
332
302
  <button
333
303
  tabIndex={currentFocusIndex == 4 ? 0 : -1}
334
304
  ref={(el) => {
@@ -337,19 +307,20 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
337
307
  itemsRef.current[index] = el;
338
308
  }
339
309
  }}
340
- onClick={formatBulletList}
310
+ onClick={() => formatBulletList(editor, toolbarState.blockType)}
341
311
  className={
342
- "peer toolbar-item " + (blockType === "bullet" && editorHasFocus ? "active" : "")
312
+ "toolbar-item " +
313
+ (toolbarState.blockType === "bullet" && editorHasFocus ? "active" : "")
343
314
  }
344
315
  aria-label={t("formatBulletList")}
345
- aria-pressed={blockType === "bullet"}
316
+ aria-pressed={toolbarState.blockType === "bullet"}
346
317
  data-testid={`bullet-list-button`}
347
318
  >
348
319
  <BulletListIcon />
349
320
  </button>
350
321
  </ToolTip>
351
322
 
352
- <ToolTip text={t("tooltipFormatNumberedList")}>
323
+ <ToolTip text={t("tooltipFormatNumberedList") + ` (${SHORTCUTS.NUMBERED_LIST})`}>
353
324
  <button
354
325
  tabIndex={currentFocusIndex == 5 ? 0 : -1}
355
326
  ref={(el) => {
@@ -358,19 +329,20 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
358
329
  itemsRef.current[index] = el;
359
330
  }
360
331
  }}
361
- onClick={formatNumberedList}
332
+ onClick={() => formatNumberedList(editor, toolbarState.blockType)}
362
333
  className={
363
- "peer toolbar-item " + (blockType === "number" && editorHasFocus ? "active" : "")
334
+ "toolbar-item " +
335
+ (toolbarState.blockType === "number" && editorHasFocus ? "active" : "")
364
336
  }
365
337
  aria-label={t("formatNumberedList")}
366
- aria-pressed={blockType === "number"}
338
+ aria-pressed={toolbarState.blockType === "number"}
367
339
  data-testid={`numbered-list-button`}
368
340
  >
369
341
  <NumberedListIcon />
370
342
  </button>
371
343
  </ToolTip>
372
344
 
373
- <ToolTip text={t("tooltipInsertLink")}>
345
+ <ToolTip text={t("tooltipInsertLink") + ` (${SHORTCUTS.INSERT_LINK})`}>
374
346
  <button
375
347
  tabIndex={currentFocusIndex == 6 ? 0 : -1}
376
348
  ref={(el) => {
@@ -381,9 +353,9 @@ export default function ToolbarPlugin({ editorId }: { editorId: string }) {
381
353
  }}
382
354
  disabled={!isEditable}
383
355
  onClick={insertLink}
384
- className={"peer toolbar-item " + (isLink && editorHasFocus ? "active" : "")}
356
+ className={"toolbar-item " + (toolbarState.isLink && editorHasFocus ? "active" : "")}
385
357
  aria-label={t("insertLink")}
386
- aria-pressed={isLink}
358
+ aria-pressed={toolbarState.isLink}
387
359
  data-testid={`link-button`}
388
360
  >
389
361
  <LinkIcon />
@@ -3,20 +3,18 @@
3
3
  border-radius: 2px 2px 0 0;
4
4
  background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
5
5
  border-bottom: 1px solid #e9ecef;
6
+ display: flex;
7
+ gap: 6px;
6
8
 
7
9
  &:focus-within {
8
10
  margin: -2px;
9
11
  border: 2px solid #015ecc;
10
12
  }
11
13
 
12
- button {
13
- padding: 4px;
14
+ button.toolbar-item {
15
+ padding: 2px 5px 5px 2px;
14
16
  border: 1.5px solid transparent;
15
17
  border-radius: 4px;
16
- margin-right: 5px;
17
- svg {
18
- display: block;
19
- }
20
18
  &.active {
21
19
  border: 1.5px solid #015ecc;
22
20
  }