@dxos/react-ui-thread 0.8.4-main.f9ba587 → 0.8.4-main.fcc0d83b33
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 +134 -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 +1 -3
- 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 +37 -29
- package/src/Message/Message.stories.tsx +21 -14
- package/src/Message/Message.tsx +19 -17
- package/src/Thread/Comments.stories.tsx +84 -53
- package/src/Thread/Thread.stories.tsx +22 -17
- package/src/Thread/Thread.tsx +25 -24
- package/src/index.ts +1 -4
- package/src/testing.tsx +9 -9
- package/src/translations.ts +11 -11
- package/src/types.ts +1 -0
- package/dist/lib/node-esm/index.mjs +0 -268
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
package/package.json
CHANGED
|
@@ -1,57 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-thread",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.fcc0d83b33",
|
|
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",
|
|
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/util": "0.8.4-main.
|
|
38
|
+
"date-fns": "^3.6.0",
|
|
39
|
+
"@dxos/react-ui-editor": "0.8.4-main.fcc0d83b33",
|
|
40
|
+
"@dxos/util": "0.8.4-main.fcc0d83b33",
|
|
41
|
+
"@dxos/ui-editor": "0.8.4-main.fcc0d83b33"
|
|
31
42
|
},
|
|
32
43
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@types/react": "~
|
|
35
|
-
"
|
|
36
|
-
"react": "~
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"@dxos/echo
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/react-ui-theme": "0.8.4-main.f9ba587",
|
|
47
|
-
"@dxos/storybook-utils": "0.8.4-main.f9ba587"
|
|
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.10",
|
|
49
|
+
"@dxos/echo-db": "0.8.4-main.fcc0d83b33",
|
|
50
|
+
"@dxos/echo": "0.8.4-main.fcc0d83b33",
|
|
51
|
+
"@dxos/keys": "0.8.4-main.fcc0d83b33",
|
|
52
|
+
"@dxos/random": "0.8.4-main.fcc0d83b33",
|
|
53
|
+
"@dxos/react-ui": "0.8.4-main.fcc0d83b33",
|
|
54
|
+
"@dxos/ui-theme": "0.8.4-main.fcc0d83b33",
|
|
55
|
+
"@dxos/storybook-utils": "0.8.4-main.fcc0d83b33",
|
|
56
|
+
"@dxos/log": "0.8.4-main.fcc0d83b33"
|
|
48
57
|
},
|
|
49
58
|
"peerDependencies": {
|
|
50
|
-
"
|
|
51
|
-
"react": "~
|
|
52
|
-
"react-
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/react-ui-theme": "0.8.4-main.f9ba587"
|
|
59
|
+
"react": "~19.2.3",
|
|
60
|
+
"react-dom": "~19.2.3",
|
|
61
|
+
"@dxos/react-ui": "0.8.4-main.fcc0d83b33",
|
|
62
|
+
"@dxos/ui-theme": "0.8.4-main.fcc0d83b33"
|
|
55
63
|
},
|
|
56
64
|
"publishConfig": {
|
|
57
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,23 +3,25 @@
|
|
|
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 },
|
|
@@ -35,16 +37,16 @@ export const MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(
|
|
|
35
37
|
className={mx('grid grid-cols-subgrid col-span-2', classNames)}
|
|
36
38
|
ref={forwardedRef}
|
|
37
39
|
>
|
|
38
|
-
<div role='none' className='flex flex-col items-center gap-2
|
|
40
|
+
<div role='none' className='flex flex-col items-center gap-2 pt-1'>
|
|
39
41
|
<Avatar.Content
|
|
40
42
|
size={avatarSize}
|
|
41
43
|
hue={authorAvatarProps?.hue || hexToHue(authorId ?? '0')}
|
|
42
44
|
fallback={authorAvatarProps?.emoji || hexToEmoji(authorId ?? '0')}
|
|
43
45
|
{...(authorImgSrc && { imgSrc: authorImgSrc })}
|
|
44
46
|
/>
|
|
45
|
-
{continues && <div role='none' className='
|
|
47
|
+
{continues && <div role='none' className='w-px grow bg-separator' />}
|
|
46
48
|
</div>
|
|
47
|
-
<div role='none' className='
|
|
49
|
+
<div role='none' className='py-1 min-w-0'>
|
|
48
50
|
{children}
|
|
49
51
|
</div>
|
|
50
52
|
</div>
|
|
@@ -73,7 +75,7 @@ export type MessageAuthorNameProps = Pick<MessageMetadata, 'authorName'>;
|
|
|
73
75
|
export const MessageAuthorName = ({ authorName }: MessageAuthorNameProps) => {
|
|
74
76
|
const { t } = useTranslation(translationKey);
|
|
75
77
|
return (
|
|
76
|
-
<Avatar.Label classNames='block truncate text-sm text-subdued'>{authorName ?? t('anonymous
|
|
78
|
+
<Avatar.Label classNames='block truncate text-sm text-subdued'>{authorName ?? t('anonymous.label')}</Avatar.Label>
|
|
77
79
|
);
|
|
78
80
|
};
|
|
79
81
|
|
|
@@ -83,7 +85,7 @@ export const MessageTime = ({ timestamp }: MessageTimeProps) => {
|
|
|
83
85
|
const { dtLocale } = useTranslation(translationKey);
|
|
84
86
|
const dt = timestamp ? new Date(timestamp) : undefined;
|
|
85
87
|
return (
|
|
86
|
-
<time className='block text-subdued text-xs
|
|
88
|
+
<time className='block text-subdued text-xs pb-0.5' dateTime={dt?.toISOString()}>
|
|
87
89
|
{dt ? formatDistanceToNow(dt, { locale: dtLocale, addSuffix: true }) : ''}
|
|
88
90
|
</time>
|
|
89
91
|
);
|
|
@@ -148,14 +150,14 @@ export const MessageTextbox = ({
|
|
|
148
150
|
extensions: [
|
|
149
151
|
keymap.of(keyBindings({ onSend, onClear })),
|
|
150
152
|
listener({
|
|
151
|
-
onFocus: (focusing) => {
|
|
153
|
+
onFocus: ({ focusing }) => {
|
|
152
154
|
if (focusing) {
|
|
153
155
|
onEditorFocus?.();
|
|
154
156
|
}
|
|
155
157
|
},
|
|
156
158
|
}),
|
|
157
159
|
extensions,
|
|
158
|
-
].filter(
|
|
160
|
+
].filter(isTruthy),
|
|
159
161
|
...editorProps,
|
|
160
162
|
}),
|
|
161
163
|
[id, extensions],
|
|
@@ -166,7 +168,7 @@ export const MessageTextbox = ({
|
|
|
166
168
|
<div
|
|
167
169
|
role='none'
|
|
168
170
|
ref={parentRef}
|
|
169
|
-
className={mx('
|
|
171
|
+
className={mx('py-0.5 me-1 rounded-xs dx-focus-ring', disabled && 'opacity-50')}
|
|
170
172
|
{...focusAttributes}
|
|
171
173
|
/>
|
|
172
174
|
</MessageRoot>
|
|
@@ -2,50 +2,66 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
7
|
-
import { Check, Trash } from '@phosphor-icons/react';
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import React, { type FC, useEffect, useMemo, useRef, useState } from 'react';
|
|
9
7
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
|
+
import { TestSchema } from '@dxos/echo/testing';
|
|
12
10
|
import { PublicKey } from '@dxos/keys';
|
|
13
|
-
import { live } from '@dxos/live-object';
|
|
14
11
|
import { log } from '@dxos/log';
|
|
15
|
-
import {
|
|
16
|
-
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';
|
|
17
16
|
import {
|
|
18
|
-
type Comment,
|
|
19
17
|
type CommentsOptions,
|
|
20
|
-
type
|
|
21
|
-
automerge,
|
|
18
|
+
type EditorView,
|
|
22
19
|
comments,
|
|
23
20
|
createBasicExtensions,
|
|
24
21
|
createThemeExtensions,
|
|
22
|
+
documentId,
|
|
25
23
|
listener,
|
|
26
24
|
scrollThreadIntoView,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from '@dxos/
|
|
30
|
-
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/
|
|
31
|
-
|
|
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';
|
|
32
31
|
|
|
33
|
-
import { Thread } from './Thread';
|
|
34
32
|
import { MessageBody, MessageHeading, MessageRoot, MessageTextbox } from '../Message';
|
|
35
33
|
import { type MessageEntity } from '../testing';
|
|
36
|
-
import {
|
|
34
|
+
import { Thread } from './Thread';
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
random.seed(101);
|
|
39
37
|
|
|
40
38
|
const authorId = PublicKey.random().toHex();
|
|
41
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
|
+
|
|
42
58
|
//
|
|
43
59
|
// Editor
|
|
44
60
|
//
|
|
45
61
|
|
|
46
62
|
const Editor: FC<{
|
|
47
63
|
id?: string;
|
|
48
|
-
|
|
64
|
+
initialValue: string;
|
|
49
65
|
comments: Comment[];
|
|
50
66
|
selected?: string;
|
|
51
67
|
onCreateComment: CommentsOptions['onCreate'];
|
|
@@ -54,7 +70,7 @@ const Editor: FC<{
|
|
|
54
70
|
onSelectComment: CommentsOptions['onSelect'];
|
|
55
71
|
}> = ({
|
|
56
72
|
id = 'test',
|
|
57
|
-
|
|
73
|
+
initialValue,
|
|
58
74
|
selected: selectedValue,
|
|
59
75
|
comments: commentRanges,
|
|
60
76
|
onCreateComment,
|
|
@@ -68,11 +84,10 @@ const Editor: FC<{
|
|
|
68
84
|
const { parentRef, view } = useTextEditor(
|
|
69
85
|
() => ({
|
|
70
86
|
id,
|
|
71
|
-
initialValue:
|
|
87
|
+
initialValue: initialValue,
|
|
72
88
|
extensions: [
|
|
73
89
|
createBasicExtensions(),
|
|
74
90
|
createThemeExtensions({ themeMode }),
|
|
75
|
-
automerge(createDocAccessor(item, ['content'])),
|
|
76
91
|
comments({
|
|
77
92
|
id,
|
|
78
93
|
onCreate: onCreateComment,
|
|
@@ -82,7 +97,7 @@ const Editor: FC<{
|
|
|
82
97
|
}),
|
|
83
98
|
],
|
|
84
99
|
}),
|
|
85
|
-
[id,
|
|
100
|
+
[id, initialValue, themeMode],
|
|
86
101
|
);
|
|
87
102
|
useComments(view, id, commentRanges);
|
|
88
103
|
useEffect(() => {
|
|
@@ -115,19 +130,21 @@ const StoryThread: FC<{
|
|
|
115
130
|
onSelect: () => void;
|
|
116
131
|
onResolve: () => void;
|
|
117
132
|
}> = ({ thread, selected, onSelect, onResolve }) => {
|
|
118
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
119
133
|
const { themeMode } = useThemeContext();
|
|
134
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
135
|
+
|
|
120
136
|
// TODO(wittjosiah): This is a hack to reset the editor after a message is sent.
|
|
121
|
-
const [
|
|
122
|
-
const rerenderEditor = () => _setCount((count) => count + 1);
|
|
137
|
+
const [state, setState] = useState({});
|
|
123
138
|
const messageRef = useRef('');
|
|
124
139
|
const extensions = useMemo(
|
|
125
140
|
() => [
|
|
126
141
|
createBasicExtensions({ placeholder: 'Enter comment' }),
|
|
127
142
|
createThemeExtensions({ themeMode }),
|
|
128
|
-
listener({
|
|
143
|
+
listener({
|
|
144
|
+
onChange: ({ text }) => (messageRef.current = text),
|
|
145
|
+
}),
|
|
129
146
|
],
|
|
130
|
-
[themeMode,
|
|
147
|
+
[themeMode, state],
|
|
131
148
|
);
|
|
132
149
|
|
|
133
150
|
const [autoFocus, setAutoFocus] = useState(false);
|
|
@@ -143,7 +160,7 @@ const StoryThread: FC<{
|
|
|
143
160
|
|
|
144
161
|
const handleCreateMessage = () => {
|
|
145
162
|
if (messageRef.current?.length) {
|
|
146
|
-
const message =
|
|
163
|
+
const message = Obj.make(TestSchema.Expando, {
|
|
147
164
|
timestamp: new Date().toISOString(),
|
|
148
165
|
sender: { identityKey: authorId },
|
|
149
166
|
text: messageRef.current,
|
|
@@ -152,7 +169,7 @@ const StoryThread: FC<{
|
|
|
152
169
|
|
|
153
170
|
messageRef.current = '';
|
|
154
171
|
setAutoFocus(true);
|
|
155
|
-
|
|
172
|
+
setState({});
|
|
156
173
|
}
|
|
157
174
|
};
|
|
158
175
|
|
|
@@ -162,7 +179,7 @@ const StoryThread: FC<{
|
|
|
162
179
|
<span>id:{thread.id.slice(0, 4)}</span>
|
|
163
180
|
<span>y:{thread.yPos}</span>
|
|
164
181
|
<span className='grow' />
|
|
165
|
-
{!thread.cursor && <
|
|
182
|
+
{!thread.cursor && <Icon icon='ph--trash--regular' />}
|
|
166
183
|
</div>
|
|
167
184
|
|
|
168
185
|
<Thread.Header>
|
|
@@ -186,20 +203,27 @@ const StoryThread: FC<{
|
|
|
186
203
|
onSend={handleCreateMessage}
|
|
187
204
|
/>
|
|
188
205
|
<Thread.Status />
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
|
|
206
|
+
<IconButton
|
|
207
|
+
variant='ghost'
|
|
208
|
+
icon='ph--check--regular'
|
|
209
|
+
iconOnly
|
|
210
|
+
classNames='px-1'
|
|
211
|
+
label='Resolve'
|
|
212
|
+
onClick={onResolve}
|
|
213
|
+
/>
|
|
192
214
|
</div>
|
|
193
215
|
</Thread.Root>
|
|
194
216
|
);
|
|
195
217
|
};
|
|
196
218
|
|
|
197
|
-
|
|
219
|
+
type SidebarProps = {
|
|
198
220
|
threads: StoryCommentThread[];
|
|
199
221
|
selected?: string;
|
|
200
222
|
onSelect: (thread: string) => void;
|
|
201
223
|
onResolve: (thread: string) => void;
|
|
202
|
-
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const Sidebar = ({ threads, selected, onSelect, onResolve }: SidebarProps) => {
|
|
203
227
|
// Sort by y-position.
|
|
204
228
|
const sortedThreads = useMemo(() => {
|
|
205
229
|
const sorted = [...threads];
|
|
@@ -227,13 +251,13 @@ const Sidebar: FC<{
|
|
|
227
251
|
// Story container.
|
|
228
252
|
//
|
|
229
253
|
|
|
230
|
-
type
|
|
254
|
+
type DefaultStoryProps = {
|
|
231
255
|
text?: string;
|
|
232
256
|
autoCreate?: boolean;
|
|
233
257
|
};
|
|
234
258
|
|
|
235
|
-
const
|
|
236
|
-
const [item] = useState(
|
|
259
|
+
const DefaultStory = ({ text, autoCreate }: DefaultStoryProps) => {
|
|
260
|
+
const [item] = useState(() => Obj.make(TestSchema.Expando, { content: text ?? '' }));
|
|
237
261
|
const [threads, setThreads] = useState<StoryCommentThread[]>([]);
|
|
238
262
|
const [selected, setSelected] = useState<string>();
|
|
239
263
|
|
|
@@ -252,12 +276,12 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
252
276
|
cursor,
|
|
253
277
|
yPos: location ? Math.floor(location.top) : undefined,
|
|
254
278
|
messages: autoCreate
|
|
255
|
-
?
|
|
279
|
+
? random.helpers.multiple(
|
|
256
280
|
() => ({
|
|
257
281
|
id: PublicKey.random().toHex(),
|
|
258
282
|
timestamp: new Date().toISOString(),
|
|
259
283
|
authorId: PublicKey.random().toHex(),
|
|
260
|
-
text:
|
|
284
|
+
text: random.lorem.sentence(),
|
|
261
285
|
}),
|
|
262
286
|
{ count: { min: 1, max: 3 } },
|
|
263
287
|
)
|
|
@@ -325,9 +349,9 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
325
349
|
|
|
326
350
|
return (
|
|
327
351
|
<main className='fixed inset-0 grid grid-cols-[1fr_24rem]'>
|
|
328
|
-
<div role='none' className='max-
|
|
352
|
+
<div role='none' className='max-h-full overflow-y-auto p-4'>
|
|
329
353
|
<Editor
|
|
330
|
-
|
|
354
|
+
initialValue={item.content}
|
|
331
355
|
selected={selected}
|
|
332
356
|
comments={comments}
|
|
333
357
|
onCreateComment={handleCreateComment}
|
|
@@ -337,7 +361,7 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
337
361
|
/>
|
|
338
362
|
</div>
|
|
339
363
|
|
|
340
|
-
<div role='none' className='max-
|
|
364
|
+
<div role='none' className='max-h-full overflow-y-auto p-4'>
|
|
341
365
|
<Sidebar
|
|
342
366
|
threads={visibleThreads}
|
|
343
367
|
selected={selected}
|
|
@@ -349,22 +373,29 @@ const Story = ({ text, autoCreate }: StoryProps) => {
|
|
|
349
373
|
);
|
|
350
374
|
};
|
|
351
375
|
|
|
352
|
-
|
|
376
|
+
const meta = {
|
|
353
377
|
title: 'ui/react-ui-thread/Comments',
|
|
354
|
-
component: StoryThread,
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
parameters: {
|
|
358
|
-
|
|
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>;
|
|
359
390
|
|
|
360
391
|
const str = (...lines: string[]) => lines.join('\n');
|
|
361
392
|
|
|
362
|
-
export const Default = {
|
|
393
|
+
export const Default: Story = {
|
|
363
394
|
args: {
|
|
364
395
|
text: str(
|
|
365
396
|
'# Comments',
|
|
366
397
|
'',
|
|
367
|
-
str(...
|
|
398
|
+
str(...random.helpers.multiple(() => [random.lorem.paragraph({ min: 5, max: 10 }), ''], { count: 20 }).flat()),
|
|
368
399
|
'',
|
|
369
400
|
),
|
|
370
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 = {};
|