@dxos/react-ui-thread 0.8.4-main.c1de068 → 0.8.4-main.c351d160a8
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 +131 -177
- 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 +8 -6
- package/dist/types/src/Message/Message.stories.d.ts.map +1 -1
- package/dist/types/src/Thread/Comments.stories.d.ts +8 -25
- 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 +29 -5
- package/dist/types/src/Thread/Thread.stories.d.ts.map +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 +27 -23
- package/src/Message/Message.stories.tsx +20 -13
- package/src/Message/Message.tsx +16 -15
- package/src/Thread/Comments.stories.tsx +75 -42
- package/src/Thread/Thread.stories.tsx +18 -13
- package/src/Thread/Thread.tsx +11 -14
- package/src/testing.tsx +9 -8
- package/src/translations.ts +1 -1
package/package.json
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-thread",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.c351d160a8",
|
|
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
|
".": {
|
|
17
|
+
"source": "./src/index.ts",
|
|
13
18
|
"types": "./dist/types/src/index.d.ts",
|
|
14
19
|
"browser": "./dist/lib/browser/index.mjs"
|
|
15
20
|
}
|
|
@@ -23,33 +28,32 @@
|
|
|
23
28
|
"src"
|
|
24
29
|
],
|
|
25
30
|
"dependencies": {
|
|
26
|
-
"@preact-signals/safe-react": "^0.9.0",
|
|
27
31
|
"@radix-ui/react-primitive": "2.0.2",
|
|
28
32
|
"date-fns": "^3.3.1",
|
|
29
|
-
"@dxos/react-ui-editor": "0.8.4-main.
|
|
30
|
-
"@dxos/
|
|
33
|
+
"@dxos/react-ui-editor": "0.8.4-main.c351d160a8",
|
|
34
|
+
"@dxos/ui-editor": "0.8.4-main.c351d160a8",
|
|
35
|
+
"@dxos/util": "0.8.4-main.c351d160a8"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
33
|
-
"@types/react": "~
|
|
34
|
-
"@types/react-dom": "~
|
|
35
|
-
"react": "~
|
|
36
|
-
"react-dom": "~
|
|
37
|
-
"vite": "
|
|
38
|
-
"@dxos/echo
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/storybook-utils": "0.8.4-main.c1de068"
|
|
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.c351d160a8",
|
|
44
|
+
"@dxos/keys": "0.8.4-main.c351d160a8",
|
|
45
|
+
"@dxos/random": "0.8.4-main.c351d160a8",
|
|
46
|
+
"@dxos/react-ui": "0.8.4-main.c351d160a8",
|
|
47
|
+
"@dxos/storybook-utils": "0.8.4-main.c351d160a8",
|
|
48
|
+
"@dxos/ui-theme": "0.8.4-main.c351d160a8",
|
|
49
|
+
"@dxos/log": "0.8.4-main.c351d160a8",
|
|
50
|
+
"@dxos/echo-db": "0.8.4-main.c351d160a8"
|
|
47
51
|
},
|
|
48
52
|
"peerDependencies": {
|
|
49
|
-
"react": "~
|
|
50
|
-
"react-dom": "~
|
|
51
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
52
|
-
"@dxos/
|
|
53
|
+
"react": "~19.2.3",
|
|
54
|
+
"react-dom": "~19.2.3",
|
|
55
|
+
"@dxos/react-ui": "0.8.4-main.c351d160a8",
|
|
56
|
+
"@dxos/ui-theme": "0.8.4-main.c351d160a8"
|
|
53
57
|
},
|
|
54
58
|
"publishConfig": {
|
|
55
59
|
"access": "public"
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
6
|
import React, { useState } from 'react';
|
|
8
7
|
|
|
9
8
|
import { PublicKey } from '@dxos/keys';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
|
|
12
11
|
|
|
13
|
-
import {
|
|
12
|
+
import { type MessageEntity, MessageStoryText } from '../testing';
|
|
14
13
|
import { Thread } from '../Thread';
|
|
15
|
-
import { MessageStoryText, type MessageEntity } from '../testing';
|
|
16
14
|
import { translations } from '../translations';
|
|
17
15
|
|
|
16
|
+
import { MessageRoot } from './Message';
|
|
17
|
+
|
|
18
18
|
const DefaultStory = () => {
|
|
19
19
|
const [identityKey] = useState(PublicKey.random());
|
|
20
20
|
const [message] = useState<MessageEntity>({
|
|
@@ -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')} />
|
|
@@ -35,12 +35,19 @@ const DefaultStory = () => {
|
|
|
35
35
|
);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const meta = {
|
|
39
39
|
title: 'ui/react-ui-thread/Message',
|
|
40
|
-
component: MessageRoot,
|
|
40
|
+
component: MessageRoot as any,
|
|
41
41
|
render: DefaultStory,
|
|
42
|
-
decorators: [withTheme, withLayout({
|
|
43
|
-
parameters: {
|
|
44
|
-
|
|
42
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
43
|
+
parameters: {
|
|
44
|
+
layout: 'fullscreen',
|
|
45
|
+
translations,
|
|
46
|
+
},
|
|
47
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
|
|
51
|
+
type Story = StoryObj<typeof meta>;
|
|
45
52
|
|
|
46
|
-
export const Default = {};
|
|
53
|
+
export const Default: Story = {};
|
package/src/Message/Message.tsx
CHANGED
|
@@ -3,23 +3,24 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
|
|
6
|
-
import React, { type ComponentPropsWithRef,
|
|
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 {
|
|
12
|
-
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
|
+
import { hexToEmoji, hexToHue, isTruthy } from '@dxos/util';
|
|
13
13
|
|
|
14
14
|
import { translationKey } from '../translations';
|
|
15
15
|
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
|
+
// TODO(burdon): Show authorName on tooltip.
|
|
23
24
|
export const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
|
|
24
25
|
(
|
|
25
26
|
{ authorImgSrc, authorId, authorName, authorAvatarProps, continues = true, children, classNames, ...rootProps },
|
|
@@ -35,16 +36,16 @@ export const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
|
|
|
35
36
|
className={mx('grid grid-cols-subgrid col-span-2', classNames)}
|
|
36
37
|
ref={forwardedRef}
|
|
37
38
|
>
|
|
38
|
-
<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'>
|
|
39
40
|
<Avatar.Content
|
|
40
41
|
size={avatarSize}
|
|
41
42
|
hue={authorAvatarProps?.hue || hexToHue(authorId ?? '0')}
|
|
42
43
|
fallback={authorAvatarProps?.emoji || hexToEmoji(authorId ?? '0')}
|
|
43
44
|
{...(authorImgSrc && { imgSrc: authorImgSrc })}
|
|
44
45
|
/>
|
|
45
|
-
{continues && <div role='none' className='
|
|
46
|
+
{continues && <div role='none' className='w-px grow bg-separator' />}
|
|
46
47
|
</div>
|
|
47
|
-
<div role='none' className='
|
|
48
|
+
<div role='none' className='py-1 min-w-0'>
|
|
48
49
|
{children}
|
|
49
50
|
</div>
|
|
50
51
|
</div>
|
|
@@ -83,7 +84,7 @@ export const MessageTime = ({ timestamp }: MessageTimeProps) => {
|
|
|
83
84
|
const { dtLocale } = useTranslation(translationKey);
|
|
84
85
|
const dt = timestamp ? new Date(timestamp) : undefined;
|
|
85
86
|
return (
|
|
86
|
-
<time className='block text-subdued text-xs
|
|
87
|
+
<time className='block text-subdued text-xs pb-0.5' dateTime={dt?.toISOString()}>
|
|
87
88
|
{dt ? formatDistanceToNow(dt, { locale: dtLocale, addSuffix: true }) : ''}
|
|
88
89
|
</time>
|
|
89
90
|
);
|
|
@@ -148,14 +149,14 @@ export const MessageTextbox = ({
|
|
|
148
149
|
extensions: [
|
|
149
150
|
keymap.of(keyBindings({ onSend, onClear })),
|
|
150
151
|
listener({
|
|
151
|
-
onFocus: (focusing) => {
|
|
152
|
+
onFocus: ({ focusing }) => {
|
|
152
153
|
if (focusing) {
|
|
153
154
|
onEditorFocus?.();
|
|
154
155
|
}
|
|
155
156
|
},
|
|
156
157
|
}),
|
|
157
158
|
extensions,
|
|
158
|
-
].filter(
|
|
159
|
+
].filter(isTruthy),
|
|
159
160
|
...editorProps,
|
|
160
161
|
}),
|
|
161
162
|
[id, extensions],
|
|
@@ -166,7 +167,7 @@ export const MessageTextbox = ({
|
|
|
166
167
|
<div
|
|
167
168
|
role='none'
|
|
168
169
|
ref={parentRef}
|
|
169
|
-
className={mx('
|
|
170
|
+
className={mx('py-0.5 me-1 rounded-xs', focusRing, disabled && 'opacity-50')}
|
|
170
171
|
{...focusAttributes}
|
|
171
172
|
/>
|
|
172
173
|
</MessageRoot>
|
|
@@ -2,49 +2,67 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
6
|
import React, { type FC, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
7
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
11
10
|
import { PublicKey } from '@dxos/keys';
|
|
12
|
-
import { live } from '@dxos/live-object';
|
|
13
11
|
import { log } from '@dxos/log';
|
|
14
12
|
import { faker } from '@dxos/random';
|
|
15
|
-
import {
|
|
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';
|
|
30
|
-
import { withTheme } from '@dxos/storybook-utils';
|
|
27
|
+
setComments,
|
|
28
|
+
} from '@dxos/ui-editor';
|
|
29
|
+
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/ui-theme';
|
|
31
30
|
|
|
32
|
-
import { Thread } from './Thread';
|
|
33
31
|
import { MessageBody, MessageHeading, MessageRoot, MessageTextbox } from '../Message';
|
|
34
32
|
import { type MessageEntity } from '../testing';
|
|
35
33
|
import { translations } from '../translations';
|
|
36
34
|
|
|
35
|
+
import { Thread } from './Thread';
|
|
36
|
+
|
|
37
37
|
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];
|
|
@@ -231,8 +257,8 @@ type StoryProps = {
|
|
|
231
257
|
autoCreate?: boolean;
|
|
232
258
|
};
|
|
233
259
|
|
|
234
|
-
const
|
|
235
|
-
const [item] = useState(
|
|
260
|
+
const DefaultStory = ({ text, autoCreate }: StoryProps) => {
|
|
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 Story = ({ 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 Story = ({ 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}
|
|
@@ -348,17 +374,24 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
348
374
|
);
|
|
349
375
|
};
|
|
350
376
|
|
|
351
|
-
|
|
377
|
+
const meta = {
|
|
352
378
|
title: 'ui/react-ui-thread/Comments',
|
|
353
|
-
component: StoryThread,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
parameters: {
|
|
357
|
-
|
|
379
|
+
component: StoryThread as any,
|
|
380
|
+
render: DefaultStory,
|
|
381
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
382
|
+
parameters: {
|
|
383
|
+
translations,
|
|
384
|
+
layout: 'fullscreen',
|
|
385
|
+
},
|
|
386
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
387
|
+
|
|
388
|
+
export default meta;
|
|
389
|
+
|
|
390
|
+
type Story = StoryObj<typeof meta>;
|
|
358
391
|
|
|
359
392
|
const str = (...lines: string[]) => lines.join('\n');
|
|
360
393
|
|
|
361
|
-
export const Default = {
|
|
394
|
+
export const Default: Story = {
|
|
362
395
|
args: {
|
|
363
396
|
text: str(
|
|
364
397
|
'# Comments',
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
6
|
import React, { useMemo, useRef, useState } from 'react';
|
|
8
7
|
|
|
9
8
|
import { PublicKey } from '@dxos/keys';
|
|
10
9
|
import { faker } from '@dxos/random';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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';
|
|
14
13
|
|
|
15
|
-
import { Thread } from './Thread';
|
|
16
14
|
import { MessageRoot, MessageTextbox } from '../Message';
|
|
17
|
-
import {
|
|
15
|
+
import { type MessageEntity, MessageStoryText } from '../testing';
|
|
18
16
|
import { translations } from '../translations';
|
|
19
17
|
|
|
18
|
+
import { Thread } from './Thread';
|
|
19
|
+
|
|
20
20
|
faker.seed(1);
|
|
21
21
|
|
|
22
22
|
const DefaultStory = () => {
|
|
@@ -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}>
|
|
@@ -81,14 +81,19 @@ const DefaultStory = () => {
|
|
|
81
81
|
);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
const meta
|
|
84
|
+
const meta = {
|
|
85
85
|
title: 'ui/react-ui-thread/Thread',
|
|
86
86
|
component: Thread.Root,
|
|
87
87
|
render: DefaultStory,
|
|
88
|
-
decorators: [withTheme, withLayout({
|
|
89
|
-
parameters: {
|
|
90
|
-
|
|
88
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
89
|
+
parameters: {
|
|
90
|
+
layout: 'fullscreen',
|
|
91
|
+
translations,
|
|
92
|
+
},
|
|
93
|
+
} satisfies Meta<typeof Thread.Root>;
|
|
91
94
|
|
|
92
95
|
export default meta;
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
type Story = StoryObj<typeof meta>;
|
|
98
|
+
|
|
99
|
+
export const Default: Story = {};
|
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'
|
|
95
|
+
classNames='w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow'
|
|
98
96
|
data-visible={activity ? 'show' : 'hide'}
|
|
99
|
-
classNames='is-6 bs-4 invisible data-[visible=show]:visible animate-spin-slow'
|
|
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>
|