@dxos/react-ui-thread 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae
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 +130 -176
- 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 +1 -1
- package/dist/types/src/Thread/Comments.stories.d.ts +1 -1
- package/dist/types/src/Thread/Comments.stories.d.ts.map +1 -1
- package/dist/types/src/Thread/Thread.d.ts +3 -3
- package/dist/types/src/Thread/Thread.d.ts.map +1 -1
- package/dist/types/src/Thread/Thread.stories.d.ts +1 -1
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -23
- package/src/Message/Message.stories.tsx +4 -4
- package/src/Message/Message.tsx +12 -12
- package/src/Thread/Comments.stories.tsx +58 -32
- package/src/Thread/Thread.stories.tsx +5 -5
- package/src/Thread/Thread.tsx +11 -14
- package/src/testing.tsx +9 -8
- package/src/translations.ts +1 -1
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.c85a9c8dae",
|
|
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
32
|
"date-fns": "^3.3.1",
|
|
30
|
-
"@dxos/react-ui-editor": "0.8.4-main.
|
|
31
|
-
"@dxos/
|
|
33
|
+
"@dxos/react-ui-editor": "0.8.4-main.c85a9c8dae",
|
|
34
|
+
"@dxos/ui-editor": "0.8.4-main.c85a9c8dae",
|
|
35
|
+
"@dxos/util": "0.8.4-main.c85a9c8dae"
|
|
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/
|
|
40
|
-
"@dxos/echo": "0.8.4-main.
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/react-ui-theme": "0.8.4-main.c4373fc"
|
|
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.c85a9c8dae",
|
|
44
|
+
"@dxos/echo-db": "0.8.4-main.c85a9c8dae",
|
|
45
|
+
"@dxos/random": "0.8.4-main.c85a9c8dae",
|
|
46
|
+
"@dxos/log": "0.8.4-main.c85a9c8dae",
|
|
47
|
+
"@dxos/react-ui": "0.8.4-main.c85a9c8dae",
|
|
48
|
+
"@dxos/storybook-utils": "0.8.4-main.c85a9c8dae",
|
|
49
|
+
"@dxos/keys": "0.8.4-main.c85a9c8dae",
|
|
50
|
+
"@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
|
|
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.c85a9c8dae",
|
|
56
|
+
"@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
|
|
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/
|
|
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='
|
|
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,
|
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 { focusRing, 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>
|
|
@@ -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
|
);
|
|
@@ -149,7 +149,7 @@ 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
|
}
|
|
@@ -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', focusRing, 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 {
|
|
9
|
-
import {
|
|
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
|
-
import {
|
|
15
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
13
|
+
import { Icon, IconButton, useThemeContext } from '@dxos/react-ui';
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
} from '@dxos/
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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,
|
|
101
|
+
[id, initialValue, themeMode],
|
|
85
102
|
);
|
|
86
103
|
useComments(view, id, commentRanges);
|
|
87
104
|
useEffect(() => {
|
|
@@ -114,19 +131,21 @@ const StoryThread: FC<{
|
|
|
114
131
|
onSelect: () => void;
|
|
115
132
|
onResolve: () => void;
|
|
116
133
|
}> = ({ thread, selected, onSelect, onResolve }) => {
|
|
117
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
118
134
|
const { themeMode } = useThemeContext();
|
|
135
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
136
|
+
|
|
119
137
|
// TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
|
|
120
|
-
const [
|
|
121
|
-
const rerenderEditor = () => _setCount((count) => count + 1);
|
|
138
|
+
const [state, setState] = useState({});
|
|
122
139
|
const messageRef = useRef('');
|
|
123
140
|
const extensions = useMemo(
|
|
124
141
|
() => [
|
|
125
142
|
createBasicExtensions({ placeholder: 'Enter comment' }),
|
|
126
143
|
createThemeExtensions({ themeMode }),
|
|
127
|
-
listener({
|
|
144
|
+
listener({
|
|
145
|
+
onChange: ({ text }) => (messageRef.current = text),
|
|
146
|
+
}),
|
|
128
147
|
],
|
|
129
|
-
[themeMode,
|
|
148
|
+
[themeMode, state],
|
|
130
149
|
);
|
|
131
150
|
|
|
132
151
|
const [autoFocus, setAutoFocus] = useState(false);
|
|
@@ -142,7 +161,7 @@ const StoryThread: FC<{
|
|
|
142
161
|
|
|
143
162
|
const handleCreateMessage = () => {
|
|
144
163
|
if (messageRef.current?.length) {
|
|
145
|
-
const message =
|
|
164
|
+
const message = Obj.make(TestSchema.Expando, {
|
|
146
165
|
timestamp: new Date().toISOString(),
|
|
147
166
|
sender: { identityKey: authorId },
|
|
148
167
|
text: messageRef.current,
|
|
@@ -151,7 +170,7 @@ const StoryThread: FC<{
|
|
|
151
170
|
|
|
152
171
|
messageRef.current = '';
|
|
153
172
|
setAutoFocus(true);
|
|
154
|
-
|
|
173
|
+
setState({});
|
|
155
174
|
}
|
|
156
175
|
};
|
|
157
176
|
|
|
@@ -185,20 +204,27 @@ const StoryThread: FC<{
|
|
|
185
204
|
onSend={handleCreateMessage}
|
|
186
205
|
/>
|
|
187
206
|
<Thread.Status />
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
207
|
+
<IconButton
|
|
208
|
+
variant='ghost'
|
|
209
|
+
icon='ph--check--regular'
|
|
210
|
+
iconOnly
|
|
211
|
+
classNames='px-1'
|
|
212
|
+
label='Resolve'
|
|
213
|
+
onClick={onResolve}
|
|
214
|
+
/>
|
|
191
215
|
</div>
|
|
192
216
|
</Thread.Root>
|
|
193
217
|
);
|
|
194
218
|
};
|
|
195
219
|
|
|
196
|
-
|
|
220
|
+
type SidebarProps = {
|
|
197
221
|
threads: StoryCommentThread[];
|
|
198
222
|
selected?: string;
|
|
199
223
|
onSelect: (thread: string) => void;
|
|
200
224
|
onResolve: (thread: string) => void;
|
|
201
|
-
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const Sidebar = ({ threads, selected, onSelect, onResolve }: SidebarProps) => {
|
|
202
228
|
// Sort by y-position.
|
|
203
229
|
const sortedThreads = useMemo(() => {
|
|
204
230
|
const sorted = [...threads];
|
|
@@ -232,7 +258,7 @@ type StoryProps = {
|
|
|
232
258
|
};
|
|
233
259
|
|
|
234
260
|
const DefaultStory = ({ text, autoCreate }: StoryProps) => {
|
|
235
|
-
const [item] = useState(
|
|
261
|
+
const [item] = useState(() => Obj.make(TestSchema.Expando, { content: text ?? '' }));
|
|
236
262
|
const [threads, setThreads] = useState<StoryCommentThread[]>([]);
|
|
237
263
|
const [selected, setSelected] = useState<string>();
|
|
238
264
|
|
|
@@ -324,9 +350,9 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
|
|
|
324
350
|
|
|
325
351
|
return (
|
|
326
352
|
<main className='fixed inset-0 grid grid-cols-[1fr_24rem]'>
|
|
327
|
-
<div role='none' className='max-
|
|
353
|
+
<div role='none' className='max-h-full overflow-y-auto p-4'>
|
|
328
354
|
<Editor
|
|
329
|
-
|
|
355
|
+
initialValue={item.content}
|
|
330
356
|
selected={selected}
|
|
331
357
|
comments={comments}
|
|
332
358
|
onCreateComment={handleCreateComment}
|
|
@@ -336,7 +362,7 @@ const DefaultStory = ({ text, autoCreate }: StoryProps) => {
|
|
|
336
362
|
/>
|
|
337
363
|
</div>
|
|
338
364
|
|
|
339
|
-
<div role='none' className='max-
|
|
365
|
+
<div role='none' className='max-h-full overflow-y-auto p-4'>
|
|
340
366
|
<Sidebar
|
|
341
367
|
threads={visibleThreads}
|
|
342
368
|
selected={selected}
|
|
@@ -352,7 +378,7 @@ const meta = {
|
|
|
352
378
|
title: 'ui/react-ui-thread/Comments',
|
|
353
379
|
component: StoryThread as any,
|
|
354
380
|
render: DefaultStory,
|
|
355
|
-
decorators: [withTheme],
|
|
381
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
356
382
|
parameters: {
|
|
357
383
|
translations,
|
|
358
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/
|
|
12
|
-
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/
|
|
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='
|
|
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,
|
package/src/Thread/Thread.tsx
CHANGED
|
@@ -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/
|
|
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
|
|
@@ -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}
|
|
@@ -45,10 +45,10 @@ const ThreadRoot = forwardRef<HTMLDivElement, ThreadRootProps>(
|
|
|
45
45
|
// Heading
|
|
46
46
|
//
|
|
47
47
|
|
|
48
|
-
type ThreadHeaderProps =
|
|
48
|
+
type ThreadHeaderProps = ComponentPropsWithRef<'div'> & { detached?: boolean };
|
|
49
49
|
|
|
50
50
|
const ThreadHeader = forwardRef<HTMLParagraphElement, ThreadHeaderProps>(
|
|
51
|
-
({
|
|
51
|
+
({ children, detached, ...props }, forwardedRef) => {
|
|
52
52
|
return (
|
|
53
53
|
<>
|
|
54
54
|
<div role='none' className='flex items-center justify-center text-description'>
|
|
@@ -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
|
|
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='
|
|
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-
|
|
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
|
|
113
|
+
export type { ThreadRootProps, ThreadHeaderProps, ThreadStatusProps };
|
package/src/testing.tsx
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/
|
|
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
|
-
<
|
|
28
|
+
<IconButton
|
|
29
|
+
icon='ph--x--regular'
|
|
30
|
+
iconOnly
|
|
31
|
+
label='Delete'
|
|
29
32
|
variant='ghost'
|
|
30
|
-
classNames={['p-1.5 min-
|
|
33
|
+
classNames={['p-1.5 min-h-0 me-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>
|
|
@@ -49,7 +50,7 @@ export const MessageStoryBlock = <PartValue,>({ part }: MessagePartProps<PartVal
|
|
|
49
50
|
role='none'
|
|
50
51
|
className={mx('grid grid-cols-subgrid col-span-3', hoverableControls, hoverableFocusedWithinControls)}
|
|
51
52
|
>
|
|
52
|
-
<pre className='font-mono max-
|
|
53
|
+
<pre className='font-mono max-w-full overflow-x-auto col-span-3'>
|
|
53
54
|
<code>{JSON.stringify(part, undefined, 2)}</code>
|
|
54
55
|
</pre>
|
|
55
56
|
</div>
|