@xcelsior/ui-chat 1.0.7 → 2.0.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/index.d.mts +69 -69
  3. package/dist/index.d.ts +69 -69
  4. package/dist/index.js +2458 -627
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +2457 -628
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +6 -5
  9. package/src/components/BrandIcons.stories.tsx +95 -0
  10. package/src/components/BrandIcons.tsx +84 -0
  11. package/src/components/Chat.stories.tsx +149 -16
  12. package/src/components/Chat.tsx +116 -96
  13. package/src/components/ChatHeader.tsx +124 -69
  14. package/src/components/ChatInput.tsx +253 -104
  15. package/src/components/ChatWidget.tsx +209 -63
  16. package/src/components/ConversationRating.stories.tsx +33 -0
  17. package/src/components/ConversationRating.tsx +156 -0
  18. package/src/components/MarkdownMessage.tsx +202 -0
  19. package/src/components/MessageItem.stories.tsx +253 -55
  20. package/src/components/MessageItem.tsx +222 -59
  21. package/src/components/MessageList.tsx +164 -35
  22. package/src/components/PreChatForm.tsx +236 -96
  23. package/src/components/ThinkingIndicator.tsx +370 -0
  24. package/src/components/TypingIndicator.tsx +27 -11
  25. package/src/hooks/useDraggablePosition.ts +91 -0
  26. package/src/hooks/useMessages.ts +12 -13
  27. package/src/hooks/useResizableWidget.ts +324 -0
  28. package/src/index.tsx +5 -0
  29. package/src/types.ts +51 -5
  30. package/src/utils/markdown-styles.ts +140 -0
  31. package/storybook-static/assets/BrandIcons-Cjy5INAp.js +4 -0
  32. package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +64 -0
  33. package/storybook-static/assets/Chat.stories-J_Yp51wU.js +803 -0
  34. package/storybook-static/assets/Color-YHDXOIA2-BMnd3YrF.js +1 -0
  35. package/storybook-static/assets/ConversationRating.stories-B5_QddHN.js +12 -0
  36. package/storybook-static/assets/DocsRenderer-CFRXHY34-i_W8iCu9.js +575 -0
  37. package/storybook-static/assets/MessageItem-DAaKZ9s9.js +14 -0
  38. package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +255 -0
  39. package/storybook-static/assets/ToastContext-Bty1K7ya.js +1 -0
  40. package/storybook-static/assets/chunk-XP5HYGXS-BpfKkqn7.js +1 -0
  41. package/storybook-static/assets/en-US-BukEqXxE.js +1 -0
  42. package/storybook-static/assets/entry-preview-docs-DHohToDm.js +46 -0
  43. package/storybook-static/assets/entry-preview-oDnntGcx.js +2 -0
  44. package/storybook-static/assets/iframe-CGBtu2Se.js +211 -0
  45. package/storybook-static/assets/index--qcDGAq6.js +1 -0
  46. package/storybook-static/assets/index-BLHw34Di.js +24 -0
  47. package/storybook-static/assets/index-B_4m48Mv.js +1 -0
  48. package/storybook-static/assets/index-DgH-xKnr.js +11 -0
  49. package/storybook-static/assets/index-DrFu-skq.js +6 -0
  50. package/storybook-static/assets/index-DrdPSA1J.js +240 -0
  51. package/storybook-static/assets/index-jvNEZhzf.js +1 -0
  52. package/storybook-static/assets/index-yBjzXJbu.js +9 -0
  53. package/storybook-static/assets/jsx-runtime-Cf8x2fCZ.js +9 -0
  54. package/storybook-static/assets/preview-B8lJiyuQ.js +34 -0
  55. package/storybook-static/assets/preview-BBWR9nbA.js +1 -0
  56. package/storybook-static/assets/preview-BRpahs9B.js +2 -0
  57. package/storybook-static/assets/preview-BWzBA1C2.js +396 -0
  58. package/storybook-static/assets/preview-CvbIS5ZJ.js +1 -0
  59. package/storybook-static/assets/preview-DD_OYowb.js +1 -0
  60. package/storybook-static/assets/preview-DGUiP6tS.js +7 -0
  61. package/storybook-static/assets/preview-DHQbi4pV.js +1 -0
  62. package/storybook-static/assets/preview-DUOvJmsz.js +1 -0
  63. package/storybook-static/assets/preview-DcGwT3kv.css +1 -0
  64. package/storybook-static/assets/preview-DwI0w3cI.js +1 -0
  65. package/storybook-static/assets/react-18-CALspjOX.js +1 -0
  66. package/storybook-static/assets/test-utils-BE0XkMtV.js +9 -0
  67. package/storybook-static/favicon.svg +1 -0
  68. package/storybook-static/iframe.html +666 -0
  69. package/storybook-static/index.html +177 -0
  70. package/storybook-static/index.json +1 -0
  71. package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
  72. package/storybook-static/nunito-sans-bold.woff2 +0 -0
  73. package/storybook-static/nunito-sans-italic.woff2 +0 -0
  74. package/storybook-static/nunito-sans-regular.woff2 +0 -0
  75. package/storybook-static/project.json +1 -0
  76. package/storybook-static/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  77. package/storybook-static/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  78. package/storybook-static/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  79. package/storybook-static/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  80. package/storybook-static/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  81. package/storybook-static/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  82. package/storybook-static/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  83. package/storybook-static/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  84. package/storybook-static/sb-addons/interactions-10/manager-bundle.js +222 -0
  85. package/storybook-static/sb-addons/links-1/manager-bundle.js +3 -0
  86. package/storybook-static/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  87. package/storybook-static/sb-common-assets/favicon.svg +1 -0
  88. package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  89. package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  90. package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  91. package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  92. package/storybook-static/sb-manager/globals-module-info.js +1052 -0
  93. package/storybook-static/sb-manager/globals-runtime.js +42127 -0
  94. package/storybook-static/sb-manager/globals.js +48 -0
  95. package/storybook-static/sb-manager/runtime.js +12048 -0
  96. package/.turbo/turbo-lint.log +0 -5
@@ -16,7 +16,8 @@ interface PreChatFormProps {
16
16
  }
17
17
 
18
18
  /**
19
- * PreChatForm component for collecting user information before starting chat
19
+ * PreChatForm component for collecting user information before starting chat.
20
+ * Styled to match the Xcelsior website design system.
20
21
  */
21
22
  export function PreChatForm({
22
23
  onSubmit,
@@ -30,6 +31,7 @@ export function PreChatForm({
30
31
  const [email, setEmail] = useState(initialEmail);
31
32
  const [errors, setErrors] = useState<{ name?: string; email?: string }>({});
32
33
  const [isSubmitting, setIsSubmitting] = useState(false);
34
+ const [focusedField, setFocusedField] = useState<string | null>(null);
33
35
 
34
36
  const validateForm = (): boolean => {
35
37
  const newErrors: { name?: string; email?: string } = {};
@@ -72,82 +74,172 @@ export function PreChatForm({
72
74
  }
73
75
  };
74
76
 
75
- // Show full form
77
+ const inputStyle = (fieldName: string, hasError: boolean): React.CSSProperties => ({
78
+ width: '100%',
79
+ padding: '10px 16px',
80
+ fontSize: '14px',
81
+ lineHeight: '20px',
82
+ letterSpacing: '0.006em',
83
+ color: '#f7f7f8',
84
+ backgroundColor: 'rgba(255,255,255,0.04)',
85
+ border: 'none',
86
+ borderRadius: '12px',
87
+ outline: 'none',
88
+ boxShadow: hasError
89
+ ? 'inset 0 0 0 1px rgba(255,99,99,0.5)'
90
+ : focusedField === fieldName
91
+ ? 'inset 0 0 0 1px rgba(51,126,255,0.4), 0 0 0 3px rgba(51,126,255,0.15)'
92
+ : 'inset 0 0 0 0.5px rgba(255,255,255,0.08)',
93
+ transition: 'box-shadow 0.2s ease',
94
+ caretColor: '#337eff',
95
+ });
96
+
76
97
  return (
77
98
  <div
78
- className={`fixed bottom-4 right-4 z-50 flex flex-col bg-white dark:bg-gray-900 rounded-lg shadow-2xl overflow-hidden ${className}`}
99
+ className={`fixed bottom-4 right-4 z-50 flex flex-col overflow-hidden ${className}`}
79
100
  style={{
80
- width: '400px',
101
+ width: 380,
81
102
  maxHeight: 'calc(100vh - 2rem)',
103
+ backgroundColor: '#00001a',
104
+ borderRadius: 16,
105
+ boxShadow: [
106
+ 'inset 0 0 0 0.5px rgba(255,255,255,0.06)',
107
+ 'inset 0 1px 0 0 rgba(255,255,255,0.12)',
108
+ '0 25px 60px -12px rgba(0,0,0,0.6)',
109
+ '0 0 40px -8px rgba(51,126,255,0.15)',
110
+ ].join(', '),
111
+ /* Dot grid texture */
112
+ backgroundImage:
113
+ 'radial-gradient(circle, rgba(255,255,255,0.03) 1px, transparent 1px)',
114
+ backgroundSize: '24px 24px',
82
115
  }}
83
116
  >
84
- {/* Header */}
85
- <div className="bg-gradient-to-r from-blue-600 to-purple-600 text-white px-6 py-4">
86
- <div className="flex items-start justify-between">
87
- <div className="flex-1">
88
- <h2 className="text-lg font-semibold">Start a Conversation</h2>
89
- <p className="text-sm text-blue-100 mt-1">
90
- Please provide your details to continue
91
- </p>
92
- </div>
93
- <div className="flex gap-2 ml-2">
94
- {/* Minimize Button */}
95
- <button
96
- type="button"
97
- onClick={onMinimize}
98
- className="text-white hover:bg-white/20 rounded p-1 transition-colors"
99
- aria-label="Minimize chat"
100
- >
101
- <svg
102
- className="w-5 h-5"
103
- fill="none"
104
- stroke="currentColor"
105
- viewBox="0 0 24 24"
117
+ {/* Header — matching ChatHeader gradient */}
118
+ <div
119
+ className="relative text-white overflow-hidden"
120
+ style={{
121
+ background: 'linear-gradient(135deg, #337eff 0%, #005eff 50%, #001a66 100%)',
122
+ }}
123
+ >
124
+ {/* Glass overlay */}
125
+ <div
126
+ className="absolute inset-0 pointer-events-none"
127
+ style={{
128
+ background:
129
+ 'linear-gradient(180deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 40%)',
130
+ }}
131
+ />
132
+ <div className="relative px-5 py-4">
133
+ <div className="flex items-start justify-between">
134
+ <div className="flex-1">
135
+ <h2
136
+ className="font-semibold"
137
+ style={{
138
+ fontSize: '17px',
139
+ letterSpacing: '-0.01em',
140
+ }}
106
141
  >
107
- <title>Minimize</title>
108
- <path
109
- strokeLinecap="round"
110
- strokeLinejoin="round"
111
- strokeWidth={2}
112
- d="M20 12H4"
113
- />
114
- </svg>
115
- </button>
116
- {/* Close Button */}
117
- <button
118
- type="button"
119
- onClick={onClose}
120
- className="text-white hover:bg-white/20 rounded p-1 transition-colors"
121
- aria-label="Close chat"
122
- >
123
- <svg
124
- className="w-5 h-5"
125
- fill="none"
126
- stroke="currentColor"
127
- viewBox="0 0 24 24"
142
+ Start a Conversation
143
+ </h2>
144
+ <p
145
+ className="mt-1"
146
+ style={{
147
+ fontSize: '13px',
148
+ letterSpacing: '0.015em',
149
+ color: 'rgba(255,255,255,0.6)',
150
+ }}
128
151
  >
129
- <title>Close</title>
130
- <path
131
- strokeLinecap="round"
132
- strokeLinejoin="round"
133
- strokeWidth={2}
134
- d="M6 18L18 6M6 6l12 12"
135
- />
136
- </svg>
137
- </button>
152
+ Please provide your details to continue
153
+ </p>
154
+ </div>
155
+ <div className="flex gap-1 ml-2">
156
+ <button
157
+ type="button"
158
+ onClick={onMinimize}
159
+ className="p-2 rounded-lg transition-all duration-150"
160
+ style={{ backgroundColor: 'transparent' }}
161
+ onMouseEnter={(e) => {
162
+ e.currentTarget.style.backgroundColor =
163
+ 'rgba(255,255,255,0.1)';
164
+ }}
165
+ onMouseLeave={(e) => {
166
+ e.currentTarget.style.backgroundColor = 'transparent';
167
+ }}
168
+ aria-label="Minimize chat"
169
+ >
170
+ <svg
171
+ width="18"
172
+ height="18"
173
+ fill="none"
174
+ stroke="currentColor"
175
+ viewBox="0 0 24 24"
176
+ >
177
+ <title>Minimize</title>
178
+ <path
179
+ strokeLinecap="round"
180
+ strokeLinejoin="round"
181
+ strokeWidth={2}
182
+ d="M20 12H4"
183
+ />
184
+ </svg>
185
+ </button>
186
+ <button
187
+ type="button"
188
+ onClick={onClose}
189
+ className="p-2 rounded-lg transition-all duration-150"
190
+ style={{ backgroundColor: 'transparent' }}
191
+ onMouseEnter={(e) => {
192
+ e.currentTarget.style.backgroundColor =
193
+ 'rgba(255,255,255,0.1)';
194
+ }}
195
+ onMouseLeave={(e) => {
196
+ e.currentTarget.style.backgroundColor = 'transparent';
197
+ }}
198
+ aria-label="Close chat"
199
+ >
200
+ <svg
201
+ width="18"
202
+ height="18"
203
+ fill="none"
204
+ stroke="currentColor"
205
+ viewBox="0 0 24 24"
206
+ >
207
+ <title>Close</title>
208
+ <path
209
+ strokeLinecap="round"
210
+ strokeLinejoin="round"
211
+ strokeWidth={2}
212
+ d="M6 18L18 6M6 6l12 12"
213
+ />
214
+ </svg>
215
+ </button>
216
+ </div>
138
217
  </div>
139
218
  </div>
219
+ {/* Bottom edge line */}
220
+ <div
221
+ className="h-px"
222
+ style={{
223
+ background:
224
+ 'linear-gradient(90deg, transparent 5%, rgba(255,255,255,0.12) 30%, rgba(255,255,255,0.12) 70%, transparent 95%)',
225
+ }}
226
+ />
140
227
  </div>
141
228
 
142
229
  {/* Form */}
143
- <form onSubmit={handleSubmit} className="p-6 space-y-5">
230
+ <form onSubmit={handleSubmit} className="p-5 flex flex-col gap-4">
144
231
  {/* Name Input */}
145
232
  <div>
146
233
  <label
147
234
  htmlFor="chat-name"
148
- className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-200"
235
+ className="block mb-2 font-medium"
236
+ style={{
237
+ fontSize: '13px',
238
+ letterSpacing: '0.015em',
239
+ color: 'rgba(247,247,248,0.72)',
240
+ }}
149
241
  >
150
- Name <span className="text-red-500">*</span>
242
+ Name <span style={{ color: '#ff6363' }}>*</span>
151
243
  </label>
152
244
  <input
153
245
  type="text"
@@ -159,17 +251,23 @@ export function PreChatForm({
159
251
  setErrors((prev) => ({ ...prev, name: undefined }));
160
252
  }
161
253
  }}
162
- className={`block w-full px-4 py-2.5 text-sm text-gray-900 bg-gray-50 rounded-lg border ${
163
- errors.name
164
- ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
165
- : 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
166
- } dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500`}
254
+ onFocus={() => setFocusedField('name')}
255
+ onBlur={() => setFocusedField(null)}
256
+ style={inputStyle('name', !!errors.name)}
167
257
  placeholder="John Doe"
168
258
  disabled={isSubmitting}
169
259
  autoComplete="name"
170
260
  />
171
261
  {errors.name && (
172
- <p className="mt-2 text-sm text-red-600 dark:text-red-500" role="alert">
262
+ <p
263
+ className="mt-1.5"
264
+ style={{
265
+ fontSize: '12px',
266
+ letterSpacing: '0.015em',
267
+ color: '#ff6363',
268
+ }}
269
+ role="alert"
270
+ >
173
271
  {errors.name}
174
272
  </p>
175
273
  )}
@@ -179,9 +277,14 @@ export function PreChatForm({
179
277
  <div>
180
278
  <label
181
279
  htmlFor="chat-email"
182
- className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-200"
280
+ className="block mb-2 font-medium"
281
+ style={{
282
+ fontSize: '13px',
283
+ letterSpacing: '0.015em',
284
+ color: 'rgba(247,247,248,0.72)',
285
+ }}
183
286
  >
184
- Email <span className="text-red-500">*</span>
287
+ Email <span style={{ color: '#ff6363' }}>*</span>
185
288
  </label>
186
289
  <input
187
290
  type="email"
@@ -193,51 +296,66 @@ export function PreChatForm({
193
296
  setErrors((prev) => ({ ...prev, email: undefined }));
194
297
  }
195
298
  }}
196
- className={`block w-full px-4 py-2.5 text-sm text-gray-900 bg-gray-50 rounded-lg border ${
197
- errors.email
198
- ? 'border-red-500 focus:ring-red-500 focus:border-red-500'
199
- : 'border-gray-300 focus:ring-blue-500 focus:border-blue-500'
200
- } dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500`}
299
+ onFocus={() => setFocusedField('email')}
300
+ onBlur={() => setFocusedField(null)}
301
+ style={inputStyle('email', !!errors.email)}
201
302
  placeholder="john@example.com"
202
303
  disabled={isSubmitting}
203
304
  autoComplete="email"
204
305
  />
205
306
  {errors.email && (
206
- <p className="mt-2 text-sm text-red-600 dark:text-red-500" role="alert">
307
+ <p
308
+ className="mt-1.5"
309
+ style={{
310
+ fontSize: '12px',
311
+ letterSpacing: '0.015em',
312
+ color: '#ff6363',
313
+ }}
314
+ role="alert"
315
+ >
207
316
  {errors.email}
208
317
  </p>
209
318
  )}
210
319
  </div>
211
320
 
212
- {/* Submit Button */}
321
+ {/* Submit Button — gradient pill matching website CTA */}
213
322
  <button
214
323
  type="submit"
215
324
  disabled={isSubmitting}
216
- className="w-full px-5 py-2.5 text-sm font-medium text-white bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-4 focus:ring-blue-300 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
325
+ className="w-full py-2.5 rounded-xl font-semibold text-white transition-all duration-200"
326
+ style={{
327
+ fontSize: '14px',
328
+ letterSpacing: '0.006em',
329
+ background: 'linear-gradient(135deg, #337eff, #005eff)',
330
+ boxShadow: '0 4px 16px -4px rgba(51,126,255,0.4)',
331
+ opacity: isSubmitting ? 0.6 : 1,
332
+ cursor: isSubmitting ? 'not-allowed' : 'pointer',
333
+ }}
334
+ onMouseEnter={(e) => {
335
+ if (!isSubmitting) {
336
+ e.currentTarget.style.boxShadow =
337
+ '0 6px 20px -4px rgba(51,126,255,0.5)';
338
+ }
339
+ }}
340
+ onMouseLeave={(e) => {
341
+ e.currentTarget.style.boxShadow =
342
+ '0 4px 16px -4px rgba(51,126,255,0.4)';
343
+ }}
217
344
  >
218
345
  {isSubmitting ? (
219
- <span className="flex items-center justify-center">
346
+ <span className="flex items-center justify-center gap-2">
220
347
  <svg
221
- className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
222
- xmlns="http://www.w3.org/2000/svg"
223
- fill="none"
348
+ className="animate-spin"
349
+ width="18"
350
+ height="18"
224
351
  viewBox="0 0 24 24"
352
+ fill="none"
353
+ stroke="currentColor"
354
+ strokeWidth="2"
225
355
  aria-label="Loading"
226
356
  >
227
357
  <title>Loading</title>
228
- <circle
229
- className="opacity-25"
230
- cx="12"
231
- cy="12"
232
- r="10"
233
- stroke="currentColor"
234
- strokeWidth="4"
235
- />
236
- <path
237
- className="opacity-75"
238
- fill="currentColor"
239
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
240
- />
358
+ <path d="M21 12a9 9 0 11-6.219-8.56" />
241
359
  </svg>
242
360
  Starting Chat...
243
361
  </span>
@@ -247,15 +365,37 @@ export function PreChatForm({
247
365
  </button>
248
366
 
249
367
  {/* Privacy Notice */}
250
- <p className="text-xs text-gray-500 dark:text-gray-400 text-center">
368
+ <p
369
+ className="text-center"
370
+ style={{
371
+ fontSize: '11px',
372
+ letterSpacing: '0.019em',
373
+ color: 'rgba(247,247,248,0.28)',
374
+ }}
375
+ >
251
376
  We respect your privacy. Your information will only be used to assist you.
252
377
  </p>
253
378
  </form>
254
379
 
255
380
  {/* Footer */}
256
- <div className="bg-gray-50 dark:bg-gray-950 px-4 py-2 text-center border-t border-gray-200 dark:border-gray-700">
257
- <p className="text-xs text-gray-500 dark:text-gray-400">
258
- Powered by <span className="font-semibold">Xcelsior Chat</span>
381
+ <div
382
+ className="px-4 py-1.5 text-center"
383
+ style={{
384
+ borderTop: '1px solid rgba(255,255,255,0.06)',
385
+ backgroundColor: 'rgba(0,0,0,0.2)',
386
+ }}
387
+ >
388
+ <p
389
+ style={{
390
+ fontSize: '10px',
391
+ letterSpacing: '0.025em',
392
+ color: 'rgba(247,247,248,0.28)',
393
+ }}
394
+ >
395
+ Powered by{' '}
396
+ <span style={{ fontWeight: 600, color: 'rgba(247,247,248,0.45)' }}>
397
+ Xcelsior
398
+ </span>
259
399
  </p>
260
400
  </div>
261
401
  </div>