@crystallize/design-system 1.10.0 → 1.11.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 (27) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.css +15 -17
  3. package/dist/index.d.ts +8 -1
  4. package/dist/index.js +522 -444
  5. package/dist/index.mjs +377 -304
  6. package/package.json +1 -1
  7. package/src/iconography/info.tsx +2 -2
  8. package/src/rich-text-editor/i18n/i18n.test.ts +14 -0
  9. package/src/rich-text-editor/i18n/index.tsx +65 -0
  10. package/src/rich-text-editor/i18n/translations/en.ts +66 -0
  11. package/src/rich-text-editor/i18n/types.ts +62 -0
  12. package/src/rich-text-editor/plugins/ActionsPlugin/index.tsx +5 -22
  13. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +4 -1
  14. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +11 -1
  15. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/index.tsx +2 -1
  16. package/src/rich-text-editor/plugins/FloatingLinkEditorPlugin/index.tsx +23 -5
  17. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +21 -10
  18. package/src/rich-text-editor/plugins/TabFocusPlugin/index.tsx +4 -12
  19. package/src/rich-text-editor/plugins/TableActionMenuPlugin/index.tsx +23 -14
  20. package/src/rich-text-editor/plugins/ToolbarPlugin/index.tsx +33 -33
  21. package/src/rich-text-editor/plugins/ToolbarPlugin/insert-table.tsx +6 -4
  22. package/src/rich-text-editor/rich-text-editor.css +6 -0
  23. package/src/rich-text-editor/rich-text-editor.stories.tsx +10 -0
  24. package/src/rich-text-editor/rich-text-editor.tsx +15 -9
  25. package/src/rich-text-editor/ui/LinkPreview.tsx +3 -1
  26. package/src/rich-text-editor/ui/ContentEditable.css +0 -13
  27. package/src/rich-text-editor/ui/ContentEditable.tsx +0 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crystallize/design-system",
3
- "version": "1.10.0",
3
+ "version": "1.11.1",
4
4
  "types": "./dist/index.d.ts",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -20,7 +20,7 @@ export const Info = forwardRef<InfoRef, InfoProps>((delegated, ref) => {
20
20
  />
21
21
  <path
22
22
  fillRule="evenodd"
23
- clip-rule="evenodd"
23
+ clipRule="evenodd"
24
24
  d="M18.253 3.85212C18.253 5.04383 17.2869 6.00991 16.0952 6.00991C14.9035 6.00991 13.9374 5.04383 13.9374 3.85212C13.9374 2.66041 14.9035 1.69434 16.0952 1.69434C17.2869 1.69434 18.253 2.66041 18.253 3.85212ZM16.0952 5.34999C16.9224 5.34999 17.5931 4.67937 17.5931 3.85212C17.5931 3.02487 16.9224 2.35425 16.0952 2.35425C15.2679 2.35425 14.5973 3.02487 14.5973 3.85212C14.5973 4.67937 15.2679 5.34999 16.0952 5.34999Z"
25
25
  fill="#528693"
26
26
  />
@@ -30,7 +30,7 @@ export const Info = forwardRef<InfoRef, InfoProps>((delegated, ref) => {
30
30
  />
31
31
  <path
32
32
  fillRule="evenodd"
33
- clip-rule="evenodd"
33
+ clipRule="evenodd"
34
34
  d="M11.2842 18.976L11.3049 18.9785C11.5112 19.0031 11.7201 18.9658 11.9049 18.8714C12.0891 18.7772 12.2409 18.6306 12.3411 18.4504C12.3471 18.4394 12.352 18.4277 12.3558 18.4158C12.3317 18.4124 12.3048 18.4091 12.2731 18.4053C12.0384 18.3881 11.8084 18.33 11.5936 18.2335C11.2334 18.0797 10.9456 17.7933 10.7906 17.433C10.6341 17.0694 10.6252 16.6594 10.7658 16.2894C10.9266 15.8663 11.1487 15.4689 11.4246 15.1101L11.6291 14.8181C11.6293 14.8178 11.629 14.8184 11.6291 14.8181C12.0038 14.2802 12.4092 13.763 12.8423 13.2707L12.9418 13.1535C12.9894 13.097 13.0361 13.0418 13.0821 12.9874C13.2314 12.8108 13.3734 12.6428 13.5141 12.4665L13.5237 12.4544L13.5339 12.4428C14.107 11.7899 14.5194 11.0128 14.7386 10.1733C14.8642 9.60674 14.7724 9.1134 14.5125 8.69961C14.2452 8.27422 13.7678 7.88337 13.0466 7.60647L13.0376 7.60304C12.2034 7.26882 11.3198 7.07373 10.4221 7.02572C10.2586 7.02444 10.0952 7.03841 9.93422 7.06745L9.91342 7.0712L9.89242 7.07362C9.77981 7.08656 9.67209 7.12639 9.57839 7.18964C9.48729 7.25113 9.41209 7.33296 9.35867 7.42842C9.35249 7.44171 9.33941 7.47402 9.32659 7.52286C9.31959 7.54951 9.31383 7.57671 9.30949 7.60325C9.36308 7.61115 9.41754 7.61364 9.47206 7.61055L9.51828 7.60793L9.63538 7.61773C9.79024 7.6263 9.94419 7.64682 10.0959 7.67913L10.1211 7.68451L10.1459 7.69185C10.6213 7.83272 11.1176 8.10198 11.3044 8.65041L11.3058 8.6547C11.4603 9.11879 11.309 9.5616 11.1547 9.87197C10.9947 10.194 10.7584 10.5161 10.546 10.7961C10.1932 11.2626 9.79323 11.726 9.4222 12.1559L9.4167 12.1622L9.30686 12.2896L9.29607 12.301C8.50411 13.1358 7.82119 14.0671 7.26353 15.0728C6.95101 15.7053 6.92585 16.2828 7.10297 16.7755C7.28379 17.2785 7.70442 17.772 8.42421 18.1631L8.42623 18.1642C9.29923 18.6426 10.2687 18.9197 11.2633 18.9749L11.2842 18.976ZM6.67909 14.7659C7.26511 13.7065 7.9836 12.7256 8.81732 11.8468L8.91693 11.7313C9.29243 11.2963 9.68044 10.8467 10.0199 10.3976C10.4486 9.83253 10.8215 9.28929 10.6797 8.86312C10.5968 8.6196 10.3605 8.44373 9.9584 8.32457C9.83715 8.29874 9.71404 8.28256 9.59022 8.27617L9.50938 8.26941C9.26232 8.2834 9.01575 8.23452 8.79281 8.12735C8.54248 7.93066 8.64731 7.36817 8.77404 7.12204C8.87835 6.92966 9.02762 6.76522 9.2092 6.64266C9.39077 6.5201 9.59929 6.44304 9.81707 6.41802C10.0235 6.38077 10.2331 6.36335 10.4429 6.36598C11.4176 6.41652 12.3773 6.6275 13.2831 6.9904C14.9259 7.62106 15.7097 8.86885 15.3801 10.3284C15.1357 11.2718 14.6733 12.1451 14.0299 12.8781C13.8828 13.0625 13.7326 13.2401 13.582 13.4182C13.5366 13.4719 13.4912 13.5256 13.4458 13.5795L13.3415 13.7024C12.9232 14.1775 12.5323 14.6759 12.1707 15.1952L11.9564 15.5012C11.7156 15.8119 11.5223 16.1565 11.3827 16.5237C11.303 16.7335 11.308 16.966 11.3968 17.1721C11.4855 17.3783 11.651 17.5421 11.8583 17.6289C12.0086 17.6973 12.1701 17.7377 12.335 17.748C12.5957 17.7793 12.8648 17.8126 12.9712 18.0603C13.0139 18.1752 13.0313 18.298 13.0223 18.4203C13.0133 18.5425 12.9781 18.6615 12.9191 18.769C12.7552 19.0647 12.5066 19.305 12.2052 19.459C11.9037 19.6131 11.563 19.6739 11.2267 19.6338C10.1339 19.5731 9.0685 19.2687 8.1091 18.743C6.45171 17.8422 5.90412 16.3181 6.67909 14.7659Z"
35
35
  fill="#528693"
36
36
  />
@@ -0,0 +1,14 @@
1
+ import { replaceI18nVariablesInString } from '.';
2
+
3
+ describe('RichTextEditor i18n', () => {
4
+ it('replaces variable parts of a translation string correctly', async () => {
5
+ expect(replaceI18nVariablesInString('Hi {{name}}!', 'you')).toBe('Hi you!');
6
+ expect(replaceI18nVariablesInString('Hi {{whatever-goes here}}!', 'you')).toBe('Hi you!');
7
+ expect(replaceI18nVariablesInString('It can be replace {{name}} many times {{name}}', 'you')).toBe(
8
+ 'It can be replace you many times you',
9
+ );
10
+ expect(replaceI18nVariablesInString('It needs correct formatting {name}}', 'you')).toBe(
11
+ 'It needs correct formatting {name}}',
12
+ );
13
+ });
14
+ });
@@ -0,0 +1,65 @@
1
+ import { createContext, ReactNode, useContext } from 'react';
2
+
3
+ import labelTranslationEn from './translations/en';
4
+ import type { I18N } from './types';
5
+
6
+ const I18nContext = createContext<I18N | null>(null);
7
+
8
+ export type SupportedLanguages = 'en';
9
+
10
+ export { default as labelTranslationEn } from './translations/en';
11
+
12
+ export function I18nProvider({
13
+ labelTranslations,
14
+ children,
15
+ }: {
16
+ language: SupportedLanguages;
17
+ labelTranslations?: I18N;
18
+ children: ReactNode;
19
+ }) {
20
+ const translations = labelTranslations || labelTranslationEn;
21
+ // const [translations, setTranslations] = useState<I18N>(labelTranslations || labelTranslationEn);
22
+
23
+ // useEffect(() => {
24
+ // let unmounted = false;
25
+ // (async function load() {
26
+ // if (language !== 'en') {
27
+ // const resource = await import(`./translations/${language}.ts`);
28
+ // if (!unmounted) {
29
+ // setTranslations(resource.default);
30
+ // }
31
+ // }
32
+ // })();
33
+
34
+ // return () => {
35
+ // unmounted = true;
36
+ // };
37
+ // }, [language, labelTranslations]);
38
+
39
+ return <I18nContext.Provider value={translations}>{children}</I18nContext.Provider>;
40
+ }
41
+
42
+ export function replaceI18nVariablesInString(str: string, replaceWith: string) {
43
+ return str.replace(/({{[^}]+}})/g, replaceWith);
44
+ }
45
+
46
+ export function useTr() {
47
+ const context = useContext(I18nContext);
48
+
49
+ return (key: keyof I18N, units?: number) => {
50
+ const thereAreUnits = typeof units === 'number';
51
+ const keyToUse = thereAreUnits && units > 1 ? `${key}_plural` : key;
52
+
53
+ if (context && keyToUse in context) {
54
+ // @ts-expect-error
55
+ const tr: string = context[keyToUse];
56
+
57
+ if (thereAreUnits) {
58
+ return replaceI18nVariablesInString(tr, units.toString());
59
+ }
60
+ return tr;
61
+ }
62
+
63
+ return '';
64
+ };
65
+ }
@@ -0,0 +1,66 @@
1
+ import type { I18N } from '../types';
2
+
3
+ const translations: I18N = {
4
+ actionClearTextFormatting: 'Clear text formatting',
5
+ actionTextFormattingOptions: 'Formatting options for additional text styles',
6
+ actionFormatAsStrongLabel: 'Format text as bold',
7
+ actionFormatAsStrongTitle: 'Bold (Ctrl+B)',
8
+ actionFormatAsStrongTitleApple: 'Bold (⌘B)',
9
+ actionFormatAsEmphasizedLabel: 'Format text as italics',
10
+ actionFormatAsEmphasizedTitle: 'Italic (Ctrl+I)',
11
+ actionFormatAsEmphasizedTitleApple: 'Italic (⌘I)',
12
+ actionFormatAsUnderlinedLabel: 'Underline text',
13
+ actionFormatAsUnderlinedTitle: 'Underlined (Ctrl+U)',
14
+ actionFormatAsUnderlinedTitleApple: 'Underlined (⌘U)',
15
+ actionFormatWithStrikethroughLabel: 'Format text with a strikethrough',
16
+ actionFormatWithStrikethroughTitle: 'Strikethrough',
17
+ actionFormatWithSubscriptLabel: 'Format text as subscript',
18
+ actionFormatWithSubscriptTitle: 'Subscript',
19
+ actionFormatWithSuperscriptLabel: 'Format text as superscript',
20
+ actionFormatWithSuperscriptTitle: 'Superscript',
21
+ actionInsertCodeBlock: 'Insert code block',
22
+ actionInsertlink: 'Insert link',
23
+ actionCopyJSON: 'Copy JSON',
24
+ actionCopyCode: 'Copy code',
25
+ actionClear: 'Clear editor',
26
+ actionFormatCode: 'Format code with Prettier',
27
+ actionTableInsertRowsAbove: 'Insert row above',
28
+ actionTableInsertRowsAbove_plural: 'Insert {{rows}} rows above',
29
+ actionTableInsertRowsBelow: 'Insert row above',
30
+ actionTableInsertRowsBelow_plural: 'Insert {{rows}} rows above',
31
+ actionTableInsertColumnsBefore: 'Insert column left',
32
+ actionTableInsertColumnsBefore_plural: 'Insert {{columns}} columns left',
33
+ actionTableInsertColumnsAfter: 'Insert column right',
34
+ actionTableInsertColumnsAfter_plural: 'Insert {{columns}} columns right',
35
+ actionTableAddRowHeader: 'Add row header',
36
+ actionTableRemoveRowHeader: 'Remove row header',
37
+ actionTableAddColumnHeader: 'Add column header',
38
+ actionTableRemoveColumnHeader: 'Remove column header',
39
+ actionTableDeleteColumn: 'Delete column',
40
+ actionTableDeleteRow: 'Delete row',
41
+ actionTableDeleteTable: 'Delete table',
42
+ actionTableOpenOptions: 'Open table options',
43
+ actionUndoLabel: 'Undo',
44
+ actionUndoTitle: 'Undo (Ctrl+Z)',
45
+ actionUndoTitleApple: 'Undo (⌘Z)',
46
+ actionRedoLabel: 'Undo',
47
+ actionRedoTitle: 'Undo (Ctrl+Y)',
48
+ actionRedoTitleApple: 'Redo (⌘Y)',
49
+ codeSelectLanguage: 'Select language',
50
+ linkEditorLink: 'Link',
51
+ linkEditorRel: 'Rel',
52
+ linkEditorTarget: 'Target',
53
+ linkEditorCommit: 'Done',
54
+ linkEditorEdit: 'Edit',
55
+ linkPreviewReplaceTextWithTitle: 'Replace link text with its title',
56
+ horizontalRule: 'Horizontal rule',
57
+ table: 'Table',
58
+ insertTableTitle: 'Insert table',
59
+ insertTableDescription:
60
+ 'Define your starting point of a table, you can add and remove columns and rows after creation.',
61
+ insertTableRows: 'Rows',
62
+ insertTableColumns: 'Columns',
63
+ insertTableCommit: 'Insert table',
64
+ } as const;
65
+
66
+ export default translations;
@@ -0,0 +1,62 @@
1
+ export type I18NKeys =
2
+ | 'actionClearTextFormatting'
3
+ | 'actionTextFormattingOptions'
4
+ | 'actionFormatAsStrongLabel'
5
+ | 'actionFormatAsStrongTitle'
6
+ | 'actionFormatAsStrongTitleApple'
7
+ | 'actionFormatAsEmphasizedLabel'
8
+ | 'actionFormatAsEmphasizedTitle'
9
+ | 'actionFormatAsEmphasizedTitleApple'
10
+ | 'actionFormatAsUnderlinedLabel'
11
+ | 'actionFormatAsUnderlinedTitle'
12
+ | 'actionFormatAsUnderlinedTitleApple'
13
+ | 'actionFormatWithStrikethroughLabel'
14
+ | 'actionFormatWithStrikethroughTitle'
15
+ | 'actionFormatWithSubscriptLabel'
16
+ | 'actionFormatWithSubscriptTitle'
17
+ | 'actionFormatWithSuperscriptLabel'
18
+ | 'actionFormatWithSuperscriptTitle'
19
+ | 'actionCopyJSON'
20
+ | 'actionClear'
21
+ | 'actionFormatCode'
22
+ | 'actionCopyCode'
23
+ | 'actionInsertCodeBlock'
24
+ | 'actionInsertlink'
25
+ | 'actionTableInsertRowsAbove'
26
+ | 'actionTableInsertRowsAbove_plural'
27
+ | 'actionTableInsertRowsBelow'
28
+ | 'actionTableInsertRowsBelow_plural'
29
+ | 'actionTableInsertColumnsBefore'
30
+ | 'actionTableInsertColumnsBefore_plural'
31
+ | 'actionTableInsertColumnsAfter'
32
+ | 'actionTableInsertColumnsAfter_plural'
33
+ | 'actionTableAddRowHeader'
34
+ | 'actionTableRemoveRowHeader'
35
+ | 'actionTableAddColumnHeader'
36
+ | 'actionTableRemoveColumnHeader'
37
+ | 'actionTableDeleteColumn'
38
+ | 'actionTableDeleteRow'
39
+ | 'actionTableDeleteTable'
40
+ | 'actionTableOpenOptions'
41
+ | 'actionUndoLabel'
42
+ | 'actionUndoTitle'
43
+ | 'actionUndoTitleApple'
44
+ | 'actionRedoLabel'
45
+ | 'actionRedoTitle'
46
+ | 'actionRedoTitleApple'
47
+ | 'codeSelectLanguage'
48
+ | 'linkEditorLink'
49
+ | 'linkEditorRel'
50
+ | 'linkEditorTarget'
51
+ | 'linkEditorCommit'
52
+ | 'linkEditorEdit'
53
+ | 'linkPreviewReplaceTextWithTitle'
54
+ | 'horizontalRule'
55
+ | 'table'
56
+ | 'insertTableTitle'
57
+ | 'insertTableDescription'
58
+ | 'insertTableRows'
59
+ | 'insertTableColumns'
60
+ | 'insertTableCommit';
61
+
62
+ export type I18N = Record<I18NKeys, string>;
@@ -11,6 +11,7 @@ import { CLEAR_EDITOR_COMMAND, LexicalEditor } from 'lexical';
11
11
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
12
12
 
13
13
  import { ActionMenu } from '../../../action-menu';
14
+ import { useTr } from '../../i18n';
14
15
  import { lexicalToCrystallizeRichText } from '../../model/lexical-to-crystallize';
15
16
  import type { CrystallizeRichTextActionMenuItem } from '../../types/types';
16
17
 
@@ -24,25 +25,6 @@ async function copyJson(editor: LexicalEditor) {
24
25
  }
25
26
  }
26
27
 
27
- async function exportJson(editor: LexicalEditor) {
28
- const json = lexicalToCrystallizeRichText({ editorState: editor.getEditorState() });
29
-
30
- const blob = new Blob([JSON.stringify(json, null, 1)], {
31
- type: 'application/json',
32
- });
33
- const href = URL.createObjectURL(blob);
34
-
35
- const link = document.createElement('a');
36
- link.href = href;
37
- link.download = 'crystallizeRichText.json';
38
-
39
- document.body.appendChild(link);
40
- link.click();
41
- document.body.removeChild(link);
42
-
43
- URL.revokeObjectURL(href);
44
- }
45
-
46
28
  export default function ActionsPlugin({
47
29
  append,
48
30
  prepend,
@@ -51,6 +33,8 @@ export default function ActionsPlugin({
51
33
  prepend?: CrystallizeRichTextActionMenuItem[];
52
34
  }): JSX.Element {
53
35
  const [editor] = useLexicalComposerContext();
36
+ const tr = useTr();
37
+
54
38
  return (
55
39
  <div className="z-50 flex items-center ">
56
40
  <div></div>
@@ -66,8 +50,7 @@ export default function ActionsPlugin({
66
50
  {actionItem.title}
67
51
  </ActionMenu.Item>
68
52
  ))}
69
- <ActionMenu.Item onSelect={() => copyJson(editor)}>Copy JSON</ActionMenu.Item>
70
- <ActionMenu.Item onSelect={() => exportJson(editor)}>Export JSON</ActionMenu.Item>
53
+ <ActionMenu.Item onSelect={() => copyJson(editor)}>{tr('actionCopyJSON')}</ActionMenu.Item>
71
54
  <ActionMenu.Item
72
55
  className="danger"
73
56
  onSelect={() => {
@@ -75,7 +58,7 @@ export default function ActionsPlugin({
75
58
  editor.focus();
76
59
  }}
77
60
  >
78
- Clear paragraph
61
+ {tr('actionClear')}
79
62
  </ActionMenu.Item>
80
63
  {!append
81
64
  ? null
@@ -12,6 +12,8 @@ import { $getNearestNodeFromDOMNode, $getSelection, $setSelection, LexicalEditor
12
12
  import { useDebouncedCallback } from 'use-debounce';
13
13
  import { $isCodeNode } from '@lexical/code';
14
14
 
15
+ import { useTr } from '../../../../i18n';
16
+
15
17
  interface Props {
16
18
  editor: LexicalEditor;
17
19
  getCodeDOMNode: () => HTMLElement | null;
@@ -19,6 +21,7 @@ interface Props {
19
21
 
20
22
  export function CopyButton({ editor, getCodeDOMNode }: Props) {
21
23
  const [isCopyCompleted, setCopyCompleted] = useState<boolean>(false);
24
+ const tr = useTr();
22
25
 
23
26
  const removeSuccessIcon = useDebouncedCallback(() => {
24
27
  setCopyCompleted(false);
@@ -54,7 +57,7 @@ export function CopyButton({ editor, getCodeDOMNode }: Props) {
54
57
  }
55
58
 
56
59
  return (
57
- <button className="menu-item" onClick={handleClick} aria-label="copy">
60
+ <button className="menu-item" onClick={handleClick} aria-label={tr('actionCopyCode')}>
58
61
  {isCopyCompleted ? <i className="format success" /> : <i className="format copy" />}
59
62
  </button>
60
63
  );
@@ -12,6 +12,8 @@ import { $getNearestNodeFromDOMNode, LexicalEditor } from 'lexical';
12
12
  import type { Options } from 'prettier';
13
13
  import { $isCodeNode } from '@lexical/code';
14
14
 
15
+ import { useTr } from '../../../../i18n';
16
+
15
17
  interface Props {
16
18
  lang: string;
17
19
  editor: LexicalEditor;
@@ -69,6 +71,7 @@ function getPrettierOptions(lang: string): Options {
69
71
  export function PrettierButton({ lang, editor, getCodeDOMNode }: Props) {
70
72
  const [syntaxError, setSyntaxError] = useState<string>('');
71
73
  const [tipsVisible, setTipsVisible] = useState<boolean>(false);
74
+ const tr = useTr();
72
75
 
73
76
  async function handleClick(): Promise<void> {
74
77
  const codeDOMNode = getCodeDOMNode();
@@ -92,6 +95,13 @@ export function PrettierButton({ lang, editor, getCodeDOMNode }: Props) {
92
95
 
93
96
  parsed = format(content, options);
94
97
 
98
+ /**
99
+ * Remove EOF from prettier output. This is useful when
100
+ * using prettier on files, but becomes weird when the code
101
+ * is embedded within a larger portion of text.
102
+ */
103
+ parsed = parsed.replace(/[\r\n]+$/, '');
104
+
95
105
  if (parsed !== '') {
96
106
  const selection = codeNode.select(0);
97
107
  selection.insertText(parsed);
@@ -129,7 +139,7 @@ export function PrettierButton({ lang, editor, getCodeDOMNode }: Props) {
129
139
  onClick={handleClick}
130
140
  onMouseEnter={handleMouseEnter}
131
141
  onMouseLeave={handleMouseLeave}
132
- aria-label="prettier"
142
+ aria-label={tr('actionFormatCode')}
133
143
  >
134
144
  {syntaxError ? <i className="format prettier-error" /> : <i className="format prettier" />}
135
145
  </button>
@@ -29,7 +29,7 @@ function CodeActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement }): J
29
29
 
30
30
  const [lang, setLang] = useState('');
31
31
  const [isShown, setShown] = useState<boolean>(false);
32
- const [shouldListenMouseMove, setShouldListenMouseMove] = useState<boolean>(false);
32
+ const [shouldListenMouseMove, setShouldListenMouseMove] = useState<boolean>(true);
33
33
  const [position, setPosition] = useState<Position>({
34
34
  right: '0',
35
35
  top: '0',
@@ -43,6 +43,7 @@ function CodeActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement }): J
43
43
 
44
44
  const debouncedOnMouseMove = useDebouncedCallback((event: MouseEvent) => {
45
45
  const { codeDOMNode, isOutside } = getMouseInfo(event);
46
+
46
47
  if (isOutside) {
47
48
  setShown(false);
48
49
  return;
@@ -30,6 +30,7 @@ import { Button } from '../../../button';
30
30
  import { IconButton } from '../../../icon-button';
31
31
  import { Icon } from '../../../iconography';
32
32
  import { InputWithLabel } from '../../../input-with-label';
33
+ import { useTr } from '../../i18n';
33
34
  import LinkPreview from '../../ui/LinkPreview';
34
35
  import { getSelectedNode } from '../../utils/getSelectedNode';
35
36
  import { setFloatingElemPosition } from '../../utils/setFloatingElemPosition';
@@ -53,6 +54,7 @@ function FloatingLinkEditor({
53
54
  const [target, setTarget] = useState<string | null>(null);
54
55
  const [isEditMode, setEditMode] = useState(false);
55
56
  const [lastSelection, setLastSelection] = useState<RangeSelection | GridSelection | NodeSelection | null>(null);
57
+ const tr = useTr();
56
58
 
57
59
  const updateLinkEditor = useCallback(() => {
58
60
  const selection = $getSelection();
@@ -187,14 +189,29 @@ function FloatingLinkEditor({
187
189
  {isEditMode ? (
188
190
  <div>
189
191
  <div className="border-0 border-b border-gray-100-800 border-solid px-3">
190
- <InputWithLabel label="Link" type="text" value={linkUrl} onChange={e => setLinkUrl(e.target.value)} />
192
+ <InputWithLabel
193
+ label={tr('linkEditorLink')}
194
+ type="text"
195
+ value={linkUrl}
196
+ onChange={e => setLinkUrl(e.target.value)}
197
+ />
191
198
  </div>
192
199
 
193
200
  <div className="border-0 border-b border-gray-100-800 border-solid px-3">
194
- <InputWithLabel label="Rel" type="text" value={rel ?? ''} onChange={e => setRel(e.target.value)} />
201
+ <InputWithLabel
202
+ label={tr('linkEditorRel')}
203
+ type="text"
204
+ value={rel ?? ''}
205
+ onChange={e => setRel(e.target.value)}
206
+ />
195
207
  </div>
196
208
  <div className="border-0 border-b border-gray-100-800 border-solid px-3">
197
- <InputWithLabel label="Target" type="text" value={target ?? ''} onChange={e => setTarget(e.target.value)} />
209
+ <InputWithLabel
210
+ label={tr('linkEditorTarget')}
211
+ type="text"
212
+ value={target ?? ''}
213
+ onChange={e => setTarget(e.target.value)}
214
+ />
198
215
  </div>
199
216
  <div className="flex px-6 py-2 justify-end">
200
217
  <Button
@@ -211,14 +228,14 @@ function FloatingLinkEditor({
211
228
  }
212
229
  }}
213
230
  >
214
- Done
231
+ {tr('linkEditorCommit')}
215
232
  </Button>
216
233
  </div>
217
234
  </div>
218
235
  ) : (
219
236
  <>
220
237
  <div className="link-input !flex flex-nowrap justify-between items-center max-w-full ">
221
- <div className=" grid">
238
+ <div className="grid">
222
239
  <a href={linkUrl} target="_blank" rel="noopener noreferrer">
223
240
  {linkUrl}
224
241
  </a>
@@ -241,6 +258,7 @@ function FloatingLinkEditor({
241
258
  tabIndex={0}
242
259
  onMouseDown={event => event.preventDefault()}
243
260
  onClick={() => setEditMode(true)}
261
+ aria-label={tr('linkEditorEdit')}
244
262
  >
245
263
  <Icon.Edit />
246
264
  </IconButton>
@@ -25,6 +25,8 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
25
25
  import { mergeRegister } from '@lexical/utils';
26
26
 
27
27
  import { IconButton } from '../../../icon-button';
28
+ import { useTr } from '../../i18n';
29
+ import { IS_APPLE } from '../../utils/environment';
28
30
  import { getDOMRangeRect } from '../../utils/getDOMRangeRect';
29
31
  import { getSelectedNode } from '../../utils/getSelectedNode';
30
32
  import { setFloatingElemPosition } from '../../utils/setFloatingElemPosition';
@@ -53,6 +55,7 @@ function TextFormatFloatingToolbar({
53
55
  isUnderline: boolean;
54
56
  }): JSX.Element {
55
57
  const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);
58
+ const tr = useTr();
56
59
 
57
60
  const insertLink = useCallback(() => {
58
61
  if (!isLink) {
@@ -139,7 +142,8 @@ function TextFormatFloatingToolbar({
139
142
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
140
143
  }}
141
144
  style={{ padding: 0, overflow: 'hidden' }}
142
- aria-label="Format text as bold"
145
+ title={tr(IS_APPLE ? 'actionFormatAsStrongTitleApple' : 'actionFormatAsStrongTitle')}
146
+ aria-label={tr('actionFormatAsStrongLabel')}
143
147
  >
144
148
  <i
145
149
  className={`format bold w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
@@ -152,7 +156,8 @@ function TextFormatFloatingToolbar({
152
156
  onClick={() => {
153
157
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
154
158
  }}
155
- aria-label="Format text as italics"
159
+ title={tr('actionFormatAsEmphasizedTitle')}
160
+ aria-label={tr('actionFormatAsEmphasizedLabel')}
156
161
  >
157
162
  <i
158
163
  className={`format italic w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
@@ -165,7 +170,8 @@ function TextFormatFloatingToolbar({
165
170
  onClick={() => {
166
171
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
167
172
  }}
168
- aria-label="Format text to underlined"
173
+ title={tr('actionFormatAsUnderlinedTitle')}
174
+ aria-label={tr('actionFormatAsUnderlinedLabel')}
169
175
  >
170
176
  <i
171
177
  className={`format underline w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
@@ -178,7 +184,8 @@ function TextFormatFloatingToolbar({
178
184
  onClick={() => {
179
185
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
180
186
  }}
181
- aria-label="Format text with a strikethrough"
187
+ title={tr('actionFormatWithStrikethroughTitle')}
188
+ aria-label={tr('actionFormatWithStrikethroughLabel')}
182
189
  >
183
190
  <i
184
191
  className={`format strikethrough w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
@@ -191,8 +198,8 @@ function TextFormatFloatingToolbar({
191
198
  onClick={() => {
192
199
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript');
193
200
  }}
194
- title="Subscript"
195
- aria-label="Format Subscript"
201
+ title={tr('actionFormatWithSubscriptTitle')}
202
+ aria-label={tr('actionFormatWithSubscriptLabel')}
196
203
  >
197
204
  <i
198
205
  className={`format subscript w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
@@ -205,8 +212,8 @@ function TextFormatFloatingToolbar({
205
212
  onClick={() => {
206
213
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript');
207
214
  }}
208
- title="Superscript"
209
- aria-label="Format Superscript"
215
+ title={tr('actionFormatWithSuperscriptTitle')}
216
+ aria-label={tr('actionFormatWithSuperscriptLabel')}
210
217
  >
211
218
  <i
212
219
  className={`format superscript w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
@@ -219,7 +226,7 @@ function TextFormatFloatingToolbar({
219
226
  onClick={() => {
220
227
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code');
221
228
  }}
222
- aria-label="Insert code block"
229
+ aria-label={tr('actionInsertCodeBlock')}
223
230
  >
224
231
  <i
225
232
  className={`format code w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
@@ -227,7 +234,11 @@ function TextFormatFloatingToolbar({
227
234
  }`}
228
235
  />
229
236
  </IconButton>
230
- <IconButton style={{ padding: 0, overflow: 'hidden' }} onClick={insertLink} aria-label="Insert link">
237
+ <IconButton
238
+ style={{ padding: 0, overflow: 'hidden' }}
239
+ onClick={insertLink}
240
+ aria-label={tr('actionInsertlink')}
241
+ >
231
242
  <i
232
243
  className={`format link w-full h-full bg-[length:18px_18px] bg-no-repeat bg-center ${
233
244
  isLink ? 'bg-purple-50-900 opacity-100' : 'opacity-60'
@@ -6,14 +6,9 @@
6
6
  *
7
7
  */
8
8
 
9
- import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
10
- import {
11
- $getSelection,
12
- $isRangeSelection,
13
- $setSelection,
14
- FOCUS_COMMAND,
15
- } from 'lexical';
16
- import {useEffect} from 'react';
9
+ import { useEffect } from 'react';
10
+ import { $getSelection, $isRangeSelection, $setSelection, FOCUS_COMMAND } from 'lexical';
11
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
17
12
 
18
13
  const COMMAND_PRIORITY_LOW = 1;
19
14
  const TAB_TO_FOCUS_INTERVAL = 100;
@@ -48,10 +43,7 @@ export default function TabFocusPlugin(): null {
48
43
  (event: FocusEvent) => {
49
44
  const selection = $getSelection();
50
45
  if ($isRangeSelection(selection)) {
51
- if (
52
- lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >
53
- event.timeStamp
54
- ) {
46
+ if (lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL > event.timeStamp) {
55
47
  $setSelection(selection.clone());
56
48
  }
57
49
  }