@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.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var React4 = require('react');
4
4
  var phoenix = require('phoenix');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var reactDom = require('react-dom');
6
7
 
7
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
9
 
@@ -1150,71 +1151,190 @@ function useDeepLinks() {
1150
1151
  function escapeRegex(string) {
1151
1152
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1152
1153
  }
1154
+ function DicomIcon({ size = 18, color = "currentColor" }) {
1155
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1156
+ "svg",
1157
+ {
1158
+ xmlns: "http://www.w3.org/2000/svg",
1159
+ width: size,
1160
+ height: size,
1161
+ viewBox: "0 0 24 24",
1162
+ fill: "none",
1163
+ stroke: color,
1164
+ strokeWidth: "2",
1165
+ strokeLinecap: "round",
1166
+ strokeLinejoin: "round",
1167
+ "aria-hidden": "true",
1168
+ children: [
1169
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4", y: "4", width: "13", height: "13", rx: "2" }),
1170
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 7h13a2 2 0 0 1 2 2v11", opacity: "0.6" }),
1171
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "9.5", r: "1.2", fill: color, stroke: "none" }),
1172
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 14l3-3 4 4 2-2 3 3" })
1173
+ ]
1174
+ }
1175
+ );
1176
+ }
1177
+ function SettingsIcon({ size = 18, color = "currentColor" }) {
1178
+ return /* @__PURE__ */ jsxRuntime.jsx(
1179
+ "svg",
1180
+ {
1181
+ xmlns: "http://www.w3.org/2000/svg",
1182
+ width: size,
1183
+ height: size,
1184
+ viewBox: "0 0 24 24",
1185
+ fill: color,
1186
+ "aria-hidden": "true",
1187
+ children: /* @__PURE__ */ jsxRuntime.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" })
1188
+ }
1189
+ );
1190
+ }
1191
+ function BackIcon({ size = 22, color = "currentColor" }) {
1192
+ return /* @__PURE__ */ jsxRuntime.jsx(
1193
+ "svg",
1194
+ {
1195
+ xmlns: "http://www.w3.org/2000/svg",
1196
+ width: size,
1197
+ height: size,
1198
+ viewBox: "0 0 24 24",
1199
+ fill: color,
1200
+ "aria-hidden": "true",
1201
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" })
1202
+ }
1203
+ );
1204
+ }
1153
1205
  function PatientHeader({
1154
1206
  patientData,
1155
1207
  onOpenDicom,
1156
1208
  onOpenSettings,
1209
+ onBack,
1157
1210
  className
1158
1211
  }) {
1159
1212
  const hasDicom = !!(patientData.studyId && patientData.storageId);
1213
+ const meta = [];
1214
+ if (patientData.patientAge) meta.push({ label: "Age", value: String(patientData.patientAge) });
1215
+ if (patientData.patientSex) meta.push({ label: "Sex", value: String(patientData.patientSex) });
1216
+ if (patientData.studyType) meta.push({ label: "Modality", value: patientData.studyType });
1217
+ if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1218
+ meta.push({ label: "Body part", value: patientData.bodyParts.join(", ") });
1219
+ }
1220
+ if (patientData.referringPhysician) {
1221
+ meta.push({ label: "Ref", value: patientData.referringPhysician });
1222
+ }
1160
1223
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles.container, children: [
1161
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.infoRow, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.patientInfo, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.patientName, children: buildChannelName(patientData) }) }) }),
1162
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.secondaryRow, children: [
1163
- patientData.patientAge && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.secondary, children: [
1164
- patientData.patientAge,
1165
- patientData.patientSex ? `/${patientData.patientSex}` : ""
1166
- ] }),
1167
- patientData.studyType && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.secondary, children: patientData.studyType }),
1168
- patientData.bodyParts && patientData.bodyParts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.secondary, children: patientData.bodyParts.join(", ") }),
1169
- patientData.referringPhysician && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.secondary, children: [
1170
- "Ref: ",
1171
- patientData.referringPhysician
1172
- ] })
1224
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.nameRow, children: [
1225
+ onBack && /* @__PURE__ */ jsxRuntime.jsx(
1226
+ "button",
1227
+ {
1228
+ type: "button",
1229
+ onClick: onBack,
1230
+ style: styles.backButton,
1231
+ "aria-label": "Back to conversations",
1232
+ title: "Back to conversations",
1233
+ children: /* @__PURE__ */ jsxRuntime.jsx(BackIcon, { size: 22, color: "#1f2937" })
1234
+ }
1235
+ ),
1236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.nameText, children: buildChannelName(patientData) })
1173
1237
  ] }),
1174
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
1175
- hasDicom && onOpenDicom && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onOpenDicom, style: styles.dicomButton, type: "button", children: "View DICOM" }),
1176
- onOpenSettings && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onOpenSettings, style: styles.settingsButton, type: "button", children: "Settings" })
1177
- ] })
1238
+ meta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.metaItem, children: [
1239
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.metaLabel, children: [
1240
+ item.label,
1241
+ ":"
1242
+ ] }),
1243
+ " ",
1244
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.metaValue, children: item.value })
1245
+ ] }, item.label)) }),
1246
+ hasDicom && onOpenDicom || onOpenSettings ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
1247
+ hasDicom && onOpenDicom && /* @__PURE__ */ jsxRuntime.jsxs(
1248
+ "button",
1249
+ {
1250
+ onClick: onOpenDicom,
1251
+ style: styles.dicomButton,
1252
+ type: "button",
1253
+ "aria-label": "View DICOM study",
1254
+ children: [
1255
+ /* @__PURE__ */ jsxRuntime.jsx(DicomIcon, { size: 18, color: "#ffffff" }),
1256
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "View DICOM" })
1257
+ ]
1258
+ }
1259
+ ),
1260
+ onOpenSettings && /* @__PURE__ */ jsxRuntime.jsxs(
1261
+ "button",
1262
+ {
1263
+ onClick: onOpenSettings,
1264
+ style: styles.settingsButton,
1265
+ type: "button",
1266
+ "aria-label": "Open channel settings",
1267
+ children: [
1268
+ /* @__PURE__ */ jsxRuntime.jsx(SettingsIcon, { size: 18, color: "#374151" }),
1269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Settings" })
1270
+ ]
1271
+ }
1272
+ )
1273
+ ] }) : null
1178
1274
  ] });
1179
1275
  }
1180
1276
  var styles = {
1181
1277
  container: {
1182
- padding: "12px 16px",
1278
+ padding: "14px 16px",
1183
1279
  borderBottom: "1px solid #e5e7eb",
1184
1280
  backgroundColor: "#f9fafb"
1185
1281
  },
1186
- infoRow: {
1282
+ nameRow: {
1187
1283
  display: "flex",
1188
- justifyContent: "space-between",
1189
- alignItems: "center"
1284
+ alignItems: "center",
1285
+ gap: "6px"
1190
1286
  },
1191
- patientInfo: {
1287
+ backButton: {
1288
+ width: "36px",
1289
+ height: "36px",
1192
1290
  display: "flex",
1193
1291
  alignItems: "center",
1194
- gap: "8px",
1195
- flexWrap: "wrap"
1292
+ justifyContent: "center",
1293
+ marginLeft: "-8px",
1294
+ backgroundColor: "transparent",
1295
+ border: "none",
1296
+ borderRadius: "50%",
1297
+ cursor: "pointer",
1298
+ flexShrink: 0
1196
1299
  },
1197
- patientName: {
1300
+ nameText: {
1301
+ fontSize: "18px",
1198
1302
  fontWeight: 600,
1199
- fontSize: "17px",
1200
- color: "#111827"
1303
+ color: "#111827",
1304
+ lineHeight: 1.3,
1305
+ minWidth: 0,
1306
+ overflow: "hidden",
1307
+ textOverflow: "ellipsis",
1308
+ whiteSpace: "nowrap"
1201
1309
  },
1202
- secondaryRow: {
1310
+ metaRow: {
1203
1311
  display: "flex",
1204
- gap: "12px",
1205
- marginTop: "4px",
1206
- alignItems: "center"
1312
+ flexWrap: "wrap",
1313
+ columnGap: "14px",
1314
+ rowGap: "4px",
1315
+ marginTop: "6px"
1207
1316
  },
1208
- secondary: {
1317
+ metaItem: {
1209
1318
  fontSize: "13px",
1210
- color: "#4b5563"
1319
+ color: "#374151",
1320
+ whiteSpace: "nowrap"
1321
+ },
1322
+ metaLabel: {
1323
+ color: "#6b7280",
1324
+ fontWeight: 500
1325
+ },
1326
+ metaValue: {
1327
+ color: "#111827"
1211
1328
  },
1212
1329
  actions: {
1213
1330
  display: "flex",
1214
1331
  gap: "8px",
1215
- marginTop: "8px"
1332
+ marginTop: "10px"
1216
1333
  },
1217
1334
  dicomButton: {
1335
+ display: "inline-flex",
1336
+ alignItems: "center",
1337
+ gap: "8px",
1218
1338
  minHeight: "40px",
1219
1339
  padding: "8px 16px",
1220
1340
  fontSize: "14px",
@@ -1226,6 +1346,9 @@ var styles = {
1226
1346
  cursor: "pointer"
1227
1347
  },
1228
1348
  settingsButton: {
1349
+ display: "inline-flex",
1350
+ alignItems: "center",
1351
+ gap: "8px",
1229
1352
  minHeight: "40px",
1230
1353
  padding: "8px 16px",
1231
1354
  fontSize: "14px",
@@ -1442,6 +1565,8 @@ function MoreIcon({ size = 18, color = "currentColor" }) {
1442
1565
  }
1443
1566
  );
1444
1567
  }
1568
+ var VIEWPORT_MARGIN = 8;
1569
+ var TRIGGER_GAP = 4;
1445
1570
  function MessageActionsMenu({
1446
1571
  message,
1447
1572
  onReply,
@@ -1453,16 +1578,43 @@ function MessageActionsMenu({
1453
1578
  className
1454
1579
  }) {
1455
1580
  const [isOpen, setIsOpen] = React4.useState(false);
1456
- const wrapperRef = React4.useRef(null);
1581
+ const [coords, setCoords] = React4.useState(null);
1457
1582
  const triggerRef = React4.useRef(null);
1583
+ const menuRef = React4.useRef(null);
1458
1584
  const isPinned = !!message.isPinned;
1459
1585
  const canCopy = message.type === "text" && !!message.body;
1586
+ React4.useLayoutEffect(() => {
1587
+ if (!isOpen) {
1588
+ setCoords(null);
1589
+ return;
1590
+ }
1591
+ const trigger = triggerRef.current;
1592
+ const menu2 = menuRef.current;
1593
+ if (!trigger || !menu2) return;
1594
+ const triggerRect = trigger.getBoundingClientRect();
1595
+ const menuRect = menu2.getBoundingClientRect();
1596
+ const vw = window.innerWidth;
1597
+ const vh = window.innerHeight;
1598
+ const spaceBelow = vh - triggerRect.bottom;
1599
+ const spaceAbove = triggerRect.top;
1600
+ const placeAbove = spaceBelow < menuRect.height + TRIGGER_GAP + VIEWPORT_MARGIN && spaceAbove > spaceBelow;
1601
+ const top = placeAbove ? triggerRect.top - menuRect.height - TRIGGER_GAP : triggerRect.bottom + TRIGGER_GAP;
1602
+ const preferredLeft = anchorRight ? triggerRect.right - menuRect.width : triggerRect.left;
1603
+ const left = Math.max(
1604
+ VIEWPORT_MARGIN,
1605
+ Math.min(preferredLeft, vw - menuRect.width - VIEWPORT_MARGIN)
1606
+ );
1607
+ const clampedTop = Math.max(
1608
+ VIEWPORT_MARGIN,
1609
+ Math.min(top, vh - menuRect.height - VIEWPORT_MARGIN)
1610
+ );
1611
+ setCoords({ top: clampedTop, left });
1612
+ }, [isOpen, anchorRight]);
1460
1613
  React4.useEffect(() => {
1461
1614
  if (!isOpen) return;
1615
+ const isInside = (node) => !!node && (triggerRef.current?.contains(node) === true || menuRef.current?.contains(node) === true);
1462
1616
  const handleClick = (e) => {
1463
- if (!wrapperRef.current?.contains(e.target)) {
1464
- setIsOpen(false);
1465
- }
1617
+ if (!isInside(e.target)) setIsOpen(false);
1466
1618
  };
1467
1619
  const handleKey = (e) => {
1468
1620
  if (e.key === "Escape") {
@@ -1470,18 +1622,100 @@ function MessageActionsMenu({
1470
1622
  triggerRef.current?.focus();
1471
1623
  }
1472
1624
  };
1625
+ const handleScroll = (e) => {
1626
+ if (!isInside(e.target)) setIsOpen(false);
1627
+ };
1628
+ const handleResize = () => setIsOpen(false);
1473
1629
  document.addEventListener("mousedown", handleClick);
1474
1630
  document.addEventListener("keydown", handleKey);
1631
+ window.addEventListener("resize", handleResize);
1632
+ window.addEventListener("scroll", handleScroll, true);
1475
1633
  return () => {
1476
1634
  document.removeEventListener("mousedown", handleClick);
1477
1635
  document.removeEventListener("keydown", handleKey);
1636
+ window.removeEventListener("resize", handleResize);
1637
+ window.removeEventListener("scroll", handleScroll, true);
1478
1638
  };
1479
1639
  }, [isOpen]);
1480
1640
  const run = (fn) => {
1481
1641
  setIsOpen(false);
1482
1642
  fn();
1483
1643
  };
1484
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className, style: styles4.wrapper, children: [
1644
+ const menu = isOpen ? /* @__PURE__ */ jsxRuntime.jsxs(
1645
+ "div",
1646
+ {
1647
+ ref: menuRef,
1648
+ role: "menu",
1649
+ "aria-label": "Message actions",
1650
+ style: {
1651
+ ...styles4.menu,
1652
+ // Render off-screen on first paint so we can measure before placing,
1653
+ // then snap to computed coords on the next layout pass.
1654
+ top: coords?.top ?? -9999,
1655
+ left: coords?.left ?? -9999,
1656
+ visibility: coords ? "visible" : "hidden"
1657
+ },
1658
+ children: [
1659
+ /* @__PURE__ */ jsxRuntime.jsxs(
1660
+ "button",
1661
+ {
1662
+ type: "button",
1663
+ role: "menuitem",
1664
+ onClick: () => run(() => onReply(message)),
1665
+ style: styles4.item,
1666
+ children: [
1667
+ /* @__PURE__ */ jsxRuntime.jsx(ReplyIcon, { size: 18, color: "#374151" }),
1668
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Reply" })
1669
+ ]
1670
+ }
1671
+ ),
1672
+ isPinned ? /* @__PURE__ */ jsxRuntime.jsxs(
1673
+ "button",
1674
+ {
1675
+ type: "button",
1676
+ role: "menuitem",
1677
+ onClick: () => run(() => onUnpin(message.id)),
1678
+ style: styles4.item,
1679
+ children: [
1680
+ /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 16, color: "#374151" }),
1681
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Unpin" })
1682
+ ]
1683
+ }
1684
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
1685
+ "button",
1686
+ {
1687
+ type: "button",
1688
+ role: "menuitem",
1689
+ onClick: () => run(() => onPin(message.id)),
1690
+ disabled: pinDisabled,
1691
+ style: {
1692
+ ...styles4.item,
1693
+ ...pinDisabled ? styles4.itemDisabled : {}
1694
+ },
1695
+ title: pinDisabled ? "Pin limit reached (max 3)" : void 0,
1696
+ children: [
1697
+ /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 16, color: "#374151" }),
1698
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: pinDisabled ? "Pin (limit reached)" : "Pin" })
1699
+ ]
1700
+ }
1701
+ ),
1702
+ canCopy && onCopy && /* @__PURE__ */ jsxRuntime.jsxs(
1703
+ "button",
1704
+ {
1705
+ type: "button",
1706
+ role: "menuitem",
1707
+ onClick: () => run(() => onCopy(message.body)),
1708
+ style: styles4.item,
1709
+ children: [
1710
+ /* @__PURE__ */ jsxRuntime.jsx(CopyIcon, { size: 16, color: "#374151" }),
1711
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Copy text" })
1712
+ ]
1713
+ }
1714
+ )
1715
+ ]
1716
+ }
1717
+ ) : null;
1718
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles4.wrapper, children: [
1485
1719
  /* @__PURE__ */ jsxRuntime.jsx(
1486
1720
  "button",
1487
1721
  {
@@ -1499,75 +1733,7 @@ function MessageActionsMenu({
1499
1733
  children: /* @__PURE__ */ jsxRuntime.jsx(MoreIcon, { size: 18, color: "#4b5563" })
1500
1734
  }
1501
1735
  ),
1502
- isOpen && /* @__PURE__ */ jsxRuntime.jsxs(
1503
- "div",
1504
- {
1505
- role: "menu",
1506
- "aria-label": "Message actions",
1507
- style: {
1508
- ...styles4.menu,
1509
- ...anchorRight ? styles4.menuRight : styles4.menuLeft
1510
- },
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
- ]
1569
- }
1570
- )
1736
+ menu && typeof document !== "undefined" ? reactDom.createPortal(menu, document.body) : null
1571
1737
  ] });
1572
1738
  }
1573
1739
  var styles4 = {
@@ -1588,22 +1754,15 @@ var styles4 = {
1588
1754
  color: "#4b5563"
1589
1755
  },
1590
1756
  menu: {
1591
- position: "absolute",
1592
- top: "100%",
1593
- marginTop: "4px",
1757
+ position: "fixed",
1594
1758
  minWidth: "160px",
1595
1759
  backgroundColor: "#ffffff",
1596
1760
  border: "1px solid #e5e7eb",
1597
1761
  borderRadius: "10px",
1598
1762
  boxShadow: "0 6px 18px rgba(0, 0, 0, 0.12)",
1599
1763
  padding: "4px",
1600
- zIndex: 20
1601
- },
1602
- menuLeft: {
1603
- left: 0
1604
- },
1605
- menuRight: {
1606
- right: 0
1764
+ // Above the floating popup (z-index 9999) but below any future modal.
1765
+ zIndex: 1e4
1607
1766
  },
1608
1767
  item: {
1609
1768
  display: "flex",
@@ -2256,31 +2415,34 @@ var MessageList = React4.forwardRef(function MessageList2({
2256
2415
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles6.emptySubtitle, children: "Start the conversation \u2014 everyone on this case will see what you write." })
2257
2416
  ] }),
2258
2417
  messages.length === 0 && isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles6.firstLoad, children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 20 }) }),
2259
- messages.map((message, index) => {
2260
- const currentBucket = dayBucketKey(message.insertedAt);
2261
- const prevBucket = index === 0 ? null : dayBucketKey(messages[index - 1].insertedAt);
2262
- const showDivider = currentBucket && currentBucket !== prevBucket;
2263
- return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
2264
- showDivider && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
2265
- /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, children: /* @__PURE__ */ jsxRuntime.jsx(
2266
- MessageBubble,
2267
- {
2268
- message,
2269
- isOwn: message.senderId === currentUserId,
2270
- participants,
2271
- currentUserId,
2272
- showSeenBy,
2273
- onDeepLinkClick,
2274
- onReply,
2275
- onPin,
2276
- onUnpin,
2277
- onCopy,
2278
- onRetry,
2279
- pinDisabled
2280
- }
2281
- ) })
2282
- ] }, message.id);
2283
- }),
2418
+ (() => {
2419
+ let lastValidBucket = null;
2420
+ return messages.map((message) => {
2421
+ const currentBucket = dayBucketKey(message.insertedAt);
2422
+ const showDivider = !!currentBucket && currentBucket !== lastValidBucket;
2423
+ if (currentBucket) lastValidBucket = currentBucket;
2424
+ return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
2425
+ showDivider && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
2426
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, "data-natoe-message-bubble": true, style: styles6.bubbleSlot, children: /* @__PURE__ */ jsxRuntime.jsx(
2427
+ MessageBubble,
2428
+ {
2429
+ message,
2430
+ isOwn: message.senderId === currentUserId,
2431
+ participants,
2432
+ currentUserId,
2433
+ showSeenBy,
2434
+ onDeepLinkClick,
2435
+ onReply,
2436
+ onPin,
2437
+ onUnpin,
2438
+ onCopy,
2439
+ onRetry,
2440
+ pinDisabled
2441
+ }
2442
+ ) })
2443
+ ] }, message.id);
2444
+ });
2445
+ })(),
2284
2446
  typingUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles6.typingIndicator, children: [
2285
2447
  typingUsers.map((t) => t.userName ?? "Someone").join(", "),
2286
2448
  typingUsers.length === 1 ? " is " : " are ",
@@ -2360,7 +2522,11 @@ var styles6 = {
2360
2522
  dayDivider: {
2361
2523
  display: "flex",
2362
2524
  justifyContent: "center",
2363
- margin: "12px 0 8px"
2525
+ margin: "12px 0 8px",
2526
+ position: "sticky",
2527
+ top: "4px",
2528
+ zIndex: 1,
2529
+ pointerEvents: "none"
2364
2530
  },
2365
2531
  dayDividerPill: {
2366
2532
  display: "inline-block",
@@ -2370,7 +2536,11 @@ var styles6 = {
2370
2536
  color: "#334155",
2371
2537
  backgroundColor: "#e2e8f0",
2372
2538
  borderRadius: "12px",
2373
- letterSpacing: "0.2px"
2539
+ letterSpacing: "0.2px",
2540
+ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.04)"
2541
+ },
2542
+ bubbleSlot: {
2543
+ animation: "natoe-colab-message-in 180ms ease-out"
2374
2544
  }
2375
2545
  };
2376
2546
  function ReplyPreview({ message, onCancel, className }) {
@@ -3422,11 +3592,52 @@ var styles12 = {
3422
3592
  letterSpacing: "0.4px"
3423
3593
  }
3424
3594
  };
3595
+
3596
+ // src/core/styles.ts
3597
+ var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
3598
+ var ROOT_CLASS = "natoe-colab-root";
3599
+ var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
3600
+ var GLOBAL_CSS = `
3601
+ @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
3602
+
3603
+ @keyframes natoe-colab-message-in {
3604
+ from { opacity: 0; transform: translateY(4px); }
3605
+ to { opacity: 1; transform: translateY(0); }
3606
+ }
3607
+
3608
+ /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
3609
+ get heavier borders and stronger text without losing the package look. */
3610
+ @media (prefers-contrast: more) {
3611
+ .${ROOT_CLASS} {
3612
+ color: #000000;
3613
+ }
3614
+ .${ROOT_CLASS} button {
3615
+ outline: 1px solid currentColor;
3616
+ }
3617
+ }
3618
+
3619
+ /* Honour reduced-motion preferences by killing entry animations. */
3620
+ @media (prefers-reduced-motion: reduce) {
3621
+ .${ROOT_CLASS} [data-natoe-message-bubble] {
3622
+ animation: none !important;
3623
+ }
3624
+ }
3625
+ `;
3626
+ function ensureGlobalStyles() {
3627
+ if (typeof document === "undefined") return;
3628
+ if (document.getElementById(GLOBAL_STYLE_ID)) return;
3629
+ const style = document.createElement("style");
3630
+ style.id = GLOBAL_STYLE_ID;
3631
+ style.textContent = GLOBAL_CSS;
3632
+ document.head.appendChild(style);
3633
+ }
3634
+ ensureGlobalStyles();
3425
3635
  function CollabPanel({
3426
3636
  orderId,
3427
3637
  patientData,
3428
3638
  participantIds,
3429
3639
  showSeenBy = true,
3640
+ onBack,
3430
3641
  className,
3431
3642
  style
3432
3643
  }) {
@@ -3500,13 +3711,13 @@ function CollabPanel({
3500
3711
  };
3501
3712
  const pinDisabled = pinnedMessages.length >= MAX_PINNED_MESSAGES;
3502
3713
  if (error) {
3503
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyles.errorState, children: [
3714
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyles.errorState, children: [
3504
3715
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: panelStyles.errorTitle, children: "Unable to load conversation" }),
3505
3716
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: panelStyles.errorMessage, children: error })
3506
3717
  ] }) });
3507
3718
  }
3508
3719
  if (isLoading && !conversation) {
3509
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: panelStyles.loadingState, children: "Loading conversation..." }) });
3720
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...panelStyles.container, ...style }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: panelStyles.loadingState, children: "Loading conversation..." }) });
3510
3721
  }
3511
3722
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...panelStyles.container, ...style }, children: [
3512
3723
  showSettings && conversation ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -3534,7 +3745,8 @@ function CollabPanel({
3534
3745
  patientData,
3535
3746
  participants,
3536
3747
  onOpenDicom: handleOpenDicom,
3537
- onOpenSettings: () => setShowSettings(true)
3748
+ onOpenSettings: () => setShowSettings(true),
3749
+ onBack
3538
3750
  }
3539
3751
  ),
3540
3752
  pinnedMessages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
@@ -3592,7 +3804,7 @@ var panelStyles = {
3592
3804
  borderRadius: "8px",
3593
3805
  overflow: "hidden",
3594
3806
  border: "1px solid #e5e7eb",
3595
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
3807
+ fontFamily: FONT_STACK
3596
3808
  },
3597
3809
  loadingState: {
3598
3810
  display: "flex",
@@ -3651,6 +3863,7 @@ function ExpandIcon({ size = 18, color = "currentColor" }) {
3651
3863
  }
3652
3864
  );
3653
3865
  }
3866
+ ensureGlobalStyles();
3654
3867
  var FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
3655
3868
  function CollabPopup({
3656
3869
  orderId,
@@ -3752,7 +3965,7 @@ function CollabPopup({
3752
3965
  "div",
3753
3966
  {
3754
3967
  ref: containerRef,
3755
- className,
3968
+ className: [ROOT_CLASS, className].filter(Boolean).join(" "),
3756
3969
  role: "dialog",
3757
3970
  "aria-modal": "true",
3758
3971
  "aria-labelledby": titleId,
@@ -3821,7 +4034,8 @@ var styles13 = {
3821
4034
  flexDirection: "column",
3822
4035
  backgroundColor: "#ffffff",
3823
4036
  border: "1px solid #e5e7eb",
3824
- transition: "height 0.2s ease"
4037
+ transition: "height 0.2s ease",
4038
+ fontFamily: FONT_STACK
3825
4039
  },
3826
4040
  titleBar: {
3827
4041
  display: "flex",
@@ -4093,6 +4307,7 @@ function useInlineCollab({
4093
4307
  containerRef
4094
4308
  };
4095
4309
  }
4310
+ ensureGlobalStyles();
4096
4311
  function CollabInline({
4097
4312
  orderId,
4098
4313
  patientData,
@@ -4115,9 +4330,9 @@ function CollabInline({
4115
4330
  containerRef
4116
4331
  } = useInlineCollab({ orderId, patientData, participantIds, messageLimit });
4117
4332
  if (isLoading && !hasConversation) {
4118
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { ...styles14.container, ...style }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.loadingState, children: "Loading..." }) });
4333
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...styles14.container, ...style }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.loadingState, children: "Loading..." }) });
4119
4334
  }
4120
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className, style: { ...styles14.container, ...style }, children: [
4335
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: { ...styles14.container, ...style }, children: [
4121
4336
  hasConversation && messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.preview, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsxRuntime.jsx(InlineMessageRow, { message }, message.id)) }),
4122
4337
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.error, children: error }),
4123
4338
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4333,7 +4548,7 @@ var styles14 = {
4333
4548
  backgroundColor: "#ffffff",
4334
4549
  border: "1px solid #e5e7eb",
4335
4550
  borderRadius: "8px",
4336
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
4551
+ fontFamily: FONT_STACK
4337
4552
  },
4338
4553
  loadingState: {
4339
4554
  padding: "8px",
@@ -4963,6 +5178,8 @@ var styles16 = {
4963
5178
  textAlign: "center"
4964
5179
  }
4965
5180
  };
5181
+ ensureGlobalStyles();
5182
+ var COMPACT_BREAKPOINT = 720;
4966
5183
  function CollabInbox({
4967
5184
  initialConversationId,
4968
5185
  onSelectConversation,
@@ -4972,34 +5189,55 @@ function CollabInbox({
4972
5189
  }) {
4973
5190
  const { conversations, isLoading, error } = useConversationList();
4974
5191
  const [selectedId, setSelectedId] = React4.useState(initialConversationId ?? null);
5192
+ const [isCompact, setIsCompact] = React4.useState(
5193
+ () => typeof window === "undefined" ? false : window.innerWidth < COMPACT_BREAKPOINT
5194
+ );
5195
+ React4.useEffect(() => {
5196
+ if (typeof window === "undefined") return;
5197
+ const mq = window.matchMedia(`(max-width: ${COMPACT_BREAKPOINT - 1}px)`);
5198
+ const sync = () => setIsCompact(mq.matches);
5199
+ sync();
5200
+ mq.addEventListener("change", sync);
5201
+ return () => mq.removeEventListener("change", sync);
5202
+ }, []);
4975
5203
  const selected = conversations.find((c) => c.id === selectedId) || null;
4976
5204
  const handleSelect = (item) => {
4977
5205
  setSelectedId(item.id);
4978
5206
  onSelectConversation?.(item);
4979
5207
  };
4980
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...styles17.container, ...style }, children: [
4981
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles17.sidebar, children: /* @__PURE__ */ jsxRuntime.jsx(
4982
- ConversationList,
4983
- {
4984
- conversations,
4985
- selectedId,
4986
- isLoading,
4987
- error,
4988
- onSelect: handleSelect,
4989
- title
4990
- }
4991
- ) }),
4992
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles17.main, children: selected ? /* @__PURE__ */ jsxRuntime.jsx(
4993
- CollabPanel,
4994
- {
4995
- orderId: selected.orderId,
4996
- patientData: buildPatientData(selected),
4997
- showSeenBy: true,
4998
- style: styles17.panel
4999
- },
5000
- selected.id
5001
- ) : /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { hasAny: conversations.length > 0 }) })
5002
- ] });
5208
+ const showDetail = !isCompact || isCompact && !!selected;
5209
+ const showList = !isCompact || isCompact && !selected;
5210
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5211
+ "div",
5212
+ {
5213
+ className: [ROOT_CLASS, className].filter(Boolean).join(" "),
5214
+ style: { ...styles17.container, ...style },
5215
+ children: [
5216
+ showList && /* @__PURE__ */ jsxRuntime.jsx("div", { style: isCompact ? styles17.sidebarCompact : styles17.sidebar, children: /* @__PURE__ */ jsxRuntime.jsx(
5217
+ ConversationList,
5218
+ {
5219
+ conversations,
5220
+ selectedId,
5221
+ isLoading,
5222
+ error,
5223
+ onSelect: handleSelect,
5224
+ title
5225
+ }
5226
+ ) }),
5227
+ showDetail && /* @__PURE__ */ jsxRuntime.jsx("div", { style: isCompact ? styles17.mainCompact : styles17.main, children: selected ? /* @__PURE__ */ jsxRuntime.jsx(
5228
+ CollabPanel,
5229
+ {
5230
+ orderId: selected.orderId,
5231
+ patientData: buildPatientData(selected),
5232
+ showSeenBy: true,
5233
+ style: styles17.panel,
5234
+ onBack: isCompact ? () => setSelectedId(null) : void 0
5235
+ },
5236
+ selected.id
5237
+ ) : /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { hasAny: conversations.length > 0 }) })
5238
+ ]
5239
+ }
5240
+ );
5003
5241
  }
5004
5242
  function buildPatientData(item) {
5005
5243
  if (item.patientSnapshot) return item.patientSnapshot;
@@ -5021,7 +5259,7 @@ var styles17 = {
5021
5259
  height: "100%",
5022
5260
  width: "100%",
5023
5261
  backgroundColor: "#ffffff",
5024
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
5262
+ fontFamily: FONT_STACK
5025
5263
  },
5026
5264
  sidebar: {
5027
5265
  width: "320px",
@@ -5030,6 +5268,12 @@ var styles17 = {
5030
5268
  display: "flex",
5031
5269
  flexDirection: "column"
5032
5270
  },
5271
+ sidebarCompact: {
5272
+ width: "100%",
5273
+ flex: 1,
5274
+ display: "flex",
5275
+ flexDirection: "column"
5276
+ },
5033
5277
  main: {
5034
5278
  flex: 1,
5035
5279
  display: "flex",
@@ -5038,6 +5282,13 @@ var styles17 = {
5038
5282
  padding: "12px",
5039
5283
  backgroundColor: "#f3f4f6"
5040
5284
  },
5285
+ mainCompact: {
5286
+ flex: 1,
5287
+ display: "flex",
5288
+ flexDirection: "column",
5289
+ minWidth: 0,
5290
+ backgroundColor: "#f3f4f6"
5291
+ },
5041
5292
  panel: {
5042
5293
  height: "100%",
5043
5294
  width: "100%"