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