@mieweb/ui 0.9.0-dev.3 → 0.9.0-dev.5

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 (44) hide show
  1. package/dist/{chunk-5VXRKOPR.cjs → chunk-H3GRO43Z.cjs} +2 -2
  2. package/dist/{chunk-5VXRKOPR.cjs.map → chunk-H3GRO43Z.cjs.map} +1 -1
  3. package/dist/{chunk-IF3X2WZP.js → chunk-MD3ZY4UO.js} +2 -2
  4. package/dist/{chunk-IF3X2WZP.js.map → chunk-MD3ZY4UO.js.map} +1 -1
  5. package/dist/{chunk-RPG2ZP2T.js → chunk-PL6TFCYR.js} +900 -899
  6. package/dist/chunk-PL6TFCYR.js.map +1 -0
  7. package/dist/{chunk-NPHZUCF3.cjs → chunk-PY7XAG4D.cjs} +4 -4
  8. package/dist/chunk-PY7XAG4D.cjs.map +1 -0
  9. package/dist/{chunk-VMQZAOFM.js → chunk-QWJGEDHV.js} +51 -5
  10. package/dist/chunk-QWJGEDHV.js.map +1 -0
  11. package/dist/{chunk-UHHYJ42E.js → chunk-X5YM3KXY.js} +4 -4
  12. package/dist/chunk-X5YM3KXY.js.map +1 -0
  13. package/dist/{chunk-B67VPJCZ.cjs → chunk-XJUIZA4G.cjs} +58 -12
  14. package/dist/chunk-XJUIZA4G.cjs.map +1 -0
  15. package/dist/{chunk-O425FKDI.cjs → chunk-ZXRVVNT5.cjs} +903 -902
  16. package/dist/chunk-ZXRVVNT5.cjs.map +1 -0
  17. package/dist/components/SuperChat/index.cjs +8 -8
  18. package/dist/components/SuperChat/index.d.cts +4 -4
  19. package/dist/components/SuperChat/index.d.ts +4 -4
  20. package/dist/components/SuperChat/index.js +2 -2
  21. package/dist/components/SuperChat/plugins/index.cjs +3 -3
  22. package/dist/components/SuperChat/plugins/index.d.cts +3 -3
  23. package/dist/components/SuperChat/plugins/index.d.ts +3 -3
  24. package/dist/components/SuperChat/plugins/index.js +2 -2
  25. package/dist/index.cjs +190 -105
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +471 -426
  28. package/dist/index.d.ts +471 -426
  29. package/dist/index.js +123 -38
  30. package/dist/index.js.map +1 -1
  31. package/dist/styles.css +1 -1
  32. package/dist/tailwind-preset.cjs +4 -4
  33. package/dist/tailwind-preset.js +1 -1
  34. package/dist/{types-DZDroUW8.d.cts → types-B97JNRNQ.d.cts} +1 -1
  35. package/dist/{types-B2RLmdVd.d.cts → types-CnPEwzlp.d.cts} +6 -2
  36. package/dist/{types-B2RLmdVd.d.ts → types-CnPEwzlp.d.ts} +6 -2
  37. package/dist/{types-9vK_YdEb.d.ts → types-gZXrfLj6.d.ts} +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-B67VPJCZ.cjs.map +0 -1
  40. package/dist/chunk-NPHZUCF3.cjs.map +0 -1
  41. package/dist/chunk-O425FKDI.cjs.map +0 -1
  42. package/dist/chunk-RPG2ZP2T.js.map +0 -1
  43. package/dist/chunk-UHHYJ42E.js.map +0 -1
  44. package/dist/chunk-VMQZAOFM.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk5VXRKOPR_cjs = require('./chunk-5VXRKOPR.cjs');
3
+ var chunkH3GRO43Z_cjs = require('./chunk-H3GRO43Z.cjs');
4
4
  var chunkS5RVSXDV_cjs = require('./chunk-S5RVSXDV.cjs');
5
5
  var React6 = require('react');
6
6
  var classVarianceAuthority = require('class-variance-authority');
@@ -26,94 +26,36 @@ function _interopNamespace(e) {
26
26
 
27
27
  var React6__namespace = /*#__PURE__*/_interopNamespace(React6);
28
28
 
29
- function isFileAccepted(file, accepted) {
30
- if (!accepted || accepted.length === 0) return true;
31
- const mime = file.type;
32
- const name = file.name.toLowerCase();
33
- return accepted.some((token) => {
34
- if (token.startsWith(".")) return name.endsWith(token.toLowerCase());
35
- if (token.endsWith("/*")) return mime.startsWith(token.slice(0, -1));
36
- if (token.includes("/")) return mime === token;
37
- return false;
38
- });
39
- }
40
- function CharacterCounter({
41
- current,
42
- max,
43
- showWarningAt = 0.9,
44
- className
45
- }) {
46
- const percentage = current / max;
47
- const isWarning = percentage >= showWarningAt && percentage < 1;
48
- const isOver = current > max;
49
- return /* @__PURE__ */ jsxRuntime.jsxs(
50
- "span",
51
- {
52
- className: chunkS5RVSXDV_cjs.cn(
53
- "text-xs tabular-nums",
54
- isOver ? "font-medium text-red-700 dark:text-red-400" : isWarning ? "text-amber-700 dark:text-amber-400" : "text-neutral-600 dark:text-neutral-400",
55
- className
56
- ),
57
- "aria-live": "polite",
58
- "aria-label": `${current} of ${max} characters`,
59
- children: [
60
- current,
61
- "/",
62
- max
63
- ]
64
- }
65
- );
66
- }
67
- CharacterCounter.displayName = "CharacterCounter";
68
- var sendButtonVariants = classVarianceAuthority.cva(
69
- [
70
- "inline-flex items-center justify-center",
71
- "rounded-full p-3 self-start",
72
- "transition-all duration-200",
73
- "focus:outline-none focus:ring-2 focus:ring-primary-500",
74
- "disabled:opacity-50 disabled:cursor-not-allowed"
75
- ],
29
+ var statusIconVariants = classVarianceAuthority.cva(
30
+ "inline-flex items-center gap-0.5 text-current",
76
31
  {
77
32
  variants: {
78
- variant: {
79
- primary: [
80
- "bg-primary-800 text-white",
81
- "hover:bg-primary-900",
82
- "active:scale-95"
83
- ],
84
- subtle: [
85
- "bg-transparent text-primary-800",
86
- "hover:bg-primary-50 dark:hover:bg-primary-900/20"
87
- ]
88
- },
89
- canSend: {
90
- true: "",
91
- false: "opacity-50 cursor-not-allowed"
33
+ status: {
34
+ sending: "text-neutral-500",
35
+ sent: "text-neutral-500",
36
+ delivered: "text-muted-foreground",
37
+ read: "text-primary-800 dark:text-primary-300",
38
+ failed: "text-red-500"
92
39
  }
93
40
  },
94
41
  defaultVariants: {
95
- variant: "primary",
96
- canSend: false
42
+ status: "sent"
97
43
  }
98
44
  }
99
45
  );
100
- var SendButton = React6__namespace.forwardRef(
101
- ({ className, variant, canSend, isLoading, disabled, ...props }, ref) => {
102
- return /* @__PURE__ */ jsxRuntime.jsx(
103
- "button",
104
- {
105
- ref,
106
- type: "submit",
107
- disabled: disabled || !canSend || isLoading,
108
- "data-slot": "composer-send-button",
109
- className: chunkS5RVSXDV_cjs.cn(sendButtonVariants({ variant, canSend }), className),
110
- "aria-label": isLoading ? "Sending message" : "Send message",
111
- ...props,
112
- children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(
46
+ function MessageStatusIcon({ status, className }) {
47
+ return /* @__PURE__ */ jsxRuntime.jsxs(
48
+ "span",
49
+ {
50
+ className: chunkS5RVSXDV_cjs.cn(statusIconVariants({ status }), className),
51
+ role: "img",
52
+ "aria-label": `Message ${status}`,
53
+ children: [
54
+ status === "sending" && /* @__PURE__ */ jsxRuntime.jsxs(
113
55
  "svg",
114
56
  {
115
57
  "aria-hidden": "true",
116
- className: "h-5 w-5 animate-spin",
58
+ className: "h-3.5 w-3.5 animate-spin",
117
59
  fill: "none",
118
60
  viewBox: "0 0 24 24",
119
61
  children: [
@@ -138,517 +80,71 @@ var SendButton = React6__namespace.forwardRef(
138
80
  )
139
81
  ]
140
82
  }
141
- ) : /* @__PURE__ */ jsxRuntime.jsx(
83
+ ),
84
+ status === "sent" && /* @__PURE__ */ jsxRuntime.jsx(
142
85
  "svg",
143
86
  {
144
87
  "aria-hidden": "true",
145
- className: "h-5 w-5",
88
+ className: "h-3.5 w-3.5",
146
89
  fill: "none",
147
90
  viewBox: "0 0 24 24",
148
91
  stroke: "currentColor",
92
+ strokeWidth: "2",
149
93
  children: /* @__PURE__ */ jsxRuntime.jsx(
150
94
  "path",
151
95
  {
152
96
  strokeLinecap: "round",
153
97
  strokeLinejoin: "round",
154
- strokeWidth: 2,
155
- d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
156
- }
157
- )
158
- }
159
- )
160
- }
161
- );
162
- }
163
- );
164
- SendButton.displayName = "SendButton";
165
- var MessageComposer = React6__namespace.forwardRef(
166
- ({
167
- onSend,
168
- onTypingStart,
169
- onTypingStop,
170
- value: controlledValue,
171
- onValueChange,
172
- placeholder = "Type a message...",
173
- maxLength = 1600,
174
- showCharacterCount = false,
175
- disabled = false,
176
- isSending = false,
177
- showAttachmentPicker = true,
178
- showCameraButton = false,
179
- acceptedFileTypes = ["image/*", "video/*", ".pdf", ".doc", ".docx"],
180
- maxFileSize = 25 * 1024 * 1024,
181
- maxAttachments = 10,
182
- onError,
183
- autoFocus = false,
184
- replyTo = null,
185
- onCancelReply,
186
- variant = "default",
187
- inputTrailing,
188
- mentionOptions,
189
- className
190
- }, ref) => {
191
- const textareaRef = React6__namespace.useRef(null);
192
- const [internalContent, setInternalContent] = React6__namespace.useState("");
193
- const isControlled = controlledValue !== void 0;
194
- const content = isControlled ? controlledValue : internalContent;
195
- const setContent = React6__namespace.useCallback(
196
- (val) => {
197
- if (isControlled) {
198
- const newVal = typeof val === "function" ? val(controlledValue) : val;
199
- onValueChange?.(newVal);
200
- } else {
201
- setInternalContent(val);
202
- }
203
- },
204
- [isControlled, controlledValue, onValueChange]
205
- );
206
- const [attachments, setAttachments] = React6__namespace.useState(
207
- []
208
- );
209
- const [isTyping, setIsTyping] = React6__namespace.useState(false);
210
- const typingTimeoutRef = React6__namespace.useRef(null);
211
- React6__namespace.useImperativeHandle(ref, () => textareaRef.current);
212
- React6__namespace.useEffect(() => {
213
- const textarea = textareaRef.current;
214
- if (textarea) {
215
- textarea.style.height = "auto";
216
- textarea.style.height = `${Math.min(textarea.scrollHeight, 150)}px`;
217
- }
218
- }, [content]);
219
- React6__namespace.useEffect(() => {
220
- if (content.length > 0 && !isTyping) {
221
- setIsTyping(true);
222
- onTypingStart?.();
223
- }
224
- if (typingTimeoutRef.current) {
225
- clearTimeout(typingTimeoutRef.current);
226
- }
227
- typingTimeoutRef.current = setTimeout(() => {
228
- if (isTyping) {
229
- setIsTyping(false);
230
- onTypingStop?.();
231
- }
232
- }, 2e3);
233
- return () => {
234
- if (typingTimeoutRef.current) {
235
- clearTimeout(typingTimeoutRef.current);
236
- }
237
- };
238
- }, [content, isTyping, onTypingStart, onTypingStop]);
239
- React6__namespace.useEffect(() => {
240
- if (autoFocus) {
241
- textareaRef.current?.focus();
242
- }
243
- }, [autoFocus]);
244
- React6__namespace.useEffect(() => {
245
- if (replyTo) {
246
- textareaRef.current?.focus();
247
- }
248
- }, [replyTo]);
249
- const mention = chunk5VXRKOPR_cjs.useMentionAutocomplete({
250
- options: mentionOptions,
251
- value: content,
252
- setValue: setContent,
253
- textareaRef
254
- });
255
- const canSend = (content.trim().length > 0 || attachments.length > 0) && content.length <= maxLength && !disabled && !isSending;
256
- const handleSubmit = async (event) => {
257
- event.preventDefault();
258
- if (!canSend) return;
259
- const message = {
260
- content: content.trim(),
261
- attachments: attachments.map((a) => a.file),
262
- replyToId: replyTo?.id
263
- };
264
- setContent("");
265
- setAttachments([]);
266
- setIsTyping(false);
267
- onTypingStop?.();
268
- try {
269
- await onSend(message);
270
- } catch {
271
- setContent(message.content);
272
- onError?.("Failed to send message");
273
- }
274
- };
275
- const handleKeyDown = (event) => {
276
- if (mention.handleKeyDown(event)) return;
277
- if (event.key === "Enter" && !event.shiftKey) {
278
- event.preventDefault();
279
- if (canSend) {
280
- handleSubmit(event);
281
- }
282
- }
283
- };
284
- const handleFilesSelected = (files) => {
285
- const remainingSlots = maxAttachments - attachments.length;
286
- const filesToAdd = files.slice(0, remainingSlots);
287
- if (files.length > remainingSlots) {
288
- onError?.(`Maximum ${maxAttachments} attachments allowed`);
289
- }
290
- const newAttachments = filesToAdd.map((file) => {
291
- const type = chunk5VXRKOPR_cjs.getFileType(file.type);
292
- let previewUrl;
293
- if (type === "image" || type === "video") {
294
- previewUrl = URL.createObjectURL(file);
295
- }
296
- return {
297
- id: chunk5VXRKOPR_cjs.generateAttachmentId(),
298
- file,
299
- previewUrl,
300
- type,
301
- state: "pending"
302
- };
303
- });
304
- setAttachments((prev) => [...prev, ...newAttachments]);
305
- };
306
- const handlePaste = (event) => {
307
- if (!showAttachmentPicker || disabled) return;
308
- const pasted = Array.from(event.clipboardData.items).filter((item) => item.kind === "file").map((item) => item.getAsFile()).filter((f) => f !== null).filter((f) => isFileAccepted(f, acceptedFileTypes));
309
- if (pasted.length === 0) return;
310
- event.preventDefault();
311
- handleFilesSelected(pasted);
312
- };
313
- const handleRemoveAttachment = (attachmentId) => {
314
- setAttachments((prev) => {
315
- const attachment = prev.find((a) => a.id === attachmentId);
316
- if (attachment?.previewUrl) {
317
- URL.revokeObjectURL(attachment.previewUrl);
318
- }
319
- return prev.filter((a) => a.id !== attachmentId);
320
- });
321
- };
322
- React6__namespace.useEffect(() => {
323
- const currentAttachments = attachments;
324
- return () => {
325
- currentAttachments.forEach((a) => {
326
- if (a.previewUrl) {
327
- URL.revokeObjectURL(a.previewUrl);
328
- }
329
- });
330
- };
331
- }, [attachments]);
332
- return /* @__PURE__ */ jsxRuntime.jsx(
333
- chunk5VXRKOPR_cjs.DragDropZone,
334
- {
335
- onFilesDropped: handleFilesSelected,
336
- acceptedTypes: acceptedFileTypes,
337
- maxFileSize,
338
- maxFiles: maxAttachments - attachments.length,
339
- disabled: disabled || attachments.length >= maxAttachments,
340
- onError,
341
- className: chunkS5RVSXDV_cjs.cn("w-full", className),
342
- children: /* @__PURE__ */ jsxRuntime.jsxs(
343
- "form",
344
- {
345
- onSubmit: handleSubmit,
346
- "data-slot": "message-composer",
347
- className: "w-full",
348
- children: [
349
- replyTo && /* @__PURE__ */ jsxRuntime.jsxs(
350
- "div",
351
- {
352
- "data-slot": "composer-reply-preview",
353
- className: chunkS5RVSXDV_cjs.cn(
354
- "flex items-center gap-2 px-4 py-2",
355
- "bg-neutral-50 dark:bg-neutral-800/50",
356
- "border-primary-500 border-s-4"
357
- ),
358
- children: [
359
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
360
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-primary-800 dark:text-primary-400 text-xs font-medium", children: [
361
- "Replying to ",
362
- replyTo.senderName
363
- ] }),
364
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-neutral-600 dark:text-neutral-300", children: replyTo.content })
365
- ] }),
366
- /* @__PURE__ */ jsxRuntime.jsx(
367
- "button",
368
- {
369
- type: "button",
370
- onClick: onCancelReply,
371
- className: chunkS5RVSXDV_cjs.cn(
372
- "shrink-0 rounded p-1",
373
- "text-neutral-500 hover:text-neutral-700",
374
- "dark:text-neutral-400 dark:hover:text-neutral-200",
375
- "focus:ring-primary-500 focus:ring-2 focus:outline-none"
376
- ),
377
- "aria-label": "Cancel reply",
378
- children: /* @__PURE__ */ jsxRuntime.jsx(
379
- "svg",
380
- {
381
- "aria-hidden": "true",
382
- className: "h-4 w-4",
383
- fill: "none",
384
- viewBox: "0 0 24 24",
385
- stroke: "currentColor",
386
- children: /* @__PURE__ */ jsxRuntime.jsx(
387
- "path",
388
- {
389
- strokeLinecap: "round",
390
- strokeLinejoin: "round",
391
- strokeWidth: 2,
392
- d: "M6 18L18 6M6 6l12 12"
393
- }
394
- )
395
- }
396
- )
397
- }
398
- )
399
- ]
400
- }
401
- ),
402
- attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
403
- "div",
404
- {
405
- "data-slot": "composer-attachments",
406
- className: chunkS5RVSXDV_cjs.cn(
407
- "flex flex-wrap gap-2 p-3",
408
- "border-t border-neutral-200 dark:border-neutral-700"
409
- ),
410
- children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(
411
- chunk5VXRKOPR_cjs.AttachmentPreviewItem,
412
- {
413
- attachment,
414
- onRemove: () => handleRemoveAttachment(attachment.id)
415
- },
416
- attachment.id
417
- ))
418
- }
419
- ),
420
- /* @__PURE__ */ jsxRuntime.jsxs(
421
- "div",
422
- {
423
- "data-slot": "composer-input-area",
424
- className: chunkS5RVSXDV_cjs.cn(
425
- "flex items-center gap-2 p-3",
426
- "bg-white dark:bg-neutral-900",
427
- variant === "default" && "border-t border-neutral-200 dark:border-neutral-700"
428
- ),
429
- children: [
430
- showAttachmentPicker && /* @__PURE__ */ jsxRuntime.jsx(
431
- chunk5VXRKOPR_cjs.AttachmentPicker,
432
- {
433
- onFilesSelected: handleFilesSelected,
434
- acceptedTypes: acceptedFileTypes,
435
- maxFileSize,
436
- maxFiles: maxAttachments - attachments.length,
437
- disabled: disabled || attachments.length >= maxAttachments,
438
- onError
439
- }
440
- ),
441
- showCameraButton && /* @__PURE__ */ jsxRuntime.jsx(
442
- chunk5VXRKOPR_cjs.CameraButton,
443
- {
444
- onCapture: (file) => handleFilesSelected([file]),
445
- disabled: disabled || attachments.length >= maxAttachments
446
- }
447
- ),
448
- /* @__PURE__ */ jsxRuntime.jsxs(
449
- "div",
450
- {
451
- "data-slot": "composer-input-wrapper",
452
- className: "relative flex-1",
453
- ref: mention.anchorRef,
454
- children: [
455
- /* @__PURE__ */ jsxRuntime.jsx(chunk5VXRKOPR_cjs.MentionMenu, { mention }),
456
- /* @__PURE__ */ jsxRuntime.jsx(
457
- "textarea",
458
- {
459
- ref: textareaRef,
460
- "data-slot": "composer-input",
461
- value: content,
462
- onChange: (e) => {
463
- setContent(e.target.value);
464
- mention.sync(
465
- e.target.value,
466
- e.target.selectionStart ?? e.target.value.length
467
- );
468
- },
469
- onClick: (e) => {
470
- const el = e.currentTarget;
471
- mention.sync(el.value, el.selectionStart ?? el.value.length);
472
- },
473
- onKeyDown: handleKeyDown,
474
- onPaste: handlePaste,
475
- placeholder,
476
- disabled: disabled || isSending,
477
- rows: 1,
478
- className: chunkS5RVSXDV_cjs.cn(
479
- "w-full resize-none rounded-2xl py-2.5",
480
- inputTrailing ? "ps-4 pe-10" : "px-4",
481
- "bg-neutral-100 dark:bg-neutral-800",
482
- "text-neutral-900 dark:text-neutral-100",
483
- "placeholder:text-neutral-400 dark:placeholder:text-neutral-500",
484
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
485
- "disabled:cursor-not-allowed disabled:opacity-50",
486
- "transition-colors",
487
- "max-h-[150px]"
488
- ),
489
- "aria-label": "Message",
490
- "aria-describedby": showCharacterCount ? "char-count" : void 0,
491
- ...mention.inputProps
492
- }
493
- ),
494
- inputTrailing && /* @__PURE__ */ jsxRuntime.jsx(
495
- "div",
496
- {
497
- "data-slot": "composer-input-trailing",
498
- className: "pointer-events-none absolute end-1 top-0 flex h-[44px] items-center [&>*]:pointer-events-auto",
499
- children: inputTrailing
500
- }
501
- ),
502
- showCharacterCount && /* @__PURE__ */ jsxRuntime.jsx(
503
- "div",
504
- {
505
- "data-slot": "composer-char-count",
506
- id: "char-count",
507
- className: "absolute end-3 bottom-1.5",
508
- children: /* @__PURE__ */ jsxRuntime.jsx(CharacterCounter, { current: content.length, max: maxLength })
509
- }
510
- )
511
- ]
512
- }
513
- ),
514
- /* @__PURE__ */ jsxRuntime.jsx(
515
- SendButton,
516
- {
517
- canSend,
518
- isLoading: isSending,
519
- disabled
520
- }
521
- )
522
- ]
523
- }
524
- )
525
- ]
526
- }
527
- )
528
- }
529
- );
530
- }
531
- );
532
- MessageComposer.displayName = "MessageComposer";
533
- var statusIconVariants = classVarianceAuthority.cva(
534
- "inline-flex items-center gap-0.5 text-current",
535
- {
536
- variants: {
537
- status: {
538
- sending: "text-neutral-500",
539
- sent: "text-neutral-500",
540
- delivered: "text-muted-foreground",
541
- read: "text-primary-800 dark:text-primary-300",
542
- failed: "text-red-500"
543
- }
544
- },
545
- defaultVariants: {
546
- status: "sent"
547
- }
548
- }
549
- );
550
- function MessageStatusIcon({ status, className }) {
551
- return /* @__PURE__ */ jsxRuntime.jsxs(
552
- "span",
553
- {
554
- className: chunkS5RVSXDV_cjs.cn(statusIconVariants({ status }), className),
555
- role: "img",
556
- "aria-label": `Message ${status}`,
557
- children: [
558
- status === "sending" && /* @__PURE__ */ jsxRuntime.jsxs(
559
- "svg",
560
- {
561
- "aria-hidden": "true",
562
- className: "h-3.5 w-3.5 animate-spin",
563
- fill: "none",
564
- viewBox: "0 0 24 24",
565
- children: [
566
- /* @__PURE__ */ jsxRuntime.jsx(
567
- "circle",
568
- {
569
- className: "opacity-25",
570
- cx: "12",
571
- cy: "12",
572
- r: "10",
573
- stroke: "currentColor",
574
- strokeWidth: "4"
575
- }
576
- ),
577
- /* @__PURE__ */ jsxRuntime.jsx(
578
- "path",
579
- {
580
- className: "opacity-75",
581
- fill: "currentColor",
582
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
583
- }
584
- )
585
- ]
586
- }
587
- ),
588
- status === "sent" && /* @__PURE__ */ jsxRuntime.jsx(
589
- "svg",
590
- {
591
- "aria-hidden": "true",
592
- className: "h-3.5 w-3.5",
593
- fill: "none",
594
- viewBox: "0 0 24 24",
595
- stroke: "currentColor",
596
- strokeWidth: "2",
597
- children: /* @__PURE__ */ jsxRuntime.jsx(
598
- "path",
599
- {
600
- strokeLinecap: "round",
601
- strokeLinejoin: "round",
602
- d: "M5 13l4 4L19 7"
603
- }
604
- )
605
- }
606
- ),
607
- (status === "delivered" || status === "read") && /* @__PURE__ */ jsxRuntime.jsxs(
608
- "svg",
609
- {
610
- "aria-hidden": "true",
611
- className: "h-4 w-4",
612
- fill: "none",
613
- viewBox: "0 0 24 24",
614
- stroke: "currentColor",
615
- strokeWidth: "2",
616
- children: [
617
- /* @__PURE__ */ jsxRuntime.jsx(
618
- "path",
619
- {
620
- strokeLinecap: "round",
621
- strokeLinejoin: "round",
622
- d: "M5 13l4 4L19 7"
623
- }
624
- ),
625
- /* @__PURE__ */ jsxRuntime.jsx(
626
- "path",
627
- {
628
- strokeLinecap: "round",
629
- strokeLinejoin: "round",
630
- d: "M12 13l4 4L26 7",
631
- transform: "translate(-5, 0)"
632
- }
633
- )
634
- ]
635
- }
636
- ),
637
- status === "failed" && /* @__PURE__ */ jsxRuntime.jsx(
638
- "svg",
639
- {
640
- "aria-hidden": "true",
641
- className: "h-3.5 w-3.5",
642
- fill: "none",
643
- viewBox: "0 0 24 24",
644
- stroke: "currentColor",
645
- strokeWidth: "2",
646
- children: /* @__PURE__ */ jsxRuntime.jsx(
647
- "path",
648
- {
649
- strokeLinecap: "round",
650
- strokeLinejoin: "round",
651
- d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
98
+ d: "M5 13l4 4L19 7"
99
+ }
100
+ )
101
+ }
102
+ ),
103
+ (status === "delivered" || status === "read") && /* @__PURE__ */ jsxRuntime.jsxs(
104
+ "svg",
105
+ {
106
+ "aria-hidden": "true",
107
+ className: "h-4 w-4",
108
+ fill: "none",
109
+ viewBox: "0 0 24 24",
110
+ stroke: "currentColor",
111
+ strokeWidth: "2",
112
+ children: [
113
+ /* @__PURE__ */ jsxRuntime.jsx(
114
+ "path",
115
+ {
116
+ strokeLinecap: "round",
117
+ strokeLinejoin: "round",
118
+ d: "M5 13l4 4L19 7"
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsxRuntime.jsx(
122
+ "path",
123
+ {
124
+ strokeLinecap: "round",
125
+ strokeLinejoin: "round",
126
+ d: "M12 13l4 4L26 7",
127
+ transform: "translate(-5, 0)"
128
+ }
129
+ )
130
+ ]
131
+ }
132
+ ),
133
+ status === "failed" && /* @__PURE__ */ jsxRuntime.jsx(
134
+ "svg",
135
+ {
136
+ "aria-hidden": "true",
137
+ className: "h-3.5 w-3.5",
138
+ fill: "none",
139
+ viewBox: "0 0 24 24",
140
+ stroke: "currentColor",
141
+ strokeWidth: "2",
142
+ children: /* @__PURE__ */ jsxRuntime.jsx(
143
+ "path",
144
+ {
145
+ strokeLinecap: "round",
146
+ strokeLinejoin: "round",
147
+ d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
652
148
  }
653
149
  )
654
150
  }
@@ -1198,373 +694,878 @@ function SkeletonMessage({
1198
694
  )
1199
695
  }
1200
696
  ),
1201
- showAvatar && isOutgoing && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "skeleton-spacer", className: "w-8" })
697
+ showAvatar && isOutgoing && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "skeleton-spacer", className: "w-8" })
698
+ ]
699
+ }
700
+ );
701
+ }
702
+ SkeletonMessage.displayName = "SkeletonMessage";
703
+ function TypingIndicator({ typingState, className }) {
704
+ const { participants } = typingState;
705
+ if (participants.length === 0) return null;
706
+ const typingText = participants.length === 1 ? `${participants[0].name} is typing` : participants.length === 2 ? `${participants[0].name} and ${participants[1].name} are typing` : `${participants[0].name} and ${participants.length - 1} others are typing`;
707
+ return /* @__PURE__ */ jsxRuntime.jsxs(
708
+ "div",
709
+ {
710
+ "data-slot": "typing-indicator",
711
+ className: chunkS5RVSXDV_cjs.cn("flex items-center gap-2 px-4 py-2", className),
712
+ role: "status",
713
+ "aria-live": "polite",
714
+ "aria-label": typingText,
715
+ children: [
716
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 rounded-2xl rounded-es-md bg-neutral-200 px-4 py-3 dark:bg-neutral-700", children: [
717
+ /* @__PURE__ */ jsxRuntime.jsx(
718
+ "span",
719
+ {
720
+ className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
721
+ style: { animationDelay: "0ms" }
722
+ }
723
+ ),
724
+ /* @__PURE__ */ jsxRuntime.jsx(
725
+ "span",
726
+ {
727
+ className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
728
+ style: { animationDelay: "150ms" }
729
+ }
730
+ ),
731
+ /* @__PURE__ */ jsxRuntime.jsx(
732
+ "span",
733
+ {
734
+ className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
735
+ style: { animationDelay: "300ms" }
736
+ }
737
+ )
738
+ ] }),
739
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: typingText })
740
+ ]
741
+ }
742
+ );
743
+ }
744
+ TypingIndicator.displayName = "TypingIndicator";
745
+ function DateSeparator({ label, className }) {
746
+ return /* @__PURE__ */ jsxRuntime.jsx(
747
+ "div",
748
+ {
749
+ "data-slot": "date-separator",
750
+ className: chunkS5RVSXDV_cjs.cn("flex items-center justify-center py-4", className),
751
+ role: "separator",
752
+ "aria-label": label,
753
+ children: /* @__PURE__ */ jsxRuntime.jsx(
754
+ "span",
755
+ {
756
+ className: chunkS5RVSXDV_cjs.cn(
757
+ "rounded-full px-3 py-1 text-xs font-medium",
758
+ "bg-neutral-100 text-neutral-600",
759
+ "dark:bg-neutral-800 dark:text-neutral-400"
760
+ ),
761
+ children: label
762
+ }
763
+ )
764
+ }
765
+ );
766
+ }
767
+ DateSeparator.displayName = "DateSeparator";
768
+ function EmptyState({
769
+ title = "No messages yet",
770
+ description = "Start the conversation by sending a message below.",
771
+ icon,
772
+ action,
773
+ className
774
+ }) {
775
+ return /* @__PURE__ */ jsxRuntime.jsxs(
776
+ "div",
777
+ {
778
+ "data-slot": "message-empty-state",
779
+ className: chunkS5RVSXDV_cjs.cn(
780
+ "flex flex-1 flex-col items-center justify-center p-8 text-center",
781
+ className
782
+ ),
783
+ role: "status",
784
+ "aria-label": title,
785
+ children: [
786
+ icon || /* @__PURE__ */ jsxRuntime.jsx(
787
+ "div",
788
+ {
789
+ "data-slot": "message-empty-state-icon",
790
+ className: "mb-4 rounded-full bg-neutral-100 p-4 dark:bg-neutral-800",
791
+ children: /* @__PURE__ */ jsxRuntime.jsx(
792
+ "svg",
793
+ {
794
+ "aria-hidden": "true",
795
+ className: "h-12 w-12 text-neutral-500",
796
+ fill: "none",
797
+ viewBox: "0 0 24 24",
798
+ stroke: "currentColor",
799
+ children: /* @__PURE__ */ jsxRuntime.jsx(
800
+ "path",
801
+ {
802
+ strokeLinecap: "round",
803
+ strokeLinejoin: "round",
804
+ strokeWidth: 1.5,
805
+ d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
806
+ }
807
+ )
808
+ }
809
+ )
810
+ }
811
+ ),
812
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-2 text-lg font-semibold text-neutral-900 dark:text-neutral-100", children: title }),
813
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mb-4 max-w-sm text-sm", children: description }),
814
+ action
1202
815
  ]
1203
816
  }
1204
817
  );
1205
818
  }
1206
- SkeletonMessage.displayName = "SkeletonMessage";
1207
- function TypingIndicator({ typingState, className }) {
1208
- const { participants } = typingState;
1209
- if (participants.length === 0) return null;
1210
- const typingText = participants.length === 1 ? `${participants[0].name} is typing` : participants.length === 2 ? `${participants[0].name} and ${participants[1].name} are typing` : `${participants[0].name} and ${participants.length - 1} others are typing`;
1211
- return /* @__PURE__ */ jsxRuntime.jsxs(
819
+ EmptyState.displayName = "EmptyState";
820
+ function LoadMoreButton({
821
+ isLoading,
822
+ onClick,
823
+ className
824
+ }) {
825
+ return /* @__PURE__ */ jsxRuntime.jsx(
1212
826
  "div",
1213
827
  {
1214
- "data-slot": "typing-indicator",
1215
- className: chunkS5RVSXDV_cjs.cn("flex items-center gap-2 px-4 py-2", className),
1216
- role: "status",
1217
- "aria-live": "polite",
1218
- "aria-label": typingText,
1219
- children: [
1220
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 rounded-2xl rounded-es-md bg-neutral-200 px-4 py-3 dark:bg-neutral-700", children: [
1221
- /* @__PURE__ */ jsxRuntime.jsx(
1222
- "span",
1223
- {
1224
- className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
1225
- style: { animationDelay: "0ms" }
1226
- }
828
+ "data-slot": "load-more-button",
829
+ className: chunkS5RVSXDV_cjs.cn("flex justify-center py-4", className),
830
+ children: /* @__PURE__ */ jsxRuntime.jsx(
831
+ "button",
832
+ {
833
+ type: "button",
834
+ onClick,
835
+ disabled: isLoading,
836
+ className: chunkS5RVSXDV_cjs.cn(
837
+ "rounded-full px-4 py-2 text-sm font-medium",
838
+ "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-300",
839
+ "hover:bg-neutral-200 dark:hover:bg-neutral-700",
840
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none",
841
+ "disabled:cursor-not-allowed disabled:opacity-50",
842
+ "transition-colors"
843
+ ),
844
+ "aria-label": isLoading ? "Loading more messages" : "Load more messages",
845
+ children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
846
+ /* @__PURE__ */ jsxRuntime.jsxs(
847
+ "svg",
848
+ {
849
+ "aria-hidden": "true",
850
+ className: "h-4 w-4 animate-spin",
851
+ fill: "none",
852
+ viewBox: "0 0 24 24",
853
+ children: [
854
+ /* @__PURE__ */ jsxRuntime.jsx(
855
+ "circle",
856
+ {
857
+ className: "opacity-25",
858
+ cx: "12",
859
+ cy: "12",
860
+ r: "10",
861
+ stroke: "currentColor",
862
+ strokeWidth: "4"
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsxRuntime.jsx(
866
+ "path",
867
+ {
868
+ className: "opacity-75",
869
+ fill: "currentColor",
870
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
871
+ }
872
+ )
873
+ ]
874
+ }
875
+ ),
876
+ "Loading..."
877
+ ] }) : "Load earlier messages"
878
+ }
879
+ )
880
+ }
881
+ );
882
+ }
883
+ LoadMoreButton.displayName = "LoadMoreButton";
884
+ var MessageList = React6__namespace.forwardRef(
885
+ ({
886
+ messages,
887
+ currentUser,
888
+ isLoading = false,
889
+ hasMore = false,
890
+ isLoadingMore = false,
891
+ typingState,
892
+ showAvatars = true,
893
+ showSenderNames = false,
894
+ groupByDate = true,
895
+ onLoadMore,
896
+ onRetryMessage,
897
+ onAttachmentClick,
898
+ emptyState,
899
+ formatTimestamp,
900
+ className,
901
+ autoScroll = "onNewMessage"
902
+ }, ref) => {
903
+ const scrollContainerRef = React6__namespace.useRef(null);
904
+ const bottomRef = React6__namespace.useRef(null);
905
+ const [isUserScrolled, setIsUserScrolled] = React6__namespace.useState(false);
906
+ const prevMessageCountRef = React6__namespace.useRef(messages.length);
907
+ React6__namespace.useImperativeHandle(ref, () => scrollContainerRef.current);
908
+ const handleScroll = React6__namespace.useCallback(() => {
909
+ const container = scrollContainerRef.current;
910
+ if (!container) return;
911
+ const { scrollTop, scrollHeight, clientHeight } = container;
912
+ const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
913
+ setIsUserScrolled(!isAtBottom);
914
+ }, []);
915
+ React6__namespace.useEffect(() => {
916
+ const container = scrollContainerRef.current;
917
+ const bottom = bottomRef.current;
918
+ if (!container || !bottom) return;
919
+ const messageCountChanged = messages.length !== prevMessageCountRef.current;
920
+ prevMessageCountRef.current = messages.length;
921
+ if (autoScroll === "always") {
922
+ bottom.scrollIntoView({ behavior: "smooth" });
923
+ } else if (autoScroll === "onNewMessage" && messageCountChanged) {
924
+ const lastMessage = messages[messages.length - 1];
925
+ const isOutgoing = lastMessage?.sender.id === currentUser.id;
926
+ if (isOutgoing || !isUserScrolled) {
927
+ bottom.scrollIntoView({ behavior: "smooth" });
928
+ }
929
+ }
930
+ }, [messages, currentUser.id, autoScroll, isUserScrolled]);
931
+ React6__namespace.useEffect(() => {
932
+ const bottom = bottomRef.current;
933
+ if (bottom && !isLoading) {
934
+ bottom.scrollIntoView();
935
+ }
936
+ }, [isLoading]);
937
+ const messageGroups = groupByDate ? groupMessagesByDate(messages) : [{ date: "all", label: "", messages }];
938
+ if (isLoading) {
939
+ return /* @__PURE__ */ jsxRuntime.jsx(
940
+ "div",
941
+ {
942
+ role: "status",
943
+ className: chunkS5RVSXDV_cjs.cn(
944
+ "flex flex-1 flex-col gap-3 overflow-y-auto p-4",
945
+ className
1227
946
  ),
947
+ "aria-busy": "true",
948
+ "aria-label": "Loading messages",
949
+ children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
950
+ SkeletonMessage,
951
+ {
952
+ isOutgoing: i % 3 === 0,
953
+ showAvatar: showAvatars
954
+ },
955
+ i
956
+ ))
957
+ }
958
+ );
959
+ }
960
+ if (messages.length === 0) {
961
+ return emptyState || /* @__PURE__ */ jsxRuntime.jsx(EmptyState, {});
962
+ }
963
+ return /* @__PURE__ */ jsxRuntime.jsxs(
964
+ "div",
965
+ {
966
+ ref: scrollContainerRef,
967
+ "data-slot": "message-list",
968
+ className: chunkS5RVSXDV_cjs.cn(
969
+ "flex flex-1 flex-col overflow-y-auto",
970
+ "scroll-smooth",
971
+ className
972
+ ),
973
+ onScroll: handleScroll,
974
+ role: "log",
975
+ "aria-label": "Message history",
976
+ "aria-live": "polite",
977
+ children: [
978
+ hasMore && onLoadMore && /* @__PURE__ */ jsxRuntime.jsx(LoadMoreButton, { isLoading: isLoadingMore, onClick: onLoadMore }),
1228
979
  /* @__PURE__ */ jsxRuntime.jsx(
1229
- "span",
980
+ "div",
1230
981
  {
1231
- className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
1232
- style: { animationDelay: "150ms" }
982
+ "data-slot": "message-list-content",
983
+ className: "flex flex-col gap-1 p-4",
984
+ children: messageGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(React6__namespace.Fragment, { children: [
985
+ groupByDate && group.label && /* @__PURE__ */ jsxRuntime.jsx(DateSeparator, { label: group.label }),
986
+ group.messages.map((message, index) => {
987
+ const prevMessage = group.messages[index - 1];
988
+ const isOutgoing = message.sender.id === currentUser.id;
989
+ const isSameGroup = isSameSenderGroup(prevMessage, message);
990
+ return /* @__PURE__ */ jsxRuntime.jsx(
991
+ "div",
992
+ {
993
+ className: chunkS5RVSXDV_cjs.cn(
994
+ "transition-opacity duration-200",
995
+ isSameGroup ? "mt-0.5" : "mt-3",
996
+ index === 0 && "mt-0"
997
+ ),
998
+ children: /* @__PURE__ */ jsxRuntime.jsx(
999
+ MessageBubble,
1000
+ {
1001
+ message,
1002
+ isOutgoing,
1003
+ showAvatar: showAvatars && !isSameGroup && !isOutgoing,
1004
+ showSenderName: showSenderNames && !isSameGroup && !isOutgoing,
1005
+ showTimestamp: !isSameGroup,
1006
+ onRetry: message.status === "failed" && onRetryMessage ? () => onRetryMessage(message.id) : void 0,
1007
+ onAttachmentClick: (attachment) => onAttachmentClick?.(attachment, message),
1008
+ formatTimestamp
1009
+ }
1010
+ )
1011
+ },
1012
+ message.id
1013
+ );
1014
+ })
1015
+ ] }, group.date))
1233
1016
  }
1234
1017
  ),
1235
- /* @__PURE__ */ jsxRuntime.jsx(
1236
- "span",
1018
+ typingState && typingState.participants.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typingState }),
1019
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef, className: "h-0", "aria-hidden": "true" }),
1020
+ isUserScrolled && /* @__PURE__ */ jsxRuntime.jsx(
1021
+ "button",
1237
1022
  {
1238
- className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
1239
- style: { animationDelay: "300ms" }
1023
+ type: "button",
1024
+ onClick: () => {
1025
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
1026
+ },
1027
+ className: chunkS5RVSXDV_cjs.cn(
1028
+ "fixed end-4 bottom-24 z-10",
1029
+ "rounded-full p-3 shadow-lg",
1030
+ "bg-white dark:bg-neutral-800",
1031
+ "border border-neutral-200 dark:border-neutral-700",
1032
+ "hover:bg-neutral-50 dark:hover:bg-neutral-700",
1033
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1034
+ "transition-all"
1035
+ ),
1036
+ "aria-label": "Scroll to bottom",
1037
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1038
+ "svg",
1039
+ {
1040
+ "aria-hidden": "true",
1041
+ className: "h-5 w-5 text-neutral-600 dark:text-neutral-300",
1042
+ fill: "none",
1043
+ viewBox: "0 0 24 24",
1044
+ stroke: "currentColor",
1045
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1046
+ "path",
1047
+ {
1048
+ strokeLinecap: "round",
1049
+ strokeLinejoin: "round",
1050
+ strokeWidth: 2,
1051
+ d: "M19 14l-7 7m0 0l-7-7m7 7V3"
1052
+ }
1053
+ )
1054
+ }
1055
+ )
1240
1056
  }
1241
1057
  )
1242
- ] }),
1243
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: typingText })
1244
- ]
1245
- }
1246
- );
1247
- }
1248
- TypingIndicator.displayName = "TypingIndicator";
1249
- function DateSeparator({ label, className }) {
1250
- return /* @__PURE__ */ jsxRuntime.jsx(
1251
- "div",
1252
- {
1253
- "data-slot": "date-separator",
1254
- className: chunkS5RVSXDV_cjs.cn("flex items-center justify-center py-4", className),
1255
- role: "separator",
1256
- "aria-label": label,
1257
- children: /* @__PURE__ */ jsxRuntime.jsx(
1258
- "span",
1259
- {
1260
- className: chunkS5RVSXDV_cjs.cn(
1261
- "rounded-full px-3 py-1 text-xs font-medium",
1262
- "bg-neutral-100 text-neutral-600",
1263
- "dark:bg-neutral-800 dark:text-neutral-400"
1264
- ),
1265
- children: label
1266
- }
1267
- )
1268
- }
1269
- );
1058
+ ]
1059
+ }
1060
+ );
1061
+ }
1062
+ );
1063
+ MessageList.displayName = "MessageList";
1064
+ function isFileAccepted(file, accepted) {
1065
+ if (!accepted || accepted.length === 0) return true;
1066
+ const mime = file.type;
1067
+ const name = file.name.toLowerCase();
1068
+ return accepted.some((token) => {
1069
+ if (token.startsWith(".")) return name.endsWith(token.toLowerCase());
1070
+ if (token.endsWith("/*")) return mime.startsWith(token.slice(0, -1));
1071
+ if (token.includes("/")) return mime === token;
1072
+ return false;
1073
+ });
1270
1074
  }
1271
- DateSeparator.displayName = "DateSeparator";
1272
- function EmptyState({
1273
- title = "No messages yet",
1274
- description = "Start the conversation by sending a message below.",
1275
- icon,
1276
- action,
1075
+ function CharacterCounter({
1076
+ current,
1077
+ max,
1078
+ showWarningAt = 0.9,
1277
1079
  className
1278
1080
  }) {
1081
+ const percentage = current / max;
1082
+ const isWarning = percentage >= showWarningAt && percentage < 1;
1083
+ const isOver = current > max;
1279
1084
  return /* @__PURE__ */ jsxRuntime.jsxs(
1280
- "div",
1085
+ "span",
1281
1086
  {
1282
- "data-slot": "message-empty-state",
1283
1087
  className: chunkS5RVSXDV_cjs.cn(
1284
- "flex flex-1 flex-col items-center justify-center p-8 text-center",
1088
+ "text-xs tabular-nums",
1089
+ isOver ? "font-medium text-red-700 dark:text-red-400" : isWarning ? "text-amber-700 dark:text-amber-400" : "text-neutral-600 dark:text-neutral-400",
1285
1090
  className
1286
1091
  ),
1287
- role: "status",
1288
- "aria-label": title,
1092
+ "aria-live": "polite",
1093
+ "aria-label": `${current} of ${max} characters`,
1289
1094
  children: [
1290
- icon || /* @__PURE__ */ jsxRuntime.jsx(
1291
- "div",
1292
- {
1293
- "data-slot": "message-empty-state-icon",
1294
- className: "mb-4 rounded-full bg-neutral-100 p-4 dark:bg-neutral-800",
1295
- children: /* @__PURE__ */ jsxRuntime.jsx(
1296
- "svg",
1297
- {
1298
- "aria-hidden": "true",
1299
- className: "h-12 w-12 text-neutral-500",
1300
- fill: "none",
1301
- viewBox: "0 0 24 24",
1302
- stroke: "currentColor",
1303
- children: /* @__PURE__ */ jsxRuntime.jsx(
1304
- "path",
1305
- {
1306
- strokeLinecap: "round",
1307
- strokeLinejoin: "round",
1308
- strokeWidth: 1.5,
1309
- d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
1310
- }
1311
- )
1312
- }
1313
- )
1314
- }
1315
- ),
1316
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-2 text-lg font-semibold text-neutral-900 dark:text-neutral-100", children: title }),
1317
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mb-4 max-w-sm text-sm", children: description }),
1318
- action
1095
+ current,
1096
+ "/",
1097
+ max
1319
1098
  ]
1320
1099
  }
1321
1100
  );
1322
1101
  }
1323
- EmptyState.displayName = "EmptyState";
1324
- function LoadMoreButton({
1325
- isLoading,
1326
- onClick,
1327
- className
1328
- }) {
1329
- return /* @__PURE__ */ jsxRuntime.jsx(
1330
- "div",
1331
- {
1332
- "data-slot": "load-more-button",
1333
- className: chunkS5RVSXDV_cjs.cn("flex justify-center py-4", className),
1334
- children: /* @__PURE__ */ jsxRuntime.jsx(
1335
- "button",
1336
- {
1337
- type: "button",
1338
- onClick,
1339
- disabled: isLoading,
1340
- className: chunkS5RVSXDV_cjs.cn(
1341
- "rounded-full px-4 py-2 text-sm font-medium",
1342
- "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-300",
1343
- "hover:bg-neutral-200 dark:hover:bg-neutral-700",
1344
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1345
- "disabled:cursor-not-allowed disabled:opacity-50",
1346
- "transition-colors"
1347
- ),
1348
- "aria-label": isLoading ? "Loading more messages" : "Load more messages",
1349
- children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
1350
- /* @__PURE__ */ jsxRuntime.jsxs(
1351
- "svg",
1352
- {
1353
- "aria-hidden": "true",
1354
- className: "h-4 w-4 animate-spin",
1355
- fill: "none",
1356
- viewBox: "0 0 24 24",
1357
- children: [
1358
- /* @__PURE__ */ jsxRuntime.jsx(
1359
- "circle",
1360
- {
1361
- className: "opacity-25",
1362
- cx: "12",
1363
- cy: "12",
1364
- r: "10",
1365
- stroke: "currentColor",
1366
- strokeWidth: "4"
1367
- }
1368
- ),
1369
- /* @__PURE__ */ jsxRuntime.jsx(
1370
- "path",
1371
- {
1372
- className: "opacity-75",
1373
- fill: "currentColor",
1374
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
1375
- }
1376
- )
1377
- ]
1378
- }
1379
- ),
1380
- "Loading..."
1381
- ] }) : "Load earlier messages"
1382
- }
1383
- )
1102
+ CharacterCounter.displayName = "CharacterCounter";
1103
+ var sendButtonVariants = classVarianceAuthority.cva(
1104
+ [
1105
+ "inline-flex items-center justify-center",
1106
+ "rounded-full p-3 self-start",
1107
+ "transition-all duration-200",
1108
+ "focus:outline-none focus:ring-2 focus:ring-primary-500",
1109
+ "disabled:opacity-50 disabled:cursor-not-allowed"
1110
+ ],
1111
+ {
1112
+ variants: {
1113
+ variant: {
1114
+ primary: [
1115
+ "bg-primary-800 text-white",
1116
+ "hover:bg-primary-900",
1117
+ "active:scale-95"
1118
+ ],
1119
+ subtle: [
1120
+ "bg-transparent text-primary-800",
1121
+ "hover:bg-primary-50 dark:hover:bg-primary-900/20"
1122
+ ]
1123
+ },
1124
+ canSend: {
1125
+ true: "",
1126
+ false: "opacity-50 cursor-not-allowed"
1127
+ }
1128
+ },
1129
+ defaultVariants: {
1130
+ variant: "primary",
1131
+ canSend: false
1384
1132
  }
1385
- );
1386
- }
1387
- LoadMoreButton.displayName = "LoadMoreButton";
1388
- var MessageList = React6__namespace.forwardRef(
1133
+ }
1134
+ );
1135
+ var SendButton = React6__namespace.forwardRef(
1136
+ ({ className, variant, canSend, isLoading, disabled, ...props }, ref) => {
1137
+ return /* @__PURE__ */ jsxRuntime.jsx(
1138
+ "button",
1139
+ {
1140
+ ref,
1141
+ type: "submit",
1142
+ disabled: disabled || !canSend || isLoading,
1143
+ "data-slot": "composer-send-button",
1144
+ className: chunkS5RVSXDV_cjs.cn(sendButtonVariants({ variant, canSend }), className),
1145
+ "aria-label": isLoading ? "Sending message" : "Send message",
1146
+ ...props,
1147
+ children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(
1148
+ "svg",
1149
+ {
1150
+ "aria-hidden": "true",
1151
+ className: "h-5 w-5 animate-spin",
1152
+ fill: "none",
1153
+ viewBox: "0 0 24 24",
1154
+ children: [
1155
+ /* @__PURE__ */ jsxRuntime.jsx(
1156
+ "circle",
1157
+ {
1158
+ className: "opacity-25",
1159
+ cx: "12",
1160
+ cy: "12",
1161
+ r: "10",
1162
+ stroke: "currentColor",
1163
+ strokeWidth: "4"
1164
+ }
1165
+ ),
1166
+ /* @__PURE__ */ jsxRuntime.jsx(
1167
+ "path",
1168
+ {
1169
+ className: "opacity-75",
1170
+ fill: "currentColor",
1171
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
1172
+ }
1173
+ )
1174
+ ]
1175
+ }
1176
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1177
+ "svg",
1178
+ {
1179
+ "aria-hidden": "true",
1180
+ className: "h-5 w-5",
1181
+ fill: "none",
1182
+ viewBox: "0 0 24 24",
1183
+ stroke: "currentColor",
1184
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1185
+ "path",
1186
+ {
1187
+ strokeLinecap: "round",
1188
+ strokeLinejoin: "round",
1189
+ strokeWidth: 2,
1190
+ d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
1191
+ }
1192
+ )
1193
+ }
1194
+ )
1195
+ }
1196
+ );
1197
+ }
1198
+ );
1199
+ SendButton.displayName = "SendButton";
1200
+ var MessageComposer = React6__namespace.forwardRef(
1389
1201
  ({
1390
- messages,
1391
- currentUser,
1392
- isLoading = false,
1393
- hasMore = false,
1394
- isLoadingMore = false,
1395
- typingState,
1396
- showAvatars = true,
1397
- showSenderNames = false,
1398
- groupByDate = true,
1399
- onLoadMore,
1400
- onRetryMessage,
1401
- onAttachmentClick,
1402
- emptyState,
1403
- formatTimestamp,
1404
- className,
1405
- autoScroll = "onNewMessage"
1202
+ onSend,
1203
+ onTypingStart,
1204
+ onTypingStop,
1205
+ value: controlledValue,
1206
+ onValueChange,
1207
+ placeholder = "Type a message...",
1208
+ maxLength = 1600,
1209
+ showCharacterCount = false,
1210
+ disabled = false,
1211
+ isSending = false,
1212
+ showAttachmentPicker = true,
1213
+ showCameraButton = false,
1214
+ acceptedFileTypes = ["image/*", "video/*", ".pdf", ".doc", ".docx"],
1215
+ maxFileSize = 25 * 1024 * 1024,
1216
+ maxAttachments = 10,
1217
+ onError,
1218
+ autoFocus = false,
1219
+ replyTo = null,
1220
+ onCancelReply,
1221
+ variant = "default",
1222
+ inputTrailing,
1223
+ mentionOptions,
1224
+ className
1406
1225
  }, ref) => {
1407
- const scrollContainerRef = React6__namespace.useRef(null);
1408
- const bottomRef = React6__namespace.useRef(null);
1409
- const [isUserScrolled, setIsUserScrolled] = React6__namespace.useState(false);
1410
- const prevMessageCountRef = React6__namespace.useRef(messages.length);
1411
- React6__namespace.useImperativeHandle(ref, () => scrollContainerRef.current);
1412
- const handleScroll = React6__namespace.useCallback(() => {
1413
- const container = scrollContainerRef.current;
1414
- if (!container) return;
1415
- const { scrollTop, scrollHeight, clientHeight } = container;
1416
- const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
1417
- setIsUserScrolled(!isAtBottom);
1418
- }, []);
1226
+ const textareaRef = React6__namespace.useRef(null);
1227
+ const [internalContent, setInternalContent] = React6__namespace.useState("");
1228
+ const isControlled = controlledValue !== void 0;
1229
+ const content = isControlled ? controlledValue : internalContent;
1230
+ const setContent = React6__namespace.useCallback(
1231
+ (val) => {
1232
+ if (isControlled) {
1233
+ const newVal = typeof val === "function" ? val(controlledValue) : val;
1234
+ onValueChange?.(newVal);
1235
+ } else {
1236
+ setInternalContent(val);
1237
+ }
1238
+ },
1239
+ [isControlled, controlledValue, onValueChange]
1240
+ );
1241
+ const [attachments, setAttachments] = React6__namespace.useState(
1242
+ []
1243
+ );
1244
+ const [isTyping, setIsTyping] = React6__namespace.useState(false);
1245
+ const typingTimeoutRef = React6__namespace.useRef(null);
1246
+ React6__namespace.useImperativeHandle(ref, () => textareaRef.current);
1419
1247
  React6__namespace.useEffect(() => {
1420
- const container = scrollContainerRef.current;
1421
- const bottom = bottomRef.current;
1422
- if (!container || !bottom) return;
1423
- const messageCountChanged = messages.length !== prevMessageCountRef.current;
1424
- prevMessageCountRef.current = messages.length;
1425
- if (autoScroll === "always") {
1426
- bottom.scrollIntoView({ behavior: "smooth" });
1427
- } else if (autoScroll === "onNewMessage" && messageCountChanged) {
1428
- const lastMessage = messages[messages.length - 1];
1429
- const isOutgoing = lastMessage?.sender.id === currentUser.id;
1430
- if (isOutgoing || !isUserScrolled) {
1431
- bottom.scrollIntoView({ behavior: "smooth" });
1248
+ const textarea = textareaRef.current;
1249
+ if (textarea) {
1250
+ textarea.style.height = "auto";
1251
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 150)}px`;
1252
+ }
1253
+ }, [content]);
1254
+ React6__namespace.useEffect(() => {
1255
+ if (content.length > 0 && !isTyping) {
1256
+ setIsTyping(true);
1257
+ onTypingStart?.();
1258
+ }
1259
+ if (typingTimeoutRef.current) {
1260
+ clearTimeout(typingTimeoutRef.current);
1261
+ }
1262
+ typingTimeoutRef.current = setTimeout(() => {
1263
+ if (isTyping) {
1264
+ setIsTyping(false);
1265
+ onTypingStop?.();
1266
+ }
1267
+ }, 2e3);
1268
+ return () => {
1269
+ if (typingTimeoutRef.current) {
1270
+ clearTimeout(typingTimeoutRef.current);
1432
1271
  }
1272
+ };
1273
+ }, [content, isTyping, onTypingStart, onTypingStop]);
1274
+ React6__namespace.useEffect(() => {
1275
+ if (autoFocus) {
1276
+ textareaRef.current?.focus();
1433
1277
  }
1434
- }, [messages, currentUser.id, autoScroll, isUserScrolled]);
1278
+ }, [autoFocus]);
1279
+ const replyToId = replyTo?.id;
1435
1280
  React6__namespace.useEffect(() => {
1436
- const bottom = bottomRef.current;
1437
- if (bottom && !isLoading) {
1438
- bottom.scrollIntoView();
1281
+ if (replyToId !== void 0) {
1282
+ textareaRef.current?.focus();
1439
1283
  }
1440
- }, [isLoading]);
1441
- const messageGroups = groupByDate ? groupMessagesByDate(messages) : [{ date: "all", label: "", messages }];
1442
- if (isLoading) {
1443
- return /* @__PURE__ */ jsxRuntime.jsx(
1444
- "div",
1445
- {
1446
- role: "status",
1447
- className: chunkS5RVSXDV_cjs.cn(
1448
- "flex flex-1 flex-col gap-3 overflow-y-auto p-4",
1449
- className
1450
- ),
1451
- "aria-busy": "true",
1452
- "aria-label": "Loading messages",
1453
- children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
1454
- SkeletonMessage,
1455
- {
1456
- isOutgoing: i % 3 === 0,
1457
- showAvatar: showAvatars
1458
- },
1459
- i
1460
- ))
1284
+ }, [replyToId]);
1285
+ const mention = chunkH3GRO43Z_cjs.useMentionAutocomplete({
1286
+ options: mentionOptions,
1287
+ value: content,
1288
+ setValue: setContent,
1289
+ textareaRef
1290
+ });
1291
+ const canSend = (content.trim().length > 0 || attachments.length > 0) && content.length <= maxLength && !disabled && !isSending;
1292
+ const handleSubmit = async (event) => {
1293
+ event.preventDefault();
1294
+ if (!canSend) return;
1295
+ const message = {
1296
+ content: content.trim(),
1297
+ attachments: attachments.map((a) => a.file),
1298
+ replyToId: replyTo?.id
1299
+ };
1300
+ setContent("");
1301
+ setAttachments([]);
1302
+ setIsTyping(false);
1303
+ onTypingStop?.();
1304
+ try {
1305
+ await onSend(message);
1306
+ } catch {
1307
+ setContent(message.content);
1308
+ onError?.("Failed to send message");
1309
+ }
1310
+ };
1311
+ const handleKeyDown = (event) => {
1312
+ if (mention.handleKeyDown(event)) return;
1313
+ if (event.key === "Enter" && !event.shiftKey) {
1314
+ event.preventDefault();
1315
+ if (canSend) {
1316
+ handleSubmit(event);
1461
1317
  }
1462
- );
1463
- }
1464
- if (messages.length === 0) {
1465
- return emptyState || /* @__PURE__ */ jsxRuntime.jsx(EmptyState, {});
1466
- }
1467
- return /* @__PURE__ */ jsxRuntime.jsxs(
1468
- "div",
1469
- {
1470
- ref: scrollContainerRef,
1471
- "data-slot": "message-list",
1472
- className: chunkS5RVSXDV_cjs.cn(
1473
- "flex flex-1 flex-col overflow-y-auto",
1474
- "scroll-smooth",
1475
- className
1476
- ),
1477
- onScroll: handleScroll,
1478
- role: "log",
1479
- "aria-label": "Message history",
1480
- "aria-live": "polite",
1481
- children: [
1482
- hasMore && onLoadMore && /* @__PURE__ */ jsxRuntime.jsx(LoadMoreButton, { isLoading: isLoadingMore, onClick: onLoadMore }),
1483
- /* @__PURE__ */ jsxRuntime.jsx(
1484
- "div",
1485
- {
1486
- "data-slot": "message-list-content",
1487
- className: "flex flex-col gap-1 p-4",
1488
- children: messageGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(React6__namespace.Fragment, { children: [
1489
- groupByDate && group.label && /* @__PURE__ */ jsxRuntime.jsx(DateSeparator, { label: group.label }),
1490
- group.messages.map((message, index) => {
1491
- const prevMessage = group.messages[index - 1];
1492
- const isOutgoing = message.sender.id === currentUser.id;
1493
- const isSameGroup = isSameSenderGroup(prevMessage, message);
1494
- return /* @__PURE__ */ jsxRuntime.jsx(
1495
- "div",
1318
+ }
1319
+ };
1320
+ const handleFilesSelected = (files) => {
1321
+ const remainingSlots = maxAttachments - attachments.length;
1322
+ const filesToAdd = files.slice(0, remainingSlots);
1323
+ if (files.length > remainingSlots) {
1324
+ onError?.(`Maximum ${maxAttachments} attachments allowed`);
1325
+ }
1326
+ const newAttachments = filesToAdd.map((file) => {
1327
+ const type = chunkH3GRO43Z_cjs.getFileType(file.type);
1328
+ let previewUrl;
1329
+ if (type === "image" || type === "video") {
1330
+ previewUrl = URL.createObjectURL(file);
1331
+ }
1332
+ return {
1333
+ id: chunkH3GRO43Z_cjs.generateAttachmentId(),
1334
+ file,
1335
+ previewUrl,
1336
+ type,
1337
+ state: "pending"
1338
+ };
1339
+ });
1340
+ setAttachments((prev) => [...prev, ...newAttachments]);
1341
+ };
1342
+ const handlePaste = (event) => {
1343
+ if (!showAttachmentPicker || disabled) return;
1344
+ const pasted = Array.from(event.clipboardData.items).filter((item) => item.kind === "file").map((item) => item.getAsFile()).filter((f) => f !== null).filter((f) => isFileAccepted(f, acceptedFileTypes));
1345
+ if (pasted.length === 0) return;
1346
+ event.preventDefault();
1347
+ handleFilesSelected(pasted);
1348
+ };
1349
+ const handleRemoveAttachment = (attachmentId) => {
1350
+ setAttachments((prev) => {
1351
+ const attachment = prev.find((a) => a.id === attachmentId);
1352
+ if (attachment?.previewUrl) {
1353
+ URL.revokeObjectURL(attachment.previewUrl);
1354
+ }
1355
+ return prev.filter((a) => a.id !== attachmentId);
1356
+ });
1357
+ };
1358
+ React6__namespace.useEffect(() => {
1359
+ const currentAttachments = attachments;
1360
+ return () => {
1361
+ currentAttachments.forEach((a) => {
1362
+ if (a.previewUrl) {
1363
+ URL.revokeObjectURL(a.previewUrl);
1364
+ }
1365
+ });
1366
+ };
1367
+ }, [attachments]);
1368
+ return /* @__PURE__ */ jsxRuntime.jsx(
1369
+ chunkH3GRO43Z_cjs.DragDropZone,
1370
+ {
1371
+ onFilesDropped: handleFilesSelected,
1372
+ acceptedTypes: acceptedFileTypes,
1373
+ maxFileSize,
1374
+ maxFiles: maxAttachments - attachments.length,
1375
+ disabled: disabled || attachments.length >= maxAttachments,
1376
+ onError,
1377
+ className: chunkS5RVSXDV_cjs.cn("w-full", className),
1378
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1379
+ "form",
1380
+ {
1381
+ onSubmit: handleSubmit,
1382
+ "data-slot": "message-composer",
1383
+ className: "w-full",
1384
+ children: [
1385
+ replyTo && /* @__PURE__ */ jsxRuntime.jsxs(
1386
+ "div",
1387
+ {
1388
+ "data-slot": "composer-reply-preview",
1389
+ className: chunkS5RVSXDV_cjs.cn(
1390
+ "flex items-center gap-2 px-4 py-2",
1391
+ "bg-neutral-50 dark:bg-neutral-800/50",
1392
+ "border-primary-500 border-s-4"
1393
+ ),
1394
+ children: [
1395
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
1396
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-primary-800 dark:text-primary-400 text-xs font-medium", children: [
1397
+ "Replying to ",
1398
+ replyTo.senderName
1399
+ ] }),
1400
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-neutral-600 dark:text-neutral-300", children: replyTo.content })
1401
+ ] }),
1402
+ /* @__PURE__ */ jsxRuntime.jsx(
1403
+ "button",
1404
+ {
1405
+ type: "button",
1406
+ onClick: onCancelReply,
1407
+ className: chunkS5RVSXDV_cjs.cn(
1408
+ "shrink-0 rounded p-1",
1409
+ "text-neutral-500 hover:text-neutral-700",
1410
+ "dark:text-neutral-400 dark:hover:text-neutral-200",
1411
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none"
1412
+ ),
1413
+ "aria-label": "Cancel reply",
1414
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1415
+ "svg",
1416
+ {
1417
+ "aria-hidden": "true",
1418
+ className: "h-4 w-4",
1419
+ fill: "none",
1420
+ viewBox: "0 0 24 24",
1421
+ stroke: "currentColor",
1422
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1423
+ "path",
1424
+ {
1425
+ strokeLinecap: "round",
1426
+ strokeLinejoin: "round",
1427
+ strokeWidth: 2,
1428
+ d: "M6 18L18 6M6 6l12 12"
1429
+ }
1430
+ )
1431
+ }
1432
+ )
1433
+ }
1434
+ )
1435
+ ]
1436
+ }
1437
+ ),
1438
+ attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1439
+ "div",
1440
+ {
1441
+ "data-slot": "composer-attachments",
1442
+ className: chunkS5RVSXDV_cjs.cn(
1443
+ "flex flex-wrap gap-2 p-3",
1444
+ "border-t border-neutral-200 dark:border-neutral-700"
1445
+ ),
1446
+ children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(
1447
+ chunkH3GRO43Z_cjs.AttachmentPreviewItem,
1496
1448
  {
1497
- className: chunkS5RVSXDV_cjs.cn(
1498
- "transition-opacity duration-200",
1499
- isSameGroup ? "mt-0.5" : "mt-3",
1500
- index === 0 && "mt-0"
1501
- ),
1502
- children: /* @__PURE__ */ jsxRuntime.jsx(
1503
- MessageBubble,
1504
- {
1505
- message,
1506
- isOutgoing,
1507
- showAvatar: showAvatars && !isSameGroup && !isOutgoing,
1508
- showSenderName: showSenderNames && !isSameGroup && !isOutgoing,
1509
- showTimestamp: !isSameGroup,
1510
- onRetry: message.status === "failed" && onRetryMessage ? () => onRetryMessage(message.id) : void 0,
1511
- onAttachmentClick: (attachment) => onAttachmentClick?.(attachment, message),
1512
- formatTimestamp
1513
- }
1514
- )
1449
+ attachment,
1450
+ onRemove: () => handleRemoveAttachment(attachment.id)
1515
1451
  },
1516
- message.id
1517
- );
1518
- })
1519
- ] }, group.date))
1520
- }
1521
- ),
1522
- typingState && typingState.participants.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typingState }),
1523
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef, className: "h-0", "aria-hidden": "true" }),
1524
- isUserScrolled && /* @__PURE__ */ jsxRuntime.jsx(
1525
- "button",
1526
- {
1527
- type: "button",
1528
- onClick: () => {
1529
- bottomRef.current?.scrollIntoView({ behavior: "smooth" });
1530
- },
1531
- className: chunkS5RVSXDV_cjs.cn(
1532
- "fixed end-4 bottom-24 z-10",
1533
- "rounded-full p-3 shadow-lg",
1534
- "bg-white dark:bg-neutral-800",
1535
- "border border-neutral-200 dark:border-neutral-700",
1536
- "hover:bg-neutral-50 dark:hover:bg-neutral-700",
1537
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1538
- "transition-all"
1452
+ attachment.id
1453
+ ))
1454
+ }
1539
1455
  ),
1540
- "aria-label": "Scroll to bottom",
1541
- children: /* @__PURE__ */ jsxRuntime.jsx(
1542
- "svg",
1456
+ /* @__PURE__ */ jsxRuntime.jsxs(
1457
+ "div",
1543
1458
  {
1544
- "aria-hidden": "true",
1545
- className: "h-5 w-5 text-neutral-600 dark:text-neutral-300",
1546
- fill: "none",
1547
- viewBox: "0 0 24 24",
1548
- stroke: "currentColor",
1549
- children: /* @__PURE__ */ jsxRuntime.jsx(
1550
- "path",
1551
- {
1552
- strokeLinecap: "round",
1553
- strokeLinejoin: "round",
1554
- strokeWidth: 2,
1555
- d: "M19 14l-7 7m0 0l-7-7m7 7V3"
1556
- }
1557
- )
1459
+ "data-slot": "composer-input-area",
1460
+ className: chunkS5RVSXDV_cjs.cn(
1461
+ "flex items-center gap-2 p-3",
1462
+ "bg-white dark:bg-neutral-900",
1463
+ variant === "default" && "border-t border-neutral-200 dark:border-neutral-700"
1464
+ ),
1465
+ children: [
1466
+ showAttachmentPicker && /* @__PURE__ */ jsxRuntime.jsx(
1467
+ chunkH3GRO43Z_cjs.AttachmentPicker,
1468
+ {
1469
+ onFilesSelected: handleFilesSelected,
1470
+ acceptedTypes: acceptedFileTypes,
1471
+ maxFileSize,
1472
+ maxFiles: maxAttachments - attachments.length,
1473
+ disabled: disabled || attachments.length >= maxAttachments,
1474
+ onError
1475
+ }
1476
+ ),
1477
+ showCameraButton && /* @__PURE__ */ jsxRuntime.jsx(
1478
+ chunkH3GRO43Z_cjs.CameraButton,
1479
+ {
1480
+ onCapture: (file) => handleFilesSelected([file]),
1481
+ disabled: disabled || attachments.length >= maxAttachments
1482
+ }
1483
+ ),
1484
+ /* @__PURE__ */ jsxRuntime.jsxs(
1485
+ "div",
1486
+ {
1487
+ "data-slot": "composer-input-wrapper",
1488
+ className: "relative flex-1",
1489
+ ref: mention.anchorRef,
1490
+ children: [
1491
+ /* @__PURE__ */ jsxRuntime.jsx(chunkH3GRO43Z_cjs.MentionMenu, { mention }),
1492
+ /* @__PURE__ */ jsxRuntime.jsx(
1493
+ "textarea",
1494
+ {
1495
+ ref: textareaRef,
1496
+ "data-slot": "composer-input",
1497
+ value: content,
1498
+ onChange: (e) => {
1499
+ setContent(e.target.value);
1500
+ mention.sync(
1501
+ e.target.value,
1502
+ e.target.selectionStart ?? e.target.value.length
1503
+ );
1504
+ },
1505
+ onClick: (e) => {
1506
+ const el = e.currentTarget;
1507
+ mention.sync(el.value, el.selectionStart ?? el.value.length);
1508
+ },
1509
+ onKeyDown: handleKeyDown,
1510
+ onPaste: handlePaste,
1511
+ placeholder,
1512
+ disabled: disabled || isSending,
1513
+ rows: 1,
1514
+ className: chunkS5RVSXDV_cjs.cn(
1515
+ "w-full resize-none rounded-2xl py-2.5",
1516
+ inputTrailing ? "ps-4 pe-10" : "px-4",
1517
+ "bg-neutral-100 dark:bg-neutral-800",
1518
+ "text-neutral-900 dark:text-neutral-100",
1519
+ "placeholder:text-neutral-400 dark:placeholder:text-neutral-500",
1520
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1521
+ "disabled:cursor-not-allowed disabled:opacity-50",
1522
+ "transition-colors",
1523
+ "max-h-[150px]"
1524
+ ),
1525
+ "aria-label": "Message",
1526
+ "aria-describedby": showCharacterCount ? "char-count" : void 0,
1527
+ ...mention.inputProps
1528
+ }
1529
+ ),
1530
+ inputTrailing && /* @__PURE__ */ jsxRuntime.jsx(
1531
+ "div",
1532
+ {
1533
+ "data-slot": "composer-input-trailing",
1534
+ className: "pointer-events-none absolute end-1 top-0 flex h-[44px] items-center [&>*]:pointer-events-auto",
1535
+ children: inputTrailing
1536
+ }
1537
+ ),
1538
+ showCharacterCount && /* @__PURE__ */ jsxRuntime.jsx(
1539
+ "div",
1540
+ {
1541
+ "data-slot": "composer-char-count",
1542
+ id: "char-count",
1543
+ className: "absolute end-3 bottom-1.5",
1544
+ children: /* @__PURE__ */ jsxRuntime.jsx(CharacterCounter, { current: content.length, max: maxLength })
1545
+ }
1546
+ )
1547
+ ]
1548
+ }
1549
+ ),
1550
+ /* @__PURE__ */ jsxRuntime.jsx(
1551
+ SendButton,
1552
+ {
1553
+ canSend,
1554
+ isLoading: isSending,
1555
+ disabled
1556
+ }
1557
+ )
1558
+ ]
1558
1559
  }
1559
1560
  )
1560
- }
1561
- )
1562
- ]
1561
+ ]
1562
+ }
1563
+ )
1563
1564
  }
1564
1565
  );
1565
1566
  }
1566
1567
  );
1567
- MessageList.displayName = "MessageList";
1568
+ MessageComposer.displayName = "MessageComposer";
1568
1569
  var headerVariants = classVarianceAuthority.cva(
1569
1570
  [
1570
1571
  "flex items-center gap-3 px-4 py-3",
@@ -2121,7 +2122,7 @@ var MessageThread = React6__namespace.forwardRef(
2121
2122
  }
2122
2123
  ),
2123
2124
  /* @__PURE__ */ jsxRuntime.jsx(
2124
- chunk5VXRKOPR_cjs.DragDropZone,
2125
+ chunkH3GRO43Z_cjs.DragDropZone,
2125
2126
  {
2126
2127
  onFilesDropped: () => {
2127
2128
  onError?.("Drop files on the composer to attach them");
@@ -2524,5 +2525,5 @@ exports.useMessageScroll = useMessageScroll;
2524
2525
  exports.useMessages = useMessages;
2525
2526
  exports.useReadReceipts = useReadReceipts;
2526
2527
  exports.useTypingIndicator = useTypingIndicator;
2527
- //# sourceMappingURL=chunk-O425FKDI.cjs.map
2528
- //# sourceMappingURL=chunk-O425FKDI.cjs.map
2528
+ //# sourceMappingURL=chunk-ZXRVVNT5.cjs.map
2529
+ //# sourceMappingURL=chunk-ZXRVVNT5.cjs.map