@dxos/react-ui-chat 0.8.4-main.1da679c

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.
Files changed (40) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +1 -0
  3. package/dist/lib/browser/index.mjs +351 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/lib/node-esm/index.mjs +353 -0
  7. package/dist/lib/node-esm/index.mjs.map +7 -0
  8. package/dist/lib/node-esm/meta.json +1 -0
  9. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts +34 -0
  10. package/dist/types/src/components/ChatDialog/ChatDialog.d.ts.map +1 -0
  11. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts +39 -0
  12. package/dist/types/src/components/ChatDialog/ChatDialog.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/ChatDialog/index.d.ts +2 -0
  14. package/dist/types/src/components/ChatDialog/index.d.ts.map +1 -0
  15. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts +21 -0
  16. package/dist/types/src/components/ChatEditor/ChatEditor.d.ts.map +1 -0
  17. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts +26 -0
  18. package/dist/types/src/components/ChatEditor/ChatEditor.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/ChatEditor/index.d.ts +3 -0
  20. package/dist/types/src/components/ChatEditor/index.d.ts.map +1 -0
  21. package/dist/types/src/components/ChatEditor/references.d.ts +30 -0
  22. package/dist/types/src/components/ChatEditor/references.d.ts.map +1 -0
  23. package/dist/types/src/components/index.d.ts +3 -0
  24. package/dist/types/src/components/index.d.ts.map +1 -0
  25. package/dist/types/src/index.d.ts +3 -0
  26. package/dist/types/src/index.d.ts.map +1 -0
  27. package/dist/types/src/translations.d.ts +7 -0
  28. package/dist/types/src/translations.d.ts.map +1 -0
  29. package/dist/types/tsconfig.tsbuildinfo +1 -0
  30. package/package.json +75 -0
  31. package/src/components/ChatDialog/ChatDialog.stories.tsx +66 -0
  32. package/src/components/ChatDialog/ChatDialog.tsx +193 -0
  33. package/src/components/ChatDialog/index.ts +5 -0
  34. package/src/components/ChatEditor/ChatEditor.stories.tsx +97 -0
  35. package/src/components/ChatEditor/ChatEditor.tsx +89 -0
  36. package/src/components/ChatEditor/index.ts +7 -0
  37. package/src/components/ChatEditor/references.ts +175 -0
  38. package/src/components/index.ts +6 -0
  39. package/src/index.ts +6 -0
  40. package/src/translations.ts +15 -0
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@dxos/react-ui-chat",
3
+ "version": "0.8.4-main.1da679c",
4
+ "description": "Chat components.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "source": "./src/index.ts",
13
+ "types": "./dist/types/src/index.d.ts",
14
+ "browser": "./dist/lib/browser/index.mjs",
15
+ "node": "./dist/lib/node-esm/index.mjs"
16
+ }
17
+ },
18
+ "types": "dist/types/src/index.d.ts",
19
+ "typesVersions": {
20
+ "*": {}
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "dependencies": {
27
+ "@codemirror/autocomplete": "^6.18.7",
28
+ "@codemirror/language": "^6.11.3",
29
+ "@codemirror/search": "^6.5.11",
30
+ "@codemirror/state": "^6.5.2",
31
+ "@codemirror/view": "^6.38.2",
32
+ "@preact-signals/safe-react": "^0.9.0",
33
+ "@preact/signals-core": "^1.9.0",
34
+ "@radix-ui/react-accordion": "1.2.3",
35
+ "@radix-ui/react-context": "1.1.1",
36
+ "@dxos/async": "0.8.4-main.1da679c",
37
+ "@dxos/debug": "0.8.4-main.1da679c",
38
+ "@dxos/echo-schema": "0.8.4-main.1da679c",
39
+ "@dxos/invariant": "0.8.4-main.1da679c",
40
+ "@dxos/log": "0.8.4-main.1da679c",
41
+ "@dxos/live-object": "0.8.4-main.1da679c",
42
+ "@dxos/react-ui": "0.8.4-main.1da679c",
43
+ "@dxos/react-ui-dnd": "0.8.4-main.1da679c",
44
+ "@dxos/react-ui-components": "0.8.4-main.1da679c",
45
+ "@dxos/react-ui-editor": "0.8.4-main.1da679c",
46
+ "@dxos/react-ui-sfx": "0.8.4-main.1da679c",
47
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.1da679c",
48
+ "@dxos/react-ui-text-tooltip": "0.8.4-main.1da679c",
49
+ "@dxos/react-ui-types": "0.8.4-main.1da679c",
50
+ "@dxos/react-ui-theme": "0.8.4-main.1da679c",
51
+ "@dxos/util": "0.8.4-main.1da679c"
52
+ },
53
+ "devDependencies": {
54
+ "@types/react": "~18.2.0",
55
+ "@types/react-dom": "~18.2.0",
56
+ "effect": "3.17.7",
57
+ "react": "~18.2.0",
58
+ "react-dom": "~18.2.0",
59
+ "vite": "7.1.1",
60
+ "@dxos/echo": "0.8.4-main.1da679c",
61
+ "@dxos/random": "0.8.4-main.1da679c",
62
+ "@dxos/react-client": "0.8.4-main.1da679c",
63
+ "@dxos/storybook-utils": "0.8.4-main.1da679c"
64
+ },
65
+ "peerDependencies": {
66
+ "effect": "^3.13.3",
67
+ "react": "~18.2.0",
68
+ "react-dom": "~18.2.0",
69
+ "@dxos/react-ui": "0.8.4-main.1da679c",
70
+ "@dxos/react-ui-theme": "0.8.4-main.1da679c"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public"
74
+ }
75
+ }
@@ -0,0 +1,66 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
+ import React, { useState } from 'react';
9
+
10
+ import { Input, Toolbar } from '@dxos/react-ui';
11
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
12
+
13
+ import { translations } from '../../translations';
14
+
15
+ import { ChatDialog } from './ChatDialog';
16
+
17
+ const items = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
18
+
19
+ const meta = {
20
+ title: 'ui/react-ui-chat/ChatDialog',
21
+ component: ChatDialog.Root,
22
+ render: (args) => {
23
+ const [open, setOpen] = useState(true);
24
+ const [expanded, setExpanded] = useState(true);
25
+ return (
26
+ <>
27
+ <Toolbar.Root>
28
+ <Toolbar.Button onClick={() => setOpen((open) => !open)}>Open</Toolbar.Button>
29
+ <Toolbar.Button onClick={() => setExpanded((expanded) => !expanded)}>Expand</Toolbar.Button>
30
+ </Toolbar.Root>
31
+
32
+ <ChatDialog.Root
33
+ {...args}
34
+ open={open}
35
+ onOpenChange={setOpen}
36
+ expanded={expanded}
37
+ onExpandedChange={setExpanded}
38
+ >
39
+ <ChatDialog.Header title='Chat' />
40
+ <ChatDialog.Content>
41
+ {items.map((item) => (
42
+ <div key={item} className='pis-4 pbe-1'>
43
+ {item}
44
+ </div>
45
+ ))}
46
+ </ChatDialog.Content>
47
+ <ChatDialog.Footer classNames='px-2 items-center'>
48
+ <Input.Root>
49
+ <Input.TextInput classNames='border-none' placeholder='Test' />
50
+ </Input.Root>
51
+ </ChatDialog.Footer>
52
+ </ChatDialog.Root>
53
+ </>
54
+ );
55
+ },
56
+ decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'flex flex-col' })],
57
+ parameters: {
58
+ translations,
59
+ },
60
+ } satisfies Meta<typeof ChatDialog.Root>;
61
+
62
+ export default meta;
63
+
64
+ type Story = StoryObj<typeof meta>;
65
+
66
+ export const Default: Story = {};
@@ -0,0 +1,193 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+ import React, { type Dispatch, type PropsWithChildren, type SetStateAction, useEffect, useState } from 'react';
7
+
8
+ import { Dialog, Icon, IconButton, type ThemedClassName, useControlledState } from '@dxos/react-ui';
9
+ import { ResizeHandle, type Size, resizeAttributes, sizeStyle } from '@dxos/react-ui-dnd';
10
+ import { mx } from '@dxos/react-ui-theme';
11
+
12
+ const preventDefault = (event: Event) => event.preventDefault();
13
+
14
+ // TODO(burdon): Factor out.
15
+ const Endcap = ({ children }: PropsWithChildren) => {
16
+ return (
17
+ <div className='grid w-[var(--rail-action)] h-[var(--rail-action)] items-center justify-center'>{children}</div>
18
+ );
19
+ };
20
+
21
+ //
22
+ // Context
23
+ //
24
+
25
+ type ChatDialogContextValue = {
26
+ open: boolean;
27
+ setOpen: Dispatch<SetStateAction<boolean>>;
28
+ expanded: boolean;
29
+ setExpanded: Dispatch<SetStateAction<boolean>>;
30
+ size: Size;
31
+ setSize: Dispatch<SetStateAction<Size>>;
32
+ };
33
+
34
+ const [ChatDialogContextProvider, useChatDialogContext] = createContext<ChatDialogContextValue>('ChatDialog');
35
+
36
+ //
37
+ // Root
38
+ //
39
+
40
+ type ChatDialogRootProps = PropsWithChildren<{
41
+ open?: boolean;
42
+ expanded?: boolean;
43
+ onOpenChange?: (open: boolean) => void;
44
+ onExpandedChange?: (expanded: boolean) => void;
45
+ onEscape?: () => void;
46
+ }>;
47
+
48
+ const ChatDialogRoot = ({
49
+ children,
50
+ open: _open = false,
51
+ expanded: expandedParam = false,
52
+ onOpenChange,
53
+ onExpandedChange,
54
+ onEscape,
55
+ }: ChatDialogRootProps) => {
56
+ const [size, setSize] = useState<Size>('min-content');
57
+ const [open, setOpen] = useControlledState<boolean>(_open, onOpenChange);
58
+ const [expanded, setExpanded] = useControlledState<boolean>(expandedParam, onExpandedChange);
59
+
60
+ // NOTE: We set the min size to 5rem (80px), and the header and prompt bar to 40px (i.e., the rail-size) each.
61
+ // The dialog has no vertical padding and has box-content so that when closed it collapses to the size of the header and prompt bar.
62
+ return (
63
+ <ChatDialogContextProvider
64
+ open={open}
65
+ setOpen={setOpen}
66
+ expanded={expanded}
67
+ setExpanded={setExpanded}
68
+ size={size}
69
+ setSize={setSize}
70
+ >
71
+ <Dialog.Root modal={false} open={open} onOpenChange={setOpen}>
72
+ <div role='none' className='dx-dialog__overlay bg-transparent pointer-events-none' data-block-align='end'>
73
+ <Dialog.Content
74
+ inOverlayLayout
75
+ classNames={[
76
+ 'grid grid-rows-[var(--rail-action)_1fr_min-content] p-0 overflow-hidden',
77
+ 'box-content md:is-[35rem] md:max-is-none pointer-events-auto',
78
+ ]}
79
+ onEscapeKeyDown={onEscape}
80
+ onInteractOutside={preventDefault}
81
+ >
82
+ {children}
83
+ </Dialog.Content>
84
+ </div>
85
+ </Dialog.Root>
86
+ </ChatDialogContextProvider>
87
+ );
88
+ };
89
+
90
+ ChatDialogRoot.displayName = 'ChatDialog.Root';
91
+
92
+ //
93
+ // Header
94
+ //
95
+
96
+ type ChatDialogHeaderProps = ThemedClassName<{
97
+ title?: string;
98
+ }>;
99
+
100
+ const ChatDialogHeader = ({ classNames, title }: ChatDialogHeaderProps) => {
101
+ const { expanded, setExpanded } = useChatDialogContext(ChatDialogHeader.displayName);
102
+
103
+ return (
104
+ <div className={mx('grid grid-cols-[var(--rail-action)_1fr_min-content] items-center overflow-hidden', classNames)}>
105
+ <Endcap>
106
+ <Dialog.Close>
107
+ <Icon icon='ph--x--regular' />
108
+ </Dialog.Close>
109
+ </Endcap>
110
+ <Dialog.Title
111
+ classNames='flex w-full justify-center text-sm text-subdued select-none cursor-pointer'
112
+ onClick={() => setExpanded((expanded) => !expanded)}
113
+ >
114
+ {title}
115
+ </Dialog.Title>
116
+ <Endcap>
117
+ <IconButton
118
+ variant='ghost'
119
+ icon='ph--caret-up--regular'
120
+ iconOnly
121
+ classNames={mx('!p-1 [&>svg]:transition [&>svg]:duration-200', expanded && '[&>svg]:rotate-180')}
122
+ label={expanded ? 'Close' : 'Open'}
123
+ onClick={() => setExpanded((expanded) => !expanded)}
124
+ />
125
+ </Endcap>
126
+ </div>
127
+ );
128
+ };
129
+
130
+ ChatDialogHeader.displayName = 'ChatDialog.Header';
131
+
132
+ //
133
+ // Content
134
+ //
135
+
136
+ type ChatDialogContentProps = ThemedClassName<PropsWithChildren>;
137
+
138
+ const ChatDialogContent = ({ children, classNames }: ChatDialogContentProps) => {
139
+ const { expanded, size, setSize } = useChatDialogContext(ChatDialogContent.displayName);
140
+ useEffect(() => {
141
+ setSize(expanded ? 'min-content' : 0);
142
+ }, [expanded]);
143
+
144
+ return (
145
+ <div
146
+ className={mx(
147
+ 'flex flex-col overflow-y-auto border-t border-b border-subduedSeparator',
148
+ 'transition-[block-size] ease-in-out duration-0 [&:not([data-dx-resizing="true"])]:duration-200',
149
+ classNames,
150
+ )}
151
+ style={{
152
+ ...sizeStyle(size, 'vertical', true),
153
+ maxBlockSize: 'calc(100dvh - env(safe-area-inset-bottom) - env(safe-area-inset-top) - 14rem)',
154
+ }}
155
+ {...resizeAttributes}
156
+ >
157
+ <ResizeHandle
158
+ side='block-start'
159
+ iconPosition='center'
160
+ defaultSize='min-content'
161
+ fallbackSize={0}
162
+ minSize={0}
163
+ onSizeChange={setSize}
164
+ />
165
+ {children}
166
+ </div>
167
+ );
168
+ };
169
+
170
+ ChatDialogContent.displayName = 'ChatDialog.Content';
171
+
172
+ //
173
+ // Footer
174
+ //
175
+
176
+ type ChatDialogFooterProps = ThemedClassName<PropsWithChildren>;
177
+
178
+ const ChatDialogFooter = ({ children, classNames }: ChatDialogFooterProps) => {
179
+ return <div className={mx(classNames)}>{children}</div>;
180
+ };
181
+
182
+ ChatDialogFooter.displayName = 'ChatDialog.Footer';
183
+
184
+ //
185
+ // ChatDialog
186
+ //
187
+
188
+ export const ChatDialog = {
189
+ Root: ChatDialogRoot,
190
+ Header: ChatDialogHeader,
191
+ Content: ChatDialogContent,
192
+ Footer: ChatDialogFooter,
193
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './ChatDialog';
@@ -0,0 +1,97 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
+
9
+ import { log } from '@dxos/log';
10
+ import { withTheme } from '@dxos/storybook-utils';
11
+
12
+ import { translations } from '../../translations';
13
+
14
+ import { ChatEditor } from './ChatEditor';
15
+ import { type ReferenceData } from './references';
16
+
17
+ const meta = {
18
+ title: 'ui/react-ui-chat/ChatEditor',
19
+ component: ChatEditor,
20
+ decorators: [withTheme],
21
+ parameters: {
22
+ layout: 'centered',
23
+ translations,
24
+ },
25
+ } satisfies Meta<typeof ChatEditor>;
26
+
27
+ export default meta;
28
+
29
+ const classNames = 'w-[20rem] p-2 border border-separator';
30
+
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ export const Default: Story = {
34
+ args: {
35
+ classNames,
36
+ },
37
+ };
38
+
39
+ export const WithSuggestions: Story = {
40
+ args: {
41
+ classNames,
42
+ onSubmit: (text) => {
43
+ log('onSubmit', { text });
44
+ },
45
+ onSuggest: (text) => {
46
+ const trimmed = text.trim().toLowerCase();
47
+ if (trimmed.length < 2) {
48
+ return [];
49
+ }
50
+
51
+ const suggestions = [
52
+ 'Create a kanban board',
53
+ 'Create a new project',
54
+ 'Find flights to Tokyo',
55
+ "Let's play chess",
56
+ 'Show me Paris on a map',
57
+ ];
58
+
59
+ return suggestions.filter((s) => s.toLowerCase().startsWith(text));
60
+ },
61
+ },
62
+ };
63
+
64
+ const references: ReferenceData[] = [
65
+ {
66
+ uri: 'dxn:echo:@:AAAAAAAA',
67
+ label: 'Meeting Notes',
68
+ },
69
+ {
70
+ uri: 'dxn:echo:@:BBBBBBBB',
71
+ label: 'Project Plan',
72
+ },
73
+ {
74
+ uri: 'dxn:echo:@:CCCCCCCC',
75
+ label: 'Meeting Plan',
76
+ },
77
+ ];
78
+
79
+ export const WithReferences: Story = {
80
+ args: {
81
+ classNames,
82
+ references: {
83
+ provider: {
84
+ getReferences: async ({ query }) => {
85
+ const res = references.filter((i) => i.label.toLowerCase().startsWith(query.toLowerCase()));
86
+ log('getReferences', { query, res });
87
+ return res;
88
+ },
89
+ resolveReference: async ({ uri }) => {
90
+ const res = references.find((i) => i.uri === uri);
91
+ log('resolveReference', { uri, res });
92
+ return res ?? null;
93
+ },
94
+ },
95
+ },
96
+ },
97
+ };
@@ -0,0 +1,89 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Extension } from '@codemirror/state';
6
+ import React, { forwardRef, useImperativeHandle } from 'react';
7
+
8
+ import { type ThemedClassName, useThemeContext } from '@dxos/react-ui';
9
+ import {
10
+ type AutocompleteOptions,
11
+ type BasicExtensionsOptions,
12
+ type UseTextEditorProps,
13
+ autocomplete,
14
+ createBasicExtensions,
15
+ createThemeExtensions,
16
+ useTextEditor,
17
+ } from '@dxos/react-ui-editor';
18
+ import { mx } from '@dxos/react-ui-theme';
19
+ import { isNonNullable } from '@dxos/util';
20
+
21
+ import { type ReferencesOptions, references as referencesExtension } from './references';
22
+
23
+ export interface ChatEditorController {
24
+ focus(): void;
25
+ getText(): string;
26
+ setText(text: string): void;
27
+ }
28
+
29
+ export type ChatEditorProps = ThemedClassName<
30
+ {
31
+ extensions?: Extension;
32
+ references?: ReferencesOptions;
33
+ } & AutocompleteOptions &
34
+ Pick<UseTextEditorProps, 'id' | 'autoFocus'> &
35
+ Pick<BasicExtensionsOptions, 'lineWrapping' | 'placeholder'>
36
+ >;
37
+
38
+ export const ChatEditor = forwardRef<ChatEditorController, ChatEditorProps>(
39
+ (
40
+ { classNames, extensions, references, autoFocus, lineWrapping = false, placeholder, onSubmit, onSuggest, onCancel },
41
+ forwardRef,
42
+ ) => {
43
+ const { themeMode } = useThemeContext();
44
+
45
+ const { parentRef, view } = useTextEditor(
46
+ () => ({
47
+ debug: true,
48
+ autoFocus,
49
+ extensions: [
50
+ createThemeExtensions({ themeMode }),
51
+ autocomplete({ onSubmit, onSuggest, onCancel }),
52
+ references ? referencesExtension({ provider: references.provider }) : [],
53
+ createBasicExtensions({
54
+ bracketMatching: false,
55
+ lineWrapping,
56
+ placeholder,
57
+ }),
58
+ extensions,
59
+ ].filter(isNonNullable),
60
+ }),
61
+ [themeMode, extensions, onSubmit, onSuggest, onCancel],
62
+ );
63
+
64
+ // Expose editor view.
65
+ useImperativeHandle(forwardRef, () => {
66
+ return {
67
+ focus: () => {
68
+ view?.focus();
69
+ },
70
+ getText: () => view?.state.doc.toString() ?? '',
71
+ setText: (text: string) => {
72
+ view?.dispatch({
73
+ changes: {
74
+ from: 0,
75
+ to: view.state.doc.length,
76
+ insert: text,
77
+ },
78
+ selection: {
79
+ anchor: text.length,
80
+ head: text.length,
81
+ },
82
+ });
83
+ },
84
+ };
85
+ }, [view, onSubmit]);
86
+
87
+ return <div ref={parentRef} className={mx('is-full', classNames)} />;
88
+ },
89
+ );
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './references';
6
+
7
+ export * from './ChatEditor';