@natoe/colab 0.1.4 → 0.1.6

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.
package/dist/index.js CHANGED
@@ -376,7 +376,9 @@ function CollabProvider({ config, apiBaseUrl, children }) {
376
376
  const data = snakeToCamel(await response.json());
377
377
  orderIds.forEach((orderId) => {
378
378
  const preview = data.previews[orderId] ?? null;
379
- previewCache.current.set(orderId, preview);
379
+ if (preview !== null) {
380
+ previewCache.current.set(orderId, preview);
381
+ }
380
382
  resolvers.get(orderId)?.forEach((resolve) => resolve(preview));
381
383
  });
382
384
  } catch (error) {
@@ -423,7 +425,8 @@ function CollabProvider({ config, apiBaseUrl, children }) {
423
425
  if (!response.ok) {
424
426
  throw { code: "FETCH_ERROR", message: "Failed to fetch messages", status: response.status };
425
427
  }
426
- return snakeToCamel(await response.json());
428
+ const msgs = snakeToCamel(await response.json());
429
+ return msgs.sort((a, b) => a.insertedAt.localeCompare(b.insertedAt));
427
430
  },
428
431
  [apiBaseUrl, authHeaders]
429
432
  );
@@ -897,8 +900,8 @@ function useConversation({
897
900
  const payload = {
898
901
  body: "Voice message",
899
902
  type: "audio",
900
- mediaUrl: url,
901
- mediaDuration: duration
903
+ media_url: url,
904
+ media_duration: duration
902
905
  };
903
906
  const { conv, persistedByCreate } = await ensureConversation(payload);
904
907
  if (persistedByCreate) return;
@@ -914,8 +917,8 @@ function useConversation({
914
917
  const payload = {
915
918
  body: file.name,
916
919
  type: isImage ? "image" : "file",
917
- mediaUrl: url,
918
- fileName: file.name
920
+ media_url: url,
921
+ file_name: file.name
919
922
  };
920
923
  const { conv, persistedByCreate } = await ensureConversation(payload);
921
924
  if (persistedByCreate) return;
@@ -1379,91 +1382,247 @@ var styles3 = {
1379
1382
  textDecoration: "underline"
1380
1383
  }
1381
1384
  };
1385
+ function ReplyIcon({ size = 18, color = "currentColor" }) {
1386
+ return /* @__PURE__ */ jsxRuntime.jsx(
1387
+ "svg",
1388
+ {
1389
+ xmlns: "http://www.w3.org/2000/svg",
1390
+ width: size,
1391
+ height: size,
1392
+ viewBox: "0 0 24 24",
1393
+ fill: color,
1394
+ "aria-hidden": "true",
1395
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z" })
1396
+ }
1397
+ );
1398
+ }
1399
+ function PinIcon({ size = 14, color = "currentColor" }) {
1400
+ return /* @__PURE__ */ jsxRuntime.jsx(
1401
+ "svg",
1402
+ {
1403
+ xmlns: "http://www.w3.org/2000/svg",
1404
+ width: size,
1405
+ height: size,
1406
+ viewBox: "0 0 24 24",
1407
+ fill: color,
1408
+ "aria-hidden": "true",
1409
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" })
1410
+ }
1411
+ );
1412
+ }
1413
+ function CopyIcon({ size = 18, color = "currentColor" }) {
1414
+ return /* @__PURE__ */ jsxRuntime.jsx(
1415
+ "svg",
1416
+ {
1417
+ xmlns: "http://www.w3.org/2000/svg",
1418
+ width: size,
1419
+ height: size,
1420
+ viewBox: "0 0 24 24",
1421
+ fill: color,
1422
+ "aria-hidden": "true",
1423
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" })
1424
+ }
1425
+ );
1426
+ }
1427
+ function MoreIcon({ size = 18, color = "currentColor" }) {
1428
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1429
+ "svg",
1430
+ {
1431
+ xmlns: "http://www.w3.org/2000/svg",
1432
+ width: size,
1433
+ height: size,
1434
+ viewBox: "0 0 24 24",
1435
+ fill: color,
1436
+ "aria-hidden": "true",
1437
+ children: [
1438
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "5", r: "2" }),
1439
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "2" }),
1440
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "19", r: "2" })
1441
+ ]
1442
+ }
1443
+ );
1444
+ }
1382
1445
  function MessageActionsMenu({
1383
1446
  message,
1384
1447
  onReply,
1385
1448
  onPin,
1386
1449
  onUnpin,
1387
1450
  onCopy,
1388
- visible,
1389
1451
  pinDisabled = false,
1452
+ anchorRight = false,
1390
1453
  className
1391
1454
  }) {
1392
- if (!visible) return null;
1455
+ const [isOpen, setIsOpen] = React4.useState(false);
1456
+ const wrapperRef = React4.useRef(null);
1457
+ const triggerRef = React4.useRef(null);
1393
1458
  const isPinned = !!message.isPinned;
1394
1459
  const canCopy = message.type === "text" && !!message.body;
1395
- const handleCopy = () => {
1396
- if (!onCopy) return;
1397
- onCopy(message.body);
1460
+ React4.useEffect(() => {
1461
+ if (!isOpen) return;
1462
+ const handleClick = (e) => {
1463
+ if (!wrapperRef.current?.contains(e.target)) {
1464
+ setIsOpen(false);
1465
+ }
1466
+ };
1467
+ const handleKey = (e) => {
1468
+ if (e.key === "Escape") {
1469
+ setIsOpen(false);
1470
+ triggerRef.current?.focus();
1471
+ }
1472
+ };
1473
+ document.addEventListener("mousedown", handleClick);
1474
+ document.addEventListener("keydown", handleKey);
1475
+ return () => {
1476
+ document.removeEventListener("mousedown", handleClick);
1477
+ document.removeEventListener("keydown", handleKey);
1478
+ };
1479
+ }, [isOpen]);
1480
+ const run = (fn) => {
1481
+ setIsOpen(false);
1482
+ fn();
1398
1483
  };
1399
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles4.container, children: [
1484
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className, style: styles4.wrapper, children: [
1400
1485
  /* @__PURE__ */ jsxRuntime.jsx(
1401
1486
  "button",
1402
1487
  {
1403
- onClick: () => onReply(message),
1404
- style: styles4.button,
1405
- title: "Reply",
1488
+ ref: triggerRef,
1406
1489
  type: "button",
1407
- children: "\u21A9"
1490
+ onClick: (e) => {
1491
+ e.stopPropagation();
1492
+ setIsOpen((v) => !v);
1493
+ },
1494
+ "aria-label": "Message actions",
1495
+ "aria-haspopup": "menu",
1496
+ "aria-expanded": isOpen ? "true" : "false",
1497
+ title: "Message actions",
1498
+ style: styles4.trigger,
1499
+ children: /* @__PURE__ */ jsxRuntime.jsx(MoreIcon, { size: 18, color: "#4b5563" })
1408
1500
  }
1409
1501
  ),
1410
- isPinned ? /* @__PURE__ */ jsxRuntime.jsx(
1411
- "button",
1502
+ isOpen && /* @__PURE__ */ jsxRuntime.jsxs(
1503
+ "div",
1412
1504
  {
1413
- onClick: () => onUnpin(message.id),
1414
- style: styles4.button,
1415
- title: "Unpin",
1416
- type: "button",
1417
- children: "\u{1F4CC}"
1418
- }
1419
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1420
- "button",
1421
- {
1422
- onClick: () => onPin(message.id),
1505
+ role: "menu",
1506
+ "aria-label": "Message actions",
1423
1507
  style: {
1424
- ...styles4.button,
1425
- opacity: pinDisabled ? 0.4 : 1,
1426
- cursor: pinDisabled ? "not-allowed" : "pointer"
1508
+ ...styles4.menu,
1509
+ ...anchorRight ? styles4.menuRight : styles4.menuLeft
1427
1510
  },
1428
- disabled: pinDisabled,
1429
- title: pinDisabled ? "Pin limit reached (max 3)" : "Pin",
1430
- type: "button",
1431
- children: "\u{1F4CC}"
1432
- }
1433
- ),
1434
- canCopy && onCopy && /* @__PURE__ */ jsxRuntime.jsx(
1435
- "button",
1436
- {
1437
- onClick: handleCopy,
1438
- style: styles4.button,
1439
- title: "Copy text",
1440
- type: "button",
1441
- children: "\u2398"
1511
+ children: [
1512
+ /* @__PURE__ */ jsxRuntime.jsxs(
1513
+ "button",
1514
+ {
1515
+ type: "button",
1516
+ role: "menuitem",
1517
+ onClick: () => run(() => onReply(message)),
1518
+ style: styles4.item,
1519
+ children: [
1520
+ /* @__PURE__ */ jsxRuntime.jsx(ReplyIcon, { size: 18, color: "#374151" }),
1521
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Reply" })
1522
+ ]
1523
+ }
1524
+ ),
1525
+ isPinned ? /* @__PURE__ */ jsxRuntime.jsxs(
1526
+ "button",
1527
+ {
1528
+ type: "button",
1529
+ role: "menuitem",
1530
+ onClick: () => run(() => onUnpin(message.id)),
1531
+ style: styles4.item,
1532
+ children: [
1533
+ /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 16, color: "#374151" }),
1534
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Unpin" })
1535
+ ]
1536
+ }
1537
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
1538
+ "button",
1539
+ {
1540
+ type: "button",
1541
+ role: "menuitem",
1542
+ onClick: () => run(() => onPin(message.id)),
1543
+ disabled: pinDisabled,
1544
+ style: {
1545
+ ...styles4.item,
1546
+ ...pinDisabled ? styles4.itemDisabled : {}
1547
+ },
1548
+ title: pinDisabled ? "Pin limit reached (max 3)" : void 0,
1549
+ children: [
1550
+ /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 16, color: "#374151" }),
1551
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: pinDisabled ? "Pin (limit reached)" : "Pin" })
1552
+ ]
1553
+ }
1554
+ ),
1555
+ canCopy && onCopy && /* @__PURE__ */ jsxRuntime.jsxs(
1556
+ "button",
1557
+ {
1558
+ type: "button",
1559
+ role: "menuitem",
1560
+ onClick: () => run(() => onCopy(message.body)),
1561
+ style: styles4.item,
1562
+ children: [
1563
+ /* @__PURE__ */ jsxRuntime.jsx(CopyIcon, { size: 16, color: "#374151" }),
1564
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Copy text" })
1565
+ ]
1566
+ }
1567
+ )
1568
+ ]
1442
1569
  }
1443
1570
  )
1444
1571
  ] });
1445
1572
  }
1446
1573
  var styles4 = {
1447
- container: {
1574
+ wrapper: {
1575
+ position: "relative",
1576
+ display: "inline-block"
1577
+ },
1578
+ trigger: {
1579
+ width: "36px",
1580
+ height: "36px",
1448
1581
  display: "flex",
1449
- gap: "2px",
1450
- padding: "2px",
1582
+ alignItems: "center",
1583
+ justifyContent: "center",
1584
+ backgroundColor: "transparent",
1585
+ border: "none",
1586
+ borderRadius: "50%",
1587
+ cursor: "pointer",
1588
+ color: "#4b5563"
1589
+ },
1590
+ menu: {
1591
+ position: "absolute",
1592
+ top: "100%",
1593
+ marginTop: "4px",
1594
+ minWidth: "160px",
1451
1595
  backgroundColor: "#ffffff",
1452
1596
  border: "1px solid #e5e7eb",
1453
- borderRadius: "20px",
1454
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.08)"
1597
+ borderRadius: "10px",
1598
+ boxShadow: "0 6px 18px rgba(0, 0, 0, 0.12)",
1599
+ padding: "4px",
1600
+ zIndex: 20
1455
1601
  },
1456
- button: {
1457
- width: "24px",
1458
- height: "24px",
1602
+ menuLeft: {
1603
+ left: 0
1604
+ },
1605
+ menuRight: {
1606
+ right: 0
1607
+ },
1608
+ item: {
1459
1609
  display: "flex",
1460
1610
  alignItems: "center",
1461
- justifyContent: "center",
1462
- fontSize: "12px",
1611
+ gap: "10px",
1612
+ width: "100%",
1613
+ minHeight: "40px",
1614
+ padding: "8px 12px",
1615
+ fontSize: "14px",
1616
+ color: "#111827",
1463
1617
  backgroundColor: "transparent",
1464
1618
  border: "none",
1465
- borderRadius: "50%",
1466
- cursor: "pointer"
1619
+ borderRadius: "6px",
1620
+ cursor: "pointer",
1621
+ textAlign: "left"
1622
+ },
1623
+ itemDisabled: {
1624
+ color: "#9ca3af",
1625
+ cursor: "not-allowed"
1467
1626
  }
1468
1627
  };
1469
1628
  function FileIcon({ size = 18, color = "currentColor" }) {
@@ -1480,20 +1639,6 @@ function FileIcon({ size = 18, color = "currentColor" }) {
1480
1639
  }
1481
1640
  );
1482
1641
  }
1483
- function PinIcon({ size = 14, color = "currentColor" }) {
1484
- return /* @__PURE__ */ jsxRuntime.jsx(
1485
- "svg",
1486
- {
1487
- xmlns: "http://www.w3.org/2000/svg",
1488
- width: size,
1489
- height: size,
1490
- viewBox: "0 0 24 24",
1491
- fill: color,
1492
- "aria-hidden": "true",
1493
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 9V4l1 0c.55 0 1-.45 1-1s-.45-1-1-1H7c-.55 0-1 .45-1 1s.45 1 1 1l1 0v5c0 1.66-1.34 3-3 3v2h5.97v7l1 1 1-1v-7H19v-2c-1.66 0-3-1.34-3-3z" })
1494
- }
1495
- );
1496
- }
1497
1642
  function AlertIcon({ size = 14, color = "currentColor" }) {
1498
1643
  return /* @__PURE__ */ jsxRuntime.jsx(
1499
1644
  "svg",
@@ -1558,12 +1703,26 @@ function MessageBubble({
1558
1703
  pinDisabled = false,
1559
1704
  className
1560
1705
  }) {
1561
- const [isHovered, setIsHovered] = React4.useState(false);
1562
1706
  if (message.type === "system") {
1563
1707
  return /* @__PURE__ */ jsxRuntime.jsx(SystemBubble, { message });
1564
1708
  }
1565
1709
  const hasActions = !!(onReply || onPin || onUnpin || onCopy);
1566
- return /* @__PURE__ */ jsxRuntime.jsx(
1710
+ const actionsSlot = hasActions ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.actionsSlot, children: /* @__PURE__ */ jsxRuntime.jsx(
1711
+ MessageActionsMenu,
1712
+ {
1713
+ message,
1714
+ onReply: onReply ?? (() => {
1715
+ }),
1716
+ onPin: onPin ?? (() => {
1717
+ }),
1718
+ onUnpin: onUnpin ?? (() => {
1719
+ }),
1720
+ onCopy,
1721
+ pinDisabled,
1722
+ anchorRight: !isOwn
1723
+ }
1724
+ ) }) : null;
1725
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1567
1726
  "div",
1568
1727
  {
1569
1728
  className,
@@ -1571,10 +1730,9 @@ function MessageBubble({
1571
1730
  ...styles5.wrapper,
1572
1731
  justifyContent: isOwn ? "flex-end" : "flex-start"
1573
1732
  },
1574
- onMouseEnter: () => setIsHovered(true),
1575
- onMouseLeave: () => setIsHovered(false),
1576
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.bubbleWrapper, children: [
1577
- /* @__PURE__ */ jsxRuntime.jsxs(
1733
+ children: [
1734
+ isOwn && actionsSlot,
1735
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.bubbleWrapper, children: /* @__PURE__ */ jsxRuntime.jsxs(
1578
1736
  "div",
1579
1737
  {
1580
1738
  style: {
@@ -1633,33 +1791,9 @@ function MessageBubble({
1633
1791
  )
1634
1792
  ]
1635
1793
  }
1636
- ),
1637
- hasActions && /* @__PURE__ */ jsxRuntime.jsx(
1638
- "div",
1639
- {
1640
- style: {
1641
- ...styles5.actionsWrapper,
1642
- [isOwn ? "right" : "left"]: "100%",
1643
- [isOwn ? "marginRight" : "marginLeft"]: "6px"
1644
- },
1645
- children: /* @__PURE__ */ jsxRuntime.jsx(
1646
- MessageActionsMenu,
1647
- {
1648
- message,
1649
- onReply: onReply ?? (() => {
1650
- }),
1651
- onPin: onPin ?? (() => {
1652
- }),
1653
- onUnpin: onUnpin ?? (() => {
1654
- }),
1655
- onCopy,
1656
- visible: isHovered,
1657
- pinDisabled
1658
- }
1659
- )
1660
- }
1661
- )
1662
- ] })
1794
+ ) }),
1795
+ !isOwn && actionsSlot
1796
+ ]
1663
1797
  }
1664
1798
  );
1665
1799
  }
@@ -1756,6 +1890,8 @@ function escapeRegex2(string) {
1756
1890
  var styles5 = {
1757
1891
  wrapper: {
1758
1892
  display: "flex",
1893
+ alignItems: "flex-end",
1894
+ gap: "4px",
1759
1895
  marginBottom: "8px",
1760
1896
  paddingLeft: "16px",
1761
1897
  paddingRight: "16px"
@@ -1764,6 +1900,10 @@ var styles5 = {
1764
1900
  position: "relative",
1765
1901
  maxWidth: "75%"
1766
1902
  },
1903
+ actionsSlot: {
1904
+ flexShrink: 0,
1905
+ alignSelf: "flex-end"
1906
+ },
1767
1907
  bubble: {
1768
1908
  padding: "10px 14px",
1769
1909
  borderRadius: "12px",
@@ -1839,12 +1979,6 @@ var styles5 = {
1839
1979
  cursor: "pointer",
1840
1980
  padding: "0 2px"
1841
1981
  },
1842
- actionsWrapper: {
1843
- position: "absolute",
1844
- top: "50%",
1845
- transform: "translateY(-50%)",
1846
- zIndex: 10
1847
- },
1848
1982
  audioContainer: {
1849
1983
  display: "flex",
1850
1984
  alignItems: "center",
@@ -2494,7 +2628,18 @@ function MessageInput({
2494
2628
  type: "file",
2495
2629
  onChange: handleFileSelect,
2496
2630
  style: { display: "none" },
2497
- accept: [...SUPPORTED_IMAGE_TYPES, "application/pdf", ".doc", ".docx"].join(",")
2631
+ accept: [
2632
+ ...SUPPORTED_IMAGE_TYPES,
2633
+ "application/pdf",
2634
+ ".doc",
2635
+ ".docx",
2636
+ ".xls",
2637
+ ".xlsx",
2638
+ ".csv",
2639
+ ".txt",
2640
+ ".heic",
2641
+ ".heif"
2642
+ ].join(",")
2498
2643
  }
2499
2644
  ),
2500
2645
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -3506,12 +3651,14 @@ function ExpandIcon({ size = 18, color = "currentColor" }) {
3506
3651
  }
3507
3652
  );
3508
3653
  }
3654
+ var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
3509
3655
  function CollabPopup({
3510
3656
  orderId,
3511
3657
  patientData,
3512
3658
  participantIds,
3513
3659
  isOpen,
3514
3660
  onClose,
3661
+ onBack,
3515
3662
  initialPosition,
3516
3663
  width = 380,
3517
3664
  height = 520,
@@ -3527,6 +3674,8 @@ function CollabPopup({
3527
3674
  const [isMinimized, setIsMinimized] = React4.useState(false);
3528
3675
  const dragOffset = React4.useRef({ x: 0, y: 0 });
3529
3676
  const containerRef = React4.useRef(null);
3677
+ const previouslyFocused = React4.useRef(null);
3678
+ const titleId = React4.useId();
3530
3679
  const handleMouseDown = React4.useCallback(
3531
3680
  (e) => {
3532
3681
  if (!e.target.closest("[data-drag-handle]")) return;
@@ -3541,13 +3690,44 @@ function CollabPopup({
3541
3690
  );
3542
3691
  React4.useEffect(() => {
3543
3692
  if (!isOpen) return;
3693
+ previouslyFocused.current = document.activeElement;
3694
+ const focusFirst = () => {
3695
+ const root = containerRef.current;
3696
+ if (!root) return;
3697
+ const first = root.querySelector(FOCUSABLE_SELECTOR);
3698
+ (first ?? root).focus();
3699
+ };
3700
+ const raf = requestAnimationFrame(focusFirst);
3544
3701
  const handleKey = (e) => {
3545
3702
  if (e.key === "Escape") {
3546
3703
  onClose();
3704
+ return;
3705
+ }
3706
+ if (e.key !== "Tab") return;
3707
+ const root = containerRef.current;
3708
+ if (!root) return;
3709
+ const focusables = Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR));
3710
+ if (focusables.length === 0) {
3711
+ e.preventDefault();
3712
+ return;
3713
+ }
3714
+ const first = focusables[0];
3715
+ const last = focusables[focusables.length - 1];
3716
+ const active = document.activeElement;
3717
+ if (e.shiftKey && active === first) {
3718
+ e.preventDefault();
3719
+ last.focus();
3720
+ } else if (!e.shiftKey && active === last) {
3721
+ e.preventDefault();
3722
+ first.focus();
3547
3723
  }
3548
3724
  };
3549
3725
  window.addEventListener("keydown", handleKey);
3550
- return () => window.removeEventListener("keydown", handleKey);
3726
+ return () => {
3727
+ cancelAnimationFrame(raf);
3728
+ window.removeEventListener("keydown", handleKey);
3729
+ previouslyFocused.current?.focus?.();
3730
+ };
3551
3731
  }, [isOpen, onClose]);
3552
3732
  React4.useEffect(() => {
3553
3733
  if (!isDragging) return;
@@ -3573,6 +3753,10 @@ function CollabPopup({
3573
3753
  {
3574
3754
  ref: containerRef,
3575
3755
  className,
3756
+ role: "dialog",
3757
+ "aria-modal": "true",
3758
+ "aria-labelledby": titleId,
3759
+ tabIndex: -1,
3576
3760
  style: {
3577
3761
  ...styles13.container,
3578
3762
  left: position.x,
@@ -3584,7 +3768,18 @@ function CollabPopup({
3584
3768
  onMouseDown: handleMouseDown,
3585
3769
  children: [
3586
3770
  /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-drag-handle": true, style: styles13.titleBar, children: [
3587
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.titleText, children: buildChannelName(patientData) }),
3771
+ onBack && /* @__PURE__ */ jsxRuntime.jsx(
3772
+ "button",
3773
+ {
3774
+ onClick: onBack,
3775
+ style: styles13.titleButton,
3776
+ "aria-label": "Go back",
3777
+ title: "Back",
3778
+ type: "button",
3779
+ children: "\u2190"
3780
+ }
3781
+ ),
3782
+ /* @__PURE__ */ jsxRuntime.jsx("span", { id: titleId, style: { ...styles13.titleText, flex: 1 }, children: buildChannelName(patientData) }),
3588
3783
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.titleActions, children: [
3589
3784
  /* @__PURE__ */ jsxRuntime.jsx(
3590
3785
  "button",
@@ -3871,8 +4066,8 @@ function useInlineCollab({
3871
4066
  await dispatchSend({
3872
4067
  body: "Voice message",
3873
4068
  type: "audio",
3874
- mediaUrl: url,
3875
- mediaDuration: duration
4069
+ media_url: url,
4070
+ media_duration: duration
3876
4071
  });
3877
4072
  } catch (err) {
3878
4073
  setError(err instanceof Error ? err.message : "Failed to send voice message");