@mieweb/ui 0.9.0-dev.6 → 0.9.0-dev.7

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.
@@ -1,4 +1,4 @@
1
- import { useMentionAutocomplete, DragDropZone, DEFAULT_MAX_FILE_SIZE, DEFAULT_ACCEPTED_FILE_TYPES, AttachmentPreviewItem, AttachmentPicker, CameraButton, MentionMenu, ChatComposer, getFileType, generateAttachmentId } from './chunk-3SD7PDCV.js';
1
+ import { notifyComposerMigrationOnce, DragDropZone, ChatComposer, DEFAULT_MAX_FILE_SIZE, DEFAULT_ACCEPTED_FILE_TYPES, CameraButton } from './chunk-IIFQ6BSJ.js';
2
2
  import { cn } from './chunk-TQONQSJO.js';
3
3
  import * as React from 'react';
4
4
  import { cva } from 'class-variance-authority';
@@ -1318,511 +1318,6 @@ var MessageList = React.forwardRef(
1318
1318
  }
1319
1319
  );
1320
1320
  MessageList.displayName = "MessageList";
1321
- function isFileAccepted(file, accepted) {
1322
- if (!accepted || accepted.length === 0) return true;
1323
- const mime = file.type;
1324
- const name = file.name.toLowerCase();
1325
- return accepted.some((token) => {
1326
- if (token.startsWith(".")) return name.endsWith(token.toLowerCase());
1327
- if (token.endsWith("/*")) return mime.startsWith(token.slice(0, -1));
1328
- if (token.includes("/")) return mime === token;
1329
- return false;
1330
- });
1331
- }
1332
- function CharacterCounter({
1333
- current,
1334
- max,
1335
- showWarningAt = 0.9,
1336
- className
1337
- }) {
1338
- const percentage = current / max;
1339
- const isWarning = percentage >= showWarningAt && percentage < 1;
1340
- const isOver = current > max;
1341
- return /* @__PURE__ */ jsxs(
1342
- "span",
1343
- {
1344
- className: cn(
1345
- "text-xs tabular-nums",
1346
- 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",
1347
- className
1348
- ),
1349
- "aria-live": "polite",
1350
- "aria-label": `${current} of ${max} characters`,
1351
- children: [
1352
- current,
1353
- "/",
1354
- max
1355
- ]
1356
- }
1357
- );
1358
- }
1359
- CharacterCounter.displayName = "CharacterCounter";
1360
- var sendButtonVariants = cva(
1361
- [
1362
- "inline-flex items-center justify-center",
1363
- "rounded-full p-3 self-start",
1364
- "transition-all duration-200",
1365
- "focus:outline-none focus:ring-2 focus:ring-primary-500",
1366
- "disabled:opacity-50 disabled:cursor-not-allowed"
1367
- ],
1368
- {
1369
- variants: {
1370
- variant: {
1371
- primary: [
1372
- "bg-primary-800 text-white",
1373
- "hover:bg-primary-900",
1374
- "active:scale-95"
1375
- ],
1376
- subtle: [
1377
- "bg-transparent text-primary-800",
1378
- "hover:bg-primary-50 dark:hover:bg-primary-900/20"
1379
- ]
1380
- },
1381
- canSend: {
1382
- true: "",
1383
- false: "opacity-50 cursor-not-allowed"
1384
- }
1385
- },
1386
- defaultVariants: {
1387
- variant: "primary",
1388
- canSend: false
1389
- }
1390
- }
1391
- );
1392
- var SendButton = React.forwardRef(
1393
- ({ className, variant, canSend, isLoading, disabled, ...props }, ref) => {
1394
- return /* @__PURE__ */ jsx(
1395
- "button",
1396
- {
1397
- ref,
1398
- type: "submit",
1399
- disabled: disabled || !canSend || isLoading,
1400
- "data-slot": "composer-send-button",
1401
- className: cn(sendButtonVariants({ variant, canSend }), className),
1402
- "aria-label": isLoading ? "Sending message" : "Send message",
1403
- ...props,
1404
- children: isLoading ? /* @__PURE__ */ jsxs(
1405
- "svg",
1406
- {
1407
- "aria-hidden": "true",
1408
- className: "h-5 w-5 animate-spin",
1409
- fill: "none",
1410
- viewBox: "0 0 24 24",
1411
- children: [
1412
- /* @__PURE__ */ jsx(
1413
- "circle",
1414
- {
1415
- className: "opacity-25",
1416
- cx: "12",
1417
- cy: "12",
1418
- r: "10",
1419
- stroke: "currentColor",
1420
- strokeWidth: "4"
1421
- }
1422
- ),
1423
- /* @__PURE__ */ jsx(
1424
- "path",
1425
- {
1426
- className: "opacity-75",
1427
- fill: "currentColor",
1428
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
1429
- }
1430
- )
1431
- ]
1432
- }
1433
- ) : /* @__PURE__ */ jsx(
1434
- "svg",
1435
- {
1436
- "aria-hidden": "true",
1437
- className: "h-5 w-5",
1438
- fill: "none",
1439
- viewBox: "0 0 24 24",
1440
- stroke: "currentColor",
1441
- children: /* @__PURE__ */ jsx(
1442
- "path",
1443
- {
1444
- strokeLinecap: "round",
1445
- strokeLinejoin: "round",
1446
- strokeWidth: 2,
1447
- d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
1448
- }
1449
- )
1450
- }
1451
- )
1452
- }
1453
- );
1454
- }
1455
- );
1456
- SendButton.displayName = "SendButton";
1457
- var MessageComposer = React.forwardRef(
1458
- ({
1459
- onSend,
1460
- onTypingStart,
1461
- onTypingStop,
1462
- value: controlledValue,
1463
- onValueChange,
1464
- placeholder = "Type a message...",
1465
- maxLength = 1600,
1466
- showCharacterCount = false,
1467
- disabled = false,
1468
- isSending = false,
1469
- showAttachmentPicker = true,
1470
- showCameraButton = false,
1471
- acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES,
1472
- maxFileSize = DEFAULT_MAX_FILE_SIZE,
1473
- maxAttachments = 10,
1474
- onError,
1475
- autoFocus = false,
1476
- replyTo = null,
1477
- onCancelReply,
1478
- variant = "default",
1479
- inputTrailing,
1480
- mentionOptions,
1481
- className
1482
- }, ref) => {
1483
- const textareaRef = React.useRef(null);
1484
- const [internalContent, setInternalContent] = React.useState("");
1485
- const isControlled = controlledValue !== void 0;
1486
- const content = isControlled ? controlledValue : internalContent;
1487
- const setContent = React.useCallback(
1488
- (val) => {
1489
- if (isControlled) {
1490
- const newVal = typeof val === "function" ? val(controlledValue) : val;
1491
- onValueChange?.(newVal);
1492
- } else {
1493
- setInternalContent(val);
1494
- }
1495
- },
1496
- [isControlled, controlledValue, onValueChange]
1497
- );
1498
- const [attachments, setAttachments] = React.useState(
1499
- []
1500
- );
1501
- const [isTyping, setIsTyping] = React.useState(false);
1502
- const typingTimeoutRef = React.useRef(null);
1503
- React.useImperativeHandle(ref, () => textareaRef.current);
1504
- React.useEffect(() => {
1505
- const textarea = textareaRef.current;
1506
- if (textarea) {
1507
- textarea.style.height = "auto";
1508
- textarea.style.height = `${Math.min(textarea.scrollHeight, 150)}px`;
1509
- }
1510
- }, [content]);
1511
- React.useEffect(() => {
1512
- if (content.length > 0 && !isTyping) {
1513
- setIsTyping(true);
1514
- onTypingStart?.();
1515
- }
1516
- if (typingTimeoutRef.current) {
1517
- clearTimeout(typingTimeoutRef.current);
1518
- }
1519
- typingTimeoutRef.current = setTimeout(() => {
1520
- if (isTyping) {
1521
- setIsTyping(false);
1522
- onTypingStop?.();
1523
- }
1524
- }, 2e3);
1525
- return () => {
1526
- if (typingTimeoutRef.current) {
1527
- clearTimeout(typingTimeoutRef.current);
1528
- }
1529
- };
1530
- }, [content, isTyping, onTypingStart, onTypingStop]);
1531
- React.useEffect(() => {
1532
- if (autoFocus) {
1533
- textareaRef.current?.focus();
1534
- }
1535
- }, [autoFocus]);
1536
- const replyToId = replyTo?.id;
1537
- React.useEffect(() => {
1538
- if (replyToId !== void 0) {
1539
- textareaRef.current?.focus();
1540
- }
1541
- }, [replyToId]);
1542
- const mention = useMentionAutocomplete({
1543
- options: mentionOptions,
1544
- value: content,
1545
- setValue: setContent,
1546
- textareaRef
1547
- });
1548
- const canSend = (content.trim().length > 0 || attachments.length > 0) && content.length <= maxLength && !disabled && !isSending;
1549
- const handleSubmit = async (event) => {
1550
- event.preventDefault();
1551
- if (!canSend) return;
1552
- const message = {
1553
- content: content.trim(),
1554
- attachments: attachments.map((a) => a.file),
1555
- replyToId: replyTo?.id
1556
- };
1557
- setContent("");
1558
- setAttachments([]);
1559
- setIsTyping(false);
1560
- onTypingStop?.();
1561
- try {
1562
- await onSend(message);
1563
- } catch {
1564
- setContent(message.content);
1565
- onError?.("Failed to send message");
1566
- }
1567
- };
1568
- const handleKeyDown = (event) => {
1569
- if (mention.handleKeyDown(event)) return;
1570
- if (event.key === "Enter" && !event.shiftKey) {
1571
- event.preventDefault();
1572
- if (canSend) {
1573
- handleSubmit(event);
1574
- }
1575
- }
1576
- };
1577
- const handleFilesSelected = (files) => {
1578
- const remainingSlots = maxAttachments - attachments.length;
1579
- const filesToAdd = files.slice(0, remainingSlots);
1580
- if (files.length > remainingSlots) {
1581
- onError?.(`Maximum ${maxAttachments} attachments allowed`);
1582
- }
1583
- const newAttachments = filesToAdd.map((file) => {
1584
- const type = getFileType(file.type);
1585
- let previewUrl;
1586
- if (type === "image" || type === "video") {
1587
- previewUrl = URL.createObjectURL(file);
1588
- }
1589
- return {
1590
- id: generateAttachmentId(),
1591
- file,
1592
- previewUrl,
1593
- type,
1594
- state: "pending"
1595
- };
1596
- });
1597
- setAttachments((prev) => [...prev, ...newAttachments]);
1598
- };
1599
- const handlePaste = (event) => {
1600
- if (!showAttachmentPicker || disabled) return;
1601
- 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));
1602
- if (pasted.length === 0) return;
1603
- event.preventDefault();
1604
- handleFilesSelected(pasted);
1605
- };
1606
- const handleRemoveAttachment = (attachmentId) => {
1607
- setAttachments((prev) => {
1608
- const attachment = prev.find((a) => a.id === attachmentId);
1609
- if (attachment?.previewUrl) {
1610
- URL.revokeObjectURL(attachment.previewUrl);
1611
- }
1612
- return prev.filter((a) => a.id !== attachmentId);
1613
- });
1614
- };
1615
- React.useEffect(() => {
1616
- const currentAttachments = attachments;
1617
- return () => {
1618
- currentAttachments.forEach((a) => {
1619
- if (a.previewUrl) {
1620
- URL.revokeObjectURL(a.previewUrl);
1621
- }
1622
- });
1623
- };
1624
- }, [attachments]);
1625
- return /* @__PURE__ */ jsx(
1626
- DragDropZone,
1627
- {
1628
- onFilesDropped: handleFilesSelected,
1629
- acceptedTypes: acceptedFileTypes,
1630
- maxFileSize,
1631
- maxFiles: maxAttachments - attachments.length,
1632
- disabled: disabled || attachments.length >= maxAttachments,
1633
- onError,
1634
- className: cn("w-full", className),
1635
- children: /* @__PURE__ */ jsxs(
1636
- "form",
1637
- {
1638
- onSubmit: handleSubmit,
1639
- "data-slot": "message-composer",
1640
- className: "w-full",
1641
- children: [
1642
- replyTo && /* @__PURE__ */ jsxs(
1643
- "div",
1644
- {
1645
- "data-slot": "composer-reply-preview",
1646
- className: cn(
1647
- "flex items-center gap-2 px-4 py-2",
1648
- "bg-neutral-50 dark:bg-neutral-800/50",
1649
- "border-primary-500 border-s-4"
1650
- ),
1651
- children: [
1652
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1653
- /* @__PURE__ */ jsxs("span", { className: "text-primary-800 dark:text-primary-400 text-xs font-medium", children: [
1654
- "Replying to ",
1655
- replyTo.senderName
1656
- ] }),
1657
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-neutral-600 dark:text-neutral-300", children: replyTo.content })
1658
- ] }),
1659
- /* @__PURE__ */ jsx(
1660
- "button",
1661
- {
1662
- type: "button",
1663
- onClick: onCancelReply,
1664
- className: cn(
1665
- "shrink-0 rounded p-1",
1666
- "text-neutral-500 hover:text-neutral-700",
1667
- "dark:text-neutral-400 dark:hover:text-neutral-200",
1668
- "focus:ring-primary-500 focus:ring-2 focus:outline-none"
1669
- ),
1670
- "aria-label": "Cancel reply",
1671
- children: /* @__PURE__ */ jsx(
1672
- "svg",
1673
- {
1674
- "aria-hidden": "true",
1675
- className: "h-4 w-4",
1676
- fill: "none",
1677
- viewBox: "0 0 24 24",
1678
- stroke: "currentColor",
1679
- children: /* @__PURE__ */ jsx(
1680
- "path",
1681
- {
1682
- strokeLinecap: "round",
1683
- strokeLinejoin: "round",
1684
- strokeWidth: 2,
1685
- d: "M6 18L18 6M6 6l12 12"
1686
- }
1687
- )
1688
- }
1689
- )
1690
- }
1691
- )
1692
- ]
1693
- }
1694
- ),
1695
- attachments.length > 0 && /* @__PURE__ */ jsx(
1696
- "div",
1697
- {
1698
- "data-slot": "composer-attachments",
1699
- className: cn(
1700
- "flex flex-wrap gap-2 p-3",
1701
- "border-t border-neutral-200 dark:border-neutral-700"
1702
- ),
1703
- children: attachments.map((attachment) => /* @__PURE__ */ jsx(
1704
- AttachmentPreviewItem,
1705
- {
1706
- attachment,
1707
- onRemove: () => handleRemoveAttachment(attachment.id)
1708
- },
1709
- attachment.id
1710
- ))
1711
- }
1712
- ),
1713
- /* @__PURE__ */ jsxs(
1714
- "div",
1715
- {
1716
- "data-slot": "composer-input-area",
1717
- className: cn(
1718
- "flex items-center gap-2 p-3",
1719
- "bg-white dark:bg-neutral-900",
1720
- variant === "default" && "border-t border-neutral-200 dark:border-neutral-700"
1721
- ),
1722
- children: [
1723
- showAttachmentPicker && /* @__PURE__ */ jsx(
1724
- AttachmentPicker,
1725
- {
1726
- onFilesSelected: handleFilesSelected,
1727
- acceptedTypes: acceptedFileTypes,
1728
- maxFileSize,
1729
- maxFiles: maxAttachments - attachments.length,
1730
- disabled: disabled || attachments.length >= maxAttachments,
1731
- onError
1732
- }
1733
- ),
1734
- showCameraButton && /* @__PURE__ */ jsx(
1735
- CameraButton,
1736
- {
1737
- onCapture: (file) => handleFilesSelected([file]),
1738
- disabled: disabled || attachments.length >= maxAttachments
1739
- }
1740
- ),
1741
- /* @__PURE__ */ jsxs(
1742
- "div",
1743
- {
1744
- "data-slot": "composer-input-wrapper",
1745
- className: "relative flex-1",
1746
- ref: mention.anchorRef,
1747
- children: [
1748
- /* @__PURE__ */ jsx(MentionMenu, { mention }),
1749
- /* @__PURE__ */ jsx(
1750
- "textarea",
1751
- {
1752
- ref: textareaRef,
1753
- "data-slot": "composer-input",
1754
- value: content,
1755
- onChange: (e) => {
1756
- setContent(e.target.value);
1757
- mention.sync(
1758
- e.target.value,
1759
- e.target.selectionStart ?? e.target.value.length
1760
- );
1761
- },
1762
- onClick: (e) => {
1763
- const el = e.currentTarget;
1764
- mention.sync(el.value, el.selectionStart ?? el.value.length);
1765
- },
1766
- onKeyDown: handleKeyDown,
1767
- onPaste: handlePaste,
1768
- placeholder,
1769
- disabled: disabled || isSending,
1770
- rows: 1,
1771
- className: cn(
1772
- "w-full resize-none rounded-2xl py-2.5",
1773
- inputTrailing ? "ps-4 pe-10" : "px-4",
1774
- "bg-neutral-100 dark:bg-neutral-800",
1775
- "text-neutral-900 dark:text-neutral-100",
1776
- "placeholder:text-neutral-400 dark:placeholder:text-neutral-500",
1777
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1778
- "disabled:cursor-not-allowed disabled:opacity-50",
1779
- "transition-colors",
1780
- "max-h-[150px]"
1781
- ),
1782
- "aria-label": "Message",
1783
- "aria-describedby": showCharacterCount ? "char-count" : void 0,
1784
- ...mention.inputProps
1785
- }
1786
- ),
1787
- inputTrailing && /* @__PURE__ */ jsx(
1788
- "div",
1789
- {
1790
- "data-slot": "composer-input-trailing",
1791
- className: "pointer-events-none absolute end-1 top-0 flex h-[44px] items-center [&>*]:pointer-events-auto",
1792
- children: inputTrailing
1793
- }
1794
- ),
1795
- showCharacterCount && /* @__PURE__ */ jsx(
1796
- "div",
1797
- {
1798
- "data-slot": "composer-char-count",
1799
- id: "char-count",
1800
- className: "absolute end-3 bottom-1.5",
1801
- children: /* @__PURE__ */ jsx(CharacterCounter, { current: content.length, max: maxLength })
1802
- }
1803
- )
1804
- ]
1805
- }
1806
- ),
1807
- /* @__PURE__ */ jsx(
1808
- SendButton,
1809
- {
1810
- canSend,
1811
- isLoading: isSending,
1812
- disabled
1813
- }
1814
- )
1815
- ]
1816
- }
1817
- )
1818
- ]
1819
- }
1820
- )
1821
- }
1822
- );
1823
- }
1824
- );
1825
- MessageComposer.displayName = "MessageComposer";
1826
1321
  var headerVariants = cva(
1827
1322
  [
1828
1323
  "flex items-center gap-3 px-4 py-3",
@@ -2343,6 +1838,9 @@ var MessageThread = React.forwardRef(
2343
1838
  const [lightboxAttachment, setLightboxAttachment] = React.useState(null);
2344
1839
  const [replyTo, setReplyTo] = React.useState(null);
2345
1840
  const composerRef = React.useRef(null);
1841
+ React.useEffect(() => {
1842
+ notifyComposerMigrationOnce("MessageThread");
1843
+ }, []);
2346
1844
  const [draft, setDraft] = React.useState("");
2347
1845
  const draftEpochRef = React.useRef(0);
2348
1846
  const handleDraftChange = React.useCallback((value) => {
@@ -2544,6 +2042,6 @@ function MessagingSplitView({
2544
2042
  }
2545
2043
  MessagingSplitView.displayName = "MessagingSplitView";
2546
2044
 
2547
- 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, useTypingEmulation, useTypingIndicator };
2548
- //# sourceMappingURL=chunk-NORSXAXL.js.map
2549
- //# sourceMappingURL=chunk-NORSXAXL.js.map
2045
+ export { AttachmentPreview, ConversationHeader, ConversationListItem, ConversationListSkeleton, DateSeparator, EmptyState, LightboxModal, LoadMoreButton, MessageBubble, MessageList, MessageStatusIcon, MessageThread, MessagingSplitView, ReadReceiptIndicator, SkeletonMessage, TypingIndicator, bubbleVariants, formatDateLabel, formatFileSize, formatLastSeen, getConversationSubtitle, getConversationTitle, groupMessagesByDate, headerVariants, isSameSenderGroup, useMessageScroll, useMessages, useReadReceipts, useTypingEmulation, useTypingIndicator };
2046
+ //# sourceMappingURL=chunk-VSVGMMNO.js.map
2047
+ //# sourceMappingURL=chunk-VSVGMMNO.js.map