@dxos/react-ui-thread 0.8.4-main.1da679c → 0.8.4-main.21d9917

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.
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-thread",
3
- "version": "0.8.4-main.1da679c",
3
+ "version": "0.8.4-main.21d9917",
4
4
  "description": "A set of components to use with threads, including comments and messages.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
@@ -24,33 +28,32 @@
24
28
  "src"
25
29
  ],
26
30
  "dependencies": {
27
- "@preact-signals/safe-react": "^0.9.0",
28
31
  "@radix-ui/react-primitive": "2.0.2",
29
32
  "date-fns": "^3.3.1",
30
- "@dxos/react-ui-editor": "0.8.4-main.1da679c",
31
- "@dxos/util": "0.8.4-main.1da679c"
33
+ "@dxos/react-ui-editor": "0.8.4-main.21d9917",
34
+ "@dxos/ui-editor": "0.8.4-main.21d9917",
35
+ "@dxos/util": "0.8.4-main.21d9917"
32
36
  },
33
37
  "devDependencies": {
34
- "@types/react": "~18.2.0",
35
- "@types/react-dom": "~18.2.0",
36
- "react": "~18.2.0",
37
- "react-dom": "~18.2.0",
38
- "vite": "7.1.1",
39
- "@dxos/echo-db": "0.8.4-main.1da679c",
40
- "@dxos/echo-schema": "0.8.4-main.1da679c",
41
- "@dxos/keys": "0.8.4-main.1da679c",
42
- "@dxos/live-object": "0.8.4-main.1da679c",
43
- "@dxos/random": "0.8.4-main.1da679c",
44
- "@dxos/react-ui-theme": "0.8.4-main.1da679c",
45
- "@dxos/storybook-utils": "0.8.4-main.1da679c",
46
- "@dxos/react-ui": "0.8.4-main.1da679c",
47
- "@dxos/log": "0.8.4-main.1da679c"
38
+ "@types/react": "~19.2.7",
39
+ "@types/react-dom": "~19.2.3",
40
+ "react": "~19.2.3",
41
+ "react-dom": "~19.2.3",
42
+ "vite": "7.1.9",
43
+ "@dxos/echo-db": "0.8.4-main.21d9917",
44
+ "@dxos/echo": "0.8.4-main.21d9917",
45
+ "@dxos/log": "0.8.4-main.21d9917",
46
+ "@dxos/react-ui": "0.8.4-main.21d9917",
47
+ "@dxos/random": "0.8.4-main.21d9917",
48
+ "@dxos/ui-theme": "0.8.4-main.21d9917",
49
+ "@dxos/storybook-utils": "0.8.4-main.21d9917",
50
+ "@dxos/keys": "0.8.4-main.21d9917"
48
51
  },
49
52
  "peerDependencies": {
50
- "react": "~18.2.0",
51
- "react-dom": "~18.2.0",
52
- "@dxos/react-ui": "0.8.4-main.1da679c",
53
- "@dxos/react-ui-theme": "0.8.4-main.1da679c"
53
+ "react": "~19.2.3",
54
+ "react-dom": "~19.2.3",
55
+ "@dxos/react-ui": "0.8.4-main.21d9917",
56
+ "@dxos/ui-theme": "0.8.4-main.21d9917"
54
57
  },
55
58
  "publishConfig": {
56
59
  "access": "public"
@@ -2,14 +2,12 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useState } from 'react';
9
7
 
10
8
  import { PublicKey } from '@dxos/keys';
11
- import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
12
- import { withLayout, withTheme } from '@dxos/storybook-utils';
9
+ import { withTheme } from '@dxos/react-ui/testing';
10
+ import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
13
11
 
14
12
  import { type MessageEntity, MessageStoryText } from '../testing';
15
13
  import { Thread } from '../Thread';
@@ -41,8 +39,11 @@ const meta = {
41
39
  title: 'ui/react-ui-thread/Message',
42
40
  component: MessageRoot as any,
43
41
  render: DefaultStory,
44
- decorators: [withTheme, withLayout({ fullscreen: true })],
45
- parameters: { translations },
42
+ decorators: [withTheme],
43
+ parameters: {
44
+ layout: 'fullscreen',
45
+ translations,
46
+ },
46
47
  } satisfies Meta<typeof DefaultStory>;
47
48
 
48
49
  export default meta;
@@ -5,11 +5,11 @@
5
5
  import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
6
6
  import React, { type ComponentPropsWithRef, type ComponentPropsWithoutRef, forwardRef } from 'react';
7
7
 
8
- // TODO(burdon): Remove dep.
9
8
  import { Avatar, type ThemedClassName, useTranslation } from '@dxos/react-ui';
10
- import { type UseTextEditorProps, keymap, listener, useTextEditor } from '@dxos/react-ui-editor';
11
- import { focusRing, mx } from '@dxos/react-ui-theme';
12
- import { hexToEmoji, hexToHue, isNotFalsy } from '@dxos/util';
9
+ import { type UseTextEditorProps, useTextEditor } from '@dxos/react-ui-editor';
10
+ import { keymap, listener } from '@dxos/ui-editor';
11
+ import { focusRing, mx } from '@dxos/ui-theme';
12
+ import { hexToEmoji, hexToHue, isTruthy } from '@dxos/util';
13
13
 
14
14
  import { translationKey } from '../translations';
15
15
  import { type MessageMetadata } from '../types';
@@ -149,14 +149,14 @@ export const MessageTextbox = ({
149
149
  extensions: [
150
150
  keymap.of(keyBindings({ onSend, onClear })),
151
151
  listener({
152
- onFocus: (focusing) => {
152
+ onFocus: ({ focusing }) => {
153
153
  if (focusing) {
154
154
  onEditorFocus?.();
155
155
  }
156
156
  },
157
157
  }),
158
158
  extensions,
159
- ].filter(isNotFalsy),
159
+ ].filter(isTruthy),
160
160
  ...editorProps,
161
161
  }),
162
162
  [id, extensions],
@@ -2,33 +2,31 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { type FC, useEffect, useMemo, useRef, useState } from 'react';
9
7
 
10
- import { createDocAccessor, createObject } from '@dxos/echo-db';
11
- import { Expando } from '@dxos/echo-schema';
8
+ import { Obj } from '@dxos/echo';
9
+ import { TestSchema } from '@dxos/echo/testing';
12
10
  import { PublicKey } from '@dxos/keys';
13
- import { live } from '@dxos/live-object';
14
11
  import { log } from '@dxos/log';
15
12
  import { faker } from '@dxos/random';
16
- import { Button, Icon, useThemeContext } from '@dxos/react-ui';
13
+ import { Icon, IconButton, useThemeContext } from '@dxos/react-ui';
14
+ import { withTheme } from '@dxos/react-ui/testing';
15
+ import { useTextEditor } from '@dxos/react-ui-editor';
17
16
  import {
18
17
  type Comment,
19
18
  type CommentsOptions,
19
+ type EditorView,
20
20
  type Range,
21
- automerge,
22
21
  comments,
23
22
  createBasicExtensions,
24
23
  createThemeExtensions,
24
+ documentId,
25
25
  listener,
26
26
  scrollThreadIntoView,
27
- useComments,
28
- useTextEditor,
29
- } from '@dxos/react-ui-editor';
30
- import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
31
- import { withTheme } from '@dxos/storybook-utils';
27
+ setComments,
28
+ } from '@dxos/ui-editor';
29
+ import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
32
30
 
33
31
  import { MessageBody, MessageHeading, MessageRoot, MessageTextbox } from '../Message';
34
32
  import { type MessageEntity } from '../testing';
@@ -40,13 +38,31 @@ faker.seed(101);
40
38
 
41
39
  const authorId = PublicKey.random().toHex();
42
40
 
41
+ /**
42
+ * @deprecated This hook will be removed in future versions. Use the new comment sync extension instead.
43
+ * Update comments state field.
44
+ */
45
+ const useComments = (view: EditorView | null | undefined, id: string, comments?: Comment[]) => {
46
+ useEffect(() => {
47
+ if (view) {
48
+ // Check same document.
49
+ // NOTE: Hook might be called before editor state is updated.
50
+ if (id === view.state.facet(documentId)) {
51
+ view.dispatch({
52
+ effects: setComments.of({ id, comments: comments ?? [] }),
53
+ });
54
+ }
55
+ }
56
+ });
57
+ };
58
+
43
59
  //
44
60
  // Editor
45
61
  //
46
62
 
47
63
  const Editor: FC<{
48
64
  id?: string;
49
- item: Expando;
65
+ initialValue: string;
50
66
  comments: Comment[];
51
67
  selected?: string;
52
68
  onCreateComment: CommentsOptions['onCreate'];
@@ -55,7 +71,7 @@ const Editor: FC<{
55
71
  onSelectComment: CommentsOptions['onSelect'];
56
72
  }> = ({
57
73
  id = 'test',
58
- item,
74
+ initialValue,
59
75
  selected: selectedValue,
60
76
  comments: commentRanges,
61
77
  onCreateComment,
@@ -69,11 +85,10 @@ const Editor: FC<{
69
85
  const { parentRef, view } = useTextEditor(
70
86
  () => ({
71
87
  id,
72
- initialValue: item.content,
88
+ initialValue: initialValue,
73
89
  extensions: [
74
90
  createBasicExtensions(),
75
91
  createThemeExtensions({ themeMode }),
76
- automerge(createDocAccessor(item, ['content'])),
77
92
  comments({
78
93
  id,
79
94
  onCreate: onCreateComment,
@@ -83,7 +98,7 @@ const Editor: FC<{
83
98
  }),
84
99
  ],
85
100
  }),
86
- [id, item, themeMode],
101
+ [id, initialValue, themeMode],
87
102
  );
88
103
  useComments(view, id, commentRanges);
89
104
  useEffect(() => {
@@ -116,19 +131,21 @@ const StoryThread: FC<{
116
131
  onSelect: () => void;
117
132
  onResolve: () => void;
118
133
  }> = ({ thread, selected, onSelect, onResolve }) => {
119
- const containerRef = useRef<HTMLDivElement>(null);
120
134
  const { themeMode } = useThemeContext();
135
+ const containerRef = useRef<HTMLDivElement>(null);
136
+
121
137
  // TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
122
- const [_count, _setCount] = useState(0);
123
- const rerenderEditor = () => _setCount((count) => count + 1);
138
+ const [state, setState] = useState({});
124
139
  const messageRef = useRef('');
125
140
  const extensions = useMemo(
126
141
  () => [
127
142
  createBasicExtensions({ placeholder: 'Enter comment' }),
128
143
  createThemeExtensions({ themeMode }),
129
- listener({ onChange: (text) => (messageRef.current = text) }),
144
+ listener({
145
+ onChange: ({ text }) => (messageRef.current = text),
146
+ }),
130
147
  ],
131
- [themeMode, _count],
148
+ [themeMode, state],
132
149
  );
133
150
 
134
151
  const [autoFocus, setAutoFocus] = useState(false);
@@ -144,7 +161,7 @@ const StoryThread: FC<{
144
161
 
145
162
  const handleCreateMessage = () => {
146
163
  if (messageRef.current?.length) {
147
- const message = live(Expando, {
164
+ const message = Obj.make(TestSchema.Expando, {
148
165
  timestamp: new Date().toISOString(),
149
166
  sender: { identityKey: authorId },
150
167
  text: messageRef.current,
@@ -153,7 +170,7 @@ const StoryThread: FC<{
153
170
 
154
171
  messageRef.current = '';
155
172
  setAutoFocus(true);
156
- rerenderEditor();
173
+ setState({});
157
174
  }
158
175
  };
159
176
 
@@ -187,9 +204,14 @@ const StoryThread: FC<{
187
204
  onSend={handleCreateMessage}
188
205
  />
189
206
  <Thread.Status />
190
- <Button variant='ghost' classNames='px-1' title='Resolve' onClick={onResolve}>
191
- <Icon icon='ph--check--regular' />
192
- </Button>
207
+ <IconButton
208
+ variant='ghost'
209
+ icon='ph--check--regular'
210
+ iconOnly
211
+ classNames='pli-1'
212
+ label='Resolve'
213
+ onClick={onResolve}
214
+ />
193
215
  </div>
194
216
  </Thread.Root>
195
217
  );
@@ -234,7 +256,7 @@ type StoryProps = {
234
256
  };
235
257
 
236
258
  const DefaultStory = ({ text, autoCreate }: StoryProps) => {
237
- const [item] = useState(createObject(live(Expando, { content: text ?? '' })));
259
+ const [item] = useState(() => Obj.make(TestSchema.Expando, { content: text ?? '' }));
238
260
  const [threads, setThreads] = useState<StoryCommentThread[]>([]);
239
261
  const [selected, setSelected] = useState<string>();
240
262
 
@@ -328,7 +350,7 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
328
350
  <main className='fixed inset-0 grid grid-cols-[1fr_24rem]'>
329
351
  <div role='none' className='max-bs-full overflow-y-auto p-4'>
330
352
  <Editor
331
- item={item}
353
+ initialValue={item.content}
332
354
  selected={selected}
333
355
  comments={comments}
334
356
  onCreateComment={handleCreateComment}
@@ -355,7 +377,10 @@ const meta = {
355
377
  component: StoryThread as any,
356
378
  render: DefaultStory,
357
379
  decorators: [withTheme],
358
- parameters: { translations, layout: 'fullscreen' },
380
+ parameters: {
381
+ translations,
382
+ layout: 'fullscreen',
383
+ },
359
384
  } satisfies Meta<typeof DefaultStory>;
360
385
 
361
386
  export default meta;
@@ -2,16 +2,14 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useMemo, useRef, useState } from 'react';
9
7
 
10
8
  import { PublicKey } from '@dxos/keys';
11
9
  import { faker } from '@dxos/random';
12
- import { createBasicExtensions, createThemeExtensions } from '@dxos/react-ui-editor';
13
- import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
14
- import { withLayout, withTheme } from '@dxos/storybook-utils';
10
+ import { withTheme } from '@dxos/react-ui/testing';
11
+ import { createBasicExtensions, createThemeExtensions } from '@dxos/ui-editor';
12
+ import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
15
13
 
16
14
  import { MessageRoot, MessageTextbox } from '../Message';
17
15
  import { type MessageEntity, MessageStoryText } from '../testing';
@@ -87,8 +85,11 @@ const meta = {
87
85
  title: 'ui/react-ui-thread/Thread',
88
86
  component: Thread.Root,
89
87
  render: DefaultStory,
90
- decorators: [withTheme, withLayout({ fullscreen: true })],
91
- parameters: { translations },
88
+ decorators: [withTheme],
89
+ parameters: {
90
+ layout: 'fullscreen',
91
+ translations,
92
+ },
92
93
  } satisfies Meta<typeof Thread.Root>;
93
94
 
94
95
  export default meta;
@@ -5,7 +5,7 @@
5
5
  import React, { type ComponentPropsWithRef, forwardRef } from 'react';
6
6
 
7
7
  import { Icon, type ThemedClassName, useTranslation } from '@dxos/react-ui';
8
- import { hoverableControlItem, hoverableFocusedWithinControls, mx } from '@dxos/react-ui-theme';
8
+ import { hoverableControlItem, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';
9
9
 
10
10
  import { translationKey } from '../translations';
11
11
  import type { ThreadEntity } from '../types';
@@ -45,10 +45,10 @@ const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
45
45
  // Heading
46
46
  //
47
47
 
48
- type ThreadHeaderProps = ThemedClassName<ComponentPropsWithRef<'div'>> & { detached?: boolean };
48
+ type ThreadHeaderProps = ComponentPropsWithRef<'div'> & { detached?: boolean };
49
49
 
50
50
  const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
51
- ({ classNames, children, detached, ...props }, forwardedRef) => {
51
+ ({ children, detached, ...props }, forwardedRef) => {
52
52
  return (
53
53
  <>
54
54
  <div role='none' className='flex items-center justify-center text-description'>
package/src/testing.tsx CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { Button, Icon } from '@dxos/react-ui';
8
- import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/react-ui-theme';
7
+ import { IconButton } from '@dxos/react-ui';
8
+ import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';
9
9
 
10
10
  import { MessageBody, MessageHeading } from './Message';
11
11
  import { type MessageMetadata } from './types';
@@ -25,13 +25,14 @@ export const MessageStoryText = ({ text, onDelete, authorName, timestamp }: Mess
25
25
  <>
26
26
  <MessageHeading authorName={authorName} timestamp={timestamp}>
27
27
  {onDelete && (
28
- <Button
28
+ <IconButton
29
+ icon='ph--x--regular'
30
+ iconOnly
31
+ label='Delete'
29
32
  variant='ghost'
30
33
  classNames={['p-1.5 min-bs-0 mie-1 items-start transition-opacity', hoverableControlItem]}
31
34
  onClick={() => onDelete()}
32
- >
33
- <Icon icon='ph--x--regular' />
34
- </Button>
35
+ />
35
36
  )}
36
37
  </MessageHeading>
37
38
  <MessageBody>{text}</MessageBody>
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { type Resource } from '@dxos/react-ui';
6
6
 
7
- export const translationKey = 'react-ui-thread';
7
+ export const translationKey = '@dxos/react-ui-thread';
8
8
 
9
9
  export const translations = [
10
10
  {