@dxos/react-ui-thread 0.8.4-main.70d3990 → 0.8.4-main.74a063c4e0
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/dist/lib/browser/index.mjs +139 -185
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/Message/Message.d.ts +2 -2
- package/dist/types/src/Message/Message.d.ts.map +1 -1
- package/dist/types/src/Message/Message.stories.d.ts +11 -11
- package/dist/types/src/Message/Message.stories.d.ts.map +1 -1
- package/dist/types/src/Thread/Comments.stories.d.ts +13 -13
- package/dist/types/src/Thread/Comments.stories.d.ts.map +1 -1
- package/dist/types/src/Thread/Thread.d.ts +25 -11
- package/dist/types/src/Thread/Thread.d.ts.map +1 -1
- package/dist/types/src/Thread/Thread.stories.d.ts +16 -12
- package/dist/types/src/Thread/Thread.stories.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +12 -12
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +1 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -24
- package/src/Message/Message.stories.tsx +4 -5
- package/src/Message/Message.tsx +12 -12
- package/src/Thread/Comments.stories.tsx +49 -28
- package/src/Thread/Thread.stories.tsx +8 -9
- package/src/Thread/Thread.tsx +18 -17
- package/src/testing.tsx +3 -3
- package/src/translations.ts +11 -11
- package/src/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-thread",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.74a063c4e0",
|
|
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":
|
|
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
|
-
"date-fns": "^3.
|
|
30
|
-
"@dxos/react-ui-editor": "0.8.4-main.
|
|
31
|
-
"@dxos/
|
|
32
|
+
"date-fns": "^3.6.0",
|
|
33
|
+
"@dxos/react-ui-editor": "0.8.4-main.74a063c4e0",
|
|
34
|
+
"@dxos/ui-editor": "0.8.4-main.74a063c4e0",
|
|
35
|
+
"@dxos/util": "0.8.4-main.74a063c4e0"
|
|
32
36
|
},
|
|
33
37
|
"devDependencies": {
|
|
34
|
-
"@types/react": "~19.2.
|
|
35
|
-
"@types/react-dom": "~19.2.
|
|
36
|
-
"react": "~19.2.
|
|
37
|
-
"react-dom": "~19.2.
|
|
38
|
-
"vite": "7.1.
|
|
39
|
-
"@dxos/echo": "0.8.4-main.
|
|
40
|
-
"@dxos/keys": "0.8.4-main.
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/echo-db": "0.8.4-main.
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/react-ui-theme": "0.8.4-main.70d3990"
|
|
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.74a063c4e0",
|
|
44
|
+
"@dxos/keys": "0.8.4-main.74a063c4e0",
|
|
45
|
+
"@dxos/log": "0.8.4-main.74a063c4e0",
|
|
46
|
+
"@dxos/echo-db": "0.8.4-main.74a063c4e0",
|
|
47
|
+
"@dxos/random": "0.8.4-main.74a063c4e0",
|
|
48
|
+
"@dxos/react-ui": "0.8.4-main.74a063c4e0",
|
|
49
|
+
"@dxos/storybook-utils": "0.8.4-main.74a063c4e0",
|
|
50
|
+
"@dxos/ui-theme": "0.8.4-main.74a063c4e0"
|
|
48
51
|
},
|
|
49
52
|
"peerDependencies": {
|
|
50
|
-
"react": "
|
|
51
|
-
"react-dom": "
|
|
52
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
53
|
-
"@dxos/
|
|
53
|
+
"react": "~19.2.3",
|
|
54
|
+
"react-dom": "~19.2.3",
|
|
55
|
+
"@dxos/react-ui": "0.8.4-main.74a063c4e0",
|
|
56
|
+
"@dxos/ui-theme": "0.8.4-main.74a063c4e0"
|
|
54
57
|
},
|
|
55
58
|
"publishConfig": {
|
|
56
59
|
"access": "public"
|
|
@@ -6,13 +6,12 @@ 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/
|
|
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';
|
|
14
14
|
import { translations } from '../translations';
|
|
15
|
-
|
|
16
15
|
import { MessageRoot } from './Message';
|
|
17
16
|
|
|
18
17
|
const DefaultStory = () => {
|
|
@@ -25,7 +24,7 @@ const DefaultStory = () => {
|
|
|
25
24
|
});
|
|
26
25
|
|
|
27
26
|
return (
|
|
28
|
-
<div className='
|
|
27
|
+
<div className='mx-auto w-96 overflow-y-auto'>
|
|
29
28
|
<Thread.Root id='t1'>
|
|
30
29
|
<MessageRoot {...message} classNames={[hoverableControls, hoverableFocusedWithinControls]}>
|
|
31
30
|
<MessageStoryText {...message} onDelete={() => console.log('delete')} />
|
|
@@ -39,7 +38,7 @@ const meta = {
|
|
|
39
38
|
title: 'ui/react-ui-thread/Message',
|
|
40
39
|
component: MessageRoot as any,
|
|
41
40
|
render: DefaultStory,
|
|
42
|
-
decorators: [withTheme],
|
|
41
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
43
42
|
parameters: {
|
|
44
43
|
layout: 'fullscreen',
|
|
45
44
|
translations,
|
package/src/Message/Message.tsx
CHANGED
|
@@ -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,
|
|
11
|
-
import {
|
|
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<
|
|
20
|
-
MessageMetadata &
|
|
21
|
-
|
|
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
|
|
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='
|
|
46
|
+
{continues && <div role='none' className='w-px grow bg-separator' />}
|
|
47
47
|
</div>
|
|
48
|
-
<div role='none' className='
|
|
48
|
+
<div role='none' className='py-1 min-w-0'>
|
|
49
49
|
{children}
|
|
50
50
|
</div>
|
|
51
51
|
</div>
|
|
@@ -74,7 +74,7 @@ export type MessageAuthorNameProps = Pick<MessageMetadata, 'authorName'>;
|
|
|
74
74
|
export const MessageAuthorName = ({ authorName }: MessageAuthorNameProps) => {
|
|
75
75
|
const { t } = useTranslation(translationKey);
|
|
76
76
|
return (
|
|
77
|
-
<Avatar.Label classNames='block truncate text-sm text-subdued'>{authorName ?? t('anonymous
|
|
77
|
+
<Avatar.Label classNames='block truncate text-sm text-subdued'>{authorName ?? t('anonymous.label')}</Avatar.Label>
|
|
78
78
|
);
|
|
79
79
|
};
|
|
80
80
|
|
|
@@ -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
|
|
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('
|
|
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,44 +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 {
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
9
10
|
import { PublicKey } from '@dxos/keys';
|
|
10
11
|
import { log } from '@dxos/log';
|
|
11
|
-
import {
|
|
12
|
+
import { random } from '@dxos/random';
|
|
12
13
|
import { Icon, IconButton, useThemeContext } from '@dxos/react-ui';
|
|
13
|
-
import {
|
|
14
|
+
import { useTextEditor } from '@dxos/react-ui-editor';
|
|
15
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
14
16
|
import {
|
|
15
17
|
type Comment,
|
|
16
18
|
type CommentsOptions,
|
|
19
|
+
type EditorView,
|
|
17
20
|
type Range,
|
|
18
21
|
comments,
|
|
19
22
|
createBasicExtensions,
|
|
20
23
|
createThemeExtensions,
|
|
24
|
+
documentId,
|
|
21
25
|
listener,
|
|
22
26
|
scrollThreadIntoView,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from '@dxos/
|
|
26
|
-
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
|
|
27
|
+
setComments,
|
|
28
|
+
} from '@dxos/ui-editor';
|
|
29
|
+
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
|
|
27
30
|
|
|
28
|
-
import { Obj } from '../../../../core/echo/echo/src';
|
|
29
31
|
import { MessageBody, MessageHeading, MessageRoot, MessageTextbox } from '../Message';
|
|
30
32
|
import { type MessageEntity } from '../testing';
|
|
31
33
|
import { translations } from '../translations';
|
|
32
|
-
|
|
33
34
|
import { Thread } from './Thread';
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
random.seed(101);
|
|
36
37
|
|
|
37
38
|
const authorId = PublicKey.random().toHex();
|
|
38
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
|
+
|
|
39
58
|
//
|
|
40
59
|
// Editor
|
|
41
60
|
//
|
|
42
61
|
|
|
43
62
|
const Editor: FC<{
|
|
44
63
|
id?: string;
|
|
45
|
-
|
|
64
|
+
initialValue: string;
|
|
46
65
|
comments: Comment[];
|
|
47
66
|
selected?: string;
|
|
48
67
|
onCreateComment: CommentsOptions['onCreate'];
|
|
@@ -51,7 +70,7 @@ const Editor: FC<{
|
|
|
51
70
|
onSelectComment: CommentsOptions['onSelect'];
|
|
52
71
|
}> = ({
|
|
53
72
|
id = 'test',
|
|
54
|
-
|
|
73
|
+
initialValue,
|
|
55
74
|
selected: selectedValue,
|
|
56
75
|
comments: commentRanges,
|
|
57
76
|
onCreateComment,
|
|
@@ -65,7 +84,7 @@ const Editor: FC<{
|
|
|
65
84
|
const { parentRef, view } = useTextEditor(
|
|
66
85
|
() => ({
|
|
67
86
|
id,
|
|
68
|
-
initialValue:
|
|
87
|
+
initialValue: initialValue,
|
|
69
88
|
extensions: [
|
|
70
89
|
createBasicExtensions(),
|
|
71
90
|
createThemeExtensions({ themeMode }),
|
|
@@ -78,7 +97,7 @@ const Editor: FC<{
|
|
|
78
97
|
}),
|
|
79
98
|
],
|
|
80
99
|
}),
|
|
81
|
-
[id,
|
|
100
|
+
[id, initialValue, themeMode],
|
|
82
101
|
);
|
|
83
102
|
useComments(view, id, commentRanges);
|
|
84
103
|
useEffect(() => {
|
|
@@ -141,7 +160,7 @@ const StoryThread: FC<{
|
|
|
141
160
|
|
|
142
161
|
const handleCreateMessage = () => {
|
|
143
162
|
if (messageRef.current?.length) {
|
|
144
|
-
const message = Obj.make(Expando, {
|
|
163
|
+
const message = Obj.make(TestSchema.Expando, {
|
|
145
164
|
timestamp: new Date().toISOString(),
|
|
146
165
|
sender: { identityKey: authorId },
|
|
147
166
|
text: messageRef.current,
|
|
@@ -188,7 +207,7 @@ const StoryThread: FC<{
|
|
|
188
207
|
variant='ghost'
|
|
189
208
|
icon='ph--check--regular'
|
|
190
209
|
iconOnly
|
|
191
|
-
classNames='
|
|
210
|
+
classNames='px-1'
|
|
192
211
|
label='Resolve'
|
|
193
212
|
onClick={onResolve}
|
|
194
213
|
/>
|
|
@@ -197,12 +216,14 @@ const StoryThread: FC<{
|
|
|
197
216
|
);
|
|
198
217
|
};
|
|
199
218
|
|
|
200
|
-
|
|
219
|
+
type SidebarProps = {
|
|
201
220
|
threads: StoryCommentThread[];
|
|
202
221
|
selected?: string;
|
|
203
222
|
onSelect: (thread: string) => void;
|
|
204
223
|
onResolve: (thread: string) => void;
|
|
205
|
-
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const Sidebar = ({ threads, selected, onSelect, onResolve }: SidebarProps) => {
|
|
206
227
|
// Sort by y-position.
|
|
207
228
|
const sortedThreads = useMemo(() => {
|
|
208
229
|
const sorted = [...threads];
|
|
@@ -230,13 +251,13 @@ const Sidebar: FC<{
|
|
|
230
251
|
// Story container.
|
|
231
252
|
//
|
|
232
253
|
|
|
233
|
-
type
|
|
254
|
+
type DefaultStoryProps = {
|
|
234
255
|
text?: string;
|
|
235
256
|
autoCreate?: boolean;
|
|
236
257
|
};
|
|
237
258
|
|
|
238
|
-
const DefaultStory = ({ text, autoCreate }:
|
|
239
|
-
const [item] = useState(() => Obj.make(Expando, { content: text ?? '' }));
|
|
259
|
+
const DefaultStory = ({ text, autoCreate }: DefaultStoryProps) => {
|
|
260
|
+
const [item] = useState(() => Obj.make(TestSchema.Expando, { content: text ?? '' }));
|
|
240
261
|
const [threads, setThreads] = useState<StoryCommentThread[]>([]);
|
|
241
262
|
const [selected, setSelected] = useState<string>();
|
|
242
263
|
|
|
@@ -255,12 +276,12 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
|
|
|
255
276
|
cursor,
|
|
256
277
|
yPos: location ? Math.floor(location.top) : undefined,
|
|
257
278
|
messages: autoCreate
|
|
258
|
-
?
|
|
279
|
+
? random.helpers.multiple(
|
|
259
280
|
() => ({
|
|
260
281
|
id: PublicKey.random().toHex(),
|
|
261
282
|
timestamp: new Date().toISOString(),
|
|
262
283
|
authorId: PublicKey.random().toHex(),
|
|
263
|
-
text:
|
|
284
|
+
text: random.lorem.sentence(),
|
|
264
285
|
}),
|
|
265
286
|
{ count: { min: 1, max: 3 } },
|
|
266
287
|
)
|
|
@@ -328,9 +349,9 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
|
|
|
328
349
|
|
|
329
350
|
return (
|
|
330
351
|
<main className='fixed inset-0 grid grid-cols-[1fr_24rem]'>
|
|
331
|
-
<div role='none' className='max-
|
|
352
|
+
<div role='none' className='max-h-full overflow-y-auto p-4'>
|
|
332
353
|
<Editor
|
|
333
|
-
|
|
354
|
+
initialValue={item.content}
|
|
334
355
|
selected={selected}
|
|
335
356
|
comments={comments}
|
|
336
357
|
onCreateComment={handleCreateComment}
|
|
@@ -340,7 +361,7 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
|
|
|
340
361
|
/>
|
|
341
362
|
</div>
|
|
342
363
|
|
|
343
|
-
<div role='none' className='max-
|
|
364
|
+
<div role='none' className='max-h-full overflow-y-auto p-4'>
|
|
344
365
|
<Sidebar
|
|
345
366
|
threads={visibleThreads}
|
|
346
367
|
selected={selected}
|
|
@@ -356,7 +377,7 @@ const meta = {
|
|
|
356
377
|
title: 'ui/react-ui-thread/Comments',
|
|
357
378
|
component: StoryThread as any,
|
|
358
379
|
render: DefaultStory,
|
|
359
|
-
decorators: [withTheme],
|
|
380
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
360
381
|
parameters: {
|
|
361
382
|
translations,
|
|
362
383
|
layout: 'fullscreen',
|
|
@@ -374,7 +395,7 @@ export const Default: Story = {
|
|
|
374
395
|
text: str(
|
|
375
396
|
'# Comments',
|
|
376
397
|
'',
|
|
377
|
-
str(...
|
|
398
|
+
str(...random.helpers.multiple(() => [random.lorem.paragraph({ min: 5, max: 10 }), ''], { count: 20 }).flat()),
|
|
378
399
|
'',
|
|
379
400
|
),
|
|
380
401
|
},
|
|
@@ -6,18 +6,17 @@ 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 {
|
|
10
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
11
|
-
import { createBasicExtensions, createThemeExtensions } from '@dxos/
|
|
12
|
-
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/
|
|
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
13
|
|
|
14
14
|
import { MessageRoot, MessageTextbox } from '../Message';
|
|
15
15
|
import { type MessageEntity, MessageStoryText } from '../testing';
|
|
16
16
|
import { translations } from '../translations';
|
|
17
|
-
|
|
18
17
|
import { Thread } from './Thread';
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
random.seed(1);
|
|
21
20
|
|
|
22
21
|
const DefaultStory = () => {
|
|
23
22
|
const [pending, setPending] = useState(false);
|
|
@@ -28,7 +27,7 @@ const DefaultStory = () => {
|
|
|
28
27
|
id: `m${i + 1}`,
|
|
29
28
|
timestamp: new Date().toISOString(),
|
|
30
29
|
authorId: [identityKey1.toHex(), identityKey2.toHex()][i % 2],
|
|
31
|
-
text:
|
|
30
|
+
text: random.lorem.paragraph(),
|
|
32
31
|
})),
|
|
33
32
|
);
|
|
34
33
|
|
|
@@ -59,7 +58,7 @@ const DefaultStory = () => {
|
|
|
59
58
|
};
|
|
60
59
|
|
|
61
60
|
return (
|
|
62
|
-
<div className='
|
|
61
|
+
<div className='mx-auto w-96 overflow-y-auto'>
|
|
63
62
|
<Thread.Root id='t1'>
|
|
64
63
|
{messages.map((message) => (
|
|
65
64
|
<MessageRoot key={message.id} classNames={[hoverableControls, hoverableFocusedWithinControls]} {...message}>
|
|
@@ -85,7 +84,7 @@ const meta = {
|
|
|
85
84
|
title: 'ui/react-ui-thread/Thread',
|
|
86
85
|
component: Thread.Root,
|
|
87
86
|
render: DefaultStory,
|
|
88
|
-
decorators: [withTheme],
|
|
87
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
89
88
|
parameters: {
|
|
90
89
|
layout: 'fullscreen',
|
|
91
90
|
translations,
|
package/src/Thread/Thread.tsx
CHANGED
|
@@ -2,22 +2,26 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import 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/
|
|
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):
|
|
14
|
-
export const threadLayout = '
|
|
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
|
|
18
18
|
//
|
|
19
19
|
|
|
20
|
-
type ThreadRootProps = ThemedClassName<
|
|
20
|
+
type ThreadRootProps = ThemedClassName<
|
|
21
|
+
PropsWithChildren<
|
|
22
|
+
ThreadEntity & { current?: boolean } & Pick<ComponentPropsWithRef<'div'>, 'onClickCapture' | 'onFocusCapture'>
|
|
23
|
+
>
|
|
24
|
+
>;
|
|
21
25
|
|
|
22
26
|
const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
|
|
23
27
|
({ current, children, classNames, ...props }, forwardedRef) => {
|
|
@@ -30,7 +34,7 @@ const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
|
|
|
30
34
|
className={mx(
|
|
31
35
|
threadLayout,
|
|
32
36
|
hoverableFocusedWithinControls,
|
|
33
|
-
'bg-[var(--surface-bg)] current-related attention-surface [--controls-opacity:0]',
|
|
37
|
+
'bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]',
|
|
34
38
|
classNames,
|
|
35
39
|
)}
|
|
36
40
|
ref={forwardedRef}
|
|
@@ -45,7 +49,7 @@ const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
|
|
|
45
49
|
// Heading
|
|
46
50
|
//
|
|
47
51
|
|
|
48
|
-
type ThreadHeaderProps =
|
|
52
|
+
type ThreadHeaderProps = PropsWithChildren<{ detached?: boolean }>;
|
|
49
53
|
|
|
50
54
|
const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
|
|
51
55
|
({ children, detached, ...props }, forwardedRef) => {
|
|
@@ -59,10 +63,7 @@ const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
|
|
|
59
63
|
role='heading'
|
|
60
64
|
data-testid='thread.heading'
|
|
61
65
|
{...props}
|
|
62
|
-
className={mx(
|
|
63
|
-
'mie-2 text-description font-medium truncate italic',
|
|
64
|
-
detached && 'line-through decoration-1',
|
|
65
|
-
)}
|
|
66
|
+
className={mx('me-2 text-description font-medium truncate italic', detached && 'line-through decoration-1')}
|
|
66
67
|
ref={forwardedRef}
|
|
67
68
|
>
|
|
68
69
|
{children}
|
|
@@ -77,7 +78,7 @@ const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
|
|
|
77
78
|
// Status
|
|
78
79
|
//
|
|
79
80
|
|
|
80
|
-
type ThreadStatusProps = ThemedClassName<
|
|
81
|
+
type ThreadStatusProps = ThemedClassName<PropsWithChildren> & {
|
|
81
82
|
activity?: boolean;
|
|
82
83
|
};
|
|
83
84
|
|
|
@@ -88,20 +89,20 @@ const ThreadStatus = forwardRef<HTMLDivElement, ThreadStatusProps>(
|
|
|
88
89
|
<div
|
|
89
90
|
{...props}
|
|
90
91
|
className={mx(
|
|
91
|
-
'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2
|
|
92
|
+
'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-description',
|
|
92
93
|
classNames,
|
|
93
94
|
)}
|
|
94
95
|
ref={forwardedRef}
|
|
95
96
|
>
|
|
96
97
|
<Icon
|
|
97
98
|
icon='ph--spinner--bold'
|
|
98
|
-
classNames='
|
|
99
|
+
classNames='w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow'
|
|
99
100
|
data-visible={activity ? 'show' : 'hide'}
|
|
100
101
|
/>
|
|
101
|
-
<span className='truncate min-
|
|
102
|
+
<span className='truncate min-w-0' aria-live='polite'>
|
|
102
103
|
{activity ? children : null}
|
|
103
104
|
</span>
|
|
104
|
-
<span className={mx('text-end', hoverableControlItem)}>{t('enter
|
|
105
|
+
<span className={mx('text-end', hoverableControlItem)}>{t('enter-to-send.message')}</span>
|
|
105
106
|
</div>
|
|
106
107
|
);
|
|
107
108
|
},
|
|
@@ -113,4 +114,4 @@ export const Thread = {
|
|
|
113
114
|
Status: ThreadStatus,
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
export type { ThreadRootProps, ThreadHeaderProps
|
|
117
|
+
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/
|
|
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-
|
|
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-
|
|
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>
|
package/src/translations.ts
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
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
|
{
|
|
11
11
|
'en-US': {
|
|
12
12
|
[translationKey]: {
|
|
13
|
-
'plugin
|
|
14
|
-
'thread
|
|
15
|
-
'thread
|
|
16
|
-
'delete
|
|
17
|
-
'create
|
|
18
|
-
'message
|
|
19
|
-
'comment
|
|
20
|
-
'settings
|
|
21
|
-
'anonymous
|
|
22
|
-
'enter
|
|
13
|
+
'plugin.name': 'Threads',
|
|
14
|
+
'thread-title.placeholder': 'New thread',
|
|
15
|
+
'thread-title.label': 'Title',
|
|
16
|
+
'delete-thread.label': 'Delete',
|
|
17
|
+
'create-thread.label': 'Create thread',
|
|
18
|
+
'message.placeholder': 'Enter message...',
|
|
19
|
+
'comment.placeholder': 'Enter comment...',
|
|
20
|
+
'settings-standalone.label': 'Enable standalone thread creation',
|
|
21
|
+
'anonymous.label': 'Anonymous',
|
|
22
|
+
'enter-to-send.message': 'Enter to add ⏎',
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
},
|