react-optimistic-chat 2.1.0 β†’ 2.2.0

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/README.md CHANGED
@@ -364,12 +364,44 @@ const {
364
364
 
365
365
  <br>
366
366
 
367
- ### πŸ” Optimistic Update Flow
368
- **1.** μ‚¬μš©μžκ°€ λ©”μ‹œμ§€ 전솑
369
- **2.** USER λ©”μ‹œμ§€ + λ‘œλ”© 쀑인 AI λ©”μ‹œμ§€λ₯Ό μ¦‰μ‹œ μΊμ‹œμ— μ‚½μž…
370
- **3.** AI 응닡이 도착
371
- **4.** λ‘œλ”© 쀑인 AI λ©”μ‹œμ§€λ₯Ό μ‹€μ œ μ‘λ‹΅μœΌλ‘œ ꡐ체
372
- **5.** μ—λŸ¬ λ°œμƒ μ‹œ 이전 μƒνƒœλ‘œ rollback
367
+ ### πŸ” useChat Flow
368
+ <table width="892" align="center" bgcolor="white">
369
+ <tr>
370
+ <td align="center" bgcolor="white">
371
+ <img
372
+ src="https://github.com/user-attachments/assets/6e61356b-0da3-45d9-8791-c11aa0d346e9"
373
+ width="690"
374
+ style="display: block; margin: 0 auto;"
375
+ alt="useChat 호좜"
376
+ />
377
+ </td>
378
+ </tr>
379
+ <tr>
380
+ <td align="center" bgcolor="white">
381
+ <b>useChat 호좜</b>
382
+ </td>
383
+ </tr>
384
+ </table>
385
+
386
+ <table width="892" align="center" bgcolor="white">
387
+ <tr>
388
+ <td align="center" bgcolor="white">
389
+ <img
390
+ src="https://github.com/user-attachments/assets/10218350-8844-4bee-b78e-fe4564844e57"
391
+ width="690"
392
+ style="display: block; margin: 0 auto;"
393
+ alt="useChat μ‹€ν–‰"
394
+ />
395
+ </td>
396
+ </tr>
397
+ <tr>
398
+ <td align="center" bgcolor="white">
399
+ <b>useChat μ‹€ν–‰</b>
400
+ </td>
401
+ </tr>
402
+ </table>
403
+
404
+
373
405
 
374
406
  <br>
375
407
 
@@ -503,14 +535,42 @@ const {
503
535
 
504
536
  <br>
505
537
 
506
- ### πŸ” Voice-based Optimistic Update Flow
507
- **1.** μŒμ„± 인식 μ‹œμž‘
508
- **2.** USER λ©”μ‹œμ§€λ₯Ό 빈 content둜 μΊμ‹œμ— μ¦‰μ‹œ μ‚½μž…
509
- **3.** μŒμ„± 인식 쀑간 κ²°κ³Όλ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ λ©”μ‹œμ§€μ— 반영
510
- **4.** μŒμ„± 인식 μ’…λ£Œ + λ‘œλ”© 쀑인 AI λ©”μ‹œμ§€λ₯Ό μ¦‰μ‹œ μΊμ‹œμ— μ‚½μž…
511
- **5.** μ΅œμ’… transcript둜 AI μš”μ²­ 전솑
512
- **6.** AI placeholder λ©”μ‹œμ§€λ₯Ό μ‹€μ œ μ‘λ‹΅μœΌλ‘œ ꡐ체
513
- **7.** μ—λŸ¬ λ˜λŠ” 빈 μž…λ ₯ μ‹œ 이전 μƒνƒœλ‘œ rollback
538
+ ### πŸ” useVoiceChat Flow
539
+ <table width="1001" align="center" bgcolor="white">
540
+ <tr>
541
+ <td align="center" bgcolor="white">
542
+ <img
543
+ src="https://github.com/user-attachments/assets/c80acf37-4886-4c36-a952-e629ccd73088"
544
+ width="690"
545
+ style="display: block; margin: 0 auto;"
546
+ alt="useVoiceChat 호좜"
547
+ />
548
+ </td>
549
+ </tr>
550
+ <tr>
551
+ <td align="center" bgcolor="white">
552
+ <b>useVoiceChat 호좜</b>
553
+ </td>
554
+ </tr>
555
+ </table>
556
+
557
+ <table width="1001" align="center" bgcolor="white">
558
+ <tr>
559
+ <td align="center" bgcolor="white">
560
+ <img
561
+ src="https://github.com/user-attachments/assets/92b44439-5db4-474d-875e-8e62a09f157b"
562
+ width="690"
563
+ style="display: block; margin: 0 auto;"
564
+ alt="useVoiceChat μ‹€ν–‰"
565
+ />
566
+ </td>
567
+ </tr>
568
+ <tr>
569
+ <td align="center" bgcolor="white">
570
+ <b>useVoiceChat μ‹€ν–‰</b>
571
+ </td>
572
+ </tr>
573
+ </table>
514
574
 
515
575
  <br>
516
576
 
@@ -843,3 +903,5 @@ See the [LICENSE](./LICENSE) file for details.
843
903
 
844
904
 
845
905
 
906
+
907
+
package/dist/index.css ADDED
@@ -0,0 +1,214 @@
1
+ /* src/styles/chatMessage.css */
2
+ .roc-chat-message {
3
+ display: flex;
4
+ align-items: flex-start;
5
+ margin-bottom: 1rem;
6
+ width: 100%;
7
+ box-sizing: border-box;
8
+ }
9
+ .roc-chat-message--left {
10
+ justify-content: flex-start;
11
+ }
12
+ .roc-chat-message--right {
13
+ justify-content: flex-end;
14
+ }
15
+ .roc-chat-message__icon {
16
+ width: 40px;
17
+ height: 40px;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ background-color: #f3f4f6;
22
+ border-radius: 9999px;
23
+ border: 2px solid #000;
24
+ flex-shrink: 0;
25
+ margin-right: 0.5rem;
26
+ }
27
+ .roc-chat-message__icon svg {
28
+ display: block;
29
+ }
30
+ .roc-chat-message__bubble {
31
+ padding: 0.75rem;
32
+ max-width: calc(100% - 3rem);
33
+ max-height: 24rem;
34
+ overflow-y: auto;
35
+ white-space: pre-wrap;
36
+ word-break: break-word;
37
+ font-size: 0.875rem;
38
+ line-height: 1.4;
39
+ box-sizing: border-box;
40
+ }
41
+ .roc-chat-message__bubble--ai {
42
+ background-color: #f3f4f6;
43
+ border-radius: 0.75rem;
44
+ }
45
+ .roc-chat-message__bubble--user {
46
+ background-color: #ffffff;
47
+ border: 1px solid #e5e7eb;
48
+ border-radius: 0.75rem 0.75rem 0 0.75rem;
49
+ }
50
+
51
+ /* src/styles/chatList.css */
52
+ .roc-chat-list {
53
+ display: flex;
54
+ flex-direction: column;
55
+ }
56
+
57
+ /* src/styles/chatInput.css */
58
+ .roc-chat-input {
59
+ display: flex;
60
+ border: 1px solid #d1d5db;
61
+ padding: 8px 8px;
62
+ border-radius: 24px;
63
+ }
64
+ .roc-chat-input__textarea {
65
+ width: 100%;
66
+ resize: none;
67
+ border: none;
68
+ font-size: 16px;
69
+ line-height: 2.2;
70
+ outline: none;
71
+ overflow: hidden;
72
+ padding: 0;
73
+ padding-left: 8px;
74
+ }
75
+ .roc-chat-input__textarea::-moz-placeholder {
76
+ color: #9ca3af;
77
+ font-family:
78
+ -apple-system,
79
+ BlinkMacSystemFont,
80
+ "Segoe UI",
81
+ "Apple SD Gothic Neo",
82
+ "Noto Sans KR",
83
+ "Noto Sans",
84
+ system-ui,
85
+ sans-serif;
86
+ }
87
+ .roc-chat-input__textarea::placeholder {
88
+ color: #9ca3af;
89
+ font-family:
90
+ -apple-system,
91
+ BlinkMacSystemFont,
92
+ "Segoe UI",
93
+ "Apple SD Gothic Neo",
94
+ "Noto Sans KR",
95
+ "Noto Sans",
96
+ system-ui,
97
+ sans-serif;
98
+ }
99
+ .roc-chat-input__button {
100
+ position: relative;
101
+ width: 36px;
102
+ height: 36px;
103
+ margin-top: auto;
104
+ flex-shrink: 0;
105
+ background: none;
106
+ border: none;
107
+ margin-left: 8px;
108
+ cursor: pointer;
109
+ }
110
+ .roc-chat-input__layer {
111
+ position: absolute;
112
+ inset: 0;
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ border-radius: 24px;
117
+ opacity: 0;
118
+ transition: opacity 150ms ease;
119
+ }
120
+ .roc-chat-input__layer.is-active {
121
+ opacity: 1;
122
+ }
123
+ .roc-chat-input__layer--mic {
124
+ background: #f3f4f6;
125
+ color: #374151;
126
+ }
127
+ .roc-chat-input__layer--recording {
128
+ background: #dc2626;
129
+ color: #ffffff;
130
+ }
131
+ .roc-chat-input__layer--send {
132
+ background: #000000;
133
+ color: #ffffff;
134
+ }
135
+ .roc-chat-input__layer--sending {
136
+ background: #9ca3af;
137
+ color: #ffffff;
138
+ }
139
+
140
+ /* src/styles/chatContainer.css */
141
+ .roc-chat-container {
142
+ display: flex;
143
+ flex-direction: column;
144
+ height: 100%;
145
+ }
146
+ .roc-chat-container__list {
147
+ flex: 1;
148
+ overflow-y: auto;
149
+ padding: 8px;
150
+ }
151
+ .roc-chat-container__loading {
152
+ display: flex;
153
+ justify-content: center;
154
+ padding: 8px 0;
155
+ }
156
+ .roc-chat-container__input {
157
+ position: relative;
158
+ flex-shrink: 0;
159
+ }
160
+ .roc-chat-container__scroll-button {
161
+ position: absolute;
162
+ bottom: 80px;
163
+ left: 50%;
164
+ transform: translateX(-50%);
165
+ width: 40px;
166
+ height: 40px;
167
+ border-radius: 50%;
168
+ background: #ffffff;
169
+ border: 1px solid #e5e7eb;
170
+ display: flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+ cursor: pointer;
174
+ }
175
+
176
+ /* src/styles/loadingSpinner.css */
177
+ .roc-spinner-wrapper {
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ }
182
+ .roc-spinner {
183
+ border: 4px solid #e5e7eb;
184
+ border-right-color: transparent;
185
+ border-radius: 50%;
186
+ animation: roc-spin 1s linear infinite;
187
+ }
188
+ @keyframes roc-spin {
189
+ to {
190
+ transform: rotate(1turn);
191
+ }
192
+ }
193
+
194
+ /* src/styles/sendingDots.css */
195
+ .roc-sending-dots {
196
+ display: inline-flex;
197
+ justify-content: space-between;
198
+ align-items: center;
199
+ }
200
+ .roc-sending-dot {
201
+ font-weight: 800;
202
+ line-height: 1;
203
+ animation: roc-sending-bounce 1.4s ease-in-out infinite;
204
+ }
205
+ @keyframes roc-sending-bounce {
206
+ 0%, to {
207
+ opacity: .2;
208
+ transform: translateY(0);
209
+ }
210
+ 50% {
211
+ opacity: 1;
212
+ transform: translateY(-2px);
213
+ }
214
+ }
package/dist/index.js CHANGED
@@ -146,12 +146,12 @@ function ChatMessage({
146
146
  loadingRenderer
147
147
  }) {
148
148
  const isAI = role === "AI";
149
- const justify = position === "auto" ? isAI ? "justify-start" : "justify-end" : position === "left" ? "justify-start" : "justify-end";
149
+ const justify = position === "auto" ? isAI ? "left" : "right" : position;
150
150
  const defaultAIIcon = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
151
151
  "svg",
152
152
  {
153
153
  xmlns: "http://www.w3.org/2000/svg",
154
- className: `${aiIconColor}`,
154
+ className: aiIconColor,
155
155
  width: "24",
156
156
  height: "24",
157
157
  viewBox: "0 0 24 24",
@@ -170,30 +170,41 @@ function ChatMessage({
170
170
  ]
171
171
  }
172
172
  );
173
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `flex mb-4 items-start ${justify} ${wrapperClassName}`, children: [
174
- isAI && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
175
- "div",
176
- {
177
- className: `
178
- mr-2 bg-gray-100 rounded-full p-2 border-2 border-black
173
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
174
+ "div",
175
+ {
176
+ className: `
177
+ roc-chat-message
178
+ roc-chat-message--${justify}
179
+ ${wrapperClassName}
180
+ `,
181
+ children: [
182
+ isAI && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
183
+ "div",
184
+ {
185
+ className: `
186
+ roc-chat-message__icon
179
187
  ${aiIconWrapperClassName}
180
188
  `,
181
- children: icon || defaultAIIcon
182
- }
183
- ),
184
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
185
- "div",
186
- {
187
- className: `
188
- py-3 px-3 max-h-96 overflow-y-auto w-fit max-w-[calc(100%-3rem)]
189
- whitespace-pre-wrap break-words text-sm
190
- ${isAI ? `bg-gray-100 border-gray-200 rounded-b-xl rounded-t-xl ${aiBubbleClassName}` : `bg-white border border-gray-200 rounded-b-xl rounded-tl-xl ${userBubbleClassName}`}
189
+ children: icon || defaultAIIcon
190
+ }
191
+ ),
192
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
193
+ "div",
194
+ {
195
+ className: `
196
+ roc-chat-message__bubble
197
+ ${isAI ? "roc-chat-message__bubble--ai" : "roc-chat-message__bubble--user"}
198
+ ${isAI ? aiBubbleClassName : userBubbleClassName}
191
199
  ${bubbleClassName}
192
200
  `,
193
- children: isLoading ? loadingRenderer != null ? loadingRenderer : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoadingSpinner, { size: "xs" }) : content
194
- }
195
- )
196
- ] }, id);
201
+ children: isLoading ? loadingRenderer != null ? loadingRenderer : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LoadingSpinner, { size: "xs" }) : content
202
+ }
203
+ )
204
+ ]
205
+ },
206
+ id
207
+ );
197
208
  }
198
209
 
199
210
  // src/components/ChatList.tsx
@@ -207,7 +218,7 @@ function ChatList({
207
218
  loadingRenderer
208
219
  }) {
209
220
  const mappedMessages = messageMapper ? messages.map((msg) => __spreadValues(__spreadValues({}, msg), messageMapper(msg))) : messages;
210
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `flex flex-col ${className}`, children: mappedMessages.map((msg) => {
221
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `roc-chat-list ${className != null ? className : ""}`, children: mappedMessages.map((msg) => {
211
222
  if (messageRenderer) {
212
223
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react2.default.Fragment, { children: messageRenderer(msg) }, msg.id);
213
224
  }
@@ -385,134 +396,95 @@ function ChatInput({
385
396
  return null;
386
397
  };
387
398
  const activeLayer = getActivityLayer();
388
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
389
- "div",
390
- {
391
- className: `
392
- flex border border-gray-300 p-2 rounded-3xl
393
- ${className}
394
- `,
395
- children: [
396
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
397
- "textarea",
398
- {
399
- ref: textareaRef,
400
- value: text,
401
- onChange: handleChange,
402
- placeholder,
403
- rows: 1,
404
- onKeyDown: handleKeyDown,
405
- className: `
406
- w-full px-3 py-2
407
- resize-none border-none
408
- text-sm focus:outline-none
409
- overflow-hidden chatinput-scroll
410
- ${inputClassName}
411
- `
412
- }
413
- ),
414
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
415
- "button",
416
- {
417
- type: "button",
418
- disabled: isSending,
419
- onClick: activeLayer === "mic" || activeLayer === "recording" ? handleRecord : handleSend,
420
- className: "relative w-10 h-10 ml-2 mt-auto flex-shrink-0",
421
- children: [
422
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
423
- "div",
424
- {
425
- className: `
426
- absolute inset-0 flex items-center justify-center rounded-3xl
427
- transition-opacity duration-150
428
- ${activeLayer === "mic" ? "opacity-100" : "opacity-0"}
429
- bg-gray-100 text-gray-700
430
- ${(micButton == null ? void 0 : micButton.className) || ""}
431
- `,
432
- children: (micButton == null ? void 0 : micButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
433
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19v3" }),
434
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
435
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
436
- ] })
437
- }
438
- ),
439
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
440
- "div",
441
- {
442
- className: `
443
- absolute inset-0 flex items-center justify-center rounded-3xl
444
- transition-opacity duration-150
445
- ${activeLayer === "recording" ? "opacity-100" : "opacity-0"}
446
- bg-red-600 text-white
447
- ${(recordingButton == null ? void 0 : recordingButton.className) || ""}
448
- `,
449
- children: (recordingButton == null ? void 0 : recordingButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
450
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19v3" }),
451
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
452
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
453
- ] })
454
- }
455
- ),
456
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
457
- "div",
399
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `roc-chat-input ${className}`, children: [
400
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
401
+ "textarea",
402
+ {
403
+ ref: textareaRef,
404
+ value: text,
405
+ onChange: handleChange,
406
+ placeholder,
407
+ rows: 1,
408
+ onKeyDown: handleKeyDown,
409
+ className: `roc-chat-input__textarea ${inputClassName}`
410
+ }
411
+ ),
412
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
413
+ "button",
414
+ {
415
+ type: "button",
416
+ disabled: isSending,
417
+ onClick: activeLayer === "mic" || activeLayer === "recording" ? handleRecord : handleSend,
418
+ className: "roc-chat-input__button",
419
+ children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
421
+ "div",
422
+ {
423
+ className: `roc-chat-input__layer roc-chat-input__layer--mic ${activeLayer === "mic" ? "is-active" : ""} ${(micButton == null ? void 0 : micButton.className) || ""}`,
424
+ children: (micButton == null ? void 0 : micButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19v3" }),
426
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
427
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
428
+ ] })
429
+ }
430
+ ),
431
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
432
+ "div",
433
+ {
434
+ className: `roc-chat-input__layer roc-chat-input__layer--recording ${activeLayer === "recording" ? "is-active" : ""} ${(recordingButton == null ? void 0 : recordingButton.className) || ""}`,
435
+ children: (recordingButton == null ? void 0 : recordingButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
436
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19v3" }),
437
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
438
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
439
+ ] })
440
+ }
441
+ ),
442
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
443
+ "div",
444
+ {
445
+ className: `roc-chat-input__layer roc-chat-input__layer--send ${activeLayer === "send" ? "is-active" : ""} ${(sendButton == null ? void 0 : sendButton.className) || ""}`,
446
+ children: (sendButton == null ? void 0 : sendButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
447
+ "svg",
458
448
  {
459
- className: `
460
- absolute inset-0 flex items-center justify-center rounded-3xl
461
- transition-opacity duration-150
462
- ${activeLayer === "send" ? "opacity-100" : "opacity-0"}
463
- bg-black text-white
464
- ${(sendButton == null ? void 0 : sendButton.className) || ""}
465
- `,
466
- children: (sendButton == null ? void 0 : sendButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
467
- "svg",
468
- {
469
- width: "20",
470
- height: "20",
471
- viewBox: "0 0 22 24",
472
- fill: "none",
473
- stroke: "currentColor",
474
- "stroke-width": "2",
475
- children: [
476
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
477
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M6 12h16" })
478
- ]
479
- }
480
- )
449
+ width: "20",
450
+ height: "20",
451
+ viewBox: "0 0 22 24",
452
+ fill: "none",
453
+ stroke: "currentColor",
454
+ "stroke-width": "2",
455
+ children: [
456
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
457
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M6 12h16" })
458
+ ]
481
459
  }
482
- ),
483
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
484
- "div",
460
+ )
461
+ }
462
+ ),
463
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
464
+ "div",
465
+ {
466
+ className: `roc-chat-input__layer roc-chat-input__layer--sending ${activeLayer === "sending" ? "is-active" : ""} ${(sendingButton == null ? void 0 : sendingButton.className) || ""}`,
467
+ children: (sendingButton == null ? void 0 : sendingButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
468
+ "svg",
485
469
  {
486
- className: `
487
- absolute inset-0 flex items-center justify-center rounded-3xl
488
- transition-opacity duration-150
489
- ${activeLayer === "sending" ? "opacity-100" : "opacity-0"}
490
- bg-gray-400 text-white
491
- ${(sendingButton == null ? void 0 : sendingButton.className) || ""}
492
- `,
493
- children: (sendingButton == null ? void 0 : sendingButton.icon) || /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
494
- "svg",
495
- {
496
- width: "20",
497
- height: "20",
498
- viewBox: "0 0 22 24",
499
- fill: "none",
500
- stroke: "currentColor",
501
- "stroke-width": "2",
502
- children: [
503
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
504
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M6 12h16" })
505
- ]
506
- }
507
- )
470
+ width: "20",
471
+ height: "20",
472
+ viewBox: "0 0 22 24",
473
+ fill: "none",
474
+ stroke: "currentColor",
475
+ "stroke-width": "2",
476
+ children: [
477
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
478
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M6 12h16" })
479
+ ]
508
480
  }
509
481
  )
510
- ]
511
- }
512
- )
513
- ]
514
- }
515
- );
482
+ }
483
+ )
484
+ ]
485
+ }
486
+ )
487
+ ] });
516
488
  }
517
489
 
518
490
  // src/components/ChatContainer.tsx
@@ -575,67 +547,59 @@ function ChatContainer(props) {
575
547
  });
576
548
  await onSend(value);
577
549
  };
578
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
579
- "div",
580
- {
581
- className: `
582
- flex flex-col ${className || ""}
583
- `,
584
- children: [
585
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
586
- "div",
587
- {
588
- ref: scrollRef,
589
- className: `flex-1 overflow-y-auto chatContainer-scroll p-2`,
590
- children: [
591
- hasNextPage && isFetchingNextPage && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LoadingSpinner, { size: "sm" }) }),
592
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
593
- ChatList,
594
- __spreadValues(__spreadValues(__spreadValues({
595
- messages: mappedMessages
596
- }, messageRenderer && { messageRenderer }), loadingRenderer && { loadingRenderer }), listClassName && { className: listClassName })
597
- )
598
- ]
599
- }
600
- ),
601
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex-shrink-0 relative", children: [
602
- !isAtBottom && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
603
- "button",
604
- {
605
- onClick: scrollToBottom,
606
- className: "\r\n absolute bottom-20 left-1/2 -translate-x-1/2\r\n w-10 h-10 rounded-full bg-white font-bold\r\n flex items-center justify-center\r\n border-gray-200 border-[1px]\r\n ",
607
- "aria-label": "scroll to bottom",
608
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
609
- "svg",
610
- {
611
- xmlns: "http://www.w3.org/2000/svg",
612
- width: "24",
613
- height: "24",
614
- viewBox: "0 0 24 24",
615
- fill: "none",
616
- stroke: "currentColor",
617
- strokeWidth: "2",
618
- strokeLinecap: "round",
619
- strokeLinejoin: "round",
620
- children: [
621
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 5v14" }),
622
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m19 12-7 7-7-7" })
623
- ]
624
- }
625
- )
626
- }
627
- ),
550
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `roc-chat-container ${className || ""}`, children: [
551
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
552
+ "div",
553
+ {
554
+ ref: scrollRef,
555
+ className: "roc-chat-container__list",
556
+ children: [
557
+ hasNextPage && isFetchingNextPage && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "roc-chat-container__loading", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LoadingSpinner, { size: "sm" }) }),
628
558
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
629
- ChatInput,
559
+ ChatList,
630
560
  __spreadValues(__spreadValues(__spreadValues({
631
- onSend: handleSend,
632
- isSending
633
- }, disableVoice && { disableVoice }), placeholder && { placeholder }), inputClassName && { className: inputClassName })
561
+ messages: mappedMessages
562
+ }, messageRenderer && { messageRenderer }), loadingRenderer && { loadingRenderer }), listClassName && { className: listClassName })
634
563
  )
635
- ] })
636
- ]
637
- }
638
- ) });
564
+ ]
565
+ }
566
+ ),
567
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "roc-chat-container__input", children: [
568
+ !isAtBottom && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
569
+ "button",
570
+ {
571
+ className: "roc-chat-container__scroll-button",
572
+ onClick: scrollToBottom,
573
+ "aria-label": "scroll to bottom",
574
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
575
+ "svg",
576
+ {
577
+ xmlns: "http://www.w3.org/2000/svg",
578
+ width: "24",
579
+ height: "24",
580
+ viewBox: "0 0 24 24",
581
+ fill: "none",
582
+ stroke: "currentColor",
583
+ strokeWidth: "2",
584
+ strokeLinecap: "round",
585
+ strokeLinejoin: "round",
586
+ children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 5v14" }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m19 12-7 7-7-7" })
589
+ ]
590
+ }
591
+ )
592
+ }
593
+ ),
594
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
595
+ ChatInput,
596
+ __spreadValues(__spreadValues(__spreadValues({
597
+ onSend: handleSend,
598
+ isSending
599
+ }, disableVoice && { disableVoice }), placeholder && { placeholder }), inputClassName && { className: inputClassName })
600
+ )
601
+ ] })
602
+ ] }) });
639
603
  }
640
604
 
641
605
  // src/hooks/useChat.ts
package/dist/index.mjs CHANGED
@@ -105,12 +105,12 @@ function ChatMessage({
105
105
  loadingRenderer
106
106
  }) {
107
107
  const isAI = role === "AI";
108
- const justify = position === "auto" ? isAI ? "justify-start" : "justify-end" : position === "left" ? "justify-start" : "justify-end";
108
+ const justify = position === "auto" ? isAI ? "left" : "right" : position;
109
109
  const defaultAIIcon = /* @__PURE__ */ jsxs2(
110
110
  "svg",
111
111
  {
112
112
  xmlns: "http://www.w3.org/2000/svg",
113
- className: `${aiIconColor}`,
113
+ className: aiIconColor,
114
114
  width: "24",
115
115
  height: "24",
116
116
  viewBox: "0 0 24 24",
@@ -129,30 +129,41 @@ function ChatMessage({
129
129
  ]
130
130
  }
131
131
  );
132
- return /* @__PURE__ */ jsxs2("div", { className: `flex mb-4 items-start ${justify} ${wrapperClassName}`, children: [
133
- isAI && /* @__PURE__ */ jsx3(
134
- "div",
135
- {
136
- className: `
137
- mr-2 bg-gray-100 rounded-full p-2 border-2 border-black
132
+ return /* @__PURE__ */ jsxs2(
133
+ "div",
134
+ {
135
+ className: `
136
+ roc-chat-message
137
+ roc-chat-message--${justify}
138
+ ${wrapperClassName}
139
+ `,
140
+ children: [
141
+ isAI && /* @__PURE__ */ jsx3(
142
+ "div",
143
+ {
144
+ className: `
145
+ roc-chat-message__icon
138
146
  ${aiIconWrapperClassName}
139
147
  `,
140
- children: icon || defaultAIIcon
141
- }
142
- ),
143
- /* @__PURE__ */ jsx3(
144
- "div",
145
- {
146
- className: `
147
- py-3 px-3 max-h-96 overflow-y-auto w-fit max-w-[calc(100%-3rem)]
148
- whitespace-pre-wrap break-words text-sm
149
- ${isAI ? `bg-gray-100 border-gray-200 rounded-b-xl rounded-t-xl ${aiBubbleClassName}` : `bg-white border border-gray-200 rounded-b-xl rounded-tl-xl ${userBubbleClassName}`}
148
+ children: icon || defaultAIIcon
149
+ }
150
+ ),
151
+ /* @__PURE__ */ jsx3(
152
+ "div",
153
+ {
154
+ className: `
155
+ roc-chat-message__bubble
156
+ ${isAI ? "roc-chat-message__bubble--ai" : "roc-chat-message__bubble--user"}
157
+ ${isAI ? aiBubbleClassName : userBubbleClassName}
150
158
  ${bubbleClassName}
151
159
  `,
152
- children: isLoading ? loadingRenderer != null ? loadingRenderer : /* @__PURE__ */ jsx3(LoadingSpinner, { size: "xs" }) : content
153
- }
154
- )
155
- ] }, id);
160
+ children: isLoading ? loadingRenderer != null ? loadingRenderer : /* @__PURE__ */ jsx3(LoadingSpinner, { size: "xs" }) : content
161
+ }
162
+ )
163
+ ]
164
+ },
165
+ id
166
+ );
156
167
  }
157
168
 
158
169
  // src/components/ChatList.tsx
@@ -166,7 +177,7 @@ function ChatList({
166
177
  loadingRenderer
167
178
  }) {
168
179
  const mappedMessages = messageMapper ? messages.map((msg) => __spreadValues(__spreadValues({}, msg), messageMapper(msg))) : messages;
169
- return /* @__PURE__ */ jsx4("div", { className: `flex flex-col ${className}`, children: mappedMessages.map((msg) => {
180
+ return /* @__PURE__ */ jsx4("div", { className: `roc-chat-list ${className != null ? className : ""}`, children: mappedMessages.map((msg) => {
170
181
  if (messageRenderer) {
171
182
  return /* @__PURE__ */ jsx4(React2.Fragment, { children: messageRenderer(msg) }, msg.id);
172
183
  }
@@ -344,134 +355,95 @@ function ChatInput({
344
355
  return null;
345
356
  };
346
357
  const activeLayer = getActivityLayer();
347
- return /* @__PURE__ */ jsxs3(
348
- "div",
349
- {
350
- className: `
351
- flex border border-gray-300 p-2 rounded-3xl
352
- ${className}
353
- `,
354
- children: [
355
- /* @__PURE__ */ jsx5(
356
- "textarea",
357
- {
358
- ref: textareaRef,
359
- value: text,
360
- onChange: handleChange,
361
- placeholder,
362
- rows: 1,
363
- onKeyDown: handleKeyDown,
364
- className: `
365
- w-full px-3 py-2
366
- resize-none border-none
367
- text-sm focus:outline-none
368
- overflow-hidden chatinput-scroll
369
- ${inputClassName}
370
- `
371
- }
372
- ),
373
- /* @__PURE__ */ jsxs3(
374
- "button",
375
- {
376
- type: "button",
377
- disabled: isSending,
378
- onClick: activeLayer === "mic" || activeLayer === "recording" ? handleRecord : handleSend,
379
- className: "relative w-10 h-10 ml-2 mt-auto flex-shrink-0",
380
- children: [
381
- /* @__PURE__ */ jsx5(
382
- "div",
383
- {
384
- className: `
385
- absolute inset-0 flex items-center justify-center rounded-3xl
386
- transition-opacity duration-150
387
- ${activeLayer === "mic" ? "opacity-100" : "opacity-0"}
388
- bg-gray-100 text-gray-700
389
- ${(micButton == null ? void 0 : micButton.className) || ""}
390
- `,
391
- children: (micButton == null ? void 0 : micButton.icon) || /* @__PURE__ */ jsxs3("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
392
- /* @__PURE__ */ jsx5("path", { d: "M12 19v3" }),
393
- /* @__PURE__ */ jsx5("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
394
- /* @__PURE__ */ jsx5("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
395
- ] })
396
- }
397
- ),
398
- /* @__PURE__ */ jsx5(
399
- "div",
400
- {
401
- className: `
402
- absolute inset-0 flex items-center justify-center rounded-3xl
403
- transition-opacity duration-150
404
- ${activeLayer === "recording" ? "opacity-100" : "opacity-0"}
405
- bg-red-600 text-white
406
- ${(recordingButton == null ? void 0 : recordingButton.className) || ""}
407
- `,
408
- children: (recordingButton == null ? void 0 : recordingButton.icon) || /* @__PURE__ */ jsxs3("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
409
- /* @__PURE__ */ jsx5("path", { d: "M12 19v3" }),
410
- /* @__PURE__ */ jsx5("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
411
- /* @__PURE__ */ jsx5("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
412
- ] })
413
- }
414
- ),
415
- /* @__PURE__ */ jsx5(
416
- "div",
358
+ return /* @__PURE__ */ jsxs3("div", { className: `roc-chat-input ${className}`, children: [
359
+ /* @__PURE__ */ jsx5(
360
+ "textarea",
361
+ {
362
+ ref: textareaRef,
363
+ value: text,
364
+ onChange: handleChange,
365
+ placeholder,
366
+ rows: 1,
367
+ onKeyDown: handleKeyDown,
368
+ className: `roc-chat-input__textarea ${inputClassName}`
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsxs3(
372
+ "button",
373
+ {
374
+ type: "button",
375
+ disabled: isSending,
376
+ onClick: activeLayer === "mic" || activeLayer === "recording" ? handleRecord : handleSend,
377
+ className: "roc-chat-input__button",
378
+ children: [
379
+ /* @__PURE__ */ jsx5(
380
+ "div",
381
+ {
382
+ className: `roc-chat-input__layer roc-chat-input__layer--mic ${activeLayer === "mic" ? "is-active" : ""} ${(micButton == null ? void 0 : micButton.className) || ""}`,
383
+ children: (micButton == null ? void 0 : micButton.icon) || /* @__PURE__ */ jsxs3("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
384
+ /* @__PURE__ */ jsx5("path", { d: "M12 19v3" }),
385
+ /* @__PURE__ */ jsx5("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
386
+ /* @__PURE__ */ jsx5("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
387
+ ] })
388
+ }
389
+ ),
390
+ /* @__PURE__ */ jsx5(
391
+ "div",
392
+ {
393
+ className: `roc-chat-input__layer roc-chat-input__layer--recording ${activeLayer === "recording" ? "is-active" : ""} ${(recordingButton == null ? void 0 : recordingButton.className) || ""}`,
394
+ children: (recordingButton == null ? void 0 : recordingButton.icon) || /* @__PURE__ */ jsxs3("svg", { width: "24", height: "24", stroke: "currentColor", fill: "none", strokeWidth: "2", children: [
395
+ /* @__PURE__ */ jsx5("path", { d: "M12 19v3" }),
396
+ /* @__PURE__ */ jsx5("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
397
+ /* @__PURE__ */ jsx5("rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" })
398
+ ] })
399
+ }
400
+ ),
401
+ /* @__PURE__ */ jsx5(
402
+ "div",
403
+ {
404
+ className: `roc-chat-input__layer roc-chat-input__layer--send ${activeLayer === "send" ? "is-active" : ""} ${(sendButton == null ? void 0 : sendButton.className) || ""}`,
405
+ children: (sendButton == null ? void 0 : sendButton.icon) || /* @__PURE__ */ jsxs3(
406
+ "svg",
417
407
  {
418
- className: `
419
- absolute inset-0 flex items-center justify-center rounded-3xl
420
- transition-opacity duration-150
421
- ${activeLayer === "send" ? "opacity-100" : "opacity-0"}
422
- bg-black text-white
423
- ${(sendButton == null ? void 0 : sendButton.className) || ""}
424
- `,
425
- children: (sendButton == null ? void 0 : sendButton.icon) || /* @__PURE__ */ jsxs3(
426
- "svg",
427
- {
428
- width: "20",
429
- height: "20",
430
- viewBox: "0 0 22 24",
431
- fill: "none",
432
- stroke: "currentColor",
433
- "stroke-width": "2",
434
- children: [
435
- /* @__PURE__ */ jsx5("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
436
- /* @__PURE__ */ jsx5("path", { d: "M6 12h16" })
437
- ]
438
- }
439
- )
408
+ width: "20",
409
+ height: "20",
410
+ viewBox: "0 0 22 24",
411
+ fill: "none",
412
+ stroke: "currentColor",
413
+ "stroke-width": "2",
414
+ children: [
415
+ /* @__PURE__ */ jsx5("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
416
+ /* @__PURE__ */ jsx5("path", { d: "M6 12h16" })
417
+ ]
440
418
  }
441
- ),
442
- /* @__PURE__ */ jsx5(
443
- "div",
419
+ )
420
+ }
421
+ ),
422
+ /* @__PURE__ */ jsx5(
423
+ "div",
424
+ {
425
+ className: `roc-chat-input__layer roc-chat-input__layer--sending ${activeLayer === "sending" ? "is-active" : ""} ${(sendingButton == null ? void 0 : sendingButton.className) || ""}`,
426
+ children: (sendingButton == null ? void 0 : sendingButton.icon) || /* @__PURE__ */ jsxs3(
427
+ "svg",
444
428
  {
445
- className: `
446
- absolute inset-0 flex items-center justify-center rounded-3xl
447
- transition-opacity duration-150
448
- ${activeLayer === "sending" ? "opacity-100" : "opacity-0"}
449
- bg-gray-400 text-white
450
- ${(sendingButton == null ? void 0 : sendingButton.className) || ""}
451
- `,
452
- children: (sendingButton == null ? void 0 : sendingButton.icon) || /* @__PURE__ */ jsxs3(
453
- "svg",
454
- {
455
- width: "20",
456
- height: "20",
457
- viewBox: "0 0 22 24",
458
- fill: "none",
459
- stroke: "currentColor",
460
- "stroke-width": "2",
461
- children: [
462
- /* @__PURE__ */ jsx5("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
463
- /* @__PURE__ */ jsx5("path", { d: "M6 12h16" })
464
- ]
465
- }
466
- )
429
+ width: "20",
430
+ height: "20",
431
+ viewBox: "0 0 22 24",
432
+ fill: "none",
433
+ stroke: "currentColor",
434
+ "stroke-width": "2",
435
+ children: [
436
+ /* @__PURE__ */ jsx5("path", { d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z" }),
437
+ /* @__PURE__ */ jsx5("path", { d: "M6 12h16" })
438
+ ]
467
439
  }
468
440
  )
469
- ]
470
- }
471
- )
472
- ]
473
- }
474
- );
441
+ }
442
+ )
443
+ ]
444
+ }
445
+ )
446
+ ] });
475
447
  }
476
448
 
477
449
  // src/components/ChatContainer.tsx
@@ -534,67 +506,59 @@ function ChatContainer(props) {
534
506
  });
535
507
  await onSend(value);
536
508
  };
537
- return /* @__PURE__ */ jsx6(Fragment, { children: /* @__PURE__ */ jsxs4(
538
- "div",
539
- {
540
- className: `
541
- flex flex-col ${className || ""}
542
- `,
543
- children: [
544
- /* @__PURE__ */ jsxs4(
545
- "div",
546
- {
547
- ref: scrollRef,
548
- className: `flex-1 overflow-y-auto chatContainer-scroll p-2`,
549
- children: [
550
- hasNextPage && isFetchingNextPage && /* @__PURE__ */ jsx6("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx6(LoadingSpinner, { size: "sm" }) }),
551
- /* @__PURE__ */ jsx6(
552
- ChatList,
553
- __spreadValues(__spreadValues(__spreadValues({
554
- messages: mappedMessages
555
- }, messageRenderer && { messageRenderer }), loadingRenderer && { loadingRenderer }), listClassName && { className: listClassName })
556
- )
557
- ]
558
- }
559
- ),
560
- /* @__PURE__ */ jsxs4("div", { className: "flex-shrink-0 relative", children: [
561
- !isAtBottom && /* @__PURE__ */ jsx6(
562
- "button",
563
- {
564
- onClick: scrollToBottom,
565
- className: "\r\n absolute bottom-20 left-1/2 -translate-x-1/2\r\n w-10 h-10 rounded-full bg-white font-bold\r\n flex items-center justify-center\r\n border-gray-200 border-[1px]\r\n ",
566
- "aria-label": "scroll to bottom",
567
- children: /* @__PURE__ */ jsxs4(
568
- "svg",
569
- {
570
- xmlns: "http://www.w3.org/2000/svg",
571
- width: "24",
572
- height: "24",
573
- viewBox: "0 0 24 24",
574
- fill: "none",
575
- stroke: "currentColor",
576
- strokeWidth: "2",
577
- strokeLinecap: "round",
578
- strokeLinejoin: "round",
579
- children: [
580
- /* @__PURE__ */ jsx6("path", { d: "M12 5v14" }),
581
- /* @__PURE__ */ jsx6("path", { d: "m19 12-7 7-7-7" })
582
- ]
583
- }
584
- )
585
- }
586
- ),
509
+ return /* @__PURE__ */ jsx6(Fragment, { children: /* @__PURE__ */ jsxs4("div", { className: `roc-chat-container ${className || ""}`, children: [
510
+ /* @__PURE__ */ jsxs4(
511
+ "div",
512
+ {
513
+ ref: scrollRef,
514
+ className: "roc-chat-container__list",
515
+ children: [
516
+ hasNextPage && isFetchingNextPage && /* @__PURE__ */ jsx6("div", { className: "roc-chat-container__loading", children: /* @__PURE__ */ jsx6(LoadingSpinner, { size: "sm" }) }),
587
517
  /* @__PURE__ */ jsx6(
588
- ChatInput,
518
+ ChatList,
589
519
  __spreadValues(__spreadValues(__spreadValues({
590
- onSend: handleSend,
591
- isSending
592
- }, disableVoice && { disableVoice }), placeholder && { placeholder }), inputClassName && { className: inputClassName })
520
+ messages: mappedMessages
521
+ }, messageRenderer && { messageRenderer }), loadingRenderer && { loadingRenderer }), listClassName && { className: listClassName })
593
522
  )
594
- ] })
595
- ]
596
- }
597
- ) });
523
+ ]
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsxs4("div", { className: "roc-chat-container__input", children: [
527
+ !isAtBottom && /* @__PURE__ */ jsx6(
528
+ "button",
529
+ {
530
+ className: "roc-chat-container__scroll-button",
531
+ onClick: scrollToBottom,
532
+ "aria-label": "scroll to bottom",
533
+ children: /* @__PURE__ */ jsxs4(
534
+ "svg",
535
+ {
536
+ xmlns: "http://www.w3.org/2000/svg",
537
+ width: "24",
538
+ height: "24",
539
+ viewBox: "0 0 24 24",
540
+ fill: "none",
541
+ stroke: "currentColor",
542
+ strokeWidth: "2",
543
+ strokeLinecap: "round",
544
+ strokeLinejoin: "round",
545
+ children: [
546
+ /* @__PURE__ */ jsx6("path", { d: "M12 5v14" }),
547
+ /* @__PURE__ */ jsx6("path", { d: "m19 12-7 7-7-7" })
548
+ ]
549
+ }
550
+ )
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsx6(
554
+ ChatInput,
555
+ __spreadValues(__spreadValues(__spreadValues({
556
+ onSend: handleSend,
557
+ isSending
558
+ }, disableVoice && { disableVoice }), placeholder && { placeholder }), inputClassName && { className: inputClassName })
559
+ )
560
+ ] })
561
+ ] }) });
598
562
  }
599
563
 
600
564
  // src/hooks/useChat.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-optimistic-chat",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -11,10 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "test": "echo \"Error: no test specified\" && exit 1",
14
- "build:css": "npx tailwindcss -i ./src/styles/style.css -o ./src/styles/style.css --minify",
15
- "copy:css": "copy src\\styles\\style.css public\\style.css",
16
- "build:lib": "tsup",
17
- "build": "npm run build:css && npm run copy:css && npm run build:lib"
14
+ "build": "tsup"
18
15
  },
19
16
  "files": [
20
17
  "dist"
@@ -42,6 +39,6 @@
42
39
  }
43
40
  },
44
41
  "sideEffects": [
45
- "./dist/style.css"
42
+ "*.css"
46
43
  ]
47
44
  }
package/dist/style.css DELETED
@@ -1 +0,0 @@
1
- *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.mb-4{margin-bottom:1rem}.ml-2{margin-left:.5rem}.mr-2{margin-right:.5rem}.mt-auto{margin-top:auto}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-6{height:1.5rem}.h-8{height:2rem}.max-h-96{max-height:24rem}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.max-w-\[calc\(100\%-3rem\)\]{max-width:calc(100% - 3rem)}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.animate-\[chatinput-loading-bounce_1\.4s_ease-in-out_0\.2s_infinite\]{animation:chatinput-loading-bounce 1.4s ease-in-out .2s infinite}.animate-\[chatinput-loading-bounce_1\.4s_ease-in-out_0\.4s_infinite\]{animation:chatinput-loading-bounce 1.4s ease-in-out .4s infinite}.animate-\[chatinput-loading-bounce_1\.4s_ease-in-out_infinite\]{animation:chatinput-loading-bounce 1.4s ease-in-out infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.resize-none{resize:none}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-b-xl{border-bottom-right-radius:.75rem;border-bottom-left-radius:.75rem}.rounded-t-xl{border-top-right-radius:.75rem}.rounded-t-xl,.rounded-tl-xl{border-top-left-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-4{border-width:4px}.border-none{border-style:none}.border-black{--tw-border-opacity:1;border-color:rgb(0 0 0/var(--tw-border-opacity,1))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity,1))}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.text-\[10px\]{font-size:10px}.text-\[12px\]{font-size:12px}.text-\[14px\]{font-size:14px}.text-\[8px\]{font-size:8px}.text-sm{font-size:.875rem;line-height:1.25rem}.font-extrabold{font-weight:800}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.opacity-0{opacity:0}.opacity-100{opacity:1}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150,.transition-opacity{transition-duration:.15s}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.chatContainer-scroll::-webkit-scrollbar{width:6px}.chatContainer-scroll::-webkit-scrollbar-track{background:transparent}.chatContainer-scroll::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:10px}.chatContainer-scroll::-webkit-scrollbar-button{display:none}.chatinput-scroll::-webkit-scrollbar{width:6px}.chatinput-scroll::-webkit-scrollbar-track{background:transparent}.chatinput-scroll::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:10px}.chatinput-scroll::-webkit-scrollbar-button{display:none}.roc-spinner-wrapper{display:flex;align-items:center;justify-content:center}.roc-spinner{border:4px solid #e5e7eb;border-right-color:transparent;border-radius:50%;animation:roc-spin 1s linear infinite}@keyframes roc-spin{to{transform:rotate(1turn)}}.roc-sending-dots{display:inline-flex;justify-content:space-between;align-items:center}.roc-sending-dot{font-weight:800;line-height:1;animation:roc-sending-bounce 1.4s ease-in-out infinite}@keyframes roc-sending-bounce{0%,to{opacity:.2;transform:translateY(0)}50%{opacity:1;transform:translateY(-2px)}}