1
- import { Box } from "@twilio-paste/box" ;
2
- import { Button } from "@twilio-paste/button" ;
3
1
import { ChatBubble , ChatMessage , ChatMessageMeta , ChatMessageMetaItem } from "@twilio-paste/chat-log" ;
4
2
import type { Chat } from "@twilio-paste/chat-log" ;
5
- import { SendIcon } from "@twilio-paste/icons/esm/SendIcon" ;
6
3
import {
7
4
CLEAR_EDITOR_COMMAND ,
8
5
COMMAND_PRIORITY_HIGH ,
@@ -94,23 +91,6 @@ export const createNewMessage = (message: string): Omit<Chat, "id"> => {
94
91
} ;
95
92
} ;
96
93
97
- export const SendButtonPlugin = ( { onClick } : { onClick : ( ) => void } ) : JSX . Element => {
98
- const [ editor ] = useLexicalComposerContext ( ) ;
99
-
100
- const handleSend = ( ) : void => {
101
- onClick ( ) ;
102
- editor . dispatchCommand ( CLEAR_EDITOR_COMMAND , undefined ) ;
103
- } ;
104
-
105
- return (
106
- < Box position = "absolute" top = "space30" right = "space30" >
107
- < Button variant = "primary_icon" size = "reset" onClick = { handleSend } >
108
- < SendIcon decorative = { false } title = "Send message" />
109
- </ Button >
110
- </ Box >
111
- ) ;
112
- } ;
113
-
114
94
export const EnterKeySubmitPlugin = ( { onKeyDown } : { onKeyDown : ( ) => void } ) : null => {
115
95
const [ editor ] = useLexicalComposerContext ( ) ;
116
96
@@ -134,7 +114,7 @@ export const EnterKeySubmitPlugin = ({ onKeyDown }: { onKeyDown: () => void }):
134
114
} ;
135
115
136
116
export const ChatDialogExample = `const ChatDialog = () => {
137
- const {chats, push} = useChatLogger(
117
+ const { chats, push } = useChatLogger(
138
118
{
139
119
content: (
140
120
<ChatBookend>
@@ -146,7 +126,7 @@ export const ChatDialogExample = `const ChatDialog = () => {
146
126
),
147
127
},
148
128
{
149
- variant: ' inbound' ,
129
+ variant: " inbound" ,
150
130
content: (
151
131
<ChatMessage variant="inbound">
152
132
<ChatBubble>Quisque ullamcorper ipsum vitae lorem euismod sodales.</ChatBubble>
@@ -170,7 +150,7 @@ export const ChatDialogExample = `const ChatDialog = () => {
170
150
),
171
151
},
172
152
{
173
- variant: ' inbound' ,
153
+ variant: " inbound" ,
174
154
content: (
175
155
<ChatMessage variant="inbound">
176
156
<ChatBubble>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</ChatBubble>
@@ -182,9 +162,9 @@ export const ChatDialogExample = `const ChatDialog = () => {
182
162
</ChatMessageMeta>
183
163
</ChatMessage>
184
164
),
185
- }
165
+ },
186
166
);
187
- const [message, setMessage] = React.useState('' );
167
+ const [message, setMessage] = React.useState("" );
188
168
189
169
const [mounted, setMounted] = React.useState(false);
190
170
const loggerRef = React.useRef(null);
@@ -196,7 +176,7 @@ export const ChatDialogExample = `const ChatDialog = () => {
196
176
197
177
React.useEffect(() => {
198
178
if (!mounted || !loggerRef.current) return;
199
- scrollerRef.current?.scrollTo({top: loggerRef.current.scrollHeight, behavior: ' smooth' });
179
+ scrollerRef.current?.scrollTo({ top: loggerRef.current.scrollHeight, behavior: " smooth" });
200
180
}, [chats, mounted]);
201
181
202
182
const handleComposerChange = (editorState) => {
@@ -207,10 +187,12 @@ export const ChatDialogExample = `const ChatDialog = () => {
207
187
};
208
188
209
189
const submitMessage = () => {
210
- if (message === '' ) return;
190
+ if (message === "" ) return;
211
191
push(createNewMessage(message));
212
192
};
213
193
194
+ const editorInstanceRef = React.useRef<LexicalEditor>(null);
195
+
214
196
return (
215
197
<Box>
216
198
<Box ref={scrollerRef} overflowX="hidden" overflowY="auto" maxHeight="size50" tabIndex={0}>
@@ -221,31 +203,148 @@ export const ChatDialogExample = `const ChatDialog = () => {
221
203
borderWidth="borderWidth0"
222
204
borderTopWidth="borderWidth10"
223
205
borderColor="colorBorderWeak"
224
- display="flex"
225
- flexDirection="row"
226
206
columnGap="space30"
227
207
paddingX="space70"
228
208
paddingTop="space50"
229
209
>
230
- <ChatComposer
231
- maxHeight="size10"
232
- config={{
233
- namespace: 'foo',
234
- onError: (error) => {
235
- throw error;
236
- },
237
- }}
238
- ariaLabel="Message"
239
- placeholder="Type here..."
240
- onChange={handleComposerChange}
241
- >
242
- <ClearEditorPlugin />
243
- <SendButtonPlugin onClick={submitMessage} />
244
- <EnterKeySubmitPlugin onKeyDown={submitMessage} />
245
- </ChatComposer>
210
+ <ChatComposerContainer>
211
+ <ChatComposer
212
+ maxHeight="size10"
213
+ config={{
214
+ namespace: "foo",
215
+ onError: (error) => {
216
+ throw error;
217
+ },
218
+ }}
219
+ ariaLabel="Message"
220
+ placeholder="Type here..."
221
+ onChange={handleComposerChange}
222
+ editorInstanceRef={editorInstanceRef}
223
+ >
224
+ <ClearEditorPlugin />
225
+ <EnterKeySubmitPlugin onKeyDown={submitMessage} />
226
+ </ChatComposer>
227
+ <ChatComposerActionGroup>
228
+ <Button variant="secondary_icon" size="reset">
229
+ <AttachIcon decorative={false} title="attach files to the message" />
230
+ </Button>
231
+ <Button
232
+ variant="primary_icon"
233
+ size="reset"
234
+ onClick={() => {
235
+ submitMessage();
236
+ editorInstanceRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
237
+ }}
238
+ >
239
+ <SendIcon decorative={false} title="Send" />
240
+ </Button>
241
+ </ChatComposerActionGroup>
242
+ </ChatComposerContainer>
246
243
</Box>
247
244
</Box>
248
245
);
249
246
};
250
247
251
248
render(<ChatDialog />)` . trim ( ) ;
249
+
250
+ export const ResponsiveContainedAttachmentsExample = `const ResponsiveContainedAttachmentsExample = () => {
251
+ const ExampleAttachment = () => (
252
+ <ChatComposerAttachmentCard onDismiss={() => {}} attachmentIcon={<DownloadIcon decorative />}>
253
+ <ChatComposerAttachmentLink href="www.google.com">Document-FINAL.doc</ChatComposerAttachmentLink>
254
+ <ChatComposerAttachmentDescription>123 MB</ChatComposerAttachmentDescription>
255
+ </ChatComposerAttachmentCard>
256
+ )
257
+
258
+ return (
259
+ <ChatComposerContainer>
260
+ <ChatComposer
261
+ ariaLabel="A chat with attachments"
262
+ initialValue="This is my initial value"
263
+ config={{
264
+ namespace: "customer-chat",
265
+ onError: (e) => {
266
+ throw e;
267
+ },
268
+ }}
269
+ />
270
+ <ChatComposerActionGroup>
271
+ <Button variant="secondary_icon" size="reset">
272
+ <AttachIcon decorative={false} title="attach files to the message" />
273
+ </Button>
274
+ <Button variant="primary_icon" size="reset">
275
+ <SendIcon decorative={false} title="Send" />
276
+ </Button>
277
+ </ChatComposerActionGroup>
278
+ <ChatComposerAttachmentGroup columns={[1, 1, 2, 3]}>
279
+ {Array.from({ length: 6 }).map((_, index) => (
280
+ <ExampleAttachment key={index} />
281
+ ))}
282
+ </ChatComposerAttachmentGroup>
283
+ </ChatComposerContainer>
284
+ )
285
+ }
286
+
287
+ render(<ResponsiveContainedAttachmentsExample />)` . trim ( ) ;
288
+
289
+ export const ContainedDisabledExample = `const ContainedDisabledExample = () => {
290
+ const [isDisabled, setIsDisabled] = React.useState(true);
291
+ return (
292
+ <>
293
+ <Box marginBottom="space50">
294
+ <Checkbox checked={isDisabled} onClick={() => setIsDisabled((disabled) => !disabled)}>
295
+ Disable Input
296
+ </Checkbox>
297
+ </Box>
298
+ <ChatComposerContainer variant="contained">
299
+ <ChatComposer
300
+ ariaLabel="A chat that is disabled"
301
+ initialValue="This is my initial value"
302
+ config={{
303
+ namespace: "customer-chat",
304
+ onError: (e) => {
305
+ throw e;
306
+ },
307
+ }}
308
+ disabled={isDisabled}
309
+ />
310
+ <ChatComposerActionGroup>
311
+ <Button variant="secondary_icon" size="reset" aria-disabled={isDisabled} disabled={isDisabled}>
312
+ <AttachIcon decorative={false} title="attach files to the message" />
313
+ </Button>
314
+ <Button variant="primary_icon" size="reset" aria-disabled={isDisabled} disabled={isDisabled}>
315
+ <SendIcon decorative={false} title="Send" />
316
+ </Button>
317
+ </ChatComposerActionGroup>
318
+ </ChatComposerContainer>
319
+ </>
320
+ );
321
+ }
322
+
323
+ render(<ContainedDisabledExample />)` . trim ( ) ;
324
+
325
+ export const ContainedExample = `const ContainedExample = () => {
326
+ return (
327
+ <ChatComposerContainer variant="contained">
328
+ <ChatComposer
329
+ ariaLabel="A chat with attachments"
330
+ initialValue="This is my initial value"
331
+ config={{
332
+ namespace: "customer-chat",
333
+ onError: (e) => {
334
+ throw e;
335
+ },
336
+ }}
337
+ />
338
+ <ChatComposerActionGroup>
339
+ <Button variant="secondary_icon" size="reset">
340
+ <AttachIcon decorative={false} title="attach files to the message" />
341
+ </Button>
342
+ <Button variant="primary_icon" size="reset">
343
+ <SendIcon decorative={false} title="Send" />
344
+ </Button>
345
+ </ChatComposerActionGroup>
346
+ </ChatComposerContainer>
347
+ );
348
+ }
349
+
350
+ render(<ContainedExample />)` . trim ( ) ;
0 commit comments