@mieweb/ui 0.9.0-dev.4 → 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 (42) 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-USZ3VEPE.js → chunk-PL6TFCYR.js} +857 -857
  6. package/dist/chunk-PL6TFCYR.js.map +1 -0
  7. package/dist/{chunk-Z3RYJWSV.cjs → chunk-PY7XAG4D.cjs} +3 -4
  8. package/dist/chunk-PY7XAG4D.cjs.map +1 -0
  9. package/dist/{chunk-DG6LIEUJ.js → chunk-QWJGEDHV.js} +3 -3
  10. package/dist/{chunk-DG6LIEUJ.js.map → chunk-QWJGEDHV.js.map} +1 -1
  11. package/dist/{chunk-XEZCHQHB.js → chunk-X5YM3KXY.js} +3 -4
  12. package/dist/chunk-X5YM3KXY.js.map +1 -0
  13. package/dist/{chunk-DH7I5HMK.cjs → chunk-XJUIZA4G.cjs} +11 -11
  14. package/dist/{chunk-DH7I5HMK.cjs.map → chunk-XJUIZA4G.cjs.map} +1 -1
  15. package/dist/{chunk-3MHG4HYB.cjs → chunk-ZXRVVNT5.cjs} +902 -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 +466 -439
  28. package/dist/index.d.ts +466 -439
  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-3MHG4HYB.cjs.map +0 -1
  40. package/dist/chunk-USZ3VEPE.js.map +0 -1
  41. package/dist/chunk-XEZCHQHB.js.map +0 -1
  42. package/dist/chunk-Z3RYJWSV.cjs.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,518 +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
- const replyToId = replyTo?.id;
245
- React6__namespace.useEffect(() => {
246
- if (replyToId !== void 0) {
247
- textareaRef.current?.focus();
248
- }
249
- }, [replyToId]);
250
- const mention = chunk5VXRKOPR_cjs.useMentionAutocomplete({
251
- options: mentionOptions,
252
- value: content,
253
- setValue: setContent,
254
- textareaRef
255
- });
256
- const canSend = (content.trim().length > 0 || attachments.length > 0) && content.length <= maxLength && !disabled && !isSending;
257
- const handleSubmit = async (event) => {
258
- event.preventDefault();
259
- if (!canSend) return;
260
- const message = {
261
- content: content.trim(),
262
- attachments: attachments.map((a) => a.file),
263
- replyToId: replyTo?.id
264
- };
265
- setContent("");
266
- setAttachments([]);
267
- setIsTyping(false);
268
- onTypingStop?.();
269
- try {
270
- await onSend(message);
271
- } catch {
272
- setContent(message.content);
273
- onError?.("Failed to send message");
274
- }
275
- };
276
- const handleKeyDown = (event) => {
277
- if (mention.handleKeyDown(event)) return;
278
- if (event.key === "Enter" && !event.shiftKey) {
279
- event.preventDefault();
280
- if (canSend) {
281
- handleSubmit(event);
282
- }
283
- }
284
- };
285
- const handleFilesSelected = (files) => {
286
- const remainingSlots = maxAttachments - attachments.length;
287
- const filesToAdd = files.slice(0, remainingSlots);
288
- if (files.length > remainingSlots) {
289
- onError?.(`Maximum ${maxAttachments} attachments allowed`);
290
- }
291
- const newAttachments = filesToAdd.map((file) => {
292
- const type = chunk5VXRKOPR_cjs.getFileType(file.type);
293
- let previewUrl;
294
- if (type === "image" || type === "video") {
295
- previewUrl = URL.createObjectURL(file);
296
- }
297
- return {
298
- id: chunk5VXRKOPR_cjs.generateAttachmentId(),
299
- file,
300
- previewUrl,
301
- type,
302
- state: "pending"
303
- };
304
- });
305
- setAttachments((prev) => [...prev, ...newAttachments]);
306
- };
307
- const handlePaste = (event) => {
308
- if (!showAttachmentPicker || disabled) return;
309
- 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));
310
- if (pasted.length === 0) return;
311
- event.preventDefault();
312
- handleFilesSelected(pasted);
313
- };
314
- const handleRemoveAttachment = (attachmentId) => {
315
- setAttachments((prev) => {
316
- const attachment = prev.find((a) => a.id === attachmentId);
317
- if (attachment?.previewUrl) {
318
- URL.revokeObjectURL(attachment.previewUrl);
319
- }
320
- return prev.filter((a) => a.id !== attachmentId);
321
- });
322
- };
323
- React6__namespace.useEffect(() => {
324
- const currentAttachments = attachments;
325
- return () => {
326
- currentAttachments.forEach((a) => {
327
- if (a.previewUrl) {
328
- URL.revokeObjectURL(a.previewUrl);
329
- }
330
- });
331
- };
332
- }, [attachments]);
333
- return /* @__PURE__ */ jsxRuntime.jsx(
334
- chunk5VXRKOPR_cjs.DragDropZone,
335
- {
336
- onFilesDropped: handleFilesSelected,
337
- acceptedTypes: acceptedFileTypes,
338
- maxFileSize,
339
- maxFiles: maxAttachments - attachments.length,
340
- disabled: disabled || attachments.length >= maxAttachments,
341
- onError,
342
- className: chunkS5RVSXDV_cjs.cn("w-full", className),
343
- children: /* @__PURE__ */ jsxRuntime.jsxs(
344
- "form",
345
- {
346
- onSubmit: handleSubmit,
347
- "data-slot": "message-composer",
348
- className: "w-full",
349
- children: [
350
- replyTo && /* @__PURE__ */ jsxRuntime.jsxs(
351
- "div",
352
- {
353
- "data-slot": "composer-reply-preview",
354
- className: chunkS5RVSXDV_cjs.cn(
355
- "flex items-center gap-2 px-4 py-2",
356
- "bg-neutral-50 dark:bg-neutral-800/50",
357
- "border-primary-500 border-s-4"
358
- ),
359
- children: [
360
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
361
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-primary-800 dark:text-primary-400 text-xs font-medium", children: [
362
- "Replying to ",
363
- replyTo.senderName
364
- ] }),
365
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-neutral-600 dark:text-neutral-300", children: replyTo.content })
366
- ] }),
367
- /* @__PURE__ */ jsxRuntime.jsx(
368
- "button",
369
- {
370
- type: "button",
371
- onClick: onCancelReply,
372
- className: chunkS5RVSXDV_cjs.cn(
373
- "shrink-0 rounded p-1",
374
- "text-neutral-500 hover:text-neutral-700",
375
- "dark:text-neutral-400 dark:hover:text-neutral-200",
376
- "focus:ring-primary-500 focus:ring-2 focus:outline-none"
377
- ),
378
- "aria-label": "Cancel reply",
379
- children: /* @__PURE__ */ jsxRuntime.jsx(
380
- "svg",
381
- {
382
- "aria-hidden": "true",
383
- className: "h-4 w-4",
384
- fill: "none",
385
- viewBox: "0 0 24 24",
386
- stroke: "currentColor",
387
- children: /* @__PURE__ */ jsxRuntime.jsx(
388
- "path",
389
- {
390
- strokeLinecap: "round",
391
- strokeLinejoin: "round",
392
- strokeWidth: 2,
393
- d: "M6 18L18 6M6 6l12 12"
394
- }
395
- )
396
- }
397
- )
398
- }
399
- )
400
- ]
401
- }
402
- ),
403
- attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
404
- "div",
405
- {
406
- "data-slot": "composer-attachments",
407
- className: chunkS5RVSXDV_cjs.cn(
408
- "flex flex-wrap gap-2 p-3",
409
- "border-t border-neutral-200 dark:border-neutral-700"
410
- ),
411
- children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(
412
- chunk5VXRKOPR_cjs.AttachmentPreviewItem,
413
- {
414
- attachment,
415
- onRemove: () => handleRemoveAttachment(attachment.id)
416
- },
417
- attachment.id
418
- ))
419
- }
420
- ),
421
- /* @__PURE__ */ jsxRuntime.jsxs(
422
- "div",
423
- {
424
- "data-slot": "composer-input-area",
425
- className: chunkS5RVSXDV_cjs.cn(
426
- "flex items-center gap-2 p-3",
427
- "bg-white dark:bg-neutral-900",
428
- variant === "default" && "border-t border-neutral-200 dark:border-neutral-700"
429
- ),
430
- children: [
431
- showAttachmentPicker && /* @__PURE__ */ jsxRuntime.jsx(
432
- chunk5VXRKOPR_cjs.AttachmentPicker,
433
- {
434
- onFilesSelected: handleFilesSelected,
435
- acceptedTypes: acceptedFileTypes,
436
- maxFileSize,
437
- maxFiles: maxAttachments - attachments.length,
438
- disabled: disabled || attachments.length >= maxAttachments,
439
- onError
440
- }
441
- ),
442
- showCameraButton && /* @__PURE__ */ jsxRuntime.jsx(
443
- chunk5VXRKOPR_cjs.CameraButton,
444
- {
445
- onCapture: (file) => handleFilesSelected([file]),
446
- disabled: disabled || attachments.length >= maxAttachments
447
- }
448
- ),
449
- /* @__PURE__ */ jsxRuntime.jsxs(
450
- "div",
451
- {
452
- "data-slot": "composer-input-wrapper",
453
- className: "relative flex-1",
454
- ref: mention.anchorRef,
455
- children: [
456
- /* @__PURE__ */ jsxRuntime.jsx(chunk5VXRKOPR_cjs.MentionMenu, { mention }),
457
- /* @__PURE__ */ jsxRuntime.jsx(
458
- "textarea",
459
- {
460
- ref: textareaRef,
461
- "data-slot": "composer-input",
462
- value: content,
463
- onChange: (e) => {
464
- setContent(e.target.value);
465
- mention.sync(
466
- e.target.value,
467
- e.target.selectionStart ?? e.target.value.length
468
- );
469
- },
470
- onClick: (e) => {
471
- const el = e.currentTarget;
472
- mention.sync(el.value, el.selectionStart ?? el.value.length);
473
- },
474
- onKeyDown: handleKeyDown,
475
- onPaste: handlePaste,
476
- placeholder,
477
- disabled: disabled || isSending,
478
- rows: 1,
479
- className: chunkS5RVSXDV_cjs.cn(
480
- "w-full resize-none rounded-2xl py-2.5",
481
- inputTrailing ? "ps-4 pe-10" : "px-4",
482
- "bg-neutral-100 dark:bg-neutral-800",
483
- "text-neutral-900 dark:text-neutral-100",
484
- "placeholder:text-neutral-400 dark:placeholder:text-neutral-500",
485
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
486
- "disabled:cursor-not-allowed disabled:opacity-50",
487
- "transition-colors",
488
- "max-h-[150px]"
489
- ),
490
- "aria-label": "Message",
491
- "aria-describedby": showCharacterCount ? "char-count" : void 0,
492
- ...mention.inputProps
493
- }
494
- ),
495
- inputTrailing && /* @__PURE__ */ jsxRuntime.jsx(
496
- "div",
497
- {
498
- "data-slot": "composer-input-trailing",
499
- className: "pointer-events-none absolute end-1 top-0 flex h-[44px] items-center [&>*]:pointer-events-auto",
500
- children: inputTrailing
501
- }
502
- ),
503
- showCharacterCount && /* @__PURE__ */ jsxRuntime.jsx(
504
- "div",
505
- {
506
- "data-slot": "composer-char-count",
507
- id: "char-count",
508
- className: "absolute end-3 bottom-1.5",
509
- children: /* @__PURE__ */ jsxRuntime.jsx(CharacterCounter, { current: content.length, max: maxLength })
510
- }
511
- )
512
- ]
513
- }
514
- ),
515
- /* @__PURE__ */ jsxRuntime.jsx(
516
- SendButton,
517
- {
518
- canSend,
519
- isLoading: isSending,
520
- disabled
521
- }
522
- )
523
- ]
524
- }
525
- )
526
- ]
527
- }
528
- )
529
- }
530
- );
531
- }
532
- );
533
- MessageComposer.displayName = "MessageComposer";
534
- var statusIconVariants = classVarianceAuthority.cva(
535
- "inline-flex items-center gap-0.5 text-current",
536
- {
537
- variants: {
538
- status: {
539
- sending: "text-neutral-500",
540
- sent: "text-neutral-500",
541
- delivered: "text-muted-foreground",
542
- read: "text-primary-800 dark:text-primary-300",
543
- failed: "text-red-500"
544
- }
545
- },
546
- defaultVariants: {
547
- status: "sent"
548
- }
549
- }
550
- );
551
- function MessageStatusIcon({ status, className }) {
552
- return /* @__PURE__ */ jsxRuntime.jsxs(
553
- "span",
554
- {
555
- className: chunkS5RVSXDV_cjs.cn(statusIconVariants({ status }), className),
556
- role: "img",
557
- "aria-label": `Message ${status}`,
558
- children: [
559
- status === "sending" && /* @__PURE__ */ jsxRuntime.jsxs(
560
- "svg",
561
- {
562
- "aria-hidden": "true",
563
- className: "h-3.5 w-3.5 animate-spin",
564
- fill: "none",
565
- viewBox: "0 0 24 24",
566
- children: [
567
- /* @__PURE__ */ jsxRuntime.jsx(
568
- "circle",
569
- {
570
- className: "opacity-25",
571
- cx: "12",
572
- cy: "12",
573
- r: "10",
574
- stroke: "currentColor",
575
- strokeWidth: "4"
576
- }
577
- ),
578
- /* @__PURE__ */ jsxRuntime.jsx(
579
- "path",
580
- {
581
- className: "opacity-75",
582
- fill: "currentColor",
583
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
584
- }
585
- )
586
- ]
587
- }
588
- ),
589
- status === "sent" && /* @__PURE__ */ jsxRuntime.jsx(
590
- "svg",
591
- {
592
- "aria-hidden": "true",
593
- className: "h-3.5 w-3.5",
594
- fill: "none",
595
- viewBox: "0 0 24 24",
596
- stroke: "currentColor",
597
- strokeWidth: "2",
598
- children: /* @__PURE__ */ jsxRuntime.jsx(
599
- "path",
600
- {
601
- strokeLinecap: "round",
602
- strokeLinejoin: "round",
603
- d: "M5 13l4 4L19 7"
604
- }
605
- )
606
- }
607
- ),
608
- (status === "delivered" || status === "read") && /* @__PURE__ */ jsxRuntime.jsxs(
609
- "svg",
610
- {
611
- "aria-hidden": "true",
612
- className: "h-4 w-4",
613
- fill: "none",
614
- viewBox: "0 0 24 24",
615
- stroke: "currentColor",
616
- strokeWidth: "2",
617
- children: [
618
- /* @__PURE__ */ jsxRuntime.jsx(
619
- "path",
620
- {
621
- strokeLinecap: "round",
622
- strokeLinejoin: "round",
623
- d: "M5 13l4 4L19 7"
624
- }
625
- ),
626
- /* @__PURE__ */ jsxRuntime.jsx(
627
- "path",
628
- {
629
- strokeLinecap: "round",
630
- strokeLinejoin: "round",
631
- d: "M12 13l4 4L26 7",
632
- transform: "translate(-5, 0)"
633
- }
634
- )
635
- ]
636
- }
637
- ),
638
- status === "failed" && /* @__PURE__ */ jsxRuntime.jsx(
639
- "svg",
640
- {
641
- "aria-hidden": "true",
642
- className: "h-3.5 w-3.5",
643
- fill: "none",
644
- viewBox: "0 0 24 24",
645
- stroke: "currentColor",
646
- strokeWidth: "2",
647
- children: /* @__PURE__ */ jsxRuntime.jsx(
648
- "path",
649
- {
650
- strokeLinecap: "round",
651
- strokeLinejoin: "round",
652
- 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"
653
148
  }
654
149
  )
655
150
  }
@@ -1199,373 +694,878 @@ function SkeletonMessage({
1199
694
  )
1200
695
  }
1201
696
  ),
1202
- 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
1203
815
  ]
1204
816
  }
1205
817
  );
1206
818
  }
1207
- SkeletonMessage.displayName = "SkeletonMessage";
1208
- function TypingIndicator({ typingState, className }) {
1209
- const { participants } = typingState;
1210
- if (participants.length === 0) return null;
1211
- 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`;
1212
- return /* @__PURE__ */ jsxRuntime.jsxs(
819
+ EmptyState.displayName = "EmptyState";
820
+ function LoadMoreButton({
821
+ isLoading,
822
+ onClick,
823
+ className
824
+ }) {
825
+ return /* @__PURE__ */ jsxRuntime.jsx(
1213
826
  "div",
1214
827
  {
1215
- "data-slot": "typing-indicator",
1216
- className: chunkS5RVSXDV_cjs.cn("flex items-center gap-2 px-4 py-2", className),
1217
- role: "status",
1218
- "aria-live": "polite",
1219
- "aria-label": typingText,
1220
- children: [
1221
- /* @__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: [
1222
- /* @__PURE__ */ jsxRuntime.jsx(
1223
- "span",
1224
- {
1225
- className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
1226
- style: { animationDelay: "0ms" }
1227
- }
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
1228
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 }),
1229
979
  /* @__PURE__ */ jsxRuntime.jsx(
1230
- "span",
980
+ "div",
1231
981
  {
1232
- className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
1233
- 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))
1234
1016
  }
1235
1017
  ),
1236
- /* @__PURE__ */ jsxRuntime.jsx(
1237
- "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",
1238
1022
  {
1239
- className: "h-2 w-2 animate-bounce rounded-full bg-neutral-500",
1240
- 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
+ )
1241
1056
  }
1242
1057
  )
1243
- ] }),
1244
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: typingText })
1245
- ]
1246
- }
1247
- );
1248
- }
1249
- TypingIndicator.displayName = "TypingIndicator";
1250
- function DateSeparator({ label, className }) {
1251
- return /* @__PURE__ */ jsxRuntime.jsx(
1252
- "div",
1253
- {
1254
- "data-slot": "date-separator",
1255
- className: chunkS5RVSXDV_cjs.cn("flex items-center justify-center py-4", className),
1256
- role: "separator",
1257
- "aria-label": label,
1258
- children: /* @__PURE__ */ jsxRuntime.jsx(
1259
- "span",
1260
- {
1261
- className: chunkS5RVSXDV_cjs.cn(
1262
- "rounded-full px-3 py-1 text-xs font-medium",
1263
- "bg-neutral-100 text-neutral-600",
1264
- "dark:bg-neutral-800 dark:text-neutral-400"
1265
- ),
1266
- children: label
1267
- }
1268
- )
1269
- }
1270
- );
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
+ });
1271
1074
  }
1272
- DateSeparator.displayName = "DateSeparator";
1273
- function EmptyState({
1274
- title = "No messages yet",
1275
- description = "Start the conversation by sending a message below.",
1276
- icon,
1277
- action,
1075
+ function CharacterCounter({
1076
+ current,
1077
+ max,
1078
+ showWarningAt = 0.9,
1278
1079
  className
1279
1080
  }) {
1081
+ const percentage = current / max;
1082
+ const isWarning = percentage >= showWarningAt && percentage < 1;
1083
+ const isOver = current > max;
1280
1084
  return /* @__PURE__ */ jsxRuntime.jsxs(
1281
- "div",
1085
+ "span",
1282
1086
  {
1283
- "data-slot": "message-empty-state",
1284
1087
  className: chunkS5RVSXDV_cjs.cn(
1285
- "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",
1286
1090
  className
1287
1091
  ),
1288
- role: "status",
1289
- "aria-label": title,
1092
+ "aria-live": "polite",
1093
+ "aria-label": `${current} of ${max} characters`,
1290
1094
  children: [
1291
- icon || /* @__PURE__ */ jsxRuntime.jsx(
1292
- "div",
1293
- {
1294
- "data-slot": "message-empty-state-icon",
1295
- className: "mb-4 rounded-full bg-neutral-100 p-4 dark:bg-neutral-800",
1296
- children: /* @__PURE__ */ jsxRuntime.jsx(
1297
- "svg",
1298
- {
1299
- "aria-hidden": "true",
1300
- className: "h-12 w-12 text-neutral-500",
1301
- fill: "none",
1302
- viewBox: "0 0 24 24",
1303
- stroke: "currentColor",
1304
- children: /* @__PURE__ */ jsxRuntime.jsx(
1305
- "path",
1306
- {
1307
- strokeLinecap: "round",
1308
- strokeLinejoin: "round",
1309
- strokeWidth: 1.5,
1310
- 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"
1311
- }
1312
- )
1313
- }
1314
- )
1315
- }
1316
- ),
1317
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-2 text-lg font-semibold text-neutral-900 dark:text-neutral-100", children: title }),
1318
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mb-4 max-w-sm text-sm", children: description }),
1319
- action
1095
+ current,
1096
+ "/",
1097
+ max
1320
1098
  ]
1321
1099
  }
1322
1100
  );
1323
1101
  }
1324
- EmptyState.displayName = "EmptyState";
1325
- function LoadMoreButton({
1326
- isLoading,
1327
- onClick,
1328
- className
1329
- }) {
1330
- return /* @__PURE__ */ jsxRuntime.jsx(
1331
- "div",
1332
- {
1333
- "data-slot": "load-more-button",
1334
- className: chunkS5RVSXDV_cjs.cn("flex justify-center py-4", className),
1335
- children: /* @__PURE__ */ jsxRuntime.jsx(
1336
- "button",
1337
- {
1338
- type: "button",
1339
- onClick,
1340
- disabled: isLoading,
1341
- className: chunkS5RVSXDV_cjs.cn(
1342
- "rounded-full px-4 py-2 text-sm font-medium",
1343
- "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-300",
1344
- "hover:bg-neutral-200 dark:hover:bg-neutral-700",
1345
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1346
- "disabled:cursor-not-allowed disabled:opacity-50",
1347
- "transition-colors"
1348
- ),
1349
- "aria-label": isLoading ? "Loading more messages" : "Load more messages",
1350
- children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
1351
- /* @__PURE__ */ jsxRuntime.jsxs(
1352
- "svg",
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
1132
+ }
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",
1353
1186
  {
1354
- "aria-hidden": "true",
1355
- className: "h-4 w-4 animate-spin",
1356
- fill: "none",
1357
- viewBox: "0 0 24 24",
1358
- children: [
1359
- /* @__PURE__ */ jsxRuntime.jsx(
1360
- "circle",
1361
- {
1362
- className: "opacity-25",
1363
- cx: "12",
1364
- cy: "12",
1365
- r: "10",
1366
- stroke: "currentColor",
1367
- strokeWidth: "4"
1368
- }
1369
- ),
1370
- /* @__PURE__ */ jsxRuntime.jsx(
1371
- "path",
1372
- {
1373
- className: "opacity-75",
1374
- fill: "currentColor",
1375
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
1376
- }
1377
- )
1378
- ]
1187
+ strokeLinecap: "round",
1188
+ strokeLinejoin: "round",
1189
+ strokeWidth: 2,
1190
+ d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
1379
1191
  }
1380
- ),
1381
- "Loading..."
1382
- ] }) : "Load earlier messages"
1383
- }
1384
- )
1385
- }
1386
- );
1387
- }
1388
- LoadMoreButton.displayName = "LoadMoreButton";
1389
- var MessageList = React6__namespace.forwardRef(
1192
+ )
1193
+ }
1194
+ )
1195
+ }
1196
+ );
1197
+ }
1198
+ );
1199
+ SendButton.displayName = "SendButton";
1200
+ var MessageComposer = React6__namespace.forwardRef(
1390
1201
  ({
1391
- messages,
1392
- currentUser,
1393
- isLoading = false,
1394
- hasMore = false,
1395
- isLoadingMore = false,
1396
- typingState,
1397
- showAvatars = true,
1398
- showSenderNames = false,
1399
- groupByDate = true,
1400
- onLoadMore,
1401
- onRetryMessage,
1402
- onAttachmentClick,
1403
- emptyState,
1404
- formatTimestamp,
1405
- className,
1406
- 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
1407
1225
  }, ref) => {
1408
- const scrollContainerRef = React6__namespace.useRef(null);
1409
- const bottomRef = React6__namespace.useRef(null);
1410
- const [isUserScrolled, setIsUserScrolled] = React6__namespace.useState(false);
1411
- const prevMessageCountRef = React6__namespace.useRef(messages.length);
1412
- React6__namespace.useImperativeHandle(ref, () => scrollContainerRef.current);
1413
- const handleScroll = React6__namespace.useCallback(() => {
1414
- const container = scrollContainerRef.current;
1415
- if (!container) return;
1416
- const { scrollTop, scrollHeight, clientHeight } = container;
1417
- const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
1418
- setIsUserScrolled(!isAtBottom);
1419
- }, []);
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);
1420
1247
  React6__namespace.useEffect(() => {
1421
- const container = scrollContainerRef.current;
1422
- const bottom = bottomRef.current;
1423
- if (!container || !bottom) return;
1424
- const messageCountChanged = messages.length !== prevMessageCountRef.current;
1425
- prevMessageCountRef.current = messages.length;
1426
- if (autoScroll === "always") {
1427
- bottom.scrollIntoView({ behavior: "smooth" });
1428
- } else if (autoScroll === "onNewMessage" && messageCountChanged) {
1429
- const lastMessage = messages[messages.length - 1];
1430
- const isOutgoing = lastMessage?.sender.id === currentUser.id;
1431
- if (isOutgoing || !isUserScrolled) {
1432
- 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);
1433
1271
  }
1272
+ };
1273
+ }, [content, isTyping, onTypingStart, onTypingStop]);
1274
+ React6__namespace.useEffect(() => {
1275
+ if (autoFocus) {
1276
+ textareaRef.current?.focus();
1434
1277
  }
1435
- }, [messages, currentUser.id, autoScroll, isUserScrolled]);
1278
+ }, [autoFocus]);
1279
+ const replyToId = replyTo?.id;
1436
1280
  React6__namespace.useEffect(() => {
1437
- const bottom = bottomRef.current;
1438
- if (bottom && !isLoading) {
1439
- bottom.scrollIntoView();
1281
+ if (replyToId !== void 0) {
1282
+ textareaRef.current?.focus();
1440
1283
  }
1441
- }, [isLoading]);
1442
- const messageGroups = groupByDate ? groupMessagesByDate(messages) : [{ date: "all", label: "", messages }];
1443
- if (isLoading) {
1444
- return /* @__PURE__ */ jsxRuntime.jsx(
1445
- "div",
1446
- {
1447
- role: "status",
1448
- className: chunkS5RVSXDV_cjs.cn(
1449
- "flex flex-1 flex-col gap-3 overflow-y-auto p-4",
1450
- className
1451
- ),
1452
- "aria-busy": "true",
1453
- "aria-label": "Loading messages",
1454
- children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
1455
- SkeletonMessage,
1456
- {
1457
- isOutgoing: i % 3 === 0,
1458
- showAvatar: showAvatars
1459
- },
1460
- i
1461
- ))
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);
1462
1317
  }
1463
- );
1464
- }
1465
- if (messages.length === 0) {
1466
- return emptyState || /* @__PURE__ */ jsxRuntime.jsx(EmptyState, {});
1467
- }
1468
- return /* @__PURE__ */ jsxRuntime.jsxs(
1469
- "div",
1470
- {
1471
- ref: scrollContainerRef,
1472
- "data-slot": "message-list",
1473
- className: chunkS5RVSXDV_cjs.cn(
1474
- "flex flex-1 flex-col overflow-y-auto",
1475
- "scroll-smooth",
1476
- className
1477
- ),
1478
- onScroll: handleScroll,
1479
- role: "log",
1480
- "aria-label": "Message history",
1481
- "aria-live": "polite",
1482
- children: [
1483
- hasMore && onLoadMore && /* @__PURE__ */ jsxRuntime.jsx(LoadMoreButton, { isLoading: isLoadingMore, onClick: onLoadMore }),
1484
- /* @__PURE__ */ jsxRuntime.jsx(
1485
- "div",
1486
- {
1487
- "data-slot": "message-list-content",
1488
- className: "flex flex-col gap-1 p-4",
1489
- children: messageGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(React6__namespace.Fragment, { children: [
1490
- groupByDate && group.label && /* @__PURE__ */ jsxRuntime.jsx(DateSeparator, { label: group.label }),
1491
- group.messages.map((message, index) => {
1492
- const prevMessage = group.messages[index - 1];
1493
- const isOutgoing = message.sender.id === currentUser.id;
1494
- const isSameGroup = isSameSenderGroup(prevMessage, message);
1495
- return /* @__PURE__ */ jsxRuntime.jsx(
1496
- "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,
1497
1448
  {
1498
- className: chunkS5RVSXDV_cjs.cn(
1499
- "transition-opacity duration-200",
1500
- isSameGroup ? "mt-0.5" : "mt-3",
1501
- index === 0 && "mt-0"
1502
- ),
1503
- children: /* @__PURE__ */ jsxRuntime.jsx(
1504
- MessageBubble,
1505
- {
1506
- message,
1507
- isOutgoing,
1508
- showAvatar: showAvatars && !isSameGroup && !isOutgoing,
1509
- showSenderName: showSenderNames && !isSameGroup && !isOutgoing,
1510
- showTimestamp: !isSameGroup,
1511
- onRetry: message.status === "failed" && onRetryMessage ? () => onRetryMessage(message.id) : void 0,
1512
- onAttachmentClick: (attachment) => onAttachmentClick?.(attachment, message),
1513
- formatTimestamp
1514
- }
1515
- )
1449
+ attachment,
1450
+ onRemove: () => handleRemoveAttachment(attachment.id)
1516
1451
  },
1517
- message.id
1518
- );
1519
- })
1520
- ] }, group.date))
1521
- }
1522
- ),
1523
- typingState && typingState.participants.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typingState }),
1524
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef, className: "h-0", "aria-hidden": "true" }),
1525
- isUserScrolled && /* @__PURE__ */ jsxRuntime.jsx(
1526
- "button",
1527
- {
1528
- type: "button",
1529
- onClick: () => {
1530
- bottomRef.current?.scrollIntoView({ behavior: "smooth" });
1531
- },
1532
- className: chunkS5RVSXDV_cjs.cn(
1533
- "fixed end-4 bottom-24 z-10",
1534
- "rounded-full p-3 shadow-lg",
1535
- "bg-white dark:bg-neutral-800",
1536
- "border border-neutral-200 dark:border-neutral-700",
1537
- "hover:bg-neutral-50 dark:hover:bg-neutral-700",
1538
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1539
- "transition-all"
1452
+ attachment.id
1453
+ ))
1454
+ }
1540
1455
  ),
1541
- "aria-label": "Scroll to bottom",
1542
- children: /* @__PURE__ */ jsxRuntime.jsx(
1543
- "svg",
1456
+ /* @__PURE__ */ jsxRuntime.jsxs(
1457
+ "div",
1544
1458
  {
1545
- "aria-hidden": "true",
1546
- className: "h-5 w-5 text-neutral-600 dark:text-neutral-300",
1547
- fill: "none",
1548
- viewBox: "0 0 24 24",
1549
- stroke: "currentColor",
1550
- children: /* @__PURE__ */ jsxRuntime.jsx(
1551
- "path",
1552
- {
1553
- strokeLinecap: "round",
1554
- strokeLinejoin: "round",
1555
- strokeWidth: 2,
1556
- d: "M19 14l-7 7m0 0l-7-7m7 7V3"
1557
- }
1558
- )
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
+ ]
1559
1559
  }
1560
1560
  )
1561
- }
1562
- )
1563
- ]
1561
+ ]
1562
+ }
1563
+ )
1564
1564
  }
1565
1565
  );
1566
1566
  }
1567
1567
  );
1568
- MessageList.displayName = "MessageList";
1568
+ MessageComposer.displayName = "MessageComposer";
1569
1569
  var headerVariants = classVarianceAuthority.cva(
1570
1570
  [
1571
1571
  "flex items-center gap-3 px-4 py-3",
@@ -2122,7 +2122,7 @@ var MessageThread = React6__namespace.forwardRef(
2122
2122
  }
2123
2123
  ),
2124
2124
  /* @__PURE__ */ jsxRuntime.jsx(
2125
- chunk5VXRKOPR_cjs.DragDropZone,
2125
+ chunkH3GRO43Z_cjs.DragDropZone,
2126
2126
  {
2127
2127
  onFilesDropped: () => {
2128
2128
  onError?.("Drop files on the composer to attach them");
@@ -2525,5 +2525,5 @@ exports.useMessageScroll = useMessageScroll;
2525
2525
  exports.useMessages = useMessages;
2526
2526
  exports.useReadReceipts = useReadReceipts;
2527
2527
  exports.useTypingIndicator = useTypingIndicator;
2528
- //# sourceMappingURL=chunk-3MHG4HYB.cjs.map
2529
- //# sourceMappingURL=chunk-3MHG4HYB.cjs.map
2528
+ //# sourceMappingURL=chunk-ZXRVVNT5.cjs.map
2529
+ //# sourceMappingURL=chunk-ZXRVVNT5.cjs.map