@dxos/react-ui-thread 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.0
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/index.mjs +50 -45
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/Message/Message.d.ts +5 -4
- package/dist/types/src/Message/Message.d.ts.map +1 -1
- package/dist/types/src/Message/index.d.ts +2 -2
- package/dist/types/src/Message/index.d.ts.map +1 -1
- package/dist/types/src/Thread/Thread.d.ts +5 -4
- package/dist/types/src/Thread/Thread.d.ts.map +1 -1
- package/dist/types/src/Thread/index.d.ts +2 -2
- package/dist/types/src/Thread/index.d.ts.map +1 -1
- package/dist/types/src/context.d.ts +4 -4
- package/dist/types/src/context.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +7 -7
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing.d.ts +1 -1
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -20
- package/src/Message/Message.stories.tsx +3 -3
- package/src/Message/Message.tsx +44 -52
- package/src/Message/index.ts +2 -2
- package/src/Thread/Thread.stories.tsx +5 -5
- package/src/Thread/Thread.tsx +30 -37
- package/src/Thread/index.ts +2 -2
- package/src/command.ts +3 -3
- package/src/context.ts +2 -2
- package/src/index.ts +7 -7
- package/src/testing.ts +1 -1
- package/src/translations.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-thread",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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",
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"type": "module",
|
|
15
15
|
"imports": {
|
|
16
|
-
"#translations":
|
|
16
|
+
"#translations": {
|
|
17
|
+
"source": "./src/translations.ts",
|
|
18
|
+
"types": "./dist/types/src/translations.d.ts",
|
|
19
|
+
"default": "./dist/lib/translations.mjs"
|
|
20
|
+
}
|
|
17
21
|
},
|
|
18
22
|
"exports": {
|
|
19
23
|
".": {
|
|
@@ -34,35 +38,36 @@
|
|
|
34
38
|
],
|
|
35
39
|
"dependencies": {
|
|
36
40
|
"@codemirror/language": "^6.12.4",
|
|
37
|
-
"@codemirror/state": "^6.7.
|
|
38
|
-
"@codemirror/view": "^6.43.
|
|
39
|
-
"@radix-ui/react-context": "1.1.1",
|
|
41
|
+
"@codemirror/state": "^6.7.6",
|
|
42
|
+
"@codemirror/view": "^6.43.13",
|
|
40
43
|
"date-fns": "^3.6.0",
|
|
41
|
-
"@dxos/echo": "0.
|
|
42
|
-
"@dxos/react-ui-dnd": "0.
|
|
43
|
-
"@dxos/react-ui-editor": "0.
|
|
44
|
-
"@dxos/react-
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
44
|
+
"@dxos/echo": "0.13.0",
|
|
45
|
+
"@dxos/react-ui-dnd": "0.13.0",
|
|
46
|
+
"@dxos/react-ui-editor": "0.13.0",
|
|
47
|
+
"@dxos/react-hooks": "0.13.0",
|
|
48
|
+
"@dxos/react-ui-mosaic": "0.13.0",
|
|
49
|
+
"@dxos/types": "0.13.0",
|
|
50
|
+
"@dxos/ui-editor": "0.13.0",
|
|
51
|
+
"@dxos/util": "0.13.0",
|
|
52
|
+
"@dxos/ui-types": "0.13.0"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
|
-
"@storybook/react-vite": "^10.
|
|
55
|
+
"@storybook/react-vite": "^10.6.0",
|
|
51
56
|
"@types/react": "~19.2.17",
|
|
52
57
|
"@types/react-dom": "~19.2.3",
|
|
53
58
|
"react": "~19.2.7",
|
|
54
59
|
"react-dom": "~19.2.7",
|
|
55
|
-
"vite": "^8.2.
|
|
56
|
-
"@dxos/random": "0.
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/
|
|
60
|
+
"vite": "^8.2.2",
|
|
61
|
+
"@dxos/random": "0.13.0",
|
|
62
|
+
"@dxos/ui-theme": "0.13.0",
|
|
63
|
+
"@dxos/react-ui": "0.13.0",
|
|
64
|
+
"@dxos/schema": "0.13.0"
|
|
60
65
|
},
|
|
61
66
|
"peerDependencies": {
|
|
62
67
|
"react": "~19.2.7",
|
|
63
68
|
"react-dom": "~19.2.7",
|
|
64
|
-
"@dxos/react-ui": "^0.
|
|
65
|
-
"@dxos/ui-theme": "^0.
|
|
69
|
+
"@dxos/react-ui": "^0.13.0",
|
|
70
|
+
"@dxos/ui-theme": "^0.13.0"
|
|
66
71
|
},
|
|
67
72
|
"publishConfig": {
|
|
68
73
|
"access": "public"
|
|
@@ -12,9 +12,9 @@ import { Message as MessageType } from '@dxos/types';
|
|
|
12
12
|
|
|
13
13
|
import { translations } from '#translations';
|
|
14
14
|
|
|
15
|
-
import { createMessages, getStoryMetadata } from '../testing';
|
|
16
|
-
import { Thread } from '../Thread';
|
|
17
|
-
import { Message } from './Message';
|
|
15
|
+
import { createMessages, getStoryMetadata } from '../testing.ts';
|
|
16
|
+
import { Thread } from '../Thread/index.ts';
|
|
17
|
+
import { Message } from './Message.tsx';
|
|
18
18
|
|
|
19
19
|
type StoryArgs = { editable: boolean };
|
|
20
20
|
|
package/src/Message/Message.tsx
CHANGED
|
@@ -17,15 +17,11 @@ import React, {
|
|
|
17
17
|
} from 'react';
|
|
18
18
|
|
|
19
19
|
import { Obj, Ref } from '@dxos/echo';
|
|
20
|
-
import {
|
|
21
|
-
Avatar,
|
|
22
|
-
IconButton,
|
|
23
|
-
type ThemedClassName,
|
|
24
|
-
useOnTransition,
|
|
25
|
-
useThemeContext,
|
|
26
|
-
useTranslation,
|
|
27
|
-
} from '@dxos/react-ui';
|
|
28
20
|
import { type UseTextEditorProps, useTextEditor } from '@dxos/react-ui-editor';
|
|
21
|
+
import * as Avatar from '@dxos/react-ui/Avatar';
|
|
22
|
+
import * as Button from '@dxos/react-ui/Button';
|
|
23
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
24
|
+
import type * as Util from '@dxos/react-ui/Util';
|
|
29
25
|
import { type ContentBlock, type Message as MessageType } from '@dxos/types';
|
|
30
26
|
import { createBasicExtensions, createThemeExtensions, keymap, listener } from '@dxos/ui-editor';
|
|
31
27
|
import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';
|
|
@@ -33,11 +29,11 @@ import { hexToEmoji, hexToHue, isTruthy } from '@dxos/util';
|
|
|
33
29
|
|
|
34
30
|
import { translationKey } from '#translations';
|
|
35
31
|
|
|
36
|
-
import { command } from '../command';
|
|
37
|
-
import { useThreadContext } from '../context';
|
|
38
|
-
import { type MessageMetadata } from '../types';
|
|
32
|
+
import { command } from '../command.ts';
|
|
33
|
+
import { useThreadContext } from '../context.ts';
|
|
34
|
+
import { type MessageMetadata } from '../types.ts';
|
|
39
35
|
|
|
40
|
-
const avatarSize =
|
|
36
|
+
const avatarSize = 'md';
|
|
41
37
|
|
|
42
38
|
const buttonGroupClassNames = 'flex flex-row items-center gap-0.5 pe-2';
|
|
43
39
|
const buttonClassNames = 'p-1! transition-opacity';
|
|
@@ -46,7 +42,7 @@ const buttonClassNames = 'p-1! transition-opacity';
|
|
|
46
42
|
// Root
|
|
47
43
|
//
|
|
48
44
|
|
|
49
|
-
export type MessageRootProps = ThemedClassName<
|
|
45
|
+
export type MessageRootProps = Util.ThemedClassName<
|
|
50
46
|
ComponentPropsWithRef<'div'> & MessageMetadata & Partial<{ continues: boolean; controls: ReactNode }>
|
|
51
47
|
>;
|
|
52
48
|
|
|
@@ -66,30 +62,28 @@ const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
|
|
|
66
62
|
},
|
|
67
63
|
forwardedRef,
|
|
68
64
|
) => {
|
|
69
|
-
// Must wrap the message since Avatar.Label may be used in the content.
|
|
70
65
|
// Columns mirror Thread.Header (avatar/rail · content · controls) so trailing
|
|
71
66
|
// controls align with the thread header's controls.
|
|
72
67
|
return (
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</div>
|
|
89
|
-
<div className='py-1 min-w-0'>{children}</div>
|
|
90
|
-
{controls && <div className='self-start'>{controls}</div>}
|
|
68
|
+
<div
|
|
69
|
+
data-testid='thread.message'
|
|
70
|
+
{...rootProps}
|
|
71
|
+
className={mx('grid grid-cols-[var(--dx-rail-size)_1fr_min-content] w-full', classNames)}
|
|
72
|
+
ref={forwardedRef}
|
|
73
|
+
>
|
|
74
|
+
<div className='flex flex-col items-center gap-2 pt-1'>
|
|
75
|
+
<Avatar.Root
|
|
76
|
+
size={avatarSize}
|
|
77
|
+
hue={authorAvatarProps?.hue || hexToHue(authorId ?? '0')}
|
|
78
|
+
fallback={authorAvatarProps?.emoji || hexToEmoji(authorId ?? '0')}
|
|
79
|
+
src={authorImgSrc}
|
|
80
|
+
label={authorName}
|
|
81
|
+
/>
|
|
82
|
+
{continues && <div className='w-px grow bg-separator' />}
|
|
91
83
|
</div>
|
|
92
|
-
|
|
84
|
+
<div className='py-1 min-w-0'>{children}</div>
|
|
85
|
+
{controls && <div className='self-start'>{controls}</div>}
|
|
86
|
+
</div>
|
|
93
87
|
);
|
|
94
88
|
},
|
|
95
89
|
);
|
|
@@ -100,7 +94,7 @@ MessageRoot.displayName = 'Message.Root';
|
|
|
100
94
|
// Heading
|
|
101
95
|
//
|
|
102
96
|
|
|
103
|
-
export type MessageHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> &
|
|
97
|
+
export type MessageHeadingProps = Util.ThemedClassName<ComponentPropsWithoutRef<'div'>> &
|
|
104
98
|
Pick<MessageMetadata, 'authorName' | 'timestamp'>;
|
|
105
99
|
|
|
106
100
|
const MessageHeading = ({ children, classNames, timestamp, authorName, ...props }: MessageHeadingProps) => {
|
|
@@ -120,11 +114,9 @@ MessageHeading.displayName = 'Message.Heading';
|
|
|
120
114
|
export type MessageAuthorNameProps = Pick<MessageMetadata, 'authorName'>;
|
|
121
115
|
|
|
122
116
|
const MessageAuthorName = ({ authorName }: MessageAuthorNameProps) => {
|
|
123
|
-
const { t } = useTranslation(translationKey);
|
|
117
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
124
118
|
return (
|
|
125
|
-
<
|
|
126
|
-
{authorName ?? t('anonymous.label')}
|
|
127
|
-
</Avatar.Label>
|
|
119
|
+
<span className='block truncate min-w-0 shrink text-sm text-fg-subtle'>{authorName ?? t('anonymous.label')}</span>
|
|
128
120
|
);
|
|
129
121
|
};
|
|
130
122
|
|
|
@@ -133,10 +125,10 @@ MessageAuthorName.displayName = 'Message.AuthorName';
|
|
|
133
125
|
export type MessageTimeProps = Pick<MessageMetadata, 'timestamp'>;
|
|
134
126
|
|
|
135
127
|
const MessageTime = ({ timestamp }: MessageTimeProps) => {
|
|
136
|
-
const { dtLocale } = useTranslation(translationKey);
|
|
128
|
+
const { dtLocale } = Hooks.useTranslation(translationKey);
|
|
137
129
|
const dt = timestamp ? new Date(timestamp) : undefined;
|
|
138
130
|
return (
|
|
139
|
-
<time className='shrink-0 text-
|
|
131
|
+
<time className='shrink-0 text-fg-subtle text-xs' dateTime={dt?.toISOString()}>
|
|
140
132
|
{dt ? format(dt, 'p', { locale: dtLocale }) : ''}
|
|
141
133
|
</time>
|
|
142
134
|
);
|
|
@@ -206,7 +198,7 @@ const TextBlock = ({
|
|
|
206
198
|
editing?: boolean;
|
|
207
199
|
onSave?: (text: string) => void;
|
|
208
200
|
}) => {
|
|
209
|
-
const
|
|
201
|
+
const themeMode = Hooks.useThemeMode();
|
|
210
202
|
const inMemoryContentRef = useRef(block.text);
|
|
211
203
|
|
|
212
204
|
const handleDocumentChange = useCallback((next: string) => {
|
|
@@ -217,7 +209,7 @@ const TextBlock = ({
|
|
|
217
209
|
onSave?.(inMemoryContentRef.current);
|
|
218
210
|
}, [onSave]);
|
|
219
211
|
|
|
220
|
-
useOnTransition(editing, true, false, saveDocumentChange);
|
|
212
|
+
Hooks.useOnTransition(editing, true, false, saveDocumentChange);
|
|
221
213
|
|
|
222
214
|
const { parentRef, focusAttributes, view } = useTextEditor(
|
|
223
215
|
() => ({
|
|
@@ -361,7 +353,7 @@ export type MessageTileProps = {
|
|
|
361
353
|
* the unit rendered by `Thread.Messages`.
|
|
362
354
|
*/
|
|
363
355
|
const MessageTile = ({ message, classNames, continues = true }: MessageTileProps) => {
|
|
364
|
-
const { t } = useTranslation(translationKey);
|
|
356
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
365
357
|
const {
|
|
366
358
|
getMetadata,
|
|
367
359
|
identityDid,
|
|
@@ -407,7 +399,7 @@ const MessageTile = ({ message, classNames, continues = true }: MessageTileProps
|
|
|
407
399
|
showEdit || showAccept || showAcceptChange || showRejectChange || showDelete ? (
|
|
408
400
|
<div className={buttonGroupClassNames}>
|
|
409
401
|
{showEdit && (
|
|
410
|
-
<
|
|
402
|
+
<Button.Root
|
|
411
403
|
data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}
|
|
412
404
|
variant='ghost'
|
|
413
405
|
icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}
|
|
@@ -418,7 +410,7 @@ const MessageTile = ({ message, classNames, continues = true }: MessageTileProps
|
|
|
418
410
|
/>
|
|
419
411
|
)}
|
|
420
412
|
{showAccept && (
|
|
421
|
-
<
|
|
413
|
+
<Button.Root
|
|
422
414
|
data-testid='thread.message.accept'
|
|
423
415
|
variant='ghost'
|
|
424
416
|
icon='ph--check--regular'
|
|
@@ -429,7 +421,7 @@ const MessageTile = ({ message, classNames, continues = true }: MessageTileProps
|
|
|
429
421
|
/>
|
|
430
422
|
)}
|
|
431
423
|
{showAcceptChange && (
|
|
432
|
-
<
|
|
424
|
+
<Button.Root
|
|
433
425
|
data-testid='thread.message.accept-change'
|
|
434
426
|
variant='ghost'
|
|
435
427
|
icon='ph--check--regular'
|
|
@@ -440,7 +432,7 @@ const MessageTile = ({ message, classNames, continues = true }: MessageTileProps
|
|
|
440
432
|
/>
|
|
441
433
|
)}
|
|
442
434
|
{showRejectChange && (
|
|
443
|
-
<
|
|
435
|
+
<Button.Root
|
|
444
436
|
data-testid='thread.message.reject-change'
|
|
445
437
|
variant='ghost'
|
|
446
438
|
icon='ph--x--regular'
|
|
@@ -451,7 +443,7 @@ const MessageTile = ({ message, classNames, continues = true }: MessageTileProps
|
|
|
451
443
|
/>
|
|
452
444
|
)}
|
|
453
445
|
{showDelete && (
|
|
454
|
-
<
|
|
446
|
+
<Button.Root
|
|
455
447
|
data-testid='thread.message.delete'
|
|
456
448
|
variant='ghost'
|
|
457
449
|
icon='ph--x--regular'
|
|
@@ -510,7 +502,7 @@ export type MessageGroupProps = {
|
|
|
510
502
|
* to a single message.
|
|
511
503
|
*/
|
|
512
504
|
const MessageGroup = ({ messages, continues = true, classNames }: MessageGroupProps) => {
|
|
513
|
-
const { t } = useTranslation(translationKey);
|
|
505
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
514
506
|
const { getMetadata, identityDid, editable, onMessageDelete, onAcceptProposal } = useThreadContext('Message.Group');
|
|
515
507
|
const [editing, setEditing] = useState(false);
|
|
516
508
|
|
|
@@ -541,7 +533,7 @@ const MessageGroup = ({ messages, continues = true, classNames }: MessageGroupPr
|
|
|
541
533
|
showEdit || showAccept || showDelete ? (
|
|
542
534
|
<div className={buttonGroupClassNames}>
|
|
543
535
|
{showEdit && (
|
|
544
|
-
<
|
|
536
|
+
<Button.Root
|
|
545
537
|
data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}
|
|
546
538
|
variant='ghost'
|
|
547
539
|
icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}
|
|
@@ -552,7 +544,7 @@ const MessageGroup = ({ messages, continues = true, classNames }: MessageGroupPr
|
|
|
552
544
|
/>
|
|
553
545
|
)}
|
|
554
546
|
{showAccept && (
|
|
555
|
-
<
|
|
547
|
+
<Button.Root
|
|
556
548
|
data-testid='thread.message.accept'
|
|
557
549
|
variant='ghost'
|
|
558
550
|
icon='ph--check--regular'
|
|
@@ -563,7 +555,7 @@ const MessageGroup = ({ messages, continues = true, classNames }: MessageGroupPr
|
|
|
563
555
|
/>
|
|
564
556
|
)}
|
|
565
557
|
{showDelete && (
|
|
566
|
-
<
|
|
558
|
+
<Button.Root
|
|
567
559
|
data-testid='thread.message.delete'
|
|
568
560
|
variant='ghost'
|
|
569
561
|
icon='ph--x--regular'
|
package/src/Message/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export { Message } from './Message';
|
|
5
|
+
export { Message } from './Message.tsx';
|
|
6
6
|
export type {
|
|
7
7
|
MessageAuthorNameProps,
|
|
8
8
|
MessageBodyProps,
|
|
@@ -12,4 +12,4 @@ export type {
|
|
|
12
12
|
MessageTextboxProps,
|
|
13
13
|
MessageTileProps,
|
|
14
14
|
MessageTimeProps,
|
|
15
|
-
} from './Message';
|
|
15
|
+
} from './Message.tsx';
|
|
@@ -11,8 +11,8 @@ import { Message as MessageType } from '@dxos/types';
|
|
|
11
11
|
|
|
12
12
|
import { translations } from '#translations';
|
|
13
13
|
|
|
14
|
-
import { createMessages, getStoryMetadata } from '../testing';
|
|
15
|
-
import { Thread } from './Thread';
|
|
14
|
+
import { createMessages, getStoryMetadata } from '../testing.ts';
|
|
15
|
+
import { Thread } from './Thread.tsx';
|
|
16
16
|
|
|
17
17
|
const IDENTITY = { role: 'user' as const, identityDid: 'did:key:alice', name: 'Alice' };
|
|
18
18
|
|
|
@@ -26,7 +26,7 @@ const DefaultStory = () => {
|
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
28
|
<Thread.Root getMetadata={getStoryMetadata} identityDid={IDENTITY.identityDid} editable onMessageDelete={() => {}}>
|
|
29
|
-
<Thread.Content classNames='grow
|
|
29
|
+
<Thread.Content classNames='dx-grow'>
|
|
30
30
|
<Thread.Messages messages={messages} />
|
|
31
31
|
<Thread.Textbox id='composer' authorId={IDENTITY.identityDid} authorName={IDENTITY.name} onSend={handleSend} />
|
|
32
32
|
<Thread.Status />
|
|
@@ -59,7 +59,7 @@ const MixedSendersStory = () => {
|
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
61
|
<Thread.Root getMetadata={getStoryMetadata} identityDid={IDENTITY.identityDid} editable={false}>
|
|
62
|
-
<Thread.Content classNames='grow
|
|
62
|
+
<Thread.Content classNames='dx-grow'>
|
|
63
63
|
<Thread.Messages messages={messages} />
|
|
64
64
|
</Thread.Content>
|
|
65
65
|
</Thread.Root>
|
|
@@ -96,7 +96,7 @@ const GroupedStory = () => {
|
|
|
96
96
|
|
|
97
97
|
return (
|
|
98
98
|
<Thread.Root getMetadata={getStoryMetadata} identityDid={IDENTITY.identityDid} editable={false}>
|
|
99
|
-
<Thread.Content classNames='grow
|
|
99
|
+
<Thread.Content classNames='dx-grow'>
|
|
100
100
|
<Thread.Messages messages={messages} />
|
|
101
101
|
</Thread.Content>
|
|
102
102
|
</Thread.Root>
|
package/src/Thread/Thread.tsx
CHANGED
|
@@ -19,31 +19,24 @@ import React, {
|
|
|
19
19
|
} from 'react';
|
|
20
20
|
|
|
21
21
|
import { Obj } from '@dxos/echo';
|
|
22
|
-
import {
|
|
23
|
-
type ComposableProps,
|
|
24
|
-
Icon,
|
|
25
|
-
IconButton,
|
|
26
|
-
ScrollArea,
|
|
27
|
-
type ThemedClassName,
|
|
28
|
-
composable,
|
|
29
|
-
composableProps,
|
|
30
|
-
useThemeContext,
|
|
31
|
-
useTranslation,
|
|
32
|
-
} from '@dxos/react-ui';
|
|
33
22
|
import { type DndContainerHandler } from '@dxos/react-ui-dnd';
|
|
34
23
|
import { Mosaic, type MosaicTileProps } from '@dxos/react-ui-mosaic';
|
|
24
|
+
import * as Button from '@dxos/react-ui/Button';
|
|
25
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
26
|
+
import * as Icon from '@dxos/react-ui/Icon';
|
|
27
|
+
import * as ScrollArea from '@dxos/react-ui/ScrollArea';
|
|
28
|
+
import * as Util from '@dxos/react-ui/Util';
|
|
35
29
|
import { type Message as MessageType } from '@dxos/types';
|
|
36
30
|
import { type Extension, createBasicExtensions, createThemeExtensions, listener } from '@dxos/ui-editor';
|
|
37
31
|
import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';
|
|
32
|
+
import { type ComposableProps } from '@dxos/ui-types';
|
|
38
33
|
|
|
39
34
|
import { translationKey } from '#translations';
|
|
40
35
|
|
|
41
|
-
import { command } from '../command';
|
|
42
|
-
import { ThreadContextProvider, useThreadContext } from '../context';
|
|
43
|
-
import { Message } from '../Message';
|
|
44
|
-
import { type MessageMetadata, type ThreadContextValue } from '../types';
|
|
45
|
-
|
|
46
|
-
const getMessageId = (message: MessageType.Message) => Obj.getURI(message);
|
|
36
|
+
import { command } from '../command.ts';
|
|
37
|
+
import { ThreadContextProvider, useThreadContext } from '../context.ts';
|
|
38
|
+
import { Message } from '../Message/index.ts';
|
|
39
|
+
import { type MessageMetadata, type ThreadContextValue } from '../types.ts';
|
|
47
40
|
|
|
48
41
|
//
|
|
49
42
|
// Root
|
|
@@ -115,11 +108,11 @@ type ThreadContentExtra = {
|
|
|
115
108
|
export type ThreadContentProps = ComposableProps<ThreadContentExtra>;
|
|
116
109
|
|
|
117
110
|
/** Visible thread container (the attention surface that hosts header / messages / composer). */
|
|
118
|
-
const ThreadContent = composable<HTMLDivElement, ThreadContentExtra>(
|
|
111
|
+
const ThreadContent = Util.composable<HTMLDivElement, ThreadContentExtra>(
|
|
119
112
|
({ children, current, id, ...props }, forwardedRef) => {
|
|
120
113
|
return (
|
|
121
114
|
<div
|
|
122
|
-
{...composableProps(props, {
|
|
115
|
+
{...Util.composableProps(props, {
|
|
123
116
|
role: 'group',
|
|
124
117
|
classNames: [
|
|
125
118
|
'flex flex-col bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]',
|
|
@@ -163,9 +156,9 @@ export type ThreadHeaderProps = Omit<
|
|
|
163
156
|
* `[rail · 1fr · controls]` grid so the trailing controls align with message-tile
|
|
164
157
|
* controls (which use the same template) — no grid is leaked in from the caller.
|
|
165
158
|
*/
|
|
166
|
-
const ThreadHeader = composable<HTMLDivElement, ThreadHeaderProps>(
|
|
159
|
+
const ThreadHeader = Util.composable<HTMLDivElement, ThreadHeaderProps>(
|
|
167
160
|
({ title, detached, current, controls, onSelect, ...props }, forwardedRef) => {
|
|
168
|
-
const { t } = useTranslation(translationKey);
|
|
161
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
169
162
|
const { focusComposer } = useThreadContext('Thread.Header');
|
|
170
163
|
const handleSelect = useCallback(() => {
|
|
171
164
|
onSelect?.();
|
|
@@ -174,7 +167,7 @@ const ThreadHeader = composable<HTMLDivElement, ThreadHeaderProps>(
|
|
|
174
167
|
|
|
175
168
|
return (
|
|
176
169
|
<div
|
|
177
|
-
{...composableProps(props, {
|
|
170
|
+
{...Util.composableProps(props, {
|
|
178
171
|
classNames: [
|
|
179
172
|
'grid grid-cols-[var(--dx-rail-size)_1fr_min-content] items-center',
|
|
180
173
|
hoverableControls,
|
|
@@ -184,13 +177,13 @@ const ThreadHeader = composable<HTMLDivElement, ThreadHeaderProps>(
|
|
|
184
177
|
ref={forwardedRef}
|
|
185
178
|
>
|
|
186
179
|
<div className='flex items-center justify-center'>
|
|
187
|
-
<
|
|
180
|
+
<Button.Root
|
|
188
181
|
iconOnly
|
|
189
182
|
variant='ghost'
|
|
190
|
-
|
|
183
|
+
size='sm'
|
|
191
184
|
icon='ph--caret-double-right--regular'
|
|
192
185
|
label={t('select-thread.label')}
|
|
193
|
-
classNames='text-
|
|
186
|
+
classNames='text-fg-muted'
|
|
194
187
|
onClick={handleSelect}
|
|
195
188
|
/>
|
|
196
189
|
</div>
|
|
@@ -200,7 +193,7 @@ const ThreadHeader = composable<HTMLDivElement, ThreadHeaderProps>(
|
|
|
200
193
|
data-testid='thread.heading'
|
|
201
194
|
className={mx(
|
|
202
195
|
'me-2 font-medium truncate italic',
|
|
203
|
-
current ? 'text-accent-text' : 'text-
|
|
196
|
+
current ? 'text-accent-text' : 'text-fg-muted',
|
|
204
197
|
detached && 'line-through decoration-1',
|
|
205
198
|
)}
|
|
206
199
|
>
|
|
@@ -297,7 +290,7 @@ const groupMessages = (
|
|
|
297
290
|
if (currentGroup && sameSenderAsGroup && withinGroupWindow) {
|
|
298
291
|
currentGroup.messages.push(message);
|
|
299
292
|
} else {
|
|
300
|
-
currentGroup = { kind: 'group', id:
|
|
293
|
+
currentGroup = { kind: 'group', id: Obj.getURI(message), messages: [message] };
|
|
301
294
|
items.push(currentGroup);
|
|
302
295
|
}
|
|
303
296
|
lastGroupMessage = message;
|
|
@@ -320,7 +313,7 @@ const groupMessages = (
|
|
|
320
313
|
|
|
321
314
|
const ThreadDivider = ({ label }: { label?: string }) =>
|
|
322
315
|
label ? (
|
|
323
|
-
<div className='flex items-center gap-2 px-2 py-2 text-xs text-
|
|
316
|
+
<div className='flex items-center gap-2 px-2 py-2 text-xs text-fg-muted'>
|
|
324
317
|
<div className='h-px grow bg-separator' />
|
|
325
318
|
<span className='shrink-0'>{label}</span>
|
|
326
319
|
<div className='h-px grow bg-separator' />
|
|
@@ -343,7 +336,7 @@ const ThreadItemAdapter = ({ id, data, location, draggable, current, selected }:
|
|
|
343
336
|
</Mosaic.Tile>
|
|
344
337
|
);
|
|
345
338
|
|
|
346
|
-
export type ThreadMessagesProps = ThemedClassName<{
|
|
339
|
+
export type ThreadMessagesProps = Util.ThemedClassName<{
|
|
347
340
|
messages: readonly MessageType.Message[];
|
|
348
341
|
/** Stable id of the owning thread; scopes the Mosaic container so multiple threads don't collide. */
|
|
349
342
|
id?: string;
|
|
@@ -372,7 +365,7 @@ const ThreadMessages = ({
|
|
|
372
365
|
gapDividerMs = DEFAULT_GAP_DIVIDER_MS,
|
|
373
366
|
classNames,
|
|
374
367
|
}: ThreadMessagesProps) => {
|
|
375
|
-
const { dtLocale } = useTranslation(translationKey);
|
|
368
|
+
const { dtLocale } = Hooks.useTranslation(translationKey);
|
|
376
369
|
const [viewport, setViewport] = useState<HTMLElement | null>(null);
|
|
377
370
|
const items = useMemo(
|
|
378
371
|
() => groupMessages(messages.filter(Boolean), { groupWindowMs, dayDivider, gapDividerMs, dtLocale }),
|
|
@@ -393,7 +386,7 @@ const ThreadMessages = ({
|
|
|
393
386
|
currentId={currentId}
|
|
394
387
|
eventHandler={eventHandler}
|
|
395
388
|
>
|
|
396
|
-
<ScrollArea.Root classNames={mx('col-span-2
|
|
389
|
+
<ScrollArea.Root classNames={mx('col-span-2 dx-grow', classNames)} orientation='vertical'>
|
|
397
390
|
<ScrollArea.Viewport ref={setViewport}>
|
|
398
391
|
<Mosaic.VirtualStack
|
|
399
392
|
Tile={ThreadItemAdapter}
|
|
@@ -429,8 +422,8 @@ export type ThreadTextboxProps = MessageMetadata & {
|
|
|
429
422
|
|
|
430
423
|
/** Message composer pinned at the foot of a thread. */
|
|
431
424
|
const ThreadTextbox = ({ placeholder, autoFocus, disabled, extensions, onSend, ...metadata }: ThreadTextboxProps) => {
|
|
432
|
-
const { t } = useTranslation(translationKey);
|
|
433
|
-
const
|
|
425
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
426
|
+
const themeMode = Hooks.useThemeMode();
|
|
434
427
|
const { registerComposerFocus } = useThreadContext('Thread.Textbox');
|
|
435
428
|
const composerRef = useRef<{ focus: () => void } | null>(null);
|
|
436
429
|
const messageRef = useRef('');
|
|
@@ -483,23 +476,23 @@ ThreadTextbox.displayName = 'Thread.Textbox';
|
|
|
483
476
|
// Status
|
|
484
477
|
//
|
|
485
478
|
|
|
486
|
-
export type ThreadStatusProps = ThemedClassName<PropsWithChildren> & {
|
|
479
|
+
export type ThreadStatusProps = Util.ThemedClassName<PropsWithChildren> & {
|
|
487
480
|
activity?: boolean;
|
|
488
481
|
};
|
|
489
482
|
|
|
490
483
|
const ThreadStatus = forwardRef<HTMLDivElement, ThreadStatusProps>(
|
|
491
484
|
({ activity, classNames, children, ...props }, forwardedRef) => {
|
|
492
|
-
const { t } = useTranslation(translationKey);
|
|
485
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
493
486
|
return (
|
|
494
487
|
<div
|
|
495
488
|
{...props}
|
|
496
489
|
className={mx(
|
|
497
|
-
'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-
|
|
490
|
+
'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-fg-muted',
|
|
498
491
|
classNames,
|
|
499
492
|
)}
|
|
500
493
|
ref={forwardedRef}
|
|
501
494
|
>
|
|
502
|
-
<Icon
|
|
495
|
+
<Icon.Icon
|
|
503
496
|
icon='ph--spinner--bold'
|
|
504
497
|
classNames='w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow'
|
|
505
498
|
data-visible={activity ? 'show' : 'hide'}
|
package/src/Thread/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export { Thread } from './Thread';
|
|
5
|
+
export { Thread } from './Thread.tsx';
|
|
6
6
|
export type {
|
|
7
7
|
ThreadContentProps,
|
|
8
8
|
ThreadHeaderProps,
|
|
@@ -10,4 +10,4 @@ export type {
|
|
|
10
10
|
ThreadRootProps,
|
|
11
11
|
ThreadStatusProps,
|
|
12
12
|
ThreadTextboxProps,
|
|
13
|
-
} from './Thread';
|
|
13
|
+
} from './Thread.tsx';
|
package/src/command.ts
CHANGED
|
@@ -37,15 +37,15 @@ const parser = StreamLanguage.define<{ count: number }>({
|
|
|
37
37
|
const styles = HighlightStyle.define([
|
|
38
38
|
{
|
|
39
39
|
tag: tags.tagName,
|
|
40
|
-
class: mx('dx-tag dx-tag
|
|
40
|
+
class: mx('dx-tag dx-tag-inline dx-tag-indigo mx-0.5'),
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
tag: tags.labelName,
|
|
44
|
-
class: mx('dx-tag dx-tag
|
|
44
|
+
class: mx('dx-tag dx-tag-inline dx-tag-blue mx-0.5'),
|
|
45
45
|
},
|
|
46
46
|
]);
|
|
47
47
|
|
|
48
|
-
// Center the inline `.dx-tag` pills on the text line.
|
|
48
|
+
// Center the inline `.dx-tag` pills on the text line. An inline tag is an inline box
|
|
49
49
|
// with vertical padding, so on a CodeMirror line it baseline-aligns and rides
|
|
50
50
|
// high; overriding `vertical-align` recentres it (cf. the `cm-reference-pill`
|
|
51
51
|
// precedent in `@dxos/react-ui-chat`).
|
package/src/context.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { createContext } from '@radix-ui/react-context';
|
|
6
5
|
import { type FC, type ReactNode } from 'react';
|
|
7
6
|
|
|
7
|
+
import { createContext } from '@dxos/react-hooks';
|
|
8
8
|
// Imported so TypeScript can name this type in the emitted .d.ts (ThreadContext → Message).
|
|
9
9
|
// eslint-disable-next-line unused-imports/no-unused-imports
|
|
10
10
|
import { type Message } from '@dxos/types';
|
|
11
11
|
|
|
12
|
-
import { type ThreadContextValue } from './types';
|
|
12
|
+
import { type ThreadContextValue } from './types.ts';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Context provided by `Thread.Root` and consumed by its parts. Extends the
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export { Thread } from './Thread';
|
|
5
|
+
export { Thread } from './Thread/index.ts';
|
|
6
6
|
export type {
|
|
7
7
|
ThreadContentProps,
|
|
8
8
|
ThreadHeaderProps,
|
|
@@ -10,8 +10,8 @@ export type {
|
|
|
10
10
|
ThreadRootProps,
|
|
11
11
|
ThreadStatusProps,
|
|
12
12
|
ThreadTextboxProps,
|
|
13
|
-
} from './Thread';
|
|
14
|
-
export { Message } from './Message';
|
|
13
|
+
} from './Thread/index.ts';
|
|
14
|
+
export { Message } from './Message/index.ts';
|
|
15
15
|
export type {
|
|
16
16
|
MessageAuthorNameProps,
|
|
17
17
|
MessageBodyProps,
|
|
@@ -21,13 +21,13 @@ export type {
|
|
|
21
21
|
MessageTextboxProps,
|
|
22
22
|
MessageTileProps,
|
|
23
23
|
MessageTimeProps,
|
|
24
|
-
} from './Message';
|
|
25
|
-
export { command } from './command';
|
|
26
|
-
export { useThreadContext } from './context';
|
|
24
|
+
} from './Message/index.ts';
|
|
25
|
+
export { command } from './command.ts';
|
|
26
|
+
export { useThreadContext } from './context.ts';
|
|
27
27
|
export type {
|
|
28
28
|
MessageCallbacks,
|
|
29
29
|
MessageMetadata,
|
|
30
30
|
ObjectTileComponent,
|
|
31
31
|
ThreadComponents,
|
|
32
32
|
ThreadContextValue,
|
|
33
|
-
} from './types';
|
|
33
|
+
} from './types.ts';
|
package/src/testing.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { type ValueGenerator, createGenerator } from '@dxos/schema/testing';
|
|
|
8
8
|
import { Message } from '@dxos/types';
|
|
9
9
|
import { hexToFallback } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
import { type MessageMetadata } from './types';
|
|
11
|
+
import { type MessageMetadata } from './types.ts';
|
|
12
12
|
|
|
13
13
|
const generator: ValueGenerator = random as any;
|
|
14
14
|
|