@natoe/colab 0.1.6 → 0.1.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.
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import React4, { createContext, forwardRef, useRef, useImperativeHandle, useEffect, useCallback, useState, useContext, useId, useMemo } from 'react';
1
+ import React4, { createContext, forwardRef, useRef, useImperativeHandle, useEffect, useCallback, useState, useLayoutEffect, useContext, useId, useMemo } from 'react';
2
2
  import { Socket, Presence } from 'phoenix';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { createPortal } from 'react-dom';
4
5
 
5
6
  // src/components/CollabPanel.tsx
6
7
 
@@ -1144,71 +1145,190 @@ function useDeepLinks() {
1144
1145
  function escapeRegex(string) {
1145
1146
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1146
1147
  }
1148
+ function DicomIcon({ size = 18, color = "currentColor" }) {
1149
+ return /* @__PURE__ */ jsxs(
1150
+ "svg",
1151
+ {
1152
+ xmlns: "http://www.w3.org/2000/svg",
1153
+ width: size,
1154
+ height: size,
1155
+ viewBox: "0 0 24 24",
1156
+ fill: "none",
1157
+ stroke: color,
1158
+ strokeWidth: "2",
1159
+ strokeLinecap: "round",
1160
+ strokeLinejoin: "round",
1161
+ "aria-hidden": "true",
1162
+ children: [
1163
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "4", width: "13", height: "13", rx: "2" }),
1164
+ /* @__PURE__ */ jsx("path", { d: "M7 7h13a2 2 0 0 1 2 2v11", opacity: "0.6" }),
1165
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9.5", r: "1.2", fill: color, stroke: "none" }),
1166
+ /* @__PURE__ */ jsx("path", { d: "M5 14l3-3 4 4 2-2 3 3" })
1167
+ ]
1168
+ }
1169
+ );
1170
+ }
1171
+ function SettingsIcon({ size = 18, color = "currentColor" }) {
1172
+ return /* @__PURE__ */ jsx(
1173
+ "svg",
1174
+ {
1175
+ xmlns: "http://www.w3.org/2000/svg",
1176
+ width: size,
1177
+ height: size,
1178
+ viewBox: "0 0 24 24",
1179
+ fill: color,
1180
+ "aria-hidden": "true",
1181
+ children: /* @__PURE__ */ jsx("path", { d: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" })
1182
+ }
1183
+ );
1184
+ }
1185
+ function BackIcon({ size = 22, color = "currentColor" }) {
1186
+ return /* @__PURE__ */ jsx(
1187
+ "svg",
1188
+ {
1189
+ xmlns: "http://www.w3.org/2000/svg",
1190
+ width: size,
1191
+ height: size,
1192
+ viewBox: "0 0 24 24",
1193
+ fill: color,
1194
+ "aria-hidden": "true",
1195
+ children: /* @__PURE__ */ jsx("path", { d: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" })
1196
+ }
1197
+ );
1198
+ }
1147
1199
  function PatientHeader({
1148
1200
  patientData,
1149
1201
  onOpenDicom,
1150
1202
  onOpenSettings,
1203
+ onBack,
1151
1204
  className
1152
1205
  }) {
1153
1206
  const hasDicom = !!(patientData.studyId && patientData.storageId);
1207
+ const meta = [];
1208
+ if (patientData.patientAge) meta.push({ label: "Age", value: String(patientData.patientAge) });
1209
+ if (patientData.patientSex) meta.push({ label: "Sex", value: String(patientData.patientSex) });
1210
+ if (patientData.studyType) meta.push({ label: "Modality", value: patientData.studyType });
1211
+ if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1212
+ meta.push({ label: "Body part", value: patientData.bodyParts.join(", ") });
1213
+ }
1214
+ if (patientData.referringPhysician) {
1215
+ meta.push({ label: "Ref", value: patientData.referringPhysician });
1216
+ }
1154
1217
  return /* @__PURE__ */ jsxs("div", { className, style: styles.container, children: [
1155
- /* @__PURE__ */ jsx("div", { style: styles.infoRow, children: /* @__PURE__ */ jsx("div", { style: styles.patientInfo, children: /* @__PURE__ */ jsx("span", { style: styles.patientName, children: buildChannelName(patientData) }) }) }),
1156
- /* @__PURE__ */ jsxs("div", { style: styles.secondaryRow, children: [
1157
- patientData.patientAge && /* @__PURE__ */ jsxs("span", { style: styles.secondary, children: [
1158
- patientData.patientAge,
1159
- patientData.patientSex ? `/${patientData.patientSex}` : ""
1160
- ] }),
1161
- patientData.studyType && /* @__PURE__ */ jsx("span", { style: styles.secondary, children: patientData.studyType }),
1162
- patientData.bodyParts && patientData.bodyParts.length > 0 && /* @__PURE__ */ jsx("span", { style: styles.secondary, children: patientData.bodyParts.join(", ") }),
1163
- patientData.referringPhysician && /* @__PURE__ */ jsxs("span", { style: styles.secondary, children: [
1164
- "Ref: ",
1165
- patientData.referringPhysician
1166
- ] })
1218
+ /* @__PURE__ */ jsxs("div", { style: styles.nameRow, children: [
1219
+ onBack && /* @__PURE__ */ jsx(
1220
+ "button",
1221
+ {
1222
+ type: "button",
1223
+ onClick: onBack,
1224
+ style: styles.backButton,
1225
+ "aria-label": "Back to conversations",
1226
+ title: "Back to conversations",
1227
+ children: /* @__PURE__ */ jsx(BackIcon, { size: 22, color: "#1f2937" })
1228
+ }
1229
+ ),
1230
+ /* @__PURE__ */ jsx("span", { style: styles.nameText, children: buildChannelName(patientData) })
1167
1231
  ] }),
1168
- /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
1169
- hasDicom && onOpenDicom && /* @__PURE__ */ jsx("button", { onClick: onOpenDicom, style: styles.dicomButton, type: "button", children: "View DICOM" }),
1170
- onOpenSettings && /* @__PURE__ */ jsx("button", { onClick: onOpenSettings, style: styles.settingsButton, type: "button", children: "Settings" })
1171
- ] })
1232
+ meta.length > 0 && /* @__PURE__ */ jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxs("span", { style: styles.metaItem, children: [
1233
+ /* @__PURE__ */ jsxs("span", { style: styles.metaLabel, children: [
1234
+ item.label,
1235
+ ":"
1236
+ ] }),
1237
+ " ",
1238
+ /* @__PURE__ */ jsx("span", { style: styles.metaValue, children: item.value })
1239
+ ] }, item.label)) }),
1240
+ hasDicom && onOpenDicom || onOpenSettings ? /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
1241
+ hasDicom && onOpenDicom && /* @__PURE__ */ jsxs(
1242
+ "button",
1243
+ {
1244
+ onClick: onOpenDicom,
1245
+ style: styles.dicomButton,
1246
+ type: "button",
1247
+ "aria-label": "View DICOM study",
1248
+ children: [
1249
+ /* @__PURE__ */ jsx(DicomIcon, { size: 18, color: "#ffffff" }),
1250
+ /* @__PURE__ */ jsx("span", { children: "View DICOM" })
1251
+ ]
1252
+ }
1253
+ ),
1254
+ onOpenSettings && /* @__PURE__ */ jsxs(
1255
+ "button",
1256
+ {
1257
+ onClick: onOpenSettings,
1258
+ style: styles.settingsButton,
1259
+ type: "button",
1260
+ "aria-label": "Open channel settings",
1261
+ children: [
1262
+ /* @__PURE__ */ jsx(SettingsIcon, { size: 18, color: "#374151" }),
1263
+ /* @__PURE__ */ jsx("span", { children: "Settings" })
1264
+ ]
1265
+ }
1266
+ )
1267
+ ] }) : null
1172
1268
  ] });
1173
1269
  }
1174
1270
  var styles = {
1175
1271
  container: {
1176
- padding: "12px 16px",
1272
+ padding: "14px 16px",
1177
1273
  borderBottom: "1px solid #e5e7eb",
1178
1274
  backgroundColor: "#f9fafb"
1179
1275
  },
1180
- infoRow: {
1276
+ nameRow: {
1181
1277
  display: "flex",
1182
- justifyContent: "space-between",
1183
- alignItems: "center"
1278
+ alignItems: "center",
1279
+ gap: "6px"
1184
1280
  },
1185
- patientInfo: {
1281
+ backButton: {
1282
+ width: "36px",
1283
+ height: "36px",
1186
1284
  display: "flex",
1187
1285
  alignItems: "center",
1188
- gap: "8px",
1189
- flexWrap: "wrap"
1286
+ justifyContent: "center",
1287
+ marginLeft: "-8px",
1288
+ backgroundColor: "transparent",
1289
+ border: "none",
1290
+ borderRadius: "50%",
1291
+ cursor: "pointer",
1292
+ flexShrink: 0
1190
1293
  },
1191
- patientName: {
1294
+ nameText: {
1295
+ fontSize: "18px",
1192
1296
  fontWeight: 600,
1193
- fontSize: "17px",
1194
- color: "#111827"
1297
+ color: "#111827",
1298
+ lineHeight: 1.3,
1299
+ minWidth: 0,
1300
+ overflow: "hidden",
1301
+ textOverflow: "ellipsis",
1302
+ whiteSpace: "nowrap"
1195
1303
  },
1196
- secondaryRow: {
1304
+ metaRow: {
1197
1305
  display: "flex",
1198
- gap: "12px",
1199
- marginTop: "4px",
1200
- alignItems: "center"
1306
+ flexWrap: "wrap",
1307
+ columnGap: "14px",
1308
+ rowGap: "4px",
1309
+ marginTop: "6px"
1201
1310
  },
1202
- secondary: {
1311
+ metaItem: {
1203
1312
  fontSize: "13px",
1204
- color: "#4b5563"
1313
+ color: "#374151",
1314
+ whiteSpace: "nowrap"
1315
+ },
1316
+ metaLabel: {
1317
+ color: "#6b7280",
1318
+ fontWeight: 500
1319
+ },
1320
+ metaValue: {
1321
+ color: "#111827"
1205
1322
  },
1206
1323
  actions: {
1207
1324
  display: "flex",
1208
1325
  gap: "8px",
1209
- marginTop: "8px"
1326
+ marginTop: "10px"
1210
1327
  },
1211
1328
  dicomButton: {
1329
+ display: "inline-flex",
1330
+ alignItems: "center",
1331
+ gap: "8px",
1212
1332
  minHeight: "40px",
1213
1333
  padding: "8px 16px",
1214
1334
  fontSize: "14px",
@@ -1220,6 +1340,9 @@ var styles = {
1220
1340
  cursor: "pointer"
1221
1341
  },
1222
1342
  settingsButton: {
1343
+ display: "inline-flex",
1344
+ alignItems: "center",
1345
+ gap: "8px",
1223
1346
  minHeight: "40px",
1224
1347
  padding: "8px 16px",
1225
1348
  fontSize: "14px",
@@ -1436,6 +1559,8 @@ function MoreIcon({ size = 18, color = "currentColor" }) {
1436
1559
  }
1437
1560
  );
1438
1561
  }
1562
+ var VIEWPORT_MARGIN = 8;
1563
+ var TRIGGER_GAP = 4;
1439
1564
  function MessageActionsMenu({
1440
1565
  message,
1441
1566
  onReply,
@@ -1447,16 +1572,43 @@ function MessageActionsMenu({
1447
1572
  className
1448
1573
  }) {
1449
1574
  const [isOpen, setIsOpen] = useState(false);
1450
- const wrapperRef = useRef(null);
1575
+ const [coords, setCoords] = useState(null);
1451
1576
  const triggerRef = useRef(null);
1577
+ const menuRef = useRef(null);
1452
1578
  const isPinned = !!message.isPinned;
1453
1579
  const canCopy = message.type === "text" && !!message.body;
1580
+ useLayoutEffect(() => {
1581
+ if (!isOpen) {
1582
+ setCoords(null);
1583
+ return;
1584
+ }
1585
+ const trigger = triggerRef.current;
1586
+ const menu2 = menuRef.current;
1587
+ if (!trigger || !menu2) return;
1588
+ const triggerRect = trigger.getBoundingClientRect();
1589
+ const menuRect = menu2.getBoundingClientRect();
1590
+ const vw = window.innerWidth;
1591
+ const vh = window.innerHeight;
1592
+ const spaceBelow = vh - triggerRect.bottom;
1593
+ const spaceAbove = triggerRect.top;
1594
+ const placeAbove = spaceBelow < menuRect.height + TRIGGER_GAP + VIEWPORT_MARGIN && spaceAbove > spaceBelow;
1595
+ const top = placeAbove ? triggerRect.top - menuRect.height - TRIGGER_GAP : triggerRect.bottom + TRIGGER_GAP;
1596
+ const preferredLeft = anchorRight ? triggerRect.right - menuRect.width : triggerRect.left;
1597
+ const left = Math.max(
1598
+ VIEWPORT_MARGIN,
1599
+ Math.min(preferredLeft, vw - menuRect.width - VIEWPORT_MARGIN)
1600
+ );
1601
+ const clampedTop = Math.max(
1602
+ VIEWPORT_MARGIN,
1603
+ Math.min(top, vh - menuRect.height - VIEWPORT_MARGIN)
1604
+ );
1605
+ setCoords({ top: clampedTop, left });
1606
+ }, [isOpen, anchorRight]);
1454
1607
  useEffect(() => {
1455
1608
  if (!isOpen) return;
1609
+ const isInside = (node) => !!node && (triggerRef.current?.contains(node) === true || menuRef.current?.contains(node) === true);
1456
1610
  const handleClick = (e) => {
1457
- if (!wrapperRef.current?.contains(e.target)) {
1458
- setIsOpen(false);
1459
- }
1611
+ if (!isInside(e.target)) setIsOpen(false);
1460
1612
  };
1461
1613
  const handleKey = (e) => {
1462
1614
  if (e.key === "Escape") {
@@ -1464,18 +1616,100 @@ function MessageActionsMenu({
1464
1616
  triggerRef.current?.focus();
1465
1617
  }
1466
1618
  };
1619
+ const handleScroll = (e) => {
1620
+ if (!isInside(e.target)) setIsOpen(false);
1621
+ };
1622
+ const handleResize = () => setIsOpen(false);
1467
1623
  document.addEventListener("mousedown", handleClick);
1468
1624
  document.addEventListener("keydown", handleKey);
1625
+ window.addEventListener("resize", handleResize);
1626
+ window.addEventListener("scroll", handleScroll, true);
1469
1627
  return () => {
1470
1628
  document.removeEventListener("mousedown", handleClick);
1471
1629
  document.removeEventListener("keydown", handleKey);
1630
+ window.removeEventListener("resize", handleResize);
1631
+ window.removeEventListener("scroll", handleScroll, true);
1472
1632
  };
1473
1633
  }, [isOpen]);
1474
1634
  const run = (fn) => {
1475
1635
  setIsOpen(false);
1476
1636
  fn();
1477
1637
  };
1478
- return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className, style: styles4.wrapper, children: [
1638
+ const menu = isOpen ? /* @__PURE__ */ jsxs(
1639
+ "div",
1640
+ {
1641
+ ref: menuRef,
1642
+ role: "menu",
1643
+ "aria-label": "Message actions",
1644
+ style: {
1645
+ ...styles4.menu,
1646
+ // Render off-screen on first paint so we can measure before placing,
1647
+ // then snap to computed coords on the next layout pass.
1648
+ top: coords?.top ?? -9999,
1649
+ left: coords?.left ?? -9999,
1650
+ visibility: coords ? "visible" : "hidden"
1651
+ },
1652
+ children: [
1653
+ /* @__PURE__ */ jsxs(
1654
+ "button",
1655
+ {
1656
+ type: "button",
1657
+ role: "menuitem",
1658
+ onClick: () => run(() => onReply(message)),
1659
+ style: styles4.item,
1660
+ children: [
1661
+ /* @__PURE__ */ jsx(ReplyIcon, { size: 18, color: "#374151" }),
1662
+ /* @__PURE__ */ jsx("span", { children: "Reply" })
1663
+ ]
1664
+ }
1665
+ ),
1666
+ isPinned ? /* @__PURE__ */ jsxs(
1667
+ "button",
1668
+ {
1669
+ type: "button",
1670
+ role: "menuitem",
1671
+ onClick: () => run(() => onUnpin(message.id)),
1672
+ style: styles4.item,
1673
+ children: [
1674
+ /* @__PURE__ */ jsx(PinIcon, { size: 16, color: "#374151" }),
1675
+ /* @__PURE__ */ jsx("span", { children: "Unpin" })
1676
+ ]
1677
+ }
1678
+ ) : /* @__PURE__ */ jsxs(
1679
+ "button",
1680
+ {
1681
+ type: "button",
1682
+ role: "menuitem",
1683
+ onClick: () => run(() => onPin(message.id)),
1684
+ disabled: pinDisabled,
1685
+ style: {
1686
+ ...styles4.item,
1687
+ ...pinDisabled ? styles4.itemDisabled : {}
1688
+ },
1689
+ title: pinDisabled ? "Pin limit reached (max 3)" : void 0,
1690
+ children: [
1691
+ /* @__PURE__ */ jsx(PinIcon, { size: 16, color: "#374151" }),
1692
+ /* @__PURE__ */ jsx("span", { children: pinDisabled ? "Pin (limit reached)" : "Pin" })
1693
+ ]
1694
+ }
1695
+ ),
1696
+ canCopy && onCopy && /* @__PURE__ */ jsxs(
1697
+ "button",
1698
+ {
1699
+ type: "button",
1700
+ role: "menuitem",
1701
+ onClick: () => run(() => onCopy(message.body)),
1702
+ style: styles4.item,
1703
+ children: [
1704
+ /* @__PURE__ */ jsx(CopyIcon, { size: 16, color: "#374151" }),
1705
+ /* @__PURE__ */ jsx("span", { children: "Copy text" })
1706
+ ]
1707
+ }
1708
+ )
1709
+ ]
1710
+ }
1711
+ ) : null;
1712
+ return /* @__PURE__ */ jsxs("div", { className, style: styles4.wrapper, children: [
1479
1713
  /* @__PURE__ */ jsx(
1480
1714
  "button",
1481
1715
  {
@@ -1493,75 +1727,7 @@ function MessageActionsMenu({
1493
1727
  children: /* @__PURE__ */ jsx(MoreIcon, { size: 18, color: "#4b5563" })
1494
1728
  }
1495
1729
  ),
1496
- isOpen && /* @__PURE__ */ jsxs(
1497
- "div",
1498
- {
1499
- role: "menu",
1500
- "aria-label": "Message actions",
1501
- style: {
1502
- ...styles4.menu,
1503
- ...anchorRight ? styles4.menuRight : styles4.menuLeft
1504
- },
1505
- children: [
1506
- /* @__PURE__ */ jsxs(
1507
- "button",
1508
- {
1509
- type: "button",
1510
- role: "menuitem",
1511
- onClick: () => run(() => onReply(message)),
1512
- style: styles4.item,
1513
- children: [
1514
- /* @__PURE__ */ jsx(ReplyIcon, { size: 18, color: "#374151" }),
1515
- /* @__PURE__ */ jsx("span", { children: "Reply" })
1516
- ]
1517
- }
1518
- ),
1519
- isPinned ? /* @__PURE__ */ jsxs(
1520
- "button",
1521
- {
1522
- type: "button",
1523
- role: "menuitem",
1524
- onClick: () => run(() => onUnpin(message.id)),
1525
- style: styles4.item,
1526
- children: [
1527
- /* @__PURE__ */ jsx(PinIcon, { size: 16, color: "#374151" }),
1528
- /* @__PURE__ */ jsx("span", { children: "Unpin" })
1529
- ]
1530
- }
1531
- ) : /* @__PURE__ */ jsxs(
1532
- "button",
1533
- {
1534
- type: "button",
1535
- role: "menuitem",
1536
- onClick: () => run(() => onPin(message.id)),
1537
- disabled: pinDisabled,
1538
- style: {
1539
- ...styles4.item,
1540
- ...pinDisabled ? styles4.itemDisabled : {}
1541
- },
1542
- title: pinDisabled ? "Pin limit reached (max 3)" : void 0,
1543
- children: [
1544
- /* @__PURE__ */ jsx(PinIcon, { size: 16, color: "#374151" }),
1545
- /* @__PURE__ */ jsx("span", { children: pinDisabled ? "Pin (limit reached)" : "Pin" })
1546
- ]
1547
- }
1548
- ),
1549
- canCopy && onCopy && /* @__PURE__ */ jsxs(
1550
- "button",
1551
- {
1552
- type: "button",
1553
- role: "menuitem",
1554
- onClick: () => run(() => onCopy(message.body)),
1555
- style: styles4.item,
1556
- children: [
1557
- /* @__PURE__ */ jsx(CopyIcon, { size: 16, color: "#374151" }),
1558
- /* @__PURE__ */ jsx("span", { children: "Copy text" })
1559
- ]
1560
- }
1561
- )
1562
- ]
1563
- }
1564
- )
1730
+ menu && typeof document !== "undefined" ? createPortal(menu, document.body) : null
1565
1731
  ] });
1566
1732
  }
1567
1733
  var styles4 = {
@@ -1582,22 +1748,15 @@ var styles4 = {
1582
1748
  color: "#4b5563"
1583
1749
  },
1584
1750
  menu: {
1585
- position: "absolute",
1586
- top: "100%",
1587
- marginTop: "4px",
1751
+ position: "fixed",
1588
1752
  minWidth: "160px",
1589
1753
  backgroundColor: "#ffffff",
1590
1754
  border: "1px solid #e5e7eb",
1591
1755
  borderRadius: "10px",
1592
1756
  boxShadow: "0 6px 18px rgba(0, 0, 0, 0.12)",
1593
1757
  padding: "4px",
1594
- zIndex: 20
1595
- },
1596
- menuLeft: {
1597
- left: 0
1598
- },
1599
- menuRight: {
1600
- right: 0
1758
+ // Above the floating popup (z-index 9999) but below any future modal.
1759
+ zIndex: 1e4
1601
1760
  },
1602
1761
  item: {
1603
1762
  display: "flex",
@@ -2250,31 +2409,34 @@ var MessageList = forwardRef(function MessageList2({
2250
2409
  /* @__PURE__ */ jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation \u2014 everyone on this case will see what you write." })
2251
2410
  ] }),
2252
2411
  messages.length === 0 && isLoading && /* @__PURE__ */ jsx("div", { style: styles6.firstLoad, children: /* @__PURE__ */ jsx(Spinner, { size: 20 }) }),
2253
- messages.map((message, index) => {
2254
- const currentBucket = dayBucketKey(message.insertedAt);
2255
- const prevBucket = index === 0 ? null : dayBucketKey(messages[index - 1].insertedAt);
2256
- const showDivider = currentBucket && currentBucket !== prevBucket;
2257
- return /* @__PURE__ */ jsxs(React4.Fragment, { children: [
2258
- showDivider && /* @__PURE__ */ jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
2259
- /* @__PURE__ */ jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsx(
2260
- MessageBubble,
2261
- {
2262
- message,
2263
- isOwn: message.senderId === currentUserId,
2264
- participants,
2265
- currentUserId,
2266
- showSeenBy,
2267
- onDeepLinkClick,
2268
- onReply,
2269
- onPin,
2270
- onUnpin,
2271
- onCopy,
2272
- onRetry,
2273
- pinDisabled
2274
- }
2275
- ) })
2276
- ] }, message.id);
2277
- }),
2412
+ (() => {
2413
+ let lastValidBucket = null;
2414
+ return messages.map((message) => {
2415
+ const currentBucket = dayBucketKey(message.insertedAt);
2416
+ const showDivider = !!currentBucket && currentBucket !== lastValidBucket;
2417
+ if (currentBucket) lastValidBucket = currentBucket;
2418
+ return /* @__PURE__ */ jsxs(React4.Fragment, { children: [
2419
+ showDivider && /* @__PURE__ */ jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
2420
+ /* @__PURE__ */ jsx("div", { "data-message-id": message.id, "data-natoe-message-bubble": true, style: styles6.bubbleSlot, children: /* @__PURE__ */ jsx(
2421
+ MessageBubble,
2422
+ {
2423
+ message,
2424
+ isOwn: message.senderId === currentUserId,
2425
+ participants,
2426
+ currentUserId,
2427
+ showSeenBy,
2428
+ onDeepLinkClick,
2429
+ onReply,
2430
+ onPin,
2431
+ onUnpin,
2432
+ onCopy,
2433
+ onRetry,
2434
+ pinDisabled
2435
+ }
2436
+ ) })
2437
+ ] }, message.id);
2438
+ });
2439
+ })(),
2278
2440
  typingUsers.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles6.typingIndicator, children: [
2279
2441
  typingUsers.map((t) => t.userName ?? "Someone").join(", "),
2280
2442
  typingUsers.length === 1 ? " is " : " are ",
@@ -2354,7 +2516,11 @@ var styles6 = {
2354
2516
  dayDivider: {
2355
2517
  display: "flex",
2356
2518
  justifyContent: "center",
2357
- margin: "12px 0 8px"
2519
+ margin: "12px 0 8px",
2520
+ position: "sticky",
2521
+ top: "4px",
2522
+ zIndex: 1,
2523
+ pointerEvents: "none"
2358
2524
  },
2359
2525
  dayDividerPill: {
2360
2526
  display: "inline-block",
@@ -2364,7 +2530,11 @@ var styles6 = {
2364
2530
  color: "#334155",
2365
2531
  backgroundColor: "#e2e8f0",
2366
2532
  borderRadius: "12px",
2367
- letterSpacing: "0.2px"
2533
+ letterSpacing: "0.2px",
2534
+ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.04)"
2535
+ },
2536
+ bubbleSlot: {
2537
+ animation: "natoe-colab-message-in 180ms ease-out"
2368
2538
  }
2369
2539
  };
2370
2540
  function ReplyPreview({ message, onCancel, className }) {
@@ -3416,11 +3586,52 @@ var styles12 = {
3416
3586
  letterSpacing: "0.4px"
3417
3587
  }
3418
3588
  };
3589
+
3590
+ // src/core/styles.ts
3591
+ var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
3592
+ var ROOT_CLASS = "natoe-colab-root";
3593
+ var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
3594
+ var GLOBAL_CSS = `
3595
+ @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
3596
+
3597
+ @keyframes natoe-colab-message-in {
3598
+ from { opacity: 0; transform: translateY(4px); }
3599
+ to { opacity: 1; transform: translateY(0); }
3600
+ }
3601
+
3602
+ /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
3603
+ get heavier borders and stronger text without losing the package look. */
3604
+ @media (prefers-contrast: more) {
3605
+ .${ROOT_CLASS} {
3606
+ color: #000000;
3607
+ }
3608
+ .${ROOT_CLASS} button {
3609
+ outline: 1px solid currentColor;
3610
+ }
3611
+ }
3612
+
3613
+ /* Honour reduced-motion preferences by killing entry animations. */
3614
+ @media (prefers-reduced-motion: reduce) {
3615
+ .${ROOT_CLASS} [data-natoe-message-bubble] {
3616
+ animation: none !important;
3617
+ }
3618
+ }
3619
+ `;
3620
+ function ensureGlobalStyles() {
3621
+ if (typeof document === "undefined") return;
3622
+ if (document.getElementById(GLOBAL_STYLE_ID)) return;
3623
+ const style = document.createElement("style");
3624
+ style.id = GLOBAL_STYLE_ID;
3625
+ style.textContent = GLOBAL_CSS;
3626
+ document.head.appendChild(style);
3627
+ }
3628
+ ensureGlobalStyles();
3419
3629
  function CollabPanel({
3420
3630
  orderId,
3421
3631
  patientData,
3422
3632
  participantIds,
3423
3633
  showSeenBy = true,
3634
+ onBack,
3424
3635
  className,
3425
3636
  style
3426
3637
  }) {
@@ -3494,13 +3705,13 @@ function CollabPanel({
3494
3705
  };
3495
3706
  const pinDisabled = pinnedMessages.length >= MAX_PINNED_MESSAGES;
3496
3707
  if (error) {
3497
- return /* @__PURE__ */ jsx("div", { className, style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsxs("div", { style: panelStyles.errorState, children: [
3708
+ return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsxs("div", { style: panelStyles.errorState, children: [
3498
3709
  /* @__PURE__ */ jsx("p", { style: panelStyles.errorTitle, children: "Unable to load conversation" }),
3499
3710
  /* @__PURE__ */ jsx("p", { style: panelStyles.errorMessage, children: error })
3500
3711
  ] }) });
3501
3712
  }
3502
3713
  if (isLoading && !conversation) {
3503
- return /* @__PURE__ */ jsx("div", { className, style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsx("div", { style: panelStyles.loadingState, children: "Loading conversation..." }) });
3714
+ return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsx("div", { style: panelStyles.loadingState, children: "Loading conversation..." }) });
3504
3715
  }
3505
3716
  return /* @__PURE__ */ jsxs("div", { className, style: { ...panelStyles.container, ...style }, children: [
3506
3717
  showSettings && conversation ? /* @__PURE__ */ jsx(
@@ -3528,7 +3739,8 @@ function CollabPanel({
3528
3739
  patientData,
3529
3740
  participants,
3530
3741
  onOpenDicom: handleOpenDicom,
3531
- onOpenSettings: () => setShowSettings(true)
3742
+ onOpenSettings: () => setShowSettings(true),
3743
+ onBack
3532
3744
  }
3533
3745
  ),
3534
3746
  pinnedMessages.length > 0 && /* @__PURE__ */ jsx(
@@ -3586,7 +3798,7 @@ var panelStyles = {
3586
3798
  borderRadius: "8px",
3587
3799
  overflow: "hidden",
3588
3800
  border: "1px solid #e5e7eb",
3589
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
3801
+ fontFamily: FONT_STACK
3590
3802
  },
3591
3803
  loadingState: {
3592
3804
  display: "flex",
@@ -3645,6 +3857,7 @@ function ExpandIcon({ size = 18, color = "currentColor" }) {
3645
3857
  }
3646
3858
  );
3647
3859
  }
3860
+ ensureGlobalStyles();
3648
3861
  var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
3649
3862
  function CollabPopup({
3650
3863
  orderId,
@@ -3746,7 +3959,7 @@ function CollabPopup({
3746
3959
  "div",
3747
3960
  {
3748
3961
  ref: containerRef,
3749
- className,
3962
+ className: [ROOT_CLASS, className].filter(Boolean).join(" "),
3750
3963
  role: "dialog",
3751
3964
  "aria-modal": "true",
3752
3965
  "aria-labelledby": titleId,
@@ -3815,7 +4028,8 @@ var styles13 = {
3815
4028
  flexDirection: "column",
3816
4029
  backgroundColor: "#ffffff",
3817
4030
  border: "1px solid #e5e7eb",
3818
- transition: "height 0.2s ease"
4031
+ transition: "height 0.2s ease",
4032
+ fontFamily: FONT_STACK
3819
4033
  },
3820
4034
  titleBar: {
3821
4035
  display: "flex",
@@ -4087,6 +4301,7 @@ function useInlineCollab({
4087
4301
  containerRef
4088
4302
  };
4089
4303
  }
4304
+ ensureGlobalStyles();
4090
4305
  function CollabInline({
4091
4306
  orderId,
4092
4307
  patientData,
@@ -4109,9 +4324,9 @@ function CollabInline({
4109
4324
  containerRef
4110
4325
  } = useInlineCollab({ orderId, patientData, participantIds, messageLimit });
4111
4326
  if (isLoading && !hasConversation) {
4112
- return /* @__PURE__ */ jsx("div", { className, style: { ...styles14.container, ...style }, children: /* @__PURE__ */ jsx("div", { style: styles14.loadingState, children: "Loading..." }) });
4327
+ return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...styles14.container, ...style }, children: /* @__PURE__ */ jsx("div", { style: styles14.loadingState, children: "Loading..." }) });
4113
4328
  }
4114
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className, style: { ...styles14.container, ...style }, children: [
4329
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...styles14.container, ...style }, children: [
4115
4330
  hasConversation && messages.length > 0 && /* @__PURE__ */ jsx("div", { style: styles14.preview, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsx(InlineMessageRow, { message }, message.id)) }),
4116
4331
  error && /* @__PURE__ */ jsx("div", { style: styles14.error, children: error }),
4117
4332
  /* @__PURE__ */ jsx(
@@ -4327,7 +4542,7 @@ var styles14 = {
4327
4542
  backgroundColor: "#ffffff",
4328
4543
  border: "1px solid #e5e7eb",
4329
4544
  borderRadius: "8px",
4330
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
4545
+ fontFamily: FONT_STACK
4331
4546
  },
4332
4547
  loadingState: {
4333
4548
  padding: "8px",
@@ -4957,6 +5172,8 @@ var styles16 = {
4957
5172
  textAlign: "center"
4958
5173
  }
4959
5174
  };
5175
+ ensureGlobalStyles();
5176
+ var COMPACT_BREAKPOINT = 720;
4960
5177
  function CollabInbox({
4961
5178
  initialConversationId,
4962
5179
  onSelectConversation,
@@ -4966,34 +5183,55 @@ function CollabInbox({
4966
5183
  }) {
4967
5184
  const { conversations, isLoading, error } = useConversationList();
4968
5185
  const [selectedId, setSelectedId] = useState(initialConversationId ?? null);
5186
+ const [isCompact, setIsCompact] = useState(
5187
+ () => typeof window === "undefined" ? false : window.innerWidth < COMPACT_BREAKPOINT
5188
+ );
5189
+ useEffect(() => {
5190
+ if (typeof window === "undefined") return;
5191
+ const mq = window.matchMedia(`(max-width: ${COMPACT_BREAKPOINT - 1}px)`);
5192
+ const sync = () => setIsCompact(mq.matches);
5193
+ sync();
5194
+ mq.addEventListener("change", sync);
5195
+ return () => mq.removeEventListener("change", sync);
5196
+ }, []);
4969
5197
  const selected = conversations.find((c) => c.id === selectedId) || null;
4970
5198
  const handleSelect = (item) => {
4971
5199
  setSelectedId(item.id);
4972
5200
  onSelectConversation?.(item);
4973
5201
  };
4974
- return /* @__PURE__ */ jsxs("div", { className, style: { ...styles17.container, ...style }, children: [
4975
- /* @__PURE__ */ jsx("div", { style: styles17.sidebar, children: /* @__PURE__ */ jsx(
4976
- ConversationList,
4977
- {
4978
- conversations,
4979
- selectedId,
4980
- isLoading,
4981
- error,
4982
- onSelect: handleSelect,
4983
- title
4984
- }
4985
- ) }),
4986
- /* @__PURE__ */ jsx("div", { style: styles17.main, children: selected ? /* @__PURE__ */ jsx(
4987
- CollabPanel,
4988
- {
4989
- orderId: selected.orderId,
4990
- patientData: buildPatientData(selected),
4991
- showSeenBy: true,
4992
- style: styles17.panel
4993
- },
4994
- selected.id
4995
- ) : /* @__PURE__ */ jsx(EmptyState, { hasAny: conversations.length > 0 }) })
4996
- ] });
5202
+ const showDetail = !isCompact || isCompact && !!selected;
5203
+ const showList = !isCompact || isCompact && !selected;
5204
+ return /* @__PURE__ */ jsxs(
5205
+ "div",
5206
+ {
5207
+ className: [ROOT_CLASS, className].filter(Boolean).join(" "),
5208
+ style: { ...styles17.container, ...style },
5209
+ children: [
5210
+ showList && /* @__PURE__ */ jsx("div", { style: isCompact ? styles17.sidebarCompact : styles17.sidebar, children: /* @__PURE__ */ jsx(
5211
+ ConversationList,
5212
+ {
5213
+ conversations,
5214
+ selectedId,
5215
+ isLoading,
5216
+ error,
5217
+ onSelect: handleSelect,
5218
+ title
5219
+ }
5220
+ ) }),
5221
+ showDetail && /* @__PURE__ */ jsx("div", { style: isCompact ? styles17.mainCompact : styles17.main, children: selected ? /* @__PURE__ */ jsx(
5222
+ CollabPanel,
5223
+ {
5224
+ orderId: selected.orderId,
5225
+ patientData: buildPatientData(selected),
5226
+ showSeenBy: true,
5227
+ style: styles17.panel,
5228
+ onBack: isCompact ? () => setSelectedId(null) : void 0
5229
+ },
5230
+ selected.id
5231
+ ) : /* @__PURE__ */ jsx(EmptyState, { hasAny: conversations.length > 0 }) })
5232
+ ]
5233
+ }
5234
+ );
4997
5235
  }
4998
5236
  function buildPatientData(item) {
4999
5237
  if (item.patientSnapshot) return item.patientSnapshot;
@@ -5015,7 +5253,7 @@ var styles17 = {
5015
5253
  height: "100%",
5016
5254
  width: "100%",
5017
5255
  backgroundColor: "#ffffff",
5018
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
5256
+ fontFamily: FONT_STACK
5019
5257
  },
5020
5258
  sidebar: {
5021
5259
  width: "320px",
@@ -5024,6 +5262,12 @@ var styles17 = {
5024
5262
  display: "flex",
5025
5263
  flexDirection: "column"
5026
5264
  },
5265
+ sidebarCompact: {
5266
+ width: "100%",
5267
+ flex: 1,
5268
+ display: "flex",
5269
+ flexDirection: "column"
5270
+ },
5027
5271
  main: {
5028
5272
  flex: 1,
5029
5273
  display: "flex",
@@ -5032,6 +5276,13 @@ var styles17 = {
5032
5276
  padding: "12px",
5033
5277
  backgroundColor: "#f3f4f6"
5034
5278
  },
5279
+ mainCompact: {
5280
+ flex: 1,
5281
+ display: "flex",
5282
+ flexDirection: "column",
5283
+ minWidth: 0,
5284
+ backgroundColor: "#f3f4f6"
5285
+ },
5035
5286
  panel: {
5036
5287
  height: "100%",
5037
5288
  width: "100%"