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