@dxos/react-ui-editor 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7

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 (70) hide show
  1. package/dist/lib/browser/index.mjs +100 -79
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +100 -79
  5. package/dist/lib/node-esm/index.mjs.map +3 -3
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  8. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts.map +1 -1
  9. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
  10. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts.map +1 -1
  11. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  12. package/dist/types/src/components/EditorToolbar/actions.d.ts +1 -0
  13. package/dist/types/src/components/EditorToolbar/actions.d.ts.map +1 -1
  14. package/dist/types/src/components/EditorToolbar/blocks.d.ts +1 -0
  15. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  16. package/dist/types/src/components/EditorToolbar/formatting.d.ts +1 -0
  17. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  18. package/dist/types/src/components/EditorToolbar/headings.d.ts +1 -0
  19. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorToolbar/image.d.ts +1 -0
  21. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorToolbar/search.d.ts +1 -0
  23. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -1
  24. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +1 -0
  25. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  26. package/dist/types/src/stories/Automerge.stories.d.ts +1 -0
  27. package/dist/types/src/stories/Automerge.stories.d.ts.map +1 -1
  28. package/dist/types/src/stories/Comments.stories.d.ts +1 -1
  29. package/dist/types/src/stories/Experimental.stories.d.ts +1 -1
  30. package/dist/types/src/stories/Markdown.stories.d.ts +1 -1
  31. package/dist/types/src/stories/Preview.stories.d.ts +1 -1
  32. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  33. package/dist/types/src/stories/TextEditor.stories.d.ts +1 -1
  34. package/dist/types/src/stories/components/EditorStory.d.ts +2 -2
  35. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  36. package/dist/types/src/stories/components/util.d.ts +3 -3
  37. package/dist/types/src/stories/components/util.d.ts.map +1 -1
  38. package/dist/types/src/util/react.d.ts +1 -4
  39. package/dist/types/src/util/react.d.ts.map +1 -1
  40. package/dist/types/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +45 -45
  42. package/src/components/Editor/Editor.stories.tsx +1 -1
  43. package/src/components/Editor/Editor.tsx +12 -6
  44. package/src/components/EditorContent/EditorContent.tsx +1 -1
  45. package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +19 -24
  46. package/src/components/EditorMenuProvider/useEditorMenu.ts +6 -0
  47. package/src/components/EditorPreviewProvider/EditorPreviewProvider.tsx +5 -7
  48. package/src/components/EditorToolbar/EditorToolbar.tsx +17 -33
  49. package/src/components/EditorToolbar/actions.ts +2 -2
  50. package/src/components/EditorToolbar/blocks.ts +2 -2
  51. package/src/components/EditorToolbar/formatting.ts +2 -2
  52. package/src/components/EditorToolbar/headings.ts +9 -9
  53. package/src/components/EditorToolbar/image.ts +2 -2
  54. package/src/components/EditorToolbar/search.ts +2 -2
  55. package/src/components/EditorToolbar/view-mode.ts +2 -2
  56. package/src/stories/Automerge.stories.tsx +8 -10
  57. package/src/stories/CommandDialog.stories.tsx +5 -5
  58. package/src/stories/Comments.stories.tsx +4 -4
  59. package/src/stories/EditorToolbar.stories.tsx +4 -4
  60. package/src/stories/Experimental.stories.tsx +3 -3
  61. package/src/stories/Markdown.stories.tsx +2 -2
  62. package/src/stories/Outliner.stories.tsx +2 -2
  63. package/src/stories/Popover.stories.tsx +3 -3
  64. package/src/stories/Preview.stories.tsx +57 -47
  65. package/src/stories/Tags.stories.tsx +5 -5
  66. package/src/stories/TextEditor.stories.tsx +2 -2
  67. package/src/stories/Theme.stories.tsx +2 -2
  68. package/src/stories/components/EditorStory.tsx +3 -5
  69. package/src/stories/components/util.tsx +19 -22
  70. package/src/util/react.tsx +2 -11
@@ -16,8 +16,7 @@ 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';
@@ -51,7 +50,7 @@ const Editor = ({ source, messenger, identity, autoFocus }: EditorProps) => {
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
  };
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { type KeyboardEvent, useState } from 'react';
7
7
 
8
8
  import { type Button, IconButton, Input } from '@dxos/react-ui';
9
- import { withTheme } from '@dxos/react-ui/testing';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
10
  import { editorWidth, join } from '@dxos/ui-editor';
11
11
  import { mx } from '@dxos/ui-theme';
12
12
 
@@ -36,10 +36,10 @@ const CommandDialog = ({ onAction }: { onAction: (action?: any) => void }) => {
36
36
  };
37
37
 
38
38
  return (
39
- <div className='flex is-full justify-center'>
39
+ <div className='flex w-full justify-center'>
40
40
  <div
41
41
  className={mx(
42
- 'flex is-full p-2 gap-2 items-center bg-modalSurface border border-separator rounded-md',
42
+ 'flex w-full p-2 gap-2 items-center bg-modal-surface border border-separator rounded-md',
43
43
  editorWidth,
44
44
  )}
45
45
  >
@@ -57,7 +57,7 @@ const CommandDialog = ({ onAction }: { onAction: (action?: any) => void }) => {
57
57
  label='Cancel'
58
58
  iconOnly
59
59
  variant='ghost'
60
- classNames='pli-0'
60
+ classNames='px-0'
61
61
  onClick={() => onAction({ type: 'cancel' })}
62
62
  />
63
63
  </div>
@@ -68,7 +68,7 @@ const CommandDialog = ({ onAction }: { onAction: (action?: any) => void }) => {
68
68
  const meta = {
69
69
  title: 'ui/react-ui-editor/CommandDialog',
70
70
  render: () => <EditorStory text={join('# Command', '', '')} extensions={[]} />,
71
- decorators: [withTheme],
71
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
72
72
  parameters: {
73
73
  layout: 'fullscreen',
74
74
  },
@@ -9,7 +9,7 @@ import React, { type FC, useContext, useMemo } from 'react';
9
9
  import { keySymbols, parseShortcut } from '@dxos/keyboard';
10
10
  import { PublicKey } from '@dxos/keys';
11
11
  import { log } from '@dxos/log';
12
- import { withTheme } from '@dxos/react-ui/testing';
12
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
13
13
  import { withRegistry } from '@dxos/storybook-utils';
14
14
  import { type Comment, annotations, comments, createExternalCommentSync } from '@dxos/ui-editor';
15
15
 
@@ -20,7 +20,7 @@ import { EditorStory, content, longText } from './components';
20
20
  const meta = {
21
21
  title: 'ui/react-ui-editor/Comments',
22
22
  component: EditorStory,
23
- decorators: [withRegistry, withTheme],
23
+ decorators: [withRegistry, withTheme(), withLayout({ layout: 'fullscreen' })],
24
24
  parameters: {
25
25
  layout: 'fullscreen',
26
26
  },
@@ -77,14 +77,14 @@ export const Comments: Story = {
77
77
  };
78
78
 
79
79
  const Key: FC<{ char: string }> = ({ char }) => (
80
- <span className='flex justify-center items-center is-[24px] bs-[24px] rounded text-xs bg-neutral-200 text-black'>
80
+ <span className='flex justify-center items-center w-[24px] h-[24px] rounded-sm text-xs bg-neutral-200 text-black'>
81
81
  {char}
82
82
  </span>
83
83
  );
84
84
 
85
85
  const CommentTooltip: FC<{ shortcut: string }> = ({ shortcut }) => {
86
86
  return (
87
- <div className='flex items-center gap-2 pli-2 plb-2 bg-neutral-700 text-white text-xs rounded'>
87
+ <div className='flex items-center gap-2 px-2 py-2 bg-neutral-700 text-white text-xs rounded-sm'>
88
88
  <div>Create comment</div>
89
89
  <div className='flex gap-1'>
90
90
  {keySymbols(parseShortcut(shortcut)).map((char) => (
@@ -8,7 +8,7 @@ import React, { useCallback, useContext, useState } from 'react';
8
8
 
9
9
  import { invariant } from '@dxos/invariant';
10
10
  import { useThemeContext } from '@dxos/react-ui';
11
- import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { withRegistry } from '@dxos/storybook-utils';
13
13
  import {
14
14
  type EditorInputMode,
@@ -22,7 +22,7 @@ import {
22
22
  formattingKeymap,
23
23
  formattingListener,
24
24
  } from '@dxos/ui-editor';
25
- import { attentionSurface, mx } from '@dxos/ui-theme';
25
+ import { mx } from '@dxos/ui-theme';
26
26
 
27
27
  import { EditorToolbar, type EditorToolbarState, useEditorToolbar } from '../components';
28
28
  import { type UseTextEditorProps, useTextEditor } from '../hooks';
@@ -81,7 +81,7 @@ const DefaultStory = ({ autoFocus, initialValue, placeholder }: StoryProps) => {
81
81
  return (
82
82
  <div role='none' className={mx('fixed inset-0 flex flex-col')}>
83
83
  {toolbarState && <EditorToolbar state={toolbarState} getView={getView} onViewModeChange={handleViewModeChange} />}
84
- <div role='none' className={mx('grow overflow-hidden', attentionSurface)}>
84
+ <div role='none' className='grow overflow-hidden'>
85
85
  <div className={mx(editorWidth)} ref={parentRef} />
86
86
  </div>
87
87
  </div>
@@ -91,7 +91,7 @@ const DefaultStory = ({ autoFocus, initialValue, placeholder }: StoryProps) => {
91
91
  const meta = {
92
92
  title: 'ui/react-ui-editor/EditorToolbar',
93
93
  render: DefaultStory,
94
- decorators: [withRegistry, withTheme],
94
+ decorators: [withRegistry, withTheme(), withLayout({ layout: 'fullscreen' })],
95
95
  parameters: {
96
96
  layout: 'fullscreen',
97
97
  translations,
@@ -8,7 +8,7 @@ import React from 'react';
8
8
 
9
9
  import { log } from '@dxos/log';
10
10
  import { faker } from '@dxos/random';
11
- import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { blast, defaultOptions, dropFile, join, typewriter } from '@dxos/ui-editor';
13
13
 
14
14
  import { EditorStory, content } from './components';
@@ -16,7 +16,7 @@ import { EditorStory, content } from './components';
16
16
  const meta = {
17
17
  title: 'ui/react-ui-editor/Experimental',
18
18
  component: EditorStory,
19
- decorators: [withTheme],
19
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
20
20
  parameters: {
21
21
  layout: 'fullscreen',
22
22
  },
@@ -30,7 +30,7 @@ type Story = StoryObj<typeof meta>;
30
30
  // Typewriter
31
31
  //
32
32
 
33
- const typewriterItems = localStorage.getItem('dxos.org/testing/typewriter')?.split(',');
33
+ const typewriterItems = localStorage.getItem('org.dxos.testing.typewriter')?.split(',');
34
34
 
35
35
  export const Typewriter: Story = {
36
36
  render: () => (
@@ -6,7 +6,7 @@ import { markdown } from '@codemirror/lang-markdown';
6
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
7
7
  import React from 'react';
8
8
 
9
- import { withTheme } from '@dxos/react-ui/testing';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
10
  import { decorateMarkdown, image, join, linkTooltip, table } from '@dxos/ui-editor';
11
11
 
12
12
  import { EditorStory, content, defaultExtensions, headings, renderLinkTooltip, text } from './components';
@@ -14,7 +14,7 @@ import { EditorStory, content, defaultExtensions, headings, renderLinkTooltip, t
14
14
  const meta = {
15
15
  title: 'ui/react-ui-editor/Markdown',
16
16
  component: EditorStory,
17
- decorators: [withTheme],
17
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
18
18
  parameters: {
19
19
  layout: 'fullscreen',
20
20
  },
@@ -5,7 +5,7 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useMemo, useState } from 'react';
7
7
 
8
- import { withTheme } from '@dxos/react-ui/testing';
8
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
9
9
  import { withAttention } from '@dxos/react-ui-attention/testing';
10
10
  import { deleteItem, hashtag, join, listItemToString, outliner, treeFacet } from '@dxos/ui-editor';
11
11
 
@@ -68,7 +68,7 @@ const DefaultStory = ({ text }: StoryProps) => {
68
68
  const meta = {
69
69
  title: 'ui/react-ui-editor/Outliner',
70
70
  render: DefaultStory,
71
- decorators: [withTheme, withAttention],
71
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withAttention()],
72
72
  parameters: {
73
73
  layout: 'fullscreen',
74
74
  },
@@ -8,7 +8,7 @@ import React, { useCallback, useState } from 'react';
8
8
  import { Obj, Query } from '@dxos/echo';
9
9
  import { faker } from '@dxos/random';
10
10
  import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
11
- import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { TestSchema, type ValueGenerator, createObjectFactory } from '@dxos/schema/testing';
13
13
  import { Domino, mx } from '@dxos/ui';
14
14
  import { insertAtCursor, insertAtLineStart, join } from '@dxos/ui-editor';
@@ -38,7 +38,7 @@ const customCompletions: EditorMenuGroup = createMenuGroup({
38
38
  const placeholder = (trigger: string[]) => {
39
39
  const pressEl = Domino.of('span').text('Press');
40
40
  const triggerEls = trigger.map((trigger) =>
41
- Domino.of('span').classNames(mx('border border-separator rounded-sm mx-1 pli-1 pbs-[2px] pbe-[3px]')).text(trigger),
41
+ Domino.of('span').classNames(mx('border border-separator rounded-xs mx-1 px-1 py-[2px] pb-[3px]')).text(trigger),
42
42
  );
43
43
  const forCommandsEl = Domino.of('span').text('for commands');
44
44
  return Domino.of('div').children(pressEl, ...triggerEls, forCommandsEl).root;
@@ -103,7 +103,7 @@ const LinkStory = (args: StoryProps) => {
103
103
  const meta = {
104
104
  title: 'ui/react-ui-editor/Popover',
105
105
  render: DefaultStory,
106
- decorators: [withTheme],
106
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
107
107
  parameters: {
108
108
  layout: 'fullscreen',
109
109
  },
@@ -10,9 +10,9 @@ import { createPortal } from 'react-dom';
10
10
 
11
11
  import { invariant } from '@dxos/invariant';
12
12
  import { faker } from '@dxos/random';
13
- import { Popover } from '@dxos/react-ui';
14
- import { withTheme } from '@dxos/react-ui/testing';
15
- import { Card } from '@dxos/react-ui-mosaic';
13
+ import { Card, Popover, Toolbar } from '@dxos/react-ui';
14
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
+ import { Menu, createMenuAction } from '@dxos/react-ui-menu';
16
16
  import {
17
17
  type PreviewBlock,
18
18
  type PreviewLinkRef,
@@ -22,15 +22,15 @@ import {
22
22
  preview,
23
23
  } from '@dxos/ui-editor';
24
24
  import { hoverableControls } from '@dxos/ui-theme';
25
- import { isTruthy, trim } from '@dxos/util';
25
+ import { trim } from '@dxos/util';
26
26
 
27
27
  import { type EditorController, EditorPreviewProvider, useEditorPreview } from '../components';
28
28
 
29
29
  import { EditorStory } from './components';
30
30
 
31
- const handlePreviewLookup = async ({ label, ref }: PreviewLinkRef): Promise<PreviewLinkTarget> => {
31
+ const handlePreviewLookup = async ({ dxn, label }: PreviewLinkRef): Promise<PreviewLinkTarget> => {
32
32
  // Random text.
33
- faker.seed(ref.split('').reduce((acc: number, char: string) => acc + char.charCodeAt(0), 1));
33
+ faker.seed(dxn.split('').reduce((acc: number, char: string) => acc + char.charCodeAt(0), 1));
34
34
  const text = Array.from({ length: 2 }, () => faker.lorem.paragraphs()).join('\n\n');
35
35
  return {
36
36
  label,
@@ -51,24 +51,25 @@ const useRefTarget = (link: PreviewLinkRef): PreviewLinkTarget | undefined => {
51
51
 
52
52
  const PreviewCard = () => {
53
53
  const { target } = useEditorPreview('PreviewCard');
54
+ if (!target) {
55
+ return null;
56
+ }
54
57
 
55
58
  return (
56
59
  <Popover.Portal>
57
60
  <Popover.Content onOpenAutoFocus={(event) => event.preventDefault()}>
58
- <Popover.Viewport classNames='popover-card-width'>
61
+ <Popover.Viewport classNames='dx-card-popover-width'>
59
62
  <Card.Root border={false}>
60
63
  <Card.Toolbar>
61
64
  <Card.Icon toolbar icon='ph--file-text--regular' />
62
- <Card.Title>{target?.label}</Card.Title>
65
+ <Card.Title>{target.label}</Card.Title>
63
66
  <Popover.Close asChild>
64
- <Card.Close />
67
+ <Card.CloseIconButton />
65
68
  </Popover.Close>
66
69
  </Card.Toolbar>
67
- {target && (
68
- <Card.Row>
69
- <Card.Text variant='description'>{target.text}</Card.Text>
70
- </Card.Row>
71
- )}
70
+ <Card.Row>
71
+ <Card.Text variant='description'>{target.text}</Card.Text>
72
+ </Card.Row>
72
73
  </Card.Root>
73
74
  </Popover.Viewport>
74
75
  <Popover.Arrow />
@@ -101,7 +102,7 @@ const PreviewBlockComponent = ({ link, el, view }: { link: PreviewLinkRef; el: H
101
102
  }
102
103
 
103
104
  const link = getLinkRef(view.state, node);
104
- if (link?.ref !== action.link.ref) {
105
+ if (link?.dxn !== action.link.dxn) {
105
106
  return;
106
107
  }
107
108
 
@@ -143,36 +144,45 @@ const PreviewBlockComponent = ({ link, el, view }: { link: PreviewLinkRef; el: H
143
144
  }
144
145
  }, [handleAction, link, target]);
145
146
 
147
+ const menuItems = useMemo(
148
+ () => [
149
+ createMenuAction('delete', handleDelete, {
150
+ label: link.suggest ? 'Discard' : 'Delete',
151
+ icon: 'ph--x--regular',
152
+ }),
153
+ ...(target
154
+ ? [
155
+ createMenuAction('apply', handleInsert, {
156
+ label: 'Apply',
157
+ icon: 'ph--check--regular',
158
+ }),
159
+ ]
160
+ : []),
161
+ ],
162
+ [handleDelete, handleInsert, link.suggest, target],
163
+ );
164
+
146
165
  return createPortal(
147
- <Card.Root classNames={hoverableControls}>
148
- {!view?.state.readOnly && (
149
- <Card.Toolbar>
150
- <Card.Icon toolbar icon='ph--bookmark--regular' />
151
- <Card.Title>{link.label}</Card.Title>
152
- <Card.Menu
153
- items={[
154
- {
155
- id: 'delete',
156
- label: link.suggest ? 'Discard' : 'Delete',
157
- icon: 'ph--x--regular',
158
- onClick: handleDelete,
159
- },
160
- target && {
161
- id: 'apply',
162
- label: 'Apply',
163
- icon: 'ph--check--regular',
164
- onClick: handleInsert,
165
- },
166
- ].filter(isTruthy)}
167
- />
168
- </Card.Toolbar>
169
- )}
170
- {target && (
171
- <Card.Row>
172
- <Card.Text className='text-description'>{target.text}</Card.Text>
173
- </Card.Row>
174
- )}
175
- </Card.Root>,
166
+ <Menu.Root>
167
+ <Card.Root classNames={hoverableControls}>
168
+ {!view?.state.readOnly && (
169
+ <Card.Toolbar>
170
+ <Card.Icon toolbar icon='ph--bookmark--regular' />
171
+ <Card.Title>{link.label}</Card.Title>
172
+ {/* TODO(wittjosiah): Reconcile with Card.Menu. */}
173
+ <Menu.Trigger asChild disabled={!menuItems?.length}>
174
+ <Toolbar.IconButton iconOnly variant='ghost' icon='ph--dots-three-vertical--regular' label='Menu' />
175
+ </Menu.Trigger>
176
+ <Menu.Content items={menuItems} />
177
+ </Card.Toolbar>
178
+ )}
179
+ {target && (
180
+ <Card.Row>
181
+ <Card.Text className='text-description'>{target.text}</Card.Text>
182
+ </Card.Row>
183
+ )}
184
+ </Card.Root>
185
+ </Menu.Root>,
176
186
  el,
177
187
  );
178
188
  };
@@ -180,7 +190,7 @@ const PreviewBlockComponent = ({ link, el, view }: { link: PreviewLinkRef; el: H
180
190
  const meta = {
181
191
  title: 'ui/react-ui-editor/Preview',
182
192
  component: EditorStory,
183
- decorators: [withTheme],
193
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
184
194
  parameters: {
185
195
  layout: 'fullscreen',
186
196
  },
@@ -217,7 +227,7 @@ export const Default: Story = {
217
227
  setPreviewBlocks((prev) => [...prev, block]);
218
228
  },
219
229
  removeBlockContainer: (block) => {
220
- setPreviewBlocks((prev) => prev.filter(({ link: prevLink }) => prevLink.ref !== block.link.ref));
230
+ setPreviewBlocks((prev) => prev.filter(({ link: prevLink }) => prevLink.dxn !== block.link.dxn));
221
231
  },
222
232
  }),
223
233
  ];
@@ -230,7 +240,7 @@ export const Default: Story = {
230
240
  <PreviewCard />
231
241
  {controller?.view &&
232
242
  previewBlocks.map(({ link, el }) => (
233
- <PreviewBlockComponent key={link.ref} link={link} el={el} view={controller.view!} />
243
+ <PreviewBlockComponent key={link.dxn} link={link} el={el} view={controller.view!} />
234
244
  ))}
235
245
  </EditorPreviewProvider>
236
246
  );
@@ -7,7 +7,7 @@ import React, { useEffect, useState } from 'react';
7
7
  import { createPortal } from 'react-dom';
8
8
 
9
9
  import { useThemeContext } from '@dxos/react-ui';
10
- import { withTheme } from '@dxos/react-ui/testing';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
11
  import {
12
12
  type XmlWidgetRegistry,
13
13
  type XmlWidgetState,
@@ -25,7 +25,7 @@ const registry = {
25
25
  /**
26
26
  * Custom tag: <test/>
27
27
  */
28
- ['test' as const]: {
28
+ test: {
29
29
  block: true,
30
30
  Component: ({ start = '0' }) => {
31
31
  const [count, setCount] = useState<number>(safeParseInt(start, 0));
@@ -43,7 +43,7 @@ const registry = {
43
43
  return () => clearInterval(interval);
44
44
  }, []);
45
45
 
46
- return <div className='p-2 border border-separator rounded'>Test {count}</div>;
46
+ return <div className='p-2 border border-separator rounded-sm'>Test {count}</div>;
47
47
  },
48
48
  },
49
49
  } satisfies XmlWidgetRegistry;
@@ -64,7 +64,7 @@ const DefaultStory = ({ text }: { text?: string }) => {
64
64
 
65
65
  return (
66
66
  <>
67
- <div ref={parentRef} className='is-full p-4' />
67
+ <div ref={parentRef} className='w-full p-4' />
68
68
  {widgets.map(({ id, root, Component, props }) => (
69
69
  <div key={id}>{createPortal(<Component {...props} />, root)}</div>
70
70
  ))}
@@ -87,7 +87,7 @@ const text = trim`
87
87
  const meta = {
88
88
  title: 'ui/react-ui-editor/Tags',
89
89
  render: DefaultStory,
90
- decorators: [withTheme],
90
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
91
91
  parameters: {
92
92
  layout: 'fullscreen',
93
93
  },
@@ -8,7 +8,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
8
  import React from 'react';
9
9
 
10
10
  import { log } from '@dxos/log';
11
- import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import {
13
13
  InputModeExtensions,
14
14
  decorateMarkdown,
@@ -37,7 +37,7 @@ import {
37
37
  const meta = {
38
38
  title: 'ui/react-ui-editor/TextEditor',
39
39
  component: EditorStory,
40
- decorators: [withTheme],
40
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
41
41
  parameters: {
42
42
  layout: 'fullscreen',
43
43
  controls: { disable: true },
@@ -32,7 +32,7 @@ const DefaultStory = () => {
32
32
  );
33
33
 
34
34
  return (
35
- <div className='is-full grid grid-cols-2 gap-2'>
35
+ <div className='w-full grid grid-cols-2 gap-2'>
36
36
  <Editor.Root>
37
37
  <Editor.Content classNames='p-2' extensions={ext1} initialValue={createText(false)} />
38
38
  </Editor.Root>
@@ -46,7 +46,7 @@ const DefaultStory = () => {
46
46
  const meta: Meta<typeof DefaultStory> = {
47
47
  title: 'ui/react-ui-editor/Theme',
48
48
  component: DefaultStory,
49
- decorators: [withTheme, withLayout()],
49
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
50
50
  parameters: {
51
51
  layout: 'fullscreen',
52
52
  },
@@ -20,7 +20,6 @@ import {
20
20
  createMarkdownExtensions,
21
21
  createThemeExtensions,
22
22
  debugTree,
23
- decorateMarkdown,
24
23
  editorSlots,
25
24
  } from '@dxos/ui-editor';
26
25
  import { mx } from '@dxos/ui-theme';
@@ -39,7 +38,7 @@ export type StoryProps = Pick<UseTextEditorProps, 'id' | 'scrollTo' | 'selection
39
38
  debug?: DebugMode;
40
39
  debugCustom?: (view: EditorView) => ReactNode;
41
40
  text?: string;
42
- object?: Obj.Obj<TestSchema.Expando>;
41
+ object?: Obj.OfShape<TestSchema.Expando>;
43
42
  readOnly?: boolean;
44
43
  placeholder?: string;
45
44
  lineNumbers?: boolean;
@@ -63,12 +62,12 @@ export const EditorStory = forwardRef<EditorController, StoryProps>(
63
62
 
64
63
  const view = controllerRef.current?.view;
65
64
  return (
66
- <div className={mx('is-full bs-full grid overflow-hidden', debug && 'grid-cols-2 lg:grid-cols-[1fr_600px]')}>
65
+ <div className={mx('w-full h-full grid overflow-hidden', debug && 'grid-cols-2 lg:grid-cols-[1fr_600px]')}>
67
66
  <EditorComponent ref={mergedRef} object={object} text={text} extensions={extensions} {...props} />
68
67
 
69
68
  {debug && (
70
69
  <div
71
- className='grid bs-full auto-rows-fr border-l border-separator divide-y divide-separator overflow-hidden'
70
+ className='grid h-full auto-rows-fr border-l border-separator divide-y divide-separator overflow-hidden'
72
71
  {...attentionAttrs}
73
72
  >
74
73
  {view && debugCustom?.(view)}
@@ -119,7 +118,6 @@ const EditorComponent = forwardRef<EditorController, StoryProps>(
119
118
  createBasicExtensions({ readOnly, placeholder, lineNumbers, scrollPastEnd: true, search: true }),
120
119
  createThemeExtensions({ monospace, themeMode, syntaxHighlighting: true, slots }),
121
120
  createMarkdownExtensions(),
122
- decorateMarkdown(),
123
121
  extensions || [],
124
122
  ],
125
123
  }),
@@ -4,12 +4,12 @@
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
6
  import { type Extension } from '@codemirror/state';
7
- import React, { type FC } from 'react';
8
7
 
9
8
  import { faker } from '@dxos/random';
10
- import { Icon } from '@dxos/react-ui';
9
+ import { Domino } from '@dxos/ui';
11
10
  import {
12
11
  type EditorSelectionState,
12
+ type RenderCallback,
13
13
  decorateMarkdown,
14
14
  folding,
15
15
  formattingKeymap,
@@ -17,9 +17,9 @@ import {
17
17
  linkTooltip,
18
18
  table,
19
19
  } from '@dxos/ui-editor';
20
- import { mx } from '@dxos/ui-theme';
20
+ import { safeUrl } from '@dxos/util';
21
21
 
22
- import { createRenderer, str } from '../../util';
22
+ import { str } from '../../util';
23
23
 
24
24
  export const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
25
25
 
@@ -204,30 +204,27 @@ export const links: Completion[] = [
204
204
  export const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
205
205
 
206
206
  const hover =
207
- 'rounded-sm text-baseText text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
208
-
209
- const LinkTooltip: FC<{ url: string }> = ({ url }) => {
210
- const web = new URL(url);
211
- return (
212
- <a href={url} target='_blank' rel='noreferrer' className={mx(hover, 'flex items-center gap-2')}>
213
- {web.origin}
214
- <Icon icon='ph--arrow-square-out--regular' size={4} />
215
- </a>
207
+ 'rounded-xs text-base-surface-text text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
208
+
209
+ export const renderLinkTooltip: RenderCallback<{ url: string }> = (el, { url }) => {
210
+ el.appendChild(
211
+ Domino.of('a')
212
+ .attributes({ href: url, target: '_blank', rel: 'noreferrer', 'aria-label': 'Open link' })
213
+ .classNames(hover, 'flex items-center gap-2')
214
+ .text(safeUrl(url)?.origin ?? url)
215
+ .children(Domino.svg('ph--arrow-square-out--regular')).root,
216
216
  );
217
217
  };
218
218
 
219
- export const renderLinkTooltip = createRenderer(LinkTooltip);
220
-
221
- const LinkButton: FC<{ url: string }> = ({ url }) => {
222
- return (
223
- <a href={url} target='_blank' rel='noreferrer' className={mx(hover)}>
224
- <Icon icon='ph--arrow-square-out--regular' size={4} classNames='inline-block mis-1 mb-[3px]' />
225
- </a>
219
+ export const renderLinkButton: RenderCallback<{ url: string }> = (el, { url }) => {
220
+ el.appendChild(
221
+ Domino.of('a')
222
+ .attributes({ href: url, target: '_blank', rel: 'noreferrer', 'aria-label': 'Open link' })
223
+ .classNames(hover, 'inline-block ms-1 align-[-0.125em]') // Center icon.
224
+ .children(Domino.svg('ph--arrow-square-out--regular')).root,
226
225
  );
227
226
  };
228
227
 
229
- export const renderLinkButton = createRenderer(LinkButton);
230
-
231
228
  // Shared extensions.
232
229
  export const defaultExtensions: Extension[] = [
233
230
  decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100 }),