@tellescope/chat 1.2.5 → 1.3.2

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 (47) hide show
  1. package/lib/cjs/chat.d.ts +11 -14
  2. package/lib/cjs/chat.d.ts.map +1 -1
  3. package/lib/cjs/chat.js +51 -64
  4. package/lib/cjs/chat.js.map +1 -1
  5. package/lib/cjs/components.d.ts +15 -0
  6. package/lib/cjs/components.d.ts.map +1 -0
  7. package/lib/cjs/components.js +76 -0
  8. package/lib/cjs/components.js.map +1 -0
  9. package/lib/cjs/components.native.d.ts +9 -0
  10. package/lib/cjs/components.native.d.ts.map +1 -0
  11. package/lib/cjs/components.native.js +169 -0
  12. package/lib/cjs/components.native.js.map +1 -0
  13. package/lib/cjs/index.d.ts +1 -0
  14. package/lib/cjs/index.d.ts.map +1 -1
  15. package/lib/cjs/index.js +1 -0
  16. package/lib/cjs/index.js.map +1 -1
  17. package/lib/cjs/index.native.d.ts +1 -0
  18. package/lib/cjs/index.native.d.ts.map +1 -1
  19. package/lib/cjs/index.native.js +1 -0
  20. package/lib/cjs/index.native.js.map +1 -1
  21. package/lib/esm/chat.d.ts +11 -14
  22. package/lib/esm/chat.d.ts.map +1 -1
  23. package/lib/esm/chat.js +50 -39
  24. package/lib/esm/chat.js.map +1 -1
  25. package/lib/esm/components.d.ts +15 -0
  26. package/lib/esm/components.d.ts.map +1 -0
  27. package/lib/esm/components.js +49 -0
  28. package/lib/esm/components.js.map +1 -0
  29. package/lib/esm/components.native.d.ts +9 -0
  30. package/lib/esm/components.native.d.ts.map +1 -0
  31. package/lib/esm/components.native.js +165 -0
  32. package/lib/esm/components.native.js.map +1 -0
  33. package/lib/esm/index.d.ts +1 -0
  34. package/lib/esm/index.d.ts.map +1 -1
  35. package/lib/esm/index.js +1 -0
  36. package/lib/esm/index.js.map +1 -1
  37. package/lib/esm/index.native.d.ts +1 -0
  38. package/lib/esm/index.native.d.ts.map +1 -1
  39. package/lib/esm/index.native.js +1 -0
  40. package/lib/esm/index.native.js.map +1 -1
  41. package/lib/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +9 -8
  43. package/src/chat.tsx +75 -93
  44. package/src/components.native.tsx +174 -0
  45. package/src/components.tsx +88 -0
  46. package/src/index.native.ts +2 -1
  47. package/src/index.ts +2 -1
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@tellescope/chat",
3
- "version": "1.2.5",
3
+ "version": "1.3.2",
4
4
  "description": "",
5
5
  "main": "./lib/cjs/index.js",
6
6
  "module": "./lib/esm/index.js",
7
+ "react-native": "./lib/esm/index.js",
7
8
  "types": "./lib/esm/index.d.ts",
8
9
  "scripts": {
9
10
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -33,13 +34,13 @@
33
34
  "@fontsource/roboto": "^4.5.1",
34
35
  "@mui/icons-material": "^5.0.1",
35
36
  "@mui/material": "^5.0.2",
36
- "@tellescope/constants": "^1.2.5",
37
- "@tellescope/react-components": "^1.2.5",
38
- "@tellescope/sdk": "^1.2.5",
39
- "@tellescope/types-client": "^1.2.5",
40
- "@tellescope/types-models": "^1.2.5",
37
+ "@tellescope/constants": "^1.3.2",
38
+ "@tellescope/react-components": "^1.3.2",
39
+ "@tellescope/sdk": "^1.3.2",
40
+ "@tellescope/types-client": "^1.3.2",
41
+ "@tellescope/types-models": "^1.3.2",
41
42
  "@tellescope/types-utilities": "^1.2.2",
42
- "@tellescope/utilities": "^1.2.5",
43
+ "@tellescope/utilities": "^1.3.2",
43
44
  "@typescript-eslint/eslint-plugin": "^4.33.0",
44
45
  "@typescript-eslint/parser": "^4.33.0",
45
46
  "eslint": "^7.32.0",
@@ -50,7 +51,7 @@
50
51
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
51
52
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
52
53
  },
53
- "gitHead": "f1e12f7af228ec1768e3183c16b7164640a4978c",
54
+ "gitHead": "df1bc04efdaa81af6df1ecb189630bb9b83f5ef2",
54
55
  "publishConfig": {
55
56
  "access": "public"
56
57
  }
package/src/chat.tsx CHANGED
@@ -1,16 +1,13 @@
1
- import React, { useState, CSSProperties, useEffect } from "react"
1
+ import React, { useState, CSSProperties, useEffect, useRef } from "react"
2
2
 
3
3
  import {
4
- AsyncIconButton,
5
4
  DisplayPicture,
6
5
  IN_REACT_WEB,
7
6
  List,
8
7
  Flex,
9
8
  Badge,
10
- SendIcon,
11
9
  Styled,
12
10
  Typography,
13
- TextField,
14
11
  LoadingLinear,
15
12
  value_is_loaded,
16
13
  useSession,
@@ -22,6 +19,7 @@ import {
22
19
  ChatRoomDisplayInfo,
23
20
  SecureImage,
24
21
  ImageDimensions,
22
+ SecureVideo,
25
23
  } from "@tellescope/react-components"
26
24
 
27
25
  import {
@@ -52,6 +50,7 @@ import {
52
50
  Session,
53
51
  EnduserSession,
54
52
  } from "@tellescope/sdk"
53
+ import { SendMessage } from "./components"
55
54
 
56
55
  export {
57
56
  user_display_name, // for convenience
@@ -114,6 +113,10 @@ interface MessageStyles {
114
113
  receivedMessageTextStyle?: CSSProperties,
115
114
  sentMessageStyle?: CSSProperties,
116
115
  sentMessageTextStyle?: CSSProperties,
116
+ sentBgColor?: string,
117
+ sentTextColor?: string,
118
+ receivedBgColor?: string,
119
+ receivedTextColor?: string,
117
120
  }
118
121
 
119
122
  interface MessageProps extends MessageStyles {
@@ -124,6 +127,10 @@ interface MessageProps extends MessageStyles {
124
127
  export const Message = ({
125
128
  message,
126
129
  iconSize=30,
130
+ sentBgColor,
131
+ sentTextColor,
132
+ receivedBgColor,
133
+ receivedTextColor,
127
134
  sentMessageStyle=defaultSentStyle,
128
135
  receivedMessageStyle=defaultReceivedStyle,
129
136
  sentMessageTextStyle=defaultSentTextStyle,
@@ -143,6 +150,22 @@ export const Message = ({
143
150
 
144
151
  if (!message.message) {
145
152
  textBGStyle.backgroundColor = undefined
153
+ } else {
154
+ if (message.senderId === chatUserId) {
155
+ if (sentBgColor) {
156
+ textBGStyle.backgroundColor = sentBgColor;
157
+ }
158
+ if (sentTextColor) {
159
+ textBGStyle.color = sentTextColor;
160
+ }
161
+ } else {
162
+ if (receivedBgColor) {
163
+ textBGStyle.backgroundColor = receivedBgColor;
164
+ }
165
+ if (receivedTextColor) {
166
+ textBGStyle.color = receivedTextColor;
167
+ }
168
+ }
146
169
  }
147
170
 
148
171
  const attachments = (
@@ -184,24 +207,38 @@ export const Message = ({
184
207
  export const MessageAttachments = ({ message, chatUserId, imageDimensions } : { message: ChatMessage, chatUserId: string, imageDimensions?: ImageDimensions }) => {
185
208
  if (!message.attachments) return null
186
209
  if (message.attachments.length === 0) return null
187
-
210
+
188
211
  return (
189
212
  <Flex column alignSelf={message.senderId === chatUserId ? "flex-end" : "flex-start"}>
190
213
  {message.attachments.filter(a => a.type === 'image').map(a=> (
191
- <Flex key={a.secureName} style={{ margin: 10 }}>
192
- <SecureImage key={a.secureName} secureName={a.secureName} alt="image attachment" {...imageDimensions} />
214
+ <Flex key={a.secureName} style={{
215
+ marginRight: message.senderId === chatUserId ? 0 : 10,
216
+ marginLeft: message.senderId === chatUserId ? 10 : 0,
217
+ justifyContent: message.senderId === chatUserId ? "flex-end" : "flex-start",
218
+ }}>
219
+ <SecureImage secureName={a.secureName} alt="image attachment" {...imageDimensions} />
220
+ </Flex>
221
+ ))}
222
+ {message.attachments.filter(a => a.type === 'video').map(a=> (
223
+ <Flex key={a.secureName} style={{
224
+ justifyContent: message.senderId === chatUserId ? "flex-end" : "flex-start",
225
+ }}>
226
+ <SecureVideo secureName={a.secureName} {...imageDimensions} />
193
227
  </Flex>
194
228
  ))}
195
229
  </Flex>
196
230
  )
197
231
  }
198
232
 
233
+ export type MessageTheme = { theme?: string }
234
+
199
235
  interface Messages_T extends MessageStyles {
200
236
  resolveSenderName?: (room: ChatRoom) => React.ReactNode;
201
237
  messages: LoadedData<ChatMessage[]>,
202
238
  chatUserId: string,
203
239
  headerProps?: MessagesHeaderProps,
204
240
  imageDimensions?: ImageDimensions,
241
+ markRead?: boolean,
205
242
  }
206
243
  export const MessagesWithHeader = ({
207
244
  resolveSenderName,
@@ -212,7 +249,7 @@ export const MessagesWithHeader = ({
212
249
  style,
213
250
  imageDimensions,
214
251
  ...messageStyles
215
- }: Messages_T & Styled & { Header?: React.JSXElementConstructor<MessagesHeaderProps> }) => (
252
+ }: Omit<Messages_T, 'markRead'> & Styled & { Header?: React.JSXElementConstructor<MessagesHeaderProps> }) => (
216
253
  <LoadingLinear data={messages} render={messages => (
217
254
  <Flex column flex={1} style={{ ...style, overflowY: 'scroll' }}>
218
255
  {Header && <Header {...headerProps}/>}
@@ -232,14 +269,37 @@ export const Messages = ({
232
269
  headerProps,
233
270
  style,
234
271
  imageDimensions,
272
+ markRead,
235
273
  ...messageStyles
236
- }: Messages_T & Styled) => (
237
- <LoadingLinear data={messages} render={messages => (
238
- <List reverse items={messages} style={style} render={(message) => (
239
- <Message message={message} imageDimensions={imageDimensions} {...messageStyles} />
240
- )}/>
241
- )}/>
242
- )
274
+ }: Messages_T & Styled) => {
275
+ const session = useResolvedSession()
276
+ const [, { updateLocalElement: updateRoom }] = useChatRooms()
277
+ const markReadRef = useRef(false)
278
+
279
+ useEffect(() => {
280
+ if (!markRead) return
281
+ if (!value_is_loaded(messages)) return
282
+ if (messages.value.length === 0) return
283
+
284
+ if (markReadRef.current) return
285
+ markReadRef.current = true
286
+
287
+ session.api.chat_rooms.mark_read({ id: messages.value[0].roomId })
288
+ .then(({ updated }) => {
289
+ updateRoom(updated.id, updated)
290
+ })
291
+ .catch(console.error)
292
+ }, [session, markRead, messages, markReadRef, updateRoom])
293
+
294
+
295
+ return (
296
+ <LoadingLinear data={messages} render={messages => (
297
+ <List reverse items={messages} style={style} render={(message) => (
298
+ <Message message={message} imageDimensions={imageDimensions} {...messageStyles} />
299
+ )}/>
300
+ )}/>
301
+ )
302
+ }
243
303
 
244
304
  const defaultSidebarStyle: CSSProperties = {
245
305
  borderRadius: 5,
@@ -351,84 +411,6 @@ export const EndusersConversations = ({ enduserId, ...p } : SidebarInfo & { endu
351
411
  // deprecated while Conversations relies on useResolvedSession
352
412
  export const UsersConversations = ({ userId, ...p } : SidebarInfo & { userId: string }) => <Conversations {...p} rooms={useChatRooms()[0]}/>
353
413
 
354
- interface SendMessage_T {
355
- roomId: string,
356
- onNewMessage?: (m: ChatMessage) => void;
357
- placeholderText?: string;
358
- Icon?: React.ElementType<any>;
359
- style?: CSSProperties;
360
-
361
- // web only
362
- sendOnEnterPress?: boolean,
363
- multiline?: boolean,
364
- maxRows?: number,
365
- }
366
- export const SendMessage = ({
367
- roomId,
368
- Icon=SendIcon,
369
- onNewMessage,
370
- placeholderText="Enter a message",
371
- style={},
372
- sendOnEnterPress,
373
- multiline,
374
- maxRows,
375
- }: SendMessage_T) => {
376
- const [message, setMessage] = useState('')
377
- const [sending, setSending] = useState(false)
378
-
379
- const [disabled, setDisabled] = useState(false)
380
- const [chatFocused, setChatFocused] = React.useState(false)
381
-
382
- const [, { createElement: createMessage }] = useChats(roomId)
383
-
384
- useEffect(() => {
385
- if (!chatFocused) return
386
- if (!sendOnEnterPress) return
387
- if (typeof window === 'undefined') return
388
-
389
- const handleSend = (e: any) => {
390
- if (e.key !== 'Enter') return
391
- setDisabled(true)
392
-
393
- createMessage({ message, roomId })
394
- .then(m => {
395
- setMessage('')
396
- onNewMessage?.(m)
397
- })
398
- .catch(console.error)
399
- .finally(() => setDisabled(false))
400
- }
401
-
402
- window.addEventListener('keypress', handleSend)
403
- return () => { window.removeEventListener('keypress', handleSend) }
404
- }, [sendOnEnterPress, chatFocused, message, roomId])
405
-
406
- return (
407
- <Flex row flex={1} alignContent="center" style={style}>
408
- <Flex column flex={1}>
409
- <TextField variant="outlined" value={message} onChange={setMessage} disabled={sending}
410
- aria-label="Enter a message"
411
- multiline={multiline} maxRows={maxRows}
412
- placeholder={placeholderText}
413
- onFocus={() => setChatFocused(true)}
414
- onBlur={() => setChatFocused(false)}
415
- />
416
- </Flex>
417
- <Flex column alignSelf="center">
418
- <AsyncIconButton label="send" Icon={Icon}
419
- disabled={message === '' || disabled}
420
- action={() => createMessage({ message, roomId })}
421
- onSuccess={m => {
422
- setMessage('')
423
- onNewMessage?.(m)
424
- }}
425
- onChange={setSending}
426
- />
427
- </Flex>
428
- </Flex>
429
- )
430
- }
431
-
432
414
  const defaultSplitChatStyle: CSSProperties = {}
433
415
  interface SplitChat_T {
434
416
  session: EnduserSession | Session,
@@ -0,0 +1,174 @@
1
+ import { launchImageLibrary, launchCamera } from 'react-native-image-picker';
2
+ import { Modal, View, TextInput } from "react-native"
3
+ import { Avatar } from 'react-native-paper'
4
+ import {
5
+ useFileUpload,
6
+ Flex,
7
+ Typography,
8
+ useEnduserSession,
9
+ AsyncIconButton,
10
+ SendIcon,
11
+ LabeledIconButton,
12
+ Button,
13
+ Paper,
14
+ useChats,
15
+ } from "@tellescope/react-components"
16
+ import { useState } from 'react';
17
+
18
+ const CHAT_ICON_SIZE = 35
19
+ const SendImageOrVideo = ({
20
+ onUpload,
21
+ disabled
22
+ } : {
23
+ disabled?: boolean,
24
+ onUpload: (file: { secureName: string }, assetType: 'image' | 'video') => void,
25
+ }) => {
26
+ const session = useEnduserSession()
27
+ const [menuOpen, setMenuOpen] = useState(false)
28
+ const [errorMessage, setErrorMessage] = useState('')
29
+ const { handleUpload, uploading } = useFileUpload({ enduserId: session.userInfo.id })
30
+
31
+ const handleImageSelect = async (type: 'library' | 'camera') => {
32
+ if (uploading) return
33
+ setErrorMessage('')
34
+
35
+ const result = await (type === 'library' ? launchImageLibrary : launchCamera)({
36
+ mediaType: 'mixed', // library' ? 'mixed' : 'photo', // mixed only works for library
37
+ maxWidth: 500,
38
+ maxHeight: 500,
39
+ })
40
+ if (result.didCancel) return
41
+ if (result.errorCode || result.errorMessage) {
42
+ setErrorMessage(
43
+ result.errorCode === 'camera_unavailable' ? "Your camera is unavailable"
44
+ : result.errorCode === 'permission' ? "We do have permission to access your camera"
45
+ : result.errorMessage || 'An error occurred'
46
+ )
47
+ return
48
+ }
49
+ const image = result.assets?.[0]
50
+ if (!image) return
51
+
52
+ const blob = await (await fetch(image.uri as string)).blob()
53
+ const actualSize = blob.size // image.size is incorrect by an unpredictable margin
54
+
55
+ try {
56
+ const file = await handleUpload({
57
+ name: image.fileName as string,
58
+ size: actualSize, // image.fileSize as number,
59
+ type: image.type as string,
60
+ }, {
61
+ uri: image.uri as string,
62
+ name: image.fileName as string,
63
+ type: image.type as string,
64
+ })
65
+ onUpload(
66
+ file,
67
+ result.assets?.[0]?.duration ? 'video' : 'image',
68
+ )
69
+ setMenuOpen(false)
70
+ } catch(err: any) {
71
+ setErrorMessage(err?.message ?? err.toString())
72
+ }
73
+ }
74
+
75
+ const menuDisabled = uploading || disabled
76
+ return (
77
+ <>
78
+ <Modal animationType='fade' transparent={true}
79
+ visible={menuOpen} onRequestClose={() => setMenuOpen(false)}
80
+ >
81
+ <Flex flex={1} alignItems="center" justifyContent="center">
82
+ <Paper flex style={{ padding: 25, margin: 10, }} >
83
+ <Button disabled={menuDisabled} variant="contained" onPress={() => handleImageSelect('library')}
84
+ style={{ marginBottom: 20 }}
85
+ >
86
+ <View style={{ flexDirection: 'row', alignItems: 'center', minHeight: 40 }}>
87
+ <Typography style={{ fontSize: 18, marginRight: 10 }}>Upload</Typography>
88
+ <Avatar.Icon icon="image" size={CHAT_ICON_SIZE} />
89
+ </View>
90
+ </Button>
91
+ <Button disabled={menuDisabled} variant="contained" onPress={() => handleImageSelect('camera')}>
92
+ <View style={{ flexDirection: 'row', alignItems: 'center', minHeight: 40 }}>
93
+ <Typography style={{ fontSize: 18, marginRight: 10 }}>Take Photo</Typography>
94
+ <Avatar.Icon icon="camera" size={CHAT_ICON_SIZE} />
95
+ </View>
96
+ </Button>
97
+ <Button variant='outlined' onPress={() => setMenuOpen(false)} style={{ marginTop: 25 }}>
98
+ Cancel
99
+ </Button>
100
+
101
+ <Typography style={{ marginTop: 5, color: '#ff4444'}}>
102
+ {errorMessage}
103
+ </Typography>
104
+ </Paper>
105
+ </Flex>
106
+ </Modal>
107
+
108
+ <LabeledIconButton label="attach image" disabled={menuDisabled}
109
+ onClick={() => setMenuOpen(true)}
110
+ Icon={() => <Avatar.Icon icon={'image'} size={CHAT_ICON_SIZE} />}
111
+ />
112
+ </>
113
+ )
114
+ }
115
+
116
+ interface SendMessage_T {
117
+ roomId: string,
118
+ placeholderText?: string;
119
+ style?: React.CSSProperties;
120
+ }
121
+ export const SendMessage = ({
122
+ roomId,
123
+ placeholderText="Enter a message",
124
+ style={},
125
+ }: SendMessage_T) => {
126
+ const [message, setMessage] = useState('')
127
+ const [sending, setSending] = useState(false)
128
+
129
+ const [, { createElement: createMessage }] = useChats(roomId)
130
+
131
+ return (
132
+ <Flex row flex={1} alignContent="center" style={{ marginTop: 8, ...style }}>
133
+ <Flex column flex={1} style={{ marginLeft: 5, marginRight: 5 }}>
134
+ <TextInput value={message} onChangeText={setMessage}
135
+ style={{
136
+ width: '100%',
137
+ borderColor: '#0f0f0f',
138
+ borderWidth: 1,
139
+ padding: 10,
140
+ borderRadius: 5,
141
+ }}
142
+ aria-label="Enter a message..."
143
+ placeholder={placeholderText}
144
+ />
145
+ </Flex>
146
+ <Flex alignSelf="center">
147
+ <Flex style={{ marginRight: 5 }}>
148
+ <AsyncIconButton label="send" Icon={() => <SendIcon size={CHAT_ICON_SIZE} />}
149
+ disabled={message === ''}
150
+ action={() => createMessage({ message, roomId })}
151
+ onSuccess={() => setMessage('')}
152
+ onChange={setSending}
153
+ />
154
+ </Flex>
155
+
156
+ <Flex style={{ marginRight: 5 }}>
157
+ <SendImageOrVideo disabled={sending} onUpload={async ({ secureName }, type) => {
158
+ setSending(true)
159
+ try {
160
+ await createMessage({
161
+ roomId, message: '',
162
+ attachments: [{ type, secureName }]
163
+ })
164
+ } catch(err: any) {
165
+ console.error(err)
166
+ } finally {
167
+ setSending(false)
168
+ }
169
+ }} />
170
+ </Flex>
171
+ </Flex>
172
+ </Flex>
173
+ )
174
+ }
@@ -0,0 +1,88 @@
1
+ import React, { CSSProperties, useEffect, useState } from "react"
2
+ import { ChatMessage } from "@tellescope/types-client";
3
+
4
+ import {
5
+ AsyncIconButton,
6
+ Flex,
7
+ SendIcon,
8
+ TextField,
9
+ useChats,
10
+ } from "@tellescope/react-components"
11
+
12
+ interface SendMessage_T {
13
+ roomId: string,
14
+ onNewMessage?: (m: ChatMessage) => void;
15
+ placeholderText?: string;
16
+ Icon?: React.ElementType<any>;
17
+ style?: CSSProperties;
18
+
19
+ // web only
20
+ sendOnEnterPress?: boolean,
21
+ multiline?: boolean,
22
+ maxRows?: number,
23
+ }
24
+ export const SendMessage = ({
25
+ roomId,
26
+ Icon=SendIcon,
27
+ onNewMessage,
28
+ placeholderText="Enter a message",
29
+ style={},
30
+ sendOnEnterPress,
31
+ multiline,
32
+ maxRows,
33
+ }: SendMessage_T) => {
34
+ const [message, setMessage] = useState('')
35
+ const [sending, setSending] = useState(false)
36
+
37
+ const [disabled, setDisabled] = useState(false)
38
+ const [chatFocused, setChatFocused] = React.useState(false)
39
+
40
+ const [, { createElement: createMessage }] = useChats(roomId)
41
+
42
+ useEffect(() => {
43
+ if (!chatFocused) return
44
+ if (!sendOnEnterPress) return
45
+ if (typeof window === 'undefined') return
46
+
47
+ const handleSend = (e: any) => {
48
+ if (e.key !== 'Enter') return
49
+ setDisabled(true)
50
+
51
+ createMessage({ message, roomId })
52
+ .then(m => {
53
+ setMessage('')
54
+ onNewMessage?.(m)
55
+ })
56
+ .catch(console.error)
57
+ .finally(() => setDisabled(false))
58
+ }
59
+
60
+ window.addEventListener('keypress', handleSend)
61
+ return () => { window.removeEventListener('keypress', handleSend) }
62
+ }, [sendOnEnterPress, chatFocused, message, roomId])
63
+
64
+ return (
65
+ <Flex row flex={1} alignContent="center" style={style}>
66
+ <Flex column flex={1}>
67
+ <TextField variant="outlined" value={message} onChange={setMessage} disabled={sending}
68
+ aria-label="Enter a message"
69
+ multiline={multiline} maxRows={maxRows}
70
+ placeholder={placeholderText}
71
+ onFocus={() => setChatFocused(true)}
72
+ onBlur={() => setChatFocused(false)}
73
+ />
74
+ </Flex>
75
+ <Flex column alignSelf="center">
76
+ <AsyncIconButton label="send" Icon={Icon}
77
+ disabled={message === '' || disabled}
78
+ action={() => createMessage({ message, roomId })}
79
+ onSuccess={m => {
80
+ setMessage('')
81
+ onNewMessage?.(m)
82
+ }}
83
+ onChange={setSending}
84
+ />
85
+ </Flex>
86
+ </Flex>
87
+ )
88
+ }
@@ -1 +1,2 @@
1
- export * from "./chat"
1
+ export * from "./chat"
2
+ export * from "./components"
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
- export * from "./chat";
1
+ export * from "./chat";
2
+ export * from "./components"