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