@dxos/react-ui-markdown 0.8.4-main.c85a9c8dae → 0.8.4-main.cb12b3f963

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 (46) hide show
  1. package/dist/lib/browser/index.mjs +541 -10
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/{MarkdownViewer/MarkdownViewer.d.ts → MarkdownBlock/MarkdownBlock.d.ts} +3 -3
  5. package/dist/types/src/MarkdownBlock/MarkdownBlock.d.ts.map +1 -0
  6. package/dist/types/src/{MarkdownViewer/MarkdownViewer.stories.d.ts → MarkdownBlock/MarkdownBlock.stories.d.ts} +4 -4
  7. package/dist/types/src/MarkdownBlock/MarkdownBlock.stories.d.ts.map +1 -0
  8. package/dist/types/src/MarkdownBlock/index.d.ts +2 -0
  9. package/dist/types/src/MarkdownBlock/index.d.ts.map +1 -0
  10. package/dist/types/src/MarkdownStream/MarkdownStream.d.ts +101 -0
  11. package/dist/types/src/MarkdownStream/MarkdownStream.d.ts.map +1 -0
  12. package/dist/types/src/MarkdownStream/MarkdownStream.stories.d.ts +23 -0
  13. package/dist/types/src/MarkdownStream/MarkdownStream.stories.d.ts.map +1 -0
  14. package/dist/types/src/MarkdownStream/footer.d.ts +23 -0
  15. package/dist/types/src/MarkdownStream/footer.d.ts.map +1 -0
  16. package/dist/types/src/MarkdownStream/index.d.ts +4 -0
  17. package/dist/types/src/MarkdownStream/index.d.ts.map +1 -0
  18. package/dist/types/src/MarkdownStream/stream.d.ts +39 -0
  19. package/dist/types/src/MarkdownStream/stream.d.ts.map +1 -0
  20. package/dist/types/src/MarkdownStream/stream.test.d.ts +2 -0
  21. package/dist/types/src/MarkdownStream/stream.test.d.ts.map +1 -0
  22. package/dist/types/src/MarkdownStream/testing/index.d.ts +2 -0
  23. package/dist/types/src/MarkdownStream/testing/index.d.ts.map +1 -0
  24. package/dist/types/src/MarkdownStream/testing/testing.d.ts +16 -0
  25. package/dist/types/src/MarkdownStream/testing/testing.d.ts.map +1 -0
  26. package/dist/types/src/index.d.ts +2 -1
  27. package/dist/types/src/index.d.ts.map +1 -1
  28. package/dist/types/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +28 -24
  30. package/src/{MarkdownViewer/MarkdownViewer.stories.tsx → MarkdownBlock/MarkdownBlock.stories.tsx} +10 -10
  31. package/src/{MarkdownViewer/MarkdownViewer.tsx → MarkdownBlock/MarkdownBlock.tsx} +14 -9
  32. package/src/{MarkdownViewer → MarkdownBlock}/index.ts +1 -1
  33. package/src/MarkdownStream/MarkdownStream.stories.tsx +215 -0
  34. package/src/MarkdownStream/MarkdownStream.tsx +446 -0
  35. package/src/MarkdownStream/footer.ts +119 -0
  36. package/src/MarkdownStream/index.ts +8 -0
  37. package/src/MarkdownStream/stream.test.ts +126 -0
  38. package/src/MarkdownStream/stream.ts +229 -0
  39. package/src/MarkdownStream/testing/index.ts +5 -0
  40. package/src/MarkdownStream/testing/testing.ts +56 -0
  41. package/src/MarkdownStream/testing/text.md +67 -0
  42. package/src/index.ts +2 -1
  43. package/dist/types/src/MarkdownViewer/MarkdownViewer.d.ts.map +0 -1
  44. package/dist/types/src/MarkdownViewer/MarkdownViewer.stories.d.ts.map +0 -1
  45. package/dist/types/src/MarkdownViewer/index.d.ts +0 -2
  46. package/dist/types/src/MarkdownViewer/index.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-markdown",
3
- "version": "0.8.4-main.c85a9c8dae",
3
+ "version": "0.8.4-main.cb12b3f963",
4
4
  "description": "Markdown components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -19,9 +19,6 @@
19
19
  }
20
20
  },
21
21
  "types": "dist/types/src/index.d.ts",
22
- "typesVersions": {
23
- "*": {}
24
- },
25
22
  "files": [
26
23
  "dist",
27
24
  "src"
@@ -37,40 +34,47 @@
37
34
  "@lezer/xml": "^1.0.6",
38
35
  "@radix-ui/react-compose-refs": "1.1.1",
39
36
  "@radix-ui/react-context": "1.1.1",
40
- "effect": "3.19.16",
37
+ "effect": "3.20.0",
41
38
  "json5": "^2.2.3",
42
39
  "react-markdown": "^10.1.0",
43
40
  "react-resize-detector": "^11.0.1",
44
41
  "remark-gfm": "^4.0.1",
45
- "@dxos/invariant": "0.8.4-main.c85a9c8dae",
46
- "@dxos/async": "0.8.4-main.c85a9c8dae",
47
- "@dxos/echo": "0.8.4-main.c85a9c8dae",
48
- "@dxos/util": "0.8.4-main.c85a9c8dae",
49
- "@dxos/react-ui-editor": "0.8.4-main.c85a9c8dae",
50
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.c85a9c8dae",
51
- "@dxos/log": "0.8.4-main.c85a9c8dae"
42
+ "@dxos/async": "0.8.4-main.cb12b3f963",
43
+ "@dxos/echo": "0.8.4-main.cb12b3f963",
44
+ "@dxos/effect": "0.8.4-main.cb12b3f963",
45
+ "@dxos/invariant": "0.8.4-main.cb12b3f963",
46
+ "@dxos/log": "0.8.4-main.cb12b3f963",
47
+ "@dxos/react-ui": "0.8.4-main.cb12b3f963",
48
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.cb12b3f963",
49
+ "@dxos/ui": "0.8.4-main.cb12b3f963",
50
+ "@dxos/ui-editor": "0.8.4-main.cb12b3f963",
51
+ "@dxos/ui-theme": "0.8.4-main.cb12b3f963",
52
+ "@dxos/util": "0.8.4-main.cb12b3f963",
53
+ "@dxos/react-ui-editor": "0.8.4-main.cb12b3f963"
52
54
  },
53
55
  "devDependencies": {
56
+ "@effect/vitest": "0.29.0",
54
57
  "@types/react": "~19.2.7",
55
58
  "@types/react-dom": "~19.2.3",
56
59
  "react": "~19.2.3",
57
60
  "react-dom": "~19.2.3",
58
- "vite": "^7.1.11",
59
- "@dxos/lit-ui": "0.8.4-main.c85a9c8dae",
60
- "@dxos/echo": "0.8.4-main.c85a9c8dae",
61
- "@dxos/random": "0.8.4-main.c85a9c8dae",
62
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
63
- "@dxos/react-client": "0.8.4-main.c85a9c8dae",
64
- "@dxos/storybook-utils": "0.8.4-main.c85a9c8dae",
65
- "@dxos/schema": "0.8.4-main.c85a9c8dae",
66
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
61
+ "vite": "^8.0.10",
62
+ "@dxos/lit-ui": "0.8.4-main.cb12b3f963",
63
+ "@dxos/keys": "0.8.4-main.cb12b3f963",
64
+ "@dxos/echo": "0.8.4-main.cb12b3f963",
65
+ "@dxos/react-client": "0.8.4-main.cb12b3f963",
66
+ "@dxos/random": "0.8.4-main.cb12b3f963",
67
+ "@dxos/react-ui": "0.8.4-main.cb12b3f963",
68
+ "@dxos/schema": "0.8.4-main.cb12b3f963",
69
+ "@dxos/storybook-utils": "0.8.4-main.cb12b3f963",
70
+ "@dxos/ui-theme": "0.8.4-main.cb12b3f963"
67
71
  },
68
72
  "peerDependencies": {
69
- "effect": "3.19.16",
73
+ "effect": "3.20.0",
70
74
  "react": "~19.2.3",
71
75
  "react-dom": "~19.2.3",
72
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
73
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
76
+ "@dxos/react-ui": "0.8.4-main.cb12b3f963",
77
+ "@dxos/ui-theme": "0.8.4-main.cb12b3f963"
74
78
  },
75
79
  "publishConfig": {
76
80
  "access": "public"
@@ -4,30 +4,30 @@
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
 
7
- import { faker } from '@dxos/random';
7
+ import { random } from '@dxos/random';
8
8
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
9
9
  import { trim } from '@dxos/util';
10
10
 
11
- import { MarkdownViewer } from './MarkdownViewer';
11
+ import { MarkdownBlock } from './MarkdownBlock';
12
12
 
13
- faker.seed(0);
13
+ random.seed(0);
14
14
 
15
15
  const meta = {
16
- title: 'ui/react-ui-markdown/MarkdownViewer',
17
- component: MarkdownViewer,
16
+ title: 'ui/react-ui-markdown/MarkdownBlock',
17
+ component: MarkdownBlock,
18
18
  decorators: [withTheme(), withLayout({ layout: 'column' })],
19
- } satisfies Meta<typeof MarkdownViewer>;
19
+ } satisfies Meta<typeof MarkdownBlock>;
20
20
 
21
21
  export default meta;
22
22
 
23
- type Story = StoryObj<typeof MarkdownViewer>;
23
+ type Story = StoryObj<typeof MarkdownBlock>;
24
24
 
25
25
  const content = trim`
26
26
  # Hello World!
27
27
 
28
- > An example of the MarkdownViewer component.
28
+ > An example of the MarkdownBlock component.
29
29
 
30
- ${faker.lorem.paragraphs(1)}
30
+ ${random.lorem.paragraphs(1)}
31
31
 
32
32
  Here's a JSON block:
33
33
 
@@ -64,7 +64,7 @@ const content = trim`
64
64
 
65
65
  ## Examples
66
66
 
67
- ${faker.lorem.paragraphs(1)}
67
+ ${random.lorem.paragraphs(1)}
68
68
  `;
69
69
 
70
70
  export const Default: Story = {
@@ -10,7 +10,7 @@ import { type ThemedClassName } from '@dxos/react-ui';
10
10
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
11
11
  import { mx } from '@dxos/ui-theme';
12
12
 
13
- export type MarkdownViewerProps = ThemedClassName<
13
+ export type MarkdownBlockProps = ThemedClassName<
14
14
  PropsWithChildren<{
15
15
  content?: string;
16
16
  components?: ReactMarkdownOptions['components'];
@@ -23,7 +23,7 @@ export type MarkdownViewerProps = ThemedClassName<
23
23
  * markdown -> remark -> [mdast -> remark plugins] -> [hast -> rehype plugins] -> components -> react elements.
24
24
  * Consider using @dxos/react-ui-editor.
25
25
  */
26
- export const MarkdownViewer = ({ classNames, children, components, content = '' }: MarkdownViewerProps) => {
26
+ export const MarkdownBlock = ({ classNames, children, components, content = '' }: MarkdownBlockProps) => {
27
27
  return (
28
28
  <div className={mx(classNames)}>
29
29
  <ReactMarkdown remarkPlugins={[remarkGfm]} skipHtml components={{ ...defaultComponents, ...components }}>
@@ -36,16 +36,16 @@ export const MarkdownViewer = ({ classNames, children, components, content = ''
36
36
 
37
37
  const defaultComponents: ReactMarkdownOptions['components'] = {
38
38
  h1: ({ children }) => {
39
- return <h1 className='pt-1 pb-1 text-xl'>{children}</h1>;
39
+ return <h1 className='pt-1 pb-1 text-accent-text text-xl'>{children}</h1>;
40
40
  },
41
41
  h2: ({ children }) => {
42
- return <h2 className='pt-1 pb-1 text-lg'>{children}</h2>;
42
+ return <h2 className='pt-1 pb-1 text-accent-text text-lg'>{children}</h2>;
43
43
  },
44
44
  h3: ({ children }) => {
45
- return <h3 className='pt-1 pb-1 text-base'>{children}</h3>;
45
+ return <h3 className='pt-1 pb-1 text-accent-text text-base'>{children}</h3>;
46
46
  },
47
47
  blockquote: ({ children, ...props }) => (
48
- <blockquote className='ps-4 mt-2 mb-2 pt-2 pb-2 border-l-4 border-accent-text text-accent-text' {...props}>
48
+ <blockquote className='my-2 py-2 ps-4 border-l-4 border-accent-text text-accent-text' {...props}>
49
49
  {children}
50
50
  </blockquote>
51
51
  ),
@@ -79,13 +79,18 @@ const defaultComponents: ReactMarkdownOptions['components'] = {
79
79
  </li>
80
80
  ),
81
81
  pre: ({ children }) => children,
82
- // TODO(burdon): Copy/paste button.
83
- code: ({ children, className }) => {
82
+ code: ({ children, className, node }) => {
84
83
  const [, language] = /language-(\w+)/.exec(className || '') || [];
84
+ const inline = !className && node?.position?.start.line === node?.position?.end.line;
85
+ if (inline) {
86
+ return <code className='rounded-xs bg-group-surface px-1 py-0.5 text-sm text-info-text'>{children}</code>;
87
+ }
88
+
85
89
  return (
86
90
  <SyntaxHighlighter
87
91
  language={language}
88
- classNames='mt-2 mb-2 border border-separator rounded-xs text-sm bg-group-surface'
92
+ classNames='mt-2 mb-2 p-2 border border-separator rounded-xs text-sm bg-group-surface'
93
+ copyButton
89
94
  PreTag='pre'
90
95
  >
91
96
  {children}
@@ -2,4 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './MarkdownViewer';
5
+ export * from './MarkdownBlock';
@@ -0,0 +1,215 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { WidgetType } from '@codemirror/view';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import React, { useCallback, useEffect, useState } from 'react';
8
+
9
+ import '@dxos/lit-ui';
10
+ import { PublicKey } from '@dxos/keys';
11
+ import { random } from '@dxos/random';
12
+ import { Input, Toolbar } from '@dxos/react-ui';
13
+ import { Panel } from '@dxos/react-ui';
14
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
+ import { Domino } from '@dxos/ui';
16
+ import { type XmlWidgetRegistry, getXmlTextChild } from '@dxos/ui-editor';
17
+ import { mx } from '@dxos/ui-theme';
18
+ import { keyToFallback, trim } from '@dxos/util';
19
+
20
+ import { MarkdownStream, type MarkdownStreamController, type MarkdownStreamProps } from './MarkdownStream';
21
+ import { type TextStreamOptions, textStream } from './testing';
22
+ import TEXT from './testing/text.md?raw';
23
+
24
+ random.seed(123);
25
+
26
+ const userHue = keyToFallback(PublicKey.random()).hue;
27
+
28
+ const defaultStreamOptions: TextStreamOptions = {
29
+ wordsPerChunk: 5,
30
+ chunkDelay: 200,
31
+ variance: 0.5,
32
+ };
33
+
34
+ class DOMWidget extends WidgetType {
35
+ constructor(private text: string) {
36
+ super();
37
+ }
38
+
39
+ override eq(other: this) {
40
+ return this.text === other.text;
41
+ }
42
+
43
+ override toDOM() {
44
+ return Domino.of('span').classNames(mx('flex m-2 p-2 border border-separator rounded')).text(this.text).root;
45
+ }
46
+ }
47
+
48
+ const ReactWidget = ({ children }: { children: string }) => {
49
+ return <div className='m-2 p-2 border border-separator rounded'>{children}</div>;
50
+ };
51
+
52
+ const registry: XmlWidgetRegistry = {
53
+ 'dom-widget': {
54
+ block: true,
55
+ streaming: true,
56
+ factory: ({ children }) => {
57
+ const text = getXmlTextChild(children);
58
+ return text ? new DOMWidget(text) : null;
59
+ },
60
+ },
61
+
62
+ 'react-widget': {
63
+ block: true,
64
+ streaming: true,
65
+ Component: ReactWidget,
66
+ },
67
+ };
68
+
69
+ type DefaultStoryProps = MarkdownStreamProps & {
70
+ initialContent?: string;
71
+ streamOptions?: TextStreamOptions;
72
+ };
73
+
74
+ const DefaultStory = ({
75
+ initialContent,
76
+ content,
77
+ streamOptions = defaultStreamOptions,
78
+ debug: debugProp,
79
+ ...props
80
+ }: DefaultStoryProps) => {
81
+ const [controller, setController] = useState<MarkdownStreamController | null>(null);
82
+ const [streaming, setStreaming] = useState(false);
83
+ const [debug, setDebug] = useState(debugProp);
84
+
85
+ useEffect(() => {
86
+ if (initialContent) {
87
+ void controller?.append(initialContent);
88
+ }
89
+ }, [controller, initialContent]);
90
+
91
+ useEffect(() => {
92
+ if (!controller || !streaming || !content) {
93
+ return;
94
+ }
95
+
96
+ let cancelled = false;
97
+ void (async () => {
98
+ for await (const chunk of textStream(content + '\n', streamOptions)) {
99
+ if (cancelled) {
100
+ break;
101
+ }
102
+
103
+ await controller.append(chunk);
104
+ }
105
+
106
+ setStreaming(false);
107
+ })();
108
+
109
+ return () => {
110
+ cancelled = true;
111
+ };
112
+ }, [controller, content, streaming]);
113
+
114
+ const handleReset = useCallback(() => {
115
+ setStreaming(false);
116
+ void controller?.setContent('');
117
+ }, [controller]);
118
+
119
+ const handleAppend = useCallback(() => {
120
+ void controller?.append(
121
+ [
122
+ random.lorem.paragraph(),
123
+ `<dom-widget>${random.lorem.paragraphs(3)}</dom-widget>`,
124
+ random.lorem.paragraph(),
125
+ `<react-widget>${random.lorem.paragraphs(3)}</react-widget>`,
126
+ '',
127
+ ].join('\n\n'),
128
+ );
129
+ }, [controller]);
130
+
131
+ return (
132
+ <Panel.Root data-hue={userHue}>
133
+ <Panel.Toolbar asChild>
134
+ <Toolbar.Root>
135
+ <Toolbar.IconButton
136
+ disabled={streaming}
137
+ icon='ph--play--regular'
138
+ iconOnly
139
+ label='Start'
140
+ onClick={() => setStreaming(true)}
141
+ />
142
+ <Toolbar.IconButton
143
+ disabled={!streaming}
144
+ icon='ph--stop--regular'
145
+ iconOnly
146
+ label='Stop'
147
+ onClick={() => setStreaming(false)}
148
+ />
149
+ <Toolbar.IconButton icon='ph--trash--regular' iconOnly label='Reset' onClick={handleReset} />
150
+ <Toolbar.IconButton
151
+ disabled={streaming}
152
+ icon='ph--plus--regular'
153
+ iconOnly
154
+ label='Append'
155
+ onClick={handleAppend}
156
+ />
157
+ <Toolbar.Separator />
158
+ <Input.Root>
159
+ <Input.Label classNames='pr-1'>Debug</Input.Label>
160
+ <Input.Switch checked={debug} onCheckedChange={setDebug} />
161
+ </Input.Root>
162
+ </Toolbar.Root>
163
+ </Panel.Toolbar>
164
+ <Panel.Content>
165
+ <MarkdownStream {...props} debug={debug} ref={setController} />
166
+ </Panel.Content>
167
+ </Panel.Root>
168
+ );
169
+ };
170
+
171
+ const meta = {
172
+ title: 'ui/react-ui-markdown/MarkdownStream',
173
+ render: DefaultStory,
174
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
175
+ parameters: { layout: 'fullscreen' },
176
+ } satisfies Meta<typeof DefaultStory>;
177
+
178
+ export default meta;
179
+
180
+ type Story = StoryObj<typeof meta>;
181
+
182
+ export const Default: Story = {
183
+ args: {
184
+ initialContent: TEXT,
185
+ options: {
186
+ autoScroll: true,
187
+ },
188
+ },
189
+ };
190
+
191
+ export const Streaming: Story = {
192
+ args: {
193
+ registry,
194
+ content: TEXT,
195
+ options: {
196
+ autoScroll: true,
197
+ typewriter: true,
198
+ fader: true,
199
+ cursor: true,
200
+ },
201
+ },
202
+ };
203
+
204
+ export const Widgets: Story = {
205
+ args: {
206
+ registry,
207
+ initialContent: trim`
208
+ # DOM Widget
209
+ <dom-widget>${random.lorem.paragraph()}</dom-widget>
210
+
211
+ # React Widget
212
+ <react-widget>${random.lorem.paragraph()}</react-widget>
213
+ `,
214
+ },
215
+ };