@dxos/react-ui-editor 0.8.4-main.9735255 → 0.8.4-main.9be5663bfe

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 (94) hide show
  1. package/dist/lib/browser/index.mjs +310 -388
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +310 -388
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Editor/Editor.d.ts +9 -15
  8. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  9. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/EditorContent/EditorContent.d.ts.map +1 -1
  11. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts +1 -3
  12. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts.map +1 -1
  13. package/dist/types/src/components/EditorMenuProvider/menu-presets.d.ts.map +1 -1
  14. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
  15. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts.map +1 -1
  16. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  17. package/dist/types/src/components/EditorToolbar/blocks.d.ts +3 -17
  18. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  19. package/dist/types/src/components/EditorToolbar/formatting.d.ts +3 -17
  20. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  21. package/dist/types/src/components/EditorToolbar/headings.d.ts +3 -17
  22. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  23. package/dist/types/src/components/EditorToolbar/image.d.ts +3 -8
  24. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -1
  25. package/dist/types/src/components/EditorToolbar/index.d.ts +0 -1
  26. package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
  27. package/dist/types/src/components/EditorToolbar/lists.d.ts +6 -0
  28. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -0
  29. package/dist/types/src/components/EditorToolbar/search.d.ts +3 -8
  30. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -1
  31. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +3 -17
  32. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  33. package/dist/types/src/stories/Automerge.stories.d.ts +25 -24
  34. package/dist/types/src/stories/Automerge.stories.d.ts.map +1 -1
  35. package/dist/types/src/stories/Comments.stories.d.ts +1 -1
  36. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  37. package/dist/types/src/stories/EditorToolbar.stories.d.ts +26 -26
  38. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  39. package/dist/types/src/stories/Experimental.stories.d.ts +1 -1
  40. package/dist/types/src/stories/Markdown.stories.d.ts +1 -1
  41. package/dist/types/src/stories/Outliner.stories.d.ts +2 -2
  42. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  43. package/dist/types/src/stories/Popover.stories.d.ts +2 -2
  44. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -1
  45. package/dist/types/src/stories/Preview.stories.d.ts +1 -1
  46. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  47. package/dist/types/src/stories/TextEditor.stories.d.ts +1 -1
  48. package/dist/types/src/stories/components/EditorStory.d.ts +3 -3
  49. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  50. package/dist/types/src/stories/components/util.d.ts +3 -3
  51. package/dist/types/src/stories/components/util.d.ts.map +1 -1
  52. package/dist/types/src/translations.d.ts +24 -24
  53. package/dist/types/src/translations.d.ts.map +1 -1
  54. package/dist/types/src/util/react.d.ts +1 -4
  55. package/dist/types/src/util/react.d.ts.map +1 -1
  56. package/dist/types/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +45 -45
  58. package/src/components/Editor/Editor.stories.tsx +6 -7
  59. package/src/components/Editor/Editor.tsx +21 -27
  60. package/src/components/EditorContent/EditorContent.tsx +1 -2
  61. package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +21 -28
  62. package/src/components/EditorMenuProvider/menu-presets.ts +1 -0
  63. package/src/components/EditorMenuProvider/useEditorMenu.ts +8 -1
  64. package/src/components/EditorPreviewProvider/EditorPreviewProvider.tsx +5 -7
  65. package/src/components/EditorToolbar/EditorToolbar.tsx +24 -61
  66. package/src/components/EditorToolbar/blocks.ts +53 -46
  67. package/src/components/EditorToolbar/formatting.ts +44 -46
  68. package/src/components/EditorToolbar/headings.ts +42 -49
  69. package/src/components/EditorToolbar/image.ts +16 -21
  70. package/src/components/EditorToolbar/index.ts +0 -1
  71. package/src/components/EditorToolbar/lists.ts +57 -0
  72. package/src/components/EditorToolbar/search.ts +16 -21
  73. package/src/components/EditorToolbar/view-mode.ts +34 -41
  74. package/src/stories/Automerge.stories.tsx +10 -12
  75. package/src/stories/Comments.stories.tsx +4 -5
  76. package/src/stories/EditorToolbar.stories.tsx +32 -17
  77. package/src/stories/Experimental.stories.tsx +6 -6
  78. package/src/stories/Markdown.stories.tsx +2 -2
  79. package/src/stories/Outliner.stories.tsx +4 -5
  80. package/src/stories/Popover.stories.tsx +9 -10
  81. package/src/stories/Preview.stories.tsx +60 -51
  82. package/src/stories/Tags.stories.tsx +5 -5
  83. package/src/stories/TextEditor.stories.tsx +2 -2
  84. package/src/stories/Theme.stories.tsx +2 -2
  85. package/src/stories/components/EditorStory.tsx +17 -12
  86. package/src/stories/components/util.tsx +49 -50
  87. package/src/translations.ts +29 -24
  88. package/src/util/react.tsx +2 -11
  89. package/dist/types/src/components/EditorToolbar/actions.d.ts +0 -24
  90. package/dist/types/src/components/EditorToolbar/actions.d.ts.map +0 -1
  91. package/dist/types/src/stories/CommandDialog.stories.d.ts +0 -14
  92. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +0 -1
  93. package/src/components/EditorToolbar/actions.ts +0 -87
  94. package/src/stories/CommandDialog.stories.tsx +0 -81
@@ -4,56 +4,63 @@
4
4
 
5
5
  import { type EditorView } from '@codemirror/view';
6
6
 
7
- import { type Node } from '@dxos/app-graph';
8
- import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+ import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
9
8
  import { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from '@dxos/ui-editor';
10
9
 
11
- import { createEditorAction, createEditorActionGroup } from './actions';
10
+ import { translationKey } from '../../translations';
12
11
  import { type EditorToolbarState } from './useEditorToolbar';
13
12
 
14
- const createBlockGroupAction = (value: string) =>
15
- createEditorActionGroup('block', {
16
- variant: 'toggleGroup',
17
- selectCardinality: 'single',
18
- value,
19
- } as ToolbarMenuActionGroupProperties);
20
-
21
- const createBlockActions = (value: string, getView: () => EditorView, blankLine?: boolean) =>
22
- Object.entries({
23
- blockquote: 'ph--quotes--regular',
24
- codeblock: 'ph--code-block--regular',
25
- table: 'ph--table--regular',
26
- }).map(([type, icon]) => {
27
- const checked = type === value;
28
- return createEditorAction(type, { checked, ...(type === 'table' && { disabled: !!blankLine }), icon }, () => {
29
- const view = getView();
30
- if (!view) {
31
- return;
32
- }
13
+ const blockTypes = {
14
+ blockquote: 'ph--quotes--regular',
15
+ codeblock: 'ph--code-block--regular',
16
+ table: 'ph--table--regular',
17
+ };
33
18
 
34
- switch (type) {
35
- case 'blockquote':
36
- checked ? removeBlockquote(view) : addBlockquote(view);
37
- break;
38
- case 'codeblock':
39
- checked ? removeCodeblock(view) : addCodeblock(view);
40
- break;
41
- case 'table':
42
- insertTable(view);
43
- break;
44
- }
45
- });
46
- });
19
+ /** Add block actions to the builder. */
20
+ export const addBlocks =
21
+ (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
22
+ (builder) => {
23
+ const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');
24
+ builder.group(
25
+ 'block',
26
+ {
27
+ label: ['block.label', { ns: translationKey }],
28
+ iconOnly: true,
29
+ variant: 'toggleGroup',
30
+ selectCardinality: 'single',
31
+ value,
32
+ } as ToolbarMenuActionGroupProperties,
33
+ (group) => {
34
+ for (const [type, icon] of Object.entries(blockTypes)) {
35
+ const checked = type === value;
36
+ group.action(
37
+ type,
38
+ {
39
+ label: [`block.${type}.label`, { ns: translationKey }],
40
+ checked,
41
+ ...(type === 'table' && { disabled: !!state.blankLine }),
42
+ icon,
43
+ },
44
+ () => {
45
+ const view = getView();
46
+ if (!view) {
47
+ return;
48
+ }
47
49
 
48
- export const createBlocks = (state: EditorToolbarState, getView: () => EditorView) => {
49
- const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');
50
- const blockGroupAction = createBlockGroupAction(value);
51
- const blockActions = createBlockActions(value, getView, state.blankLine);
52
- return {
53
- nodes: [blockGroupAction as Node.NodeArg<any>, ...blockActions],
54
- edges: [
55
- { source: 'root', target: 'block' },
56
- ...blockActions.map(({ id }) => ({ source: blockGroupAction.id, target: id })),
57
- ],
50
+ switch (type) {
51
+ case 'blockquote':
52
+ checked ? removeBlockquote(view) : addBlockquote(view);
53
+ break;
54
+ case 'codeblock':
55
+ checked ? removeCodeblock(view) : addCodeblock(view);
56
+ break;
57
+ case 'table':
58
+ insertTable(view);
59
+ break;
60
+ }
61
+ },
62
+ );
63
+ }
64
+ },
65
+ );
58
66
  };
59
- };
@@ -4,11 +4,10 @@
4
4
 
5
5
  import { type EditorView } from '@codemirror/view';
6
6
 
7
- import { type Node } from '@dxos/app-graph';
8
- import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+ import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
9
8
  import { type Formatting, Inline, addLink, removeLink, setStyle } from '@dxos/ui-editor';
10
9
 
11
- import { createEditorAction, createEditorActionGroup } from './actions';
10
+ import { translationKey } from '../../translations';
12
11
  import { type EditorToolbarState } from './useEditorToolbar';
13
12
 
14
13
  const formats = {
@@ -19,47 +18,46 @@ const formats = {
19
18
  link: 'ph--link--regular',
20
19
  };
21
20
 
22
- const createFormattingGroup = (formatting: Formatting) =>
23
- createEditorActionGroup('formatting', {
24
- variant: 'toggleGroup',
25
- selectCardinality: 'multiple',
26
- value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),
27
- } as ToolbarMenuActionGroupProperties);
28
-
29
- const createFormattingActions = (formatting: Formatting, getView: () => EditorView) =>
30
- Object.entries(formats).map(([type, icon]) => {
31
- const checked = !!formatting[type as keyof Formatting];
32
- return createEditorAction(type, { checked, icon }, () => {
33
- const view = getView();
34
- if (!view) {
35
- return;
36
- }
37
-
38
- if (type === 'link') {
39
- checked ? removeLink(view) : addLink()(view);
40
- return;
41
- }
42
-
43
- const inlineType =
44
- type === 'strong'
45
- ? Inline.Strong
46
- : type === 'emphasis'
47
- ? Inline.Emphasis
48
- : type === 'strikethrough'
49
- ? Inline.Strikethrough
50
- : Inline.Code;
51
- setStyle(inlineType, !checked)(view);
52
- });
53
- });
54
-
55
- export const createFormatting = (state: EditorToolbarState, getView: () => EditorView) => {
56
- const formattingGroupAction = createFormattingGroup(state);
57
- const formattingActions = createFormattingActions(state, getView);
58
- return {
59
- nodes: [formattingGroupAction as Node.NodeArg<any>, ...formattingActions],
60
- edges: [
61
- { source: 'root', target: 'formatting' },
62
- ...formattingActions.map(({ id }) => ({ source: formattingGroupAction.id, target: id })),
63
- ],
21
+ /** Add formatting actions to the builder. */
22
+ export const addFormatting =
23
+ (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
24
+ (builder) => {
25
+ const formatting: Formatting = state;
26
+ builder.group(
27
+ 'formatting',
28
+ {
29
+ label: ['formatting.label', { ns: translationKey }],
30
+ iconOnly: true,
31
+ variant: 'toggleGroup',
32
+ selectCardinality: 'multiple',
33
+ value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),
34
+ } as ToolbarMenuActionGroupProperties,
35
+ (group) => {
36
+ for (const [type, icon] of Object.entries(formats)) {
37
+ const checked = !!formatting[type as keyof Formatting];
38
+ group.action(type, { label: [`formatting.${type}.label`, { ns: translationKey }], checked, icon }, () => {
39
+ const view = getView();
40
+ if (!view) {
41
+ return;
42
+ }
43
+
44
+ if (type === 'link') {
45
+ checked ? removeLink(view) : addLink()(view);
46
+ return;
47
+ }
48
+
49
+ setStyle(
50
+ type === 'strong'
51
+ ? Inline.Strong
52
+ : type === 'emphasis'
53
+ ? Inline.Emphasis
54
+ : type === 'strikethrough'
55
+ ? Inline.Strikethrough
56
+ : Inline.Code,
57
+ !checked,
58
+ )(view);
59
+ });
60
+ }
61
+ },
62
+ );
64
63
  };
65
- };
@@ -4,49 +4,21 @@
4
4
 
5
5
  import { type EditorView } from '@codemirror/view';
6
6
 
7
- import { type Node } from '@dxos/app-graph';
8
- import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+ import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
9
8
  import { setHeading } from '@dxos/ui-editor';
10
9
 
11
10
  import { translationKey } from '../../translations';
12
-
13
- import { createEditorAction, createEditorActionGroup } from './actions';
14
11
  import { type EditorToolbarState } from './useEditorToolbar';
15
12
 
16
- const createHeadingGroupAction = (value: string) =>
17
- createEditorActionGroup(
18
- 'heading',
19
- {
20
- variant: 'dropdownMenu',
21
- applyActive: true,
22
- selectCardinality: 'single',
23
- // TODO(wittjosiah): Remove? Not sure this does anything.
24
- value,
25
- } as ToolbarMenuActionGroupProperties,
26
- 'ph--text-h--regular',
27
- );
28
-
29
- const createHeadingActions = (currentLevel: string, getView: () => EditorView) =>
30
- Object.entries({
31
- '0': 'ph--paragraph--regular',
32
- '1': 'ph--text-h-one--regular',
33
- '2': 'ph--text-h-two--regular',
34
- '3': 'ph--text-h-three--regular',
35
- '4': 'ph--text-h-four--regular',
36
- '5': 'ph--text-h-five--regular',
37
- '6': 'ph--text-h-six--regular',
38
- }).map(([levelStr, icon]) => {
39
- const level = parseInt(levelStr);
40
- return createEditorAction(
41
- `heading--${levelStr}`,
42
- {
43
- label: ['heading level label', { count: level, ns: translationKey }],
44
- icon,
45
- checked: levelStr === currentLevel,
46
- },
47
- () => setHeading(level)(getView()),
48
- );
49
- });
13
+ const headingIcons: Record<string, string> = {
14
+ 0: 'ph--paragraph--regular',
15
+ 1: 'ph--text-h-one--regular',
16
+ 2: 'ph--text-h-two--regular',
17
+ 3: 'ph--text-h-three--regular',
18
+ 4: 'ph--text-h-four--regular',
19
+ 5: 'ph--text-h-five--regular',
20
+ 6: 'ph--text-h-six--regular',
21
+ };
50
22
 
51
23
  const computeHeadingValue = (state: EditorToolbarState) => {
52
24
  const blockType = state ? state.blockType : 'paragraph';
@@ -54,15 +26,36 @@ const computeHeadingValue = (state: EditorToolbarState) => {
54
26
  return heading ? heading[1] : blockType === 'paragraph' || !blockType ? '0' : '';
55
27
  };
56
28
 
57
- export const createHeadings = (state: EditorToolbarState, getView: () => EditorView) => {
58
- const headingValue = computeHeadingValue(state);
59
- const headingGroupAction = createHeadingGroupAction(headingValue);
60
- const headingActions = createHeadingActions(headingValue, getView);
61
- return {
62
- nodes: [headingGroupAction as Node.NodeArg<any>, ...headingActions],
63
- edges: [
64
- { source: 'root', target: 'heading' },
65
- ...headingActions.map(({ id }) => ({ source: headingGroupAction.id, target: id })),
66
- ],
29
+ /** Add heading actions to the builder. */
30
+ export const addHeadings =
31
+ (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
32
+ (builder) => {
33
+ const headingValue = computeHeadingValue(state);
34
+ builder.group(
35
+ 'heading',
36
+ {
37
+ label: ['heading.label', { ns: translationKey }],
38
+ icon: 'ph--text-h--regular',
39
+ iconOnly: true,
40
+ variant: 'dropdownMenu',
41
+ applyActive: true,
42
+ selectCardinality: 'single',
43
+ // TODO(wittjosiah): Remove? Not sure this does anything.
44
+ value: headingValue,
45
+ } as ToolbarMenuActionGroupProperties,
46
+ (group) => {
47
+ for (const [levelStr, icon] of Object.entries(headingIcons)) {
48
+ const level = parseInt(levelStr);
49
+ group.action(
50
+ `heading--${levelStr}`,
51
+ {
52
+ label: ['heading-level.label', { count: level, ns: translationKey }],
53
+ icon,
54
+ checked: levelStr === headingValue,
55
+ },
56
+ () => setHeading(level)(getView()),
57
+ );
58
+ }
59
+ },
60
+ );
67
61
  };
68
- };
@@ -2,26 +2,21 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Node } from '@dxos/app-graph';
5
+ import { type ActionGroupBuilderFn } from '@dxos/react-ui-menu';
6
6
 
7
- import { createEditorAction } from './actions';
7
+ import { translationKey } from '../../translations';
8
8
 
9
- const createImageUploadAction = (onImageUpload: () => void) =>
10
- createEditorAction(
11
- 'image',
12
- {
13
- testId: 'editor.toolbar.image',
14
- icon: 'ph--image-square--regular',
15
- },
16
- onImageUpload,
17
- );
18
-
19
- export const createImageUpload = (
20
- onImageUpload: () => void,
21
- ): {
22
- nodes: Node.NodeArg<any>[];
23
- edges: Array<{ source: string; target: string }>;
24
- } => ({
25
- nodes: [createImageUploadAction(onImageUpload)],
26
- edges: [{ source: 'root', target: 'image' }],
27
- });
9
+ /** Add image upload action to the builder. */
10
+ export const addImageUpload =
11
+ (onImageUpload: () => void): ActionGroupBuilderFn =>
12
+ (builder) => {
13
+ builder.action(
14
+ 'image',
15
+ {
16
+ label: ['image.label', { ns: translationKey }],
17
+ testId: 'editor.toolbar.image',
18
+ icon: 'ph--image-square--regular',
19
+ },
20
+ onImageUpload,
21
+ );
22
+ };
@@ -4,5 +4,4 @@
4
4
 
5
5
  export * from './EditorToolbar';
6
6
 
7
- export { createEditorAction, createEditorActionGroup } from './actions';
8
7
  export { type EditorToolbarState, useEditorToolbar } from './useEditorToolbar';
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type EditorView } from '@codemirror/view';
6
+
7
+ import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
8
+ import { List, addList, removeList } from '@dxos/ui-editor';
9
+
10
+ import { translationKey } from '../../translations';
11
+ import { type EditorToolbarState } from './useEditorToolbar';
12
+
13
+ const listStyles = {
14
+ bullet: 'ph--list-bullets--regular',
15
+ ordered: 'ph--list-numbers--regular',
16
+ task: 'ph--list-checks--regular',
17
+ };
18
+
19
+ /** Add list actions to the builder. */
20
+ export const addLists =
21
+ (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
22
+ (builder) => {
23
+ const value = state.listStyle ?? '';
24
+ builder.group(
25
+ 'list',
26
+ {
27
+ label: ['list.label', { ns: translationKey }],
28
+ iconOnly: true,
29
+ variant: 'toggleGroup',
30
+ selectCardinality: 'single',
31
+ value,
32
+ } as ToolbarMenuActionGroupProperties,
33
+ (group) => {
34
+ for (const [listStyle, icon] of Object.entries(listStyles)) {
35
+ const checked = value === listStyle;
36
+ group.action(
37
+ `list-${listStyle}`,
38
+ { label: [`list.${listStyle}.label`, { ns: translationKey }], checked, icon },
39
+ () => {
40
+ const view = getView();
41
+ if (!view) {
42
+ return;
43
+ }
44
+
45
+ const listType =
46
+ listStyle === 'ordered' ? List.Ordered : listStyle === 'bullet' ? List.Bullet : List.Task;
47
+ if (checked) {
48
+ removeList(listType)(view);
49
+ } else {
50
+ addList(listType)(view);
51
+ }
52
+ },
53
+ );
54
+ }
55
+ },
56
+ );
57
+ };
@@ -5,26 +5,21 @@
5
5
  import { openSearchPanel } from '@codemirror/search';
6
6
  import { type EditorView } from '@codemirror/view';
7
7
 
8
- import { type Node } from '@dxos/app-graph';
8
+ import { type ActionGroupBuilderFn } from '@dxos/react-ui-menu';
9
9
 
10
- import { createEditorAction } from './actions';
10
+ import { translationKey } from '../../translations';
11
11
 
12
- const createSearchAction = (getView: () => EditorView) =>
13
- createEditorAction(
14
- 'search',
15
- {
16
- testId: 'editor.toolbar.search',
17
- icon: 'ph--magnifying-glass--regular',
18
- },
19
- () => openSearchPanel(getView()),
20
- );
21
-
22
- export const createSearch = (
23
- getView: () => EditorView,
24
- ): {
25
- nodes: Node.NodeArg<any>[];
26
- edges: Array<{ source: string; target: string }>;
27
- } => ({
28
- nodes: [createSearchAction(getView)],
29
- edges: [{ source: 'root', target: 'search' }],
30
- });
12
+ /** Add search action to the builder. */
13
+ export const addSearch =
14
+ (getView: () => EditorView): ActionGroupBuilderFn =>
15
+ (builder) => {
16
+ builder.action(
17
+ 'search',
18
+ {
19
+ label: ['search.label', { ns: translationKey }],
20
+ testId: 'editor.toolbar.search',
21
+ icon: 'ph--magnifying-glass--regular',
22
+ },
23
+ () => openSearchPanel(getView()),
24
+ );
25
+ };
@@ -2,54 +2,47 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Node } from '@dxos/app-graph';
6
- import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
5
+ import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
6
  import { type EditorViewMode } from '@dxos/ui-editor';
8
7
 
9
8
  import { translationKey } from '../../translations';
10
-
11
- import { createEditorAction, createEditorActionGroup } from './actions';
12
9
  import { type EditorToolbarState } from './useEditorToolbar';
13
10
 
14
- const createViewModeGroupAction = (value: string) =>
15
- createEditorActionGroup(
16
- 'viewMode',
17
- {
18
- variant: 'dropdownMenu',
19
- applyActive: true,
20
- selectCardinality: 'single',
21
- value,
22
- } as ToolbarMenuActionGroupProperties,
23
- 'ph--eye--regular',
24
- );
11
+ const viewModes = {
12
+ preview: 'ph--eye--regular',
13
+ source: 'ph--pencil-simple--regular',
14
+ readonly: 'ph--pencil-slash--regular',
15
+ };
25
16
 
26
- const createViewModeActions = (value: string, onViewModeChange: (mode: EditorViewMode) => void) =>
27
- Object.entries({
28
- preview: 'ph--eye--regular',
29
- source: 'ph--pencil-simple--regular',
30
- readonly: 'ph--pencil-slash--regular',
31
- }).map(([viewMode, icon]) => {
32
- const checked = viewMode === value;
33
- return createEditorAction(
34
- `view-mode--${viewMode}`,
17
+ /** Add view mode actions to the builder. */
18
+ export const addViewMode =
19
+ (state: EditorToolbarState, onViewModeChange: (mode: EditorViewMode) => void): ActionGroupBuilderFn =>
20
+ (builder) => {
21
+ const value = state.viewMode ?? 'source';
22
+ builder.group(
23
+ 'viewMode',
35
24
  {
36
- label: [`${viewMode} mode label`, { ns: translationKey }],
37
- checked,
38
- icon,
25
+ label: ['view-mode.label', { ns: translationKey }],
26
+ icon: 'ph--eye--regular',
27
+ iconOnly: true,
28
+ variant: 'dropdownMenu',
29
+ applyActive: true,
30
+ selectCardinality: 'single',
31
+ value,
32
+ } as ToolbarMenuActionGroupProperties,
33
+ (group) => {
34
+ for (const [viewMode, icon] of Object.entries(viewModes)) {
35
+ const checked = viewMode === value;
36
+ group.action(
37
+ `view-mode--${viewMode}`,
38
+ {
39
+ label: [`view-mode.${viewMode}.label`, { ns: translationKey }],
40
+ checked,
41
+ icon,
42
+ },
43
+ () => onViewModeChange(viewMode as EditorViewMode),
44
+ );
45
+ }
39
46
  },
40
- () => onViewModeChange(viewMode as EditorViewMode),
41
47
  );
42
- });
43
-
44
- export const createViewMode = (state: EditorToolbarState, onViewModeChange: (mode: EditorViewMode) => void) => {
45
- const value = state.viewMode ?? 'source';
46
- const viewModeGroupAction = createViewModeGroupAction(value);
47
- const viewModeActions = createViewModeActions(value, onViewModeChange);
48
- return {
49
- nodes: [viewModeGroupAction as Node.NodeArg<any>, ...viewModeActions],
50
- edges: [
51
- { source: 'root', target: 'viewMode' },
52
- ...viewModeActions.map(({ id }) => ({ source: viewModeGroupAction.id, target: id })),
53
- ],
54
48
  };
55
- };
@@ -8,16 +8,15 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
8
  import React, { useCallback, useEffect, useState } from 'react';
9
9
 
10
10
  import { Obj, Ref } from '@dxos/echo';
11
- import { TestSchema } from '@dxos/echo/testing';
12
11
  import { DocAccessor, createDocAccessor } from '@dxos/echo-db';
12
+ import { TestSchema } from '@dxos/echo/testing';
13
13
  import { log } from '@dxos/log';
14
14
  import { type Messenger } from '@dxos/protocols';
15
15
  import { Query, useQuery, useSpace } from '@dxos/react-client/echo';
16
16
  import { type Identity, useIdentity } from '@dxos/react-client/halo';
17
17
  import { useClientStory, withMultiClientProvider } from '@dxos/react-client/testing';
18
18
  import { Button, useThemeContext } from '@dxos/react-ui';
19
- import { withTheme } from '@dxos/react-ui/testing';
20
- import { render } from '@dxos/storybook-utils';
19
+ import { withLayout, withTheme, Loading } from '@dxos/react-ui/testing';
21
20
  import { createBasicExtensions, createDataExtensions, createThemeExtensions } from '@dxos/ui-editor';
22
21
 
23
22
  import { useTextEditor } from '../hooks';
@@ -44,14 +43,14 @@ const Editor = ({ source, messenger, identity, autoFocus }: EditorProps) => {
44
43
  initialValue: DocAccessor.getValue(source),
45
44
  extensions: [
46
45
  createBasicExtensions({ placeholder: 'Type here...', search: true }),
47
- createThemeExtensions({ themeMode, slots: { scroll: { className: 'p-2' } } }),
46
+ createThemeExtensions({ themeMode, slots: { scroller: { className: 'p-2' } } }),
48
47
  createDataExtensions({ id: 'test', text: source, messenger, identity }),
49
48
  ],
50
49
  }),
51
50
  [source, themeMode],
52
51
  );
53
52
 
54
- return <div ref={parentRef} className='flex is-full' />;
53
+ return <div ref={parentRef} className='flex w-full' />;
55
54
  };
56
55
 
57
56
  const DefaultStory = () => {
@@ -77,11 +76,11 @@ const DefaultStory = () => {
77
76
  }, []);
78
77
 
79
78
  if (!object1 || !object2) {
80
- return null;
79
+ return <Loading data={{ object1: !!object1, object2: !!object2 }} />;
81
80
  }
82
81
 
83
82
  return (
84
- <div className='bs-full is-full grid grid-cols-2 gap-4'>
83
+ <div className='h-full w-full grid grid-cols-2 gap-4'>
85
84
  <Editor source={object1} autoFocus />
86
85
  <Editor source={object2} />
87
86
  </div>
@@ -117,7 +116,7 @@ const EchoStory = () => {
117
116
  }, [objects, source]);
118
117
 
119
118
  return (
120
- <div className='bs-full is-full flex flex-col overflow-hidden'>
119
+ <div className='h-full w-full flex flex-col overflow-hidden'>
121
120
  <pre className='p-2 text-xs text-subdued'>
122
121
  {JSON.stringify({ index, identity: identity?.identityKey.truncate(), spaceId, objects }, null, 2)}
123
122
  </pre>
@@ -137,6 +136,7 @@ const EchoStory = () => {
137
136
  const meta = {
138
137
  title: 'ui/react-ui-editor/Automerge',
139
138
  component: Editor as any,
139
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
140
140
  parameters: {
141
141
  layout: 'fullscreen',
142
142
  translations,
@@ -149,14 +149,12 @@ type Story = StoryObj<typeof meta>;
149
149
 
150
150
  // TODO(burdon): ERROR: factories.ts:126 Error: Non-base58 character
151
151
  export const Default: Story = {
152
- decorators: [withTheme],
153
- render: render(DefaultStory),
152
+ render: DefaultStory,
154
153
  };
155
154
 
156
155
  // TODO(burdon): Failing (doesn't sync)
157
156
  export const WithEcho: Story = {
158
157
  decorators: [
159
- withTheme,
160
158
  withMultiClientProvider({
161
159
  numClients: 2,
162
160
  createIdentity: true,
@@ -171,5 +169,5 @@ export const WithEcho: Story = {
171
169
  },
172
170
  }),
173
171
  ],
174
- render: render(EchoStory),
172
+ render: EchoStory,
175
173
  };