@dxos/react-ui-thread 0.8.4-main.72ec0f3 → 0.8.4-main.7996785055

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.72ec0f3",
3
+ "version": "0.8.4-main.7996785055",
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.72ec0f3",
31
- "@dxos/util": "0.8.4-main.72ec0f3"
33
+ "@dxos/react-ui-editor": "0.8.4-main.7996785055",
34
+ "@dxos/util": "0.8.4-main.7996785055",
35
+ "@dxos/ui-editor": "0.8.4-main.7996785055"
32
36
  },
33
37
  "devDependencies": {
34
- "@types/react": "~19.2.2",
35
- "@types/react-dom": "~19.2.2",
36
- "react": "~19.2.0",
37
- "react-dom": "~19.2.0",
38
- "vite": "7.1.9",
39
- "@dxos/echo": "0.8.4-main.72ec0f3",
40
- "@dxos/live-object": "0.8.4-main.72ec0f3",
41
- "@dxos/keys": "0.8.4-main.72ec0f3",
42
- "@dxos/log": "0.8.4-main.72ec0f3",
43
- "@dxos/random": "0.8.4-main.72ec0f3",
44
- "@dxos/react-ui": "0.8.4-main.72ec0f3",
45
- "@dxos/react-ui-theme": "0.8.4-main.72ec0f3",
46
- "@dxos/echo-db": "0.8.4-main.72ec0f3",
47
- "@dxos/storybook-utils": "0.8.4-main.72ec0f3"
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.11",
43
+ "@dxos/echo": "0.8.4-main.7996785055",
44
+ "@dxos/keys": "0.8.4-main.7996785055",
45
+ "@dxos/echo-db": "0.8.4-main.7996785055",
46
+ "@dxos/log": "0.8.4-main.7996785055",
47
+ "@dxos/react-ui": "0.8.4-main.7996785055",
48
+ "@dxos/random": "0.8.4-main.7996785055",
49
+ "@dxos/storybook-utils": "0.8.4-main.7996785055",
50
+ "@dxos/ui-theme": "0.8.4-main.7996785055"
48
51
  },
49
52
  "peerDependencies": {
50
- "react": "^19.0.0",
51
- "react-dom": "^19.0.0",
52
- "@dxos/react-ui": "0.8.4-main.72ec0f3",
53
- "@dxos/react-ui-theme": "0.8.4-main.72ec0f3"
53
+ "react": "~19.2.3",
54
+ "react-dom": "~19.2.3",
55
+ "@dxos/ui-theme": "0.8.4-main.7996785055",
56
+ "@dxos/react-ui": "0.8.4-main.7996785055"
54
57
  },
55
58
  "publishConfig": {
56
59
  "access": "public"
@@ -6,8 +6,8 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useState } from 'react';
7
7
 
8
8
  import { PublicKey } from '@dxos/keys';
9
- import { withTheme } from '@dxos/react-ui/testing';
10
- import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+ import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
11
11
 
12
12
  import { type MessageEntity, MessageStoryText } from '../testing';
13
13
  import { Thread } from '../Thread';
@@ -25,7 +25,7 @@ const DefaultStory = () => {
25
25
  });
26
26
 
27
27
  return (
28
- <div className='mli-auto is-96 overflow-y-auto'>
28
+ <div className='mx-auto w-96 overflow-y-auto'>
29
29
  <Thread.Root id='t1'>
30
30
  <MessageRoot {...message} classNames={[hoverableControls, hoverableFocusedWithinControls]}>
31
31
  <MessageStoryText {...message} onDelete={() => console.log('delete')} />
@@ -39,7 +39,7 @@ const meta = {
39
39
  title: 'ui/react-ui-thread/Message',
40
40
  component: MessageRoot as any,
41
41
  render: DefaultStory,
42
- decorators: [withTheme],
42
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
43
43
  parameters: {
44
44
  layout: 'fullscreen',
45
45
  translations,
@@ -5,10 +5,10 @@
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';
9
+ import { type UseTextEditorProps, useTextEditor } from '@dxos/react-ui-editor';
10
+ import { keymap, listener } from '@dxos/ui-editor';
11
+ import { mx } from '@dxos/ui-theme';
12
12
  import { hexToEmoji, hexToHue, isTruthy } from '@dxos/util';
13
13
 
14
14
  import { translationKey } from '../translations';
@@ -16,9 +16,9 @@ import { type MessageMetadata } from '../types';
16
16
 
17
17
  const avatarSize = 7;
18
18
 
19
- export type MessageRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> &
20
- MessageMetadata &
21
- Partial<{ continues: boolean }>;
19
+ export type MessageRootProps = ThemedClassName<
20
+ ComponentPropsWithRef<'div'> & MessageMetadata & Partial<{ continues: boolean }>
21
+ >;
22
22
 
23
23
  // TODO(burdon): Show authorName on tooltip.
24
24
  export const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
@@ -36,16 +36,16 @@ export const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
36
36
  className={mx('grid grid-cols-subgrid col-span-2', classNames)}
37
37
  ref={forwardedRef}
38
38
  >
39
- <div role='none' className='flex flex-col items-center gap-2 pbs-1'>
39
+ <div role='none' className='flex flex-col items-center gap-2 pt-1'>
40
40
  <Avatar.Content
41
41
  size={avatarSize}
42
42
  hue={authorAvatarProps?.hue || hexToHue(authorId ?? '0')}
43
43
  fallback={authorAvatarProps?.emoji || hexToEmoji(authorId ?? '0')}
44
44
  {...(authorImgSrc && { imgSrc: authorImgSrc })}
45
45
  />
46
- {continues && <div role='none' className='is-px grow bg-separator' />}
46
+ {continues && <div role='none' className='w-px grow bg-separator' />}
47
47
  </div>
48
- <div role='none' className='plb-1 min-is-0'>
48
+ <div role='none' className='py-1 min-w-0'>
49
49
  {children}
50
50
  </div>
51
51
  </div>
@@ -84,7 +84,7 @@ export const MessageTime = ({ timestamp }: MessageTimeProps) => {
84
84
  const { dtLocale } = useTranslation(translationKey);
85
85
  const dt = timestamp ? new Date(timestamp) : undefined;
86
86
  return (
87
- <time className='block text-subdued text-xs pbe-0.5' dateTime={dt?.toISOString()}>
87
+ <time className='block text-subdued text-xs pb-0.5' dateTime={dt?.toISOString()}>
88
88
  {dt ? formatDistanceToNow(dt, { locale: dtLocale, addSuffix: true }) : ''}
89
89
  </time>
90
90
  );
@@ -167,7 +167,7 @@ export const MessageTextbox = ({
167
167
  <div
168
168
  role='none'
169
169
  ref={parentRef}
170
- className={mx('plb-0.5 mie-1 rounded-sm', focusRing, disabled && 'opacity-50')}
170
+ className={mx('py-0.5 me-1 rounded-xs dx-focus-ring', disabled && 'opacity-50')}
171
171
  {...focusAttributes}
172
172
  />
173
173
  </MessageRoot>
@@ -5,28 +5,28 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { type FC, useEffect, useMemo, useRef, useState } from 'react';
7
7
 
8
- import { Expando } from '@dxos/echo/internal';
9
- import { live } from '@dxos/echo/internal';
10
- import { createDocAccessor, createObject } from '@dxos/echo-db';
8
+ import { Obj } from '@dxos/echo';
9
+ import { TestSchema } from '@dxos/echo/testing';
11
10
  import { PublicKey } from '@dxos/keys';
12
11
  import { log } from '@dxos/log';
13
12
  import { faker } from '@dxos/random';
14
13
  import { Icon, IconButton, useThemeContext } from '@dxos/react-ui';
15
- import { withTheme } from '@dxos/react-ui/testing';
14
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
+ import { useTextEditor } from '@dxos/react-ui-editor';
16
16
  import {
17
17
  type Comment,
18
18
  type CommentsOptions,
19
+ type EditorView,
19
20
  type Range,
20
- automerge,
21
21
  comments,
22
22
  createBasicExtensions,
23
23
  createThemeExtensions,
24
+ documentId,
24
25
  listener,
25
26
  scrollThreadIntoView,
26
- useComments,
27
- useTextEditor,
28
- } from '@dxos/react-ui-editor';
29
- import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
27
+ setComments,
28
+ } from '@dxos/ui-editor';
29
+ import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
30
30
 
31
31
  import { MessageBody, MessageHeading, MessageRoot, MessageTextbox } from '../Message';
32
32
  import { type MessageEntity } from '../testing';
@@ -38,13 +38,31 @@ faker.seed(101);
38
38
 
39
39
  const authorId = PublicKey.random().toHex();
40
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
+
41
59
  //
42
60
  // Editor
43
61
  //
44
62
 
45
63
  const Editor: FC<{
46
64
  id?: string;
47
- item: Expando;
65
+ initialValue: string;
48
66
  comments: Comment[];
49
67
  selected?: string;
50
68
  onCreateComment: CommentsOptions['onCreate'];
@@ -53,7 +71,7 @@ const Editor: FC<{
53
71
  onSelectComment: CommentsOptions['onSelect'];
54
72
  }> = ({
55
73
  id = 'test',
56
- item,
74
+ initialValue,
57
75
  selected: selectedValue,
58
76
  comments: commentRanges,
59
77
  onCreateComment,
@@ -67,11 +85,10 @@ const Editor: FC<{
67
85
  const { parentRef, view } = useTextEditor(
68
86
  () => ({
69
87
  id,
70
- initialValue: item.content,
88
+ initialValue: initialValue,
71
89
  extensions: [
72
90
  createBasicExtensions(),
73
91
  createThemeExtensions({ themeMode }),
74
- automerge(createDocAccessor(item, ['content'])),
75
92
  comments({
76
93
  id,
77
94
  onCreate: onCreateComment,
@@ -81,7 +98,7 @@ const Editor: FC<{
81
98
  }),
82
99
  ],
83
100
  }),
84
- [id, item, themeMode],
101
+ [id, initialValue, themeMode],
85
102
  );
86
103
  useComments(view, id, commentRanges);
87
104
  useEffect(() => {
@@ -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,
@@ -191,7 +208,7 @@ const StoryThread: FC<{
191
208
  variant='ghost'
192
209
  icon='ph--check--regular'
193
210
  iconOnly
194
- classNames='pli-1'
211
+ classNames='px-1'
195
212
  label='Resolve'
196
213
  onClick={onResolve}
197
214
  />
@@ -200,12 +217,14 @@ const StoryThread: FC<{
200
217
  );
201
218
  };
202
219
 
203
- const Sidebar: FC<{
220
+ type SidebarProps = {
204
221
  threads: StoryCommentThread[];
205
222
  selected?: string;
206
223
  onSelect: (thread: string) => void;
207
224
  onResolve: (thread: string) => void;
208
- }> = ({ threads, selected, onSelect, onResolve }) => {
225
+ };
226
+
227
+ const Sidebar = ({ threads, selected, onSelect, onResolve }: SidebarProps) => {
209
228
  // Sort by y-position.
210
229
  const sortedThreads = useMemo(() => {
211
230
  const sorted = [...threads];
@@ -233,13 +252,13 @@ const Sidebar: FC<{
233
252
  // Story container.
234
253
  //
235
254
 
236
- type StoryProps = {
255
+ type DefaultStoryProps = {
237
256
  text?: string;
238
257
  autoCreate?: boolean;
239
258
  };
240
259
 
241
- const DefaultStory = ({ text, autoCreate }: StoryProps) => {
242
- const [item] = useState(createObject(live(Expando, { content: text ?? '' })));
260
+ const DefaultStory = ({ text, autoCreate }: DefaultStoryProps) => {
261
+ const [item] = useState(() => Obj.make(TestSchema.Expando, { content: text ?? '' }));
243
262
  const [threads, setThreads] = useState<StoryCommentThread[]>([]);
244
263
  const [selected, setSelected] = useState<string>();
245
264
 
@@ -331,9 +350,9 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
331
350
 
332
351
  return (
333
352
  <main className='fixed inset-0 grid grid-cols-[1fr_24rem]'>
334
- <div role='none' className='max-bs-full overflow-y-auto p-4'>
353
+ <div role='none' className='max-h-full overflow-y-auto p-4'>
335
354
  <Editor
336
- item={item}
355
+ initialValue={item.content}
337
356
  selected={selected}
338
357
  comments={comments}
339
358
  onCreateComment={handleCreateComment}
@@ -343,7 +362,7 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
343
362
  />
344
363
  </div>
345
364
 
346
- <div role='none' className='max-bs-full overflow-y-auto p-4'>
365
+ <div role='none' className='max-h-full overflow-y-auto p-4'>
347
366
  <Sidebar
348
367
  threads={visibleThreads}
349
368
  selected={selected}
@@ -359,7 +378,7 @@ const meta = {
359
378
  title: 'ui/react-ui-thread/Comments',
360
379
  component: StoryThread as any,
361
380
  render: DefaultStory,
362
- decorators: [withTheme],
381
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
363
382
  parameters: {
364
383
  translations,
365
384
  layout: 'fullscreen',
@@ -7,9 +7,9 @@ import React, { useMemo, useRef, useState } from 'react';
7
7
 
8
8
  import { PublicKey } from '@dxos/keys';
9
9
  import { faker } from '@dxos/random';
10
- import { withTheme } from '@dxos/react-ui/testing';
11
- import { createBasicExtensions, createThemeExtensions } from '@dxos/react-ui-editor';
12
- import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+ import { createBasicExtensions, createThemeExtensions } from '@dxos/ui-editor';
12
+ import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
13
13
 
14
14
  import { MessageRoot, MessageTextbox } from '../Message';
15
15
  import { type MessageEntity, MessageStoryText } from '../testing';
@@ -59,7 +59,7 @@ const DefaultStory = () => {
59
59
  };
60
60
 
61
61
  return (
62
- <div className='mli-auto is-96 overflow-y-auto'>
62
+ <div className='mx-auto w-96 overflow-y-auto'>
63
63
  <Thread.Root id='t1'>
64
64
  {messages.map((message) => (
65
65
  <MessageRoot key={message.id} classNames={[hoverableControls, hoverableFocusedWithinControls]} {...message}>
@@ -85,7 +85,7 @@ const meta = {
85
85
  title: 'ui/react-ui-thread/Thread',
86
86
  component: Thread.Root,
87
87
  render: DefaultStory,
88
- decorators: [withTheme],
88
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
89
89
  parameters: {
90
90
  layout: 'fullscreen',
91
91
  translations,
@@ -5,13 +5,13 @@
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';
12
12
 
13
- // TODO(burdon): Why is this exported?
14
- export const threadLayout = 'is-full place-self-start grid grid-cols-[var(--rail-size)_1fr]';
13
+ // TODO(burdon): Avoid exporting fragments.
14
+ export const threadLayout = 'w-full place-self-start grid grid-cols-[var(--dx-rail-size)_1fr]';
15
15
 
16
16
  //
17
17
  // Root
@@ -30,7 +30,7 @@ const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
30
30
  className={mx(
31
31
  threadLayout,
32
32
  hoverableFocusedWithinControls,
33
- 'bg-[var(--surface-bg)] current-related attention-surface [--controls-opacity:0]',
33
+ 'bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]',
34
34
  classNames,
35
35
  )}
36
36
  ref={forwardedRef}
@@ -59,10 +59,7 @@ const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
59
59
  role='heading'
60
60
  data-testid='thread.heading'
61
61
  {...props}
62
- className={mx(
63
- 'mie-2 text-description font-medium truncate italic',
64
- detached && 'line-through decoration-1',
65
- )}
62
+ className={mx('me-2 text-description font-medium truncate italic', detached && 'line-through decoration-1')}
66
63
  ref={forwardedRef}
67
64
  >
68
65
  {children}
@@ -88,17 +85,17 @@ const ThreadStatus = forwardRef<HTMLDivElement, ThreadStatusProps>(
88
85
  <div
89
86
  {...props}
90
87
  className={mx(
91
- 'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pie-2 text-xs text-description',
88
+ 'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-description',
92
89
  classNames,
93
90
  )}
94
91
  ref={forwardedRef}
95
92
  >
96
93
  <Icon
97
94
  icon='ph--spinner--bold'
98
- classNames='is-6 bs-4 invisible data-[visible=show]:visible animate-spin-slow'
95
+ classNames='w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow'
99
96
  data-visible={activity ? 'show' : 'hide'}
100
97
  />
101
- <span className='truncate min-is-0' aria-live='polite'>
98
+ <span className='truncate min-w-0' aria-live='polite'>
102
99
  {activity ? children : null}
103
100
  </span>
104
101
  <span className={mx('text-end', hoverableControlItem)}>{t('enter to send message')}</span>
@@ -113,4 +110,4 @@ export const Thread = {
113
110
  Status: ThreadStatus,
114
111
  };
115
112
 
116
- export type { ThreadRootProps, ThreadHeaderProps as ThreadHeadingProps, ThreadStatusProps };
113
+ export type { ThreadRootProps, ThreadHeaderProps, ThreadStatusProps };
package/src/testing.tsx CHANGED
@@ -5,7 +5,7 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { IconButton } from '@dxos/react-ui';
8
- import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/react-ui-theme';
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';
@@ -30,7 +30,7 @@ export const MessageStoryText = ({ text, onDelete, authorName, timestamp }: Mess
30
30
  iconOnly
31
31
  label='Delete'
32
32
  variant='ghost'
33
- classNames={['p-1.5 min-bs-0 mie-1 items-start transition-opacity', hoverableControlItem]}
33
+ classNames={['p-1.5 min-h-0 me-1 items-start transition-opacity', hoverableControlItem]}
34
34
  onClick={() => onDelete()}
35
35
  />
36
36
  )}
@@ -50,7 +50,7 @@ export const MessageStoryBlock = <PartValue,>({ part }: MessagePartProps<PartVal
50
50
  role='none'
51
51
  className={mx('grid grid-cols-subgrid col-span-3', hoverableControls, hoverableFocusedWithinControls)}
52
52
  >
53
- <pre className='font-mono max-is-full overflow-x-auto col-span-3'>
53
+ <pre className='font-mono max-w-full overflow-x-auto col-span-3'>
54
54
  <code>{JSON.stringify(part, undefined, 2)}</code>
55
55
  </pre>
56
56
  </div>
@@ -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
  {