@dxos/react-ui-thread 0.8.4-main.ead640a → 0.8.4-main.effb148878

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 (34) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/index.mjs +121 -198
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/translations.mjs +25 -0
  6. package/dist/lib/browser/translations.mjs.map +7 -0
  7. package/dist/types/src/Message/Message.d.ts +2 -2
  8. package/dist/types/src/Message/Message.d.ts.map +1 -1
  9. package/dist/types/src/Message/Message.stories.d.ts +11 -11
  10. package/dist/types/src/Message/Message.stories.d.ts.map +1 -1
  11. package/dist/types/src/Thread/Comments.stories.d.ts +13 -13
  12. package/dist/types/src/Thread/Comments.stories.d.ts.map +1 -1
  13. package/dist/types/src/Thread/Thread.d.ts +25 -11
  14. package/dist/types/src/Thread/Thread.d.ts.map +1 -1
  15. package/dist/types/src/Thread/Thread.stories.d.ts +16 -12
  16. package/dist/types/src/Thread/Thread.stories.d.ts.map +1 -1
  17. package/dist/types/src/index.d.ts +0 -2
  18. package/dist/types/src/index.d.ts.map +1 -1
  19. package/dist/types/src/testing.d.ts.map +1 -1
  20. package/dist/types/src/translations.d.ts +12 -12
  21. package/dist/types/src/translations.d.ts.map +1 -1
  22. package/dist/types/src/types.d.ts +1 -0
  23. package/dist/types/src/types.d.ts.map +1 -1
  24. package/dist/types/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +37 -28
  26. package/src/Message/Message.stories.tsx +6 -6
  27. package/src/Message/Message.tsx +17 -20
  28. package/src/Thread/Comments.stories.tsx +68 -43
  29. package/src/Thread/Thread.stories.tsx +10 -10
  30. package/src/Thread/Thread.tsx +23 -21
  31. package/src/index.ts +0 -3
  32. package/src/testing.tsx +10 -12
  33. package/src/translations.ts +11 -11
  34. package/src/types.ts +1 -0
package/package.json CHANGED
@@ -1,56 +1,65 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-thread",
3
- "version": "0.8.4-main.ead640a",
3
+ "version": "0.8.4-main.effb148878",
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
- "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "FSL-1.1-Apache-2.0",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
15
+ "imports": {
16
+ "#translations": "./src/translations.ts"
17
+ },
11
18
  "exports": {
12
19
  ".": {
13
20
  "source": "./src/index.ts",
14
21
  "types": "./dist/types/src/index.d.ts",
15
22
  "browser": "./dist/lib/browser/index.mjs"
23
+ },
24
+ "./translations": {
25
+ "source": "./src/translations.ts",
26
+ "types": "./dist/types/src/translations.d.ts",
27
+ "browser": "./dist/lib/browser/translations.mjs",
28
+ "node": "./dist/lib/node-esm/translations.mjs"
16
29
  }
17
30
  },
18
31
  "types": "dist/types/src/index.d.ts",
19
- "typesVersions": {
20
- "*": {}
21
- },
22
32
  "files": [
23
33
  "dist",
24
34
  "src"
25
35
  ],
26
36
  "dependencies": {
27
- "@preact-signals/safe-react": "^0.9.0",
28
37
  "@radix-ui/react-primitive": "2.0.2",
29
- "date-fns": "^3.3.1",
30
- "@dxos/react-ui-editor": "0.8.4-main.ead640a",
31
- "@dxos/util": "0.8.4-main.ead640a"
38
+ "date-fns": "^3.6.0",
39
+ "@dxos/react-ui-editor": "0.8.4-main.effb148878",
40
+ "@dxos/util": "0.8.4-main.effb148878",
41
+ "@dxos/ui-editor": "0.8.4-main.effb148878"
32
42
  },
33
43
  "devDependencies": {
34
- "@types/react": "~19.2.2",
35
- "@types/react-dom": "~19.2.1",
36
- "react": "~19.2.0",
37
- "react-dom": "~19.2.0",
38
- "vite": "7.1.9",
39
- "@dxos/echo": "0.8.4-main.ead640a",
40
- "@dxos/keys": "0.8.4-main.ead640a",
41
- "@dxos/echo-db": "0.8.4-main.ead640a",
42
- "@dxos/live-object": "0.8.4-main.ead640a",
43
- "@dxos/random": "0.8.4-main.ead640a",
44
- "@dxos/react-ui": "0.8.4-main.ead640a",
45
- "@dxos/react-ui-theme": "0.8.4-main.ead640a",
46
- "@dxos/log": "0.8.4-main.ead640a",
47
- "@dxos/storybook-utils": "0.8.4-main.ead640a"
44
+ "@types/react": "~19.2.7",
45
+ "@types/react-dom": "~19.2.3",
46
+ "react": "~19.2.3",
47
+ "react-dom": "~19.2.3",
48
+ "vite": "^8.0.14",
49
+ "@dxos/echo": "0.8.4-main.effb148878",
50
+ "@dxos/keys": "0.8.4-main.effb148878",
51
+ "@dxos/echo-db": "0.8.4-main.effb148878",
52
+ "@dxos/random": "0.8.4-main.effb148878",
53
+ "@dxos/react-ui": "0.8.4-main.effb148878",
54
+ "@dxos/log": "0.8.4-main.effb148878",
55
+ "@dxos/storybook-utils": "0.8.4-main.effb148878",
56
+ "@dxos/ui-theme": "0.8.4-main.effb148878"
48
57
  },
49
58
  "peerDependencies": {
50
- "react": "^19.0.0",
51
- "react-dom": "^19.0.0",
52
- "@dxos/react-ui": "0.8.4-main.ead640a",
53
- "@dxos/react-ui-theme": "0.8.4-main.ead640a"
59
+ "react": "~19.2.3",
60
+ "react-dom": "~19.2.3",
61
+ "@dxos/react-ui": "0.8.4-main.effb148878",
62
+ "@dxos/ui-theme": "0.8.4-main.effb148878"
54
63
  },
55
64
  "publishConfig": {
56
65
  "access": "public"
@@ -6,13 +6,13 @@ 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
+
12
+ import { translations } from '#translations';
11
13
 
12
14
  import { type MessageEntity, MessageStoryText } from '../testing';
13
15
  import { Thread } from '../Thread';
14
- import { translations } from '../translations';
15
-
16
16
  import { MessageRoot } from './Message';
17
17
 
18
18
  const DefaultStory = () => {
@@ -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,20 +5,21 @@
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
- import { translationKey } from '../translations';
14
+ import { translationKey } from '#translations';
15
+
15
16
  import { type MessageMetadata } from '../types';
16
17
 
17
18
  const avatarSize = 7;
18
19
 
19
- export type MessageRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> &
20
- MessageMetadata &
21
- Partial<{ continues: boolean }>;
20
+ export type MessageRootProps = ThemedClassName<
21
+ ComponentPropsWithRef<'div'> & MessageMetadata & Partial<{ continues: boolean }>
22
+ >;
22
23
 
23
24
  // TODO(burdon): Show authorName on tooltip.
24
25
  export const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
@@ -26,28 +27,25 @@ export const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
26
27
  { authorImgSrc, authorId, authorName, authorAvatarProps, continues = true, children, classNames, ...rootProps },
27
28
  forwardedRef,
28
29
  ) => {
30
+ // Must wrap the message since Avatar.Label may be used in the content.
29
31
  return (
30
- // Must wrap the message since Avatar.Label may be used in the content.
31
32
  <Avatar.Root>
32
33
  <div
33
- role='none'
34
34
  data-testid='thread.message'
35
35
  {...rootProps}
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 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' />}
47
- </div>
48
- <div role='none' className='plb-1 min-is-0'>
49
- {children}
46
+ {continues && <div className='w-px grow bg-separator' />}
50
47
  </div>
48
+ <div className='py-1 min-w-0'>{children}</div>
51
49
  </div>
52
50
  </Avatar.Root>
53
51
  );
@@ -59,7 +57,7 @@ export type MessageHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'div'
59
57
 
60
58
  export const MessageHeading = ({ children, classNames, timestamp, authorName, ...props }: MessageHeadingProps) => {
61
59
  return (
62
- <div role='none' {...props} className={mx('flex gap-2 items-start', classNames)}>
60
+ <div {...props} className={mx('flex gap-2 items-start', classNames)}>
63
61
  <p className='grow'>
64
62
  <MessageAuthorName authorName={authorName} />
65
63
  {timestamp && <MessageTime timestamp={timestamp} />}
@@ -74,7 +72,7 @@ export type MessageAuthorNameProps = Pick<MessageMetadata, 'authorName'>;
74
72
  export const MessageAuthorName = ({ authorName }: MessageAuthorNameProps) => {
75
73
  const { t } = useTranslation(translationKey);
76
74
  return (
77
- <Avatar.Label classNames='block truncate text-sm text-subdued'>{authorName ?? t('anonymous label')}</Avatar.Label>
75
+ <Avatar.Label classNames='block truncate text-sm text-subdued'>{authorName ?? t('anonymous.label')}</Avatar.Label>
78
76
  );
79
77
  };
80
78
 
@@ -84,7 +82,7 @@ export const MessageTime = ({ timestamp }: MessageTimeProps) => {
84
82
  const { dtLocale } = useTranslation(translationKey);
85
83
  const dt = timestamp ? new Date(timestamp) : undefined;
86
84
  return (
87
- <time className='block text-subdued text-xs pbe-0.5' dateTime={dt?.toISOString()}>
85
+ <time className='block text-subdued text-xs pb-0.5' dateTime={dt?.toISOString()}>
88
86
  {dt ? formatDistanceToNow(dt, { locale: dtLocale, addSuffix: true }) : ''}
89
87
  </time>
90
88
  );
@@ -149,7 +147,7 @@ export const MessageTextbox = ({
149
147
  extensions: [
150
148
  keymap.of(keyBindings({ onSend, onClear })),
151
149
  listener({
152
- onFocus: (focusing) => {
150
+ onFocus: ({ focusing }) => {
153
151
  if (focusing) {
154
152
  onEditorFocus?.();
155
153
  }
@@ -165,9 +163,8 @@ export const MessageTextbox = ({
165
163
  return (
166
164
  <MessageRoot {...{ id, authorId, authorName, authorImgSrc, authorAvatarProps }} continues={false}>
167
165
  <div
168
- role='none'
169
166
  ref={parentRef}
170
- className={mx('plb-0.5 mie-1 rounded-sm', focusRing, disabled && 'opacity-50')}
167
+ className={mx('py-0.5 me-1 rounded-xs dx-focus-ring', disabled && 'opacity-50')}
171
168
  {...focusAttributes}
172
169
  />
173
170
  </MessageRoot>
@@ -5,46 +5,63 @@
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
- import { faker } from '@dxos/random';
14
- import { Button, Icon, useThemeContext } from '@dxos/react-ui';
15
- import { withTheme } from '@dxos/react-ui/testing';
12
+ import { random } from '@dxos/random';
13
+ import { Icon, IconButton, useThemeContext } from '@dxos/react-ui';
14
+ import { useTextEditor } from '@dxos/react-ui-editor';
15
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
16
16
  import {
17
- type Comment,
18
17
  type CommentsOptions,
19
- type Range,
20
- automerge,
18
+ type EditorView,
21
19
  comments,
22
20
  createBasicExtensions,
23
21
  createThemeExtensions,
22
+ documentId,
24
23
  listener,
25
24
  scrollThreadIntoView,
26
- useComments,
27
- useTextEditor,
28
- } from '@dxos/react-ui-editor';
29
- import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
25
+ setComments,
26
+ } from '@dxos/ui-editor';
27
+ import { type Comment, type Range } from '@dxos/ui-editor/types';
28
+ import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
29
+
30
+ import { translations } from '#translations';
30
31
 
31
32
  import { MessageBody, MessageHeading, MessageRoot, MessageTextbox } from '../Message';
32
33
  import { type MessageEntity } from '../testing';
33
- import { translations } from '../translations';
34
-
35
34
  import { Thread } from './Thread';
36
35
 
37
- faker.seed(101);
36
+ random.seed(101);
38
37
 
39
38
  const authorId = PublicKey.random().toHex();
40
39
 
40
+ /**
41
+ * @deprecated This hook will be removed in future versions. Use the new comment sync extension instead.
42
+ * Update comments state field.
43
+ */
44
+ const useComments = (view: EditorView | null | undefined, id: string, comments?: Comment[]) => {
45
+ useEffect(() => {
46
+ if (view) {
47
+ // Check same document.
48
+ // NOTE: Hook might be called before editor state is updated.
49
+ if (id === view.state.facet(documentId)) {
50
+ view.dispatch({
51
+ effects: setComments.of({ id, comments: comments ?? [] }),
52
+ });
53
+ }
54
+ }
55
+ });
56
+ };
57
+
41
58
  //
42
59
  // Editor
43
60
  //
44
61
 
45
62
  const Editor: FC<{
46
63
  id?: string;
47
- item: Expando;
64
+ initialValue: string;
48
65
  comments: Comment[];
49
66
  selected?: string;
50
67
  onCreateComment: CommentsOptions['onCreate'];
@@ -53,7 +70,7 @@ const Editor: FC<{
53
70
  onSelectComment: CommentsOptions['onSelect'];
54
71
  }> = ({
55
72
  id = 'test',
56
- item,
73
+ initialValue,
57
74
  selected: selectedValue,
58
75
  comments: commentRanges,
59
76
  onCreateComment,
@@ -67,11 +84,10 @@ const Editor: FC<{
67
84
  const { parentRef, view } = useTextEditor(
68
85
  () => ({
69
86
  id,
70
- initialValue: item.content,
87
+ initialValue: initialValue,
71
88
  extensions: [
72
89
  createBasicExtensions(),
73
90
  createThemeExtensions({ themeMode }),
74
- automerge(createDocAccessor(item, ['content'])),
75
91
  comments({
76
92
  id,
77
93
  onCreate: onCreateComment,
@@ -81,7 +97,7 @@ const Editor: FC<{
81
97
  }),
82
98
  ],
83
99
  }),
84
- [id, item, themeMode],
100
+ [id, initialValue, themeMode],
85
101
  );
86
102
  useComments(view, id, commentRanges);
87
103
  useEffect(() => {
@@ -114,19 +130,21 @@ const StoryThread: FC<{
114
130
  onSelect: () => void;
115
131
  onResolve: () => void;
116
132
  }> = ({ thread, selected, onSelect, onResolve }) => {
117
- const containerRef = useRef<HTMLDivElement>(null);
118
133
  const { themeMode } = useThemeContext();
134
+ const containerRef = useRef<HTMLDivElement>(null);
135
+
119
136
  // TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
120
- const [_count, _setCount] = useState(0);
121
- const rerenderEditor = () => _setCount((count) => count + 1);
137
+ const [state, setState] = useState({});
122
138
  const messageRef = useRef('');
123
139
  const extensions = useMemo(
124
140
  () => [
125
141
  createBasicExtensions({ placeholder: 'Enter comment' }),
126
142
  createThemeExtensions({ themeMode }),
127
- listener({ onChange: (text) => (messageRef.current = text) }),
143
+ listener({
144
+ onChange: ({ text }) => (messageRef.current = text),
145
+ }),
128
146
  ],
129
- [themeMode, _count],
147
+ [themeMode, state],
130
148
  );
131
149
 
132
150
  const [autoFocus, setAutoFocus] = useState(false);
@@ -142,7 +160,7 @@ const StoryThread: FC<{
142
160
 
143
161
  const handleCreateMessage = () => {
144
162
  if (messageRef.current?.length) {
145
- const message = live(Expando, {
163
+ const message = Obj.make(TestSchema.Expando, {
146
164
  timestamp: new Date().toISOString(),
147
165
  sender: { identityKey: authorId },
148
166
  text: messageRef.current,
@@ -151,7 +169,7 @@ const StoryThread: FC<{
151
169
 
152
170
  messageRef.current = '';
153
171
  setAutoFocus(true);
154
- rerenderEditor();
172
+ setState({});
155
173
  }
156
174
  };
157
175
 
@@ -185,20 +203,27 @@ const StoryThread: FC<{
185
203
  onSend={handleCreateMessage}
186
204
  />
187
205
  <Thread.Status />
188
- <Button variant='ghost' classNames='px-1' title='Resolve' onClick={onResolve}>
189
- <Icon icon='ph--check--regular' />
190
- </Button>
206
+ <IconButton
207
+ variant='ghost'
208
+ icon='ph--check--regular'
209
+ iconOnly
210
+ classNames='px-1'
211
+ label='Resolve'
212
+ onClick={onResolve}
213
+ />
191
214
  </div>
192
215
  </Thread.Root>
193
216
  );
194
217
  };
195
218
 
196
- const Sidebar: FC<{
219
+ type SidebarProps = {
197
220
  threads: StoryCommentThread[];
198
221
  selected?: string;
199
222
  onSelect: (thread: string) => void;
200
223
  onResolve: (thread: string) => void;
201
- }> = ({ threads, selected, onSelect, onResolve }) => {
224
+ };
225
+
226
+ const Sidebar = ({ threads, selected, onSelect, onResolve }: SidebarProps) => {
202
227
  // Sort by y-position.
203
228
  const sortedThreads = useMemo(() => {
204
229
  const sorted = [...threads];
@@ -226,13 +251,13 @@ const Sidebar: FC<{
226
251
  // Story container.
227
252
  //
228
253
 
229
- type StoryProps = {
254
+ type DefaultStoryProps = {
230
255
  text?: string;
231
256
  autoCreate?: boolean;
232
257
  };
233
258
 
234
- const DefaultStory = ({ text, autoCreate }: StoryProps) => {
235
- const [item] = useState(createObject(live(Expando, { content: text ?? '' })));
259
+ const DefaultStory = ({ text, autoCreate }: DefaultStoryProps) => {
260
+ const [item] = useState(() => Obj.make(TestSchema.Expando, { content: text ?? '' }));
236
261
  const [threads, setThreads] = useState<StoryCommentThread[]>([]);
237
262
  const [selected, setSelected] = useState<string>();
238
263
 
@@ -251,12 +276,12 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
251
276
  cursor,
252
277
  yPos: location ? Math.floor(location.top) : undefined,
253
278
  messages: autoCreate
254
- ? faker.helpers.multiple(
279
+ ? random.helpers.multiple(
255
280
  () => ({
256
281
  id: PublicKey.random().toHex(),
257
282
  timestamp: new Date().toISOString(),
258
283
  authorId: PublicKey.random().toHex(),
259
- text: faker.lorem.sentence(),
284
+ text: random.lorem.sentence(),
260
285
  }),
261
286
  { count: { min: 1, max: 3 } },
262
287
  )
@@ -324,9 +349,9 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
324
349
 
325
350
  return (
326
351
  <main className='fixed inset-0 grid grid-cols-[1fr_24rem]'>
327
- <div role='none' className='max-bs-full overflow-y-auto p-4'>
352
+ <div className='max-h-full overflow-y-auto p-4'>
328
353
  <Editor
329
- item={item}
354
+ initialValue={item.content}
330
355
  selected={selected}
331
356
  comments={comments}
332
357
  onCreateComment={handleCreateComment}
@@ -336,7 +361,7 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
336
361
  />
337
362
  </div>
338
363
 
339
- <div role='none' className='max-bs-full overflow-y-auto p-4'>
364
+ <div className='max-h-full overflow-y-auto p-4'>
340
365
  <Sidebar
341
366
  threads={visibleThreads}
342
367
  selected={selected}
@@ -352,7 +377,7 @@ const meta = {
352
377
  title: 'ui/react-ui-thread/Comments',
353
378
  component: StoryThread as any,
354
379
  render: DefaultStory,
355
- decorators: [withTheme],
380
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
356
381
  parameters: {
357
382
  translations,
358
383
  layout: 'fullscreen',
@@ -370,7 +395,7 @@ export const Default: Story = {
370
395
  text: str(
371
396
  '# Comments',
372
397
  '',
373
- str(...faker.helpers.multiple(() => [faker.lorem.paragraph({ min: 5, max: 10 }), ''], { count: 20 }).flat()),
398
+ str(...random.helpers.multiple(() => [random.lorem.paragraph({ min: 5, max: 10 }), ''], { count: 20 }).flat()),
374
399
  '',
375
400
  ),
376
401
  },
@@ -6,18 +6,18 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useMemo, useRef, useState } from 'react';
7
7
 
8
8
  import { PublicKey } from '@dxos/keys';
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';
9
+ import { random } from '@dxos/random';
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
+
14
+ import { translations } from '#translations';
13
15
 
14
16
  import { MessageRoot, MessageTextbox } from '../Message';
15
17
  import { type MessageEntity, MessageStoryText } from '../testing';
16
- import { translations } from '../translations';
17
-
18
18
  import { Thread } from './Thread';
19
19
 
20
- faker.seed(1);
20
+ random.seed(1);
21
21
 
22
22
  const DefaultStory = () => {
23
23
  const [pending, setPending] = useState(false);
@@ -28,7 +28,7 @@ const DefaultStory = () => {
28
28
  id: `m${i + 1}`,
29
29
  timestamp: new Date().toISOString(),
30
30
  authorId: [identityKey1.toHex(), identityKey2.toHex()][i % 2],
31
- text: faker.lorem.paragraph(),
31
+ text: random.lorem.paragraph(),
32
32
  })),
33
33
  );
34
34
 
@@ -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,
@@ -2,22 +2,27 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React, { type ComponentPropsWithRef, forwardRef } from 'react';
5
+ import React, { ComponentPropsWithRef, forwardRef, PropsWithChildren } 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
+
10
+ import { translationKey } from '#translations';
9
11
 
10
- import { translationKey } from '../translations';
11
12
  import type { ThreadEntity } from '../types';
12
13
 
13
- // TODO(burdon): Why is this exported?
14
- export const threadLayout = 'is-full place-self-start grid grid-cols-[var(--rail-size)_1fr]';
14
+ // TODO(burdon): Avoid exporting fragments.
15
+ export const threadLayout = 'w-full place-self-start grid grid-cols-[var(--dx-rail-size)_1fr]';
15
16
 
16
17
  //
17
18
  // Root
18
19
  //
19
20
 
20
- type ThreadRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> & ThreadEntity & { current?: boolean };
21
+ type ThreadRootProps = ThemedClassName<
22
+ PropsWithChildren<
23
+ ThreadEntity & { current?: boolean } & Pick<ComponentPropsWithRef<'div'>, 'onClickCapture' | 'onFocusCapture'>
24
+ >
25
+ >;
21
26
 
22
27
  const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
23
28
  ({ current, children, classNames, ...props }, forwardedRef) => {
@@ -30,7 +35,7 @@ const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
30
35
  className={mx(
31
36
  threadLayout,
32
37
  hoverableFocusedWithinControls,
33
- 'bg-[var(--surface-bg)] current-related attention-surface [--controls-opacity:0]',
38
+ 'bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]',
34
39
  classNames,
35
40
  )}
36
41
  ref={forwardedRef}
@@ -45,24 +50,21 @@ const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
45
50
  // Heading
46
51
  //
47
52
 
48
- type ThreadHeaderProps = ThemedClassName<ComponentPropsWithRef<'div'>> & { detached?: boolean };
53
+ type ThreadHeaderProps = PropsWithChildren<{ detached?: boolean }>;
49
54
 
50
55
  const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
51
- ({ classNames, children, detached, ...props }, forwardedRef) => {
56
+ ({ children, detached, ...props }, forwardedRef) => {
52
57
  return (
53
58
  <>
54
- <div role='none' className='flex items-center justify-center text-description'>
59
+ <div className='flex items-center justify-center text-description'>
55
60
  <Icon icon='ph--caret-double-right--regular' />
56
61
  </div>
57
- <div role='none' className='flex items-center overflow-hidden'>
62
+ <div className='flex items-center overflow-hidden'>
58
63
  <p
59
64
  role='heading'
60
65
  data-testid='thread.heading'
61
66
  {...props}
62
- className={mx(
63
- 'mie-2 text-description font-medium truncate italic',
64
- detached && 'line-through decoration-1',
65
- )}
67
+ className={mx('me-2 text-description font-medium truncate italic', detached && 'line-through decoration-1')}
66
68
  ref={forwardedRef}
67
69
  >
68
70
  {children}
@@ -77,7 +79,7 @@ const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
77
79
  // Status
78
80
  //
79
81
 
80
- type ThreadStatusProps = ThemedClassName<ComponentPropsWithRef<'div'>> & {
82
+ type ThreadStatusProps = ThemedClassName<PropsWithChildren> & {
81
83
  activity?: boolean;
82
84
  };
83
85
 
@@ -88,20 +90,20 @@ const ThreadStatus = forwardRef<HTMLDivElement, ThreadStatusProps>(
88
90
  <div
89
91
  {...props}
90
92
  className={mx(
91
- 'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pie-2 text-xs text-description',
93
+ 'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-description',
92
94
  classNames,
93
95
  )}
94
96
  ref={forwardedRef}
95
97
  >
96
98
  <Icon
97
99
  icon='ph--spinner--bold'
98
- classNames='is-6 bs-4 invisible data-[visible=show]:visible animate-spin-slow'
100
+ classNames='w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow'
99
101
  data-visible={activity ? 'show' : 'hide'}
100
102
  />
101
- <span className='truncate min-is-0' aria-live='polite'>
103
+ <span className='truncate min-w-0' aria-live='polite'>
102
104
  {activity ? children : null}
103
105
  </span>
104
- <span className={mx('text-end', hoverableControlItem)}>{t('enter to send message')}</span>
106
+ <span className={mx('text-end', hoverableControlItem)}>{t('enter-to-send.message')}</span>
105
107
  </div>
106
108
  );
107
109
  },
@@ -113,4 +115,4 @@ export const Thread = {
113
115
  Status: ThreadStatus,
114
116
  };
115
117
 
116
- export type { ThreadRootProps, ThreadHeaderProps as ThreadHeadingProps, ThreadStatusProps };
118
+ export type { ThreadRootProps, ThreadHeaderProps, ThreadStatusProps };
package/src/index.ts CHANGED
@@ -2,9 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { translations } from './translations';
6
-
7
5
  export * from './Thread';
8
6
  export * from './Message';
9
7
  export type * from './types';
10
- export { translations };