@natoe/colab 0.1.12 → 0.1.13

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
@@ -321,6 +321,173 @@ var CollabSocket = class {
321
321
  this.config = null;
322
322
  }
323
323
  };
324
+
325
+ // src/core/theme.ts
326
+ var FONT_SIZE = {
327
+ xs: "12px",
328
+ sm: "13px",
329
+ md: "15px",
330
+ lg: "17px",
331
+ xxl: "28px"
332
+ };
333
+ var FONT_WEIGHT = {
334
+ regular: 400,
335
+ medium: 500,
336
+ semibold: 600,
337
+ bold: 700
338
+ };
339
+ var LINE_HEIGHT = {
340
+ tight: 1.3,
341
+ normal: 1.45};
342
+ var SPACE = {
343
+ S1: "4px",
344
+ S2: "8px",
345
+ S3: "12px",
346
+ S4: "16px",
347
+ S5: "20px",
348
+ S6: "24px",
349
+ S12: "48px"
350
+ };
351
+ var RADIUS = {
352
+ sm: "4px",
353
+ md: "8px",
354
+ lg: "12px",
355
+ xl: "16px",
356
+ pill: "9999px",
357
+ full: "50%"
358
+ };
359
+ var THEME_VAR = {
360
+ primary: "--natoe-colab-primary",
361
+ primaryHover: "--natoe-colab-primary-hover",
362
+ primaryBg: "--natoe-colab-primary-bg",
363
+ primaryFg: "--natoe-colab-primary-fg",
364
+ success: "--natoe-colab-success",
365
+ warning: "--natoe-colab-warning",
366
+ danger: "--natoe-colab-danger",
367
+ dangerBg: "--natoe-colab-danger-bg",
368
+ dangerBorder: "--natoe-colab-danger-border",
369
+ dangerFg: "--natoe-colab-danger-fg",
370
+ fontStack: "--natoe-colab-font-stack"
371
+ };
372
+ var THEME_DEFAULTS = {
373
+ primary: "#2563eb",
374
+ primaryHover: "#1d4ed8",
375
+ primaryBg: "#dbeafe",
376
+ primaryFg: "#1d4ed8",
377
+ success: "#059669",
378
+ warning: "#d97706",
379
+ danger: "#dc2626",
380
+ dangerBg: "#fef2f2",
381
+ dangerBorder: "#fecaca",
382
+ dangerFg: "#b91c1c",
383
+ fontStack: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
384
+ };
385
+ var cssVar = (name, fallback) => `var(${name}, ${fallback})`;
386
+ var COLOR = {
387
+ // Brand — themeable
388
+ primary: cssVar(THEME_VAR.primary, THEME_DEFAULTS.primary),
389
+ primaryHover: cssVar(THEME_VAR.primaryHover, THEME_DEFAULTS.primaryHover),
390
+ /** Tinted surface for chips/cards on brand-coloured states. */
391
+ primaryBg: cssVar(THEME_VAR.primaryBg, THEME_DEFAULTS.primaryBg),
392
+ primaryFg: cssVar(THEME_VAR.primaryFg, THEME_DEFAULTS.primaryFg),
393
+ // Neutral grayscale — not themeable, taken from Tailwind's zinc-leaning
394
+ // slate to match the package's existing tonal balance.
395
+ white: "#ffffff",
396
+ neutral50: "#f9fafb",
397
+ neutral100: "#f3f4f6",
398
+ neutral200: "#e5e7eb",
399
+ neutral300: "#d1d5db",
400
+ neutral400: "#9ca3af",
401
+ neutral500: "#6b7280",
402
+ neutral600: "#4b5563",
403
+ neutral700: "#374151",
404
+ neutral800: "#1f2937",
405
+ neutral900: "#111827",
406
+ slate800: "#1e293b",
407
+ // Semantic — themeable
408
+ success: cssVar(THEME_VAR.success, THEME_DEFAULTS.success),
409
+ warning: cssVar(THEME_VAR.warning, THEME_DEFAULTS.warning),
410
+ danger: cssVar(THEME_VAR.danger, THEME_DEFAULTS.danger),
411
+ dangerBg: cssVar(THEME_VAR.dangerBg, THEME_DEFAULTS.dangerBg),
412
+ dangerBorder: cssVar(THEME_VAR.dangerBorder, THEME_DEFAULTS.dangerBorder),
413
+ dangerFg: cssVar(THEME_VAR.dangerFg, THEME_DEFAULTS.dangerFg)
414
+ };
415
+ var SIZE = {
416
+ /** Default interactive height (text buttons, list rows). */
417
+ control: "40px",
418
+ /** Square icon-only buttons inside chrome (popup title, menu trigger). */
419
+ controlIcon: "36px"};
420
+ var SHADOW = {
421
+ /** Cards / floating popovers (message-actions menu). */
422
+ popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
423
+ toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
424
+ };
425
+ var Z_INDEX = {
426
+ /** Sticky day divider — above bubbles, below interactive popovers. */
427
+ sticky: 1,
428
+ /** Toasts inside a panel. */
429
+ toast: 20,
430
+ /** Floating CollabPopup dialog. */
431
+ dialog: 9999,
432
+ /** Portaled message-actions menu — must sit above the dialog. */
433
+ menu: 1e4
434
+ };
435
+
436
+ // src/core/styles.ts
437
+ var FONT_STACK = `var(${THEME_VAR.fontStack}, ${THEME_DEFAULTS.fontStack})`;
438
+ var ROOT_CLASS = "natoe-colab-root";
439
+ var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
440
+ var ROOT_DEFAULTS_BLOCK = Object.keys(THEME_DEFAULTS).map((key) => ` ${THEME_VAR[key]}: ${THEME_DEFAULTS[key]};`).join("\n");
441
+ var GLOBAL_CSS = `
442
+ :root {
443
+ ${ROOT_DEFAULTS_BLOCK}
444
+ }
445
+
446
+ @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
447
+
448
+ @keyframes natoe-colab-message-in {
449
+ from { opacity: 0; transform: translateY(4px); }
450
+ to { opacity: 1; transform: translateY(0); }
451
+ }
452
+
453
+ /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
454
+ get heavier borders and stronger text without losing the package look. */
455
+ @media (prefers-contrast: more) {
456
+ .${ROOT_CLASS} {
457
+ color: #000000;
458
+ }
459
+ .${ROOT_CLASS} button {
460
+ outline: 1px solid currentColor;
461
+ }
462
+ }
463
+
464
+ /* Honour reduced-motion preferences by killing entry animations. */
465
+ @media (prefers-reduced-motion: reduce) {
466
+ .${ROOT_CLASS} [data-natoe-message-bubble] {
467
+ animation: none !important;
468
+ }
469
+ }
470
+ `;
471
+ function ensureGlobalStyles() {
472
+ if (typeof document === "undefined") return;
473
+ if (document.getElementById(GLOBAL_STYLE_ID)) return;
474
+ const style = document.createElement("style");
475
+ style.id = GLOBAL_STYLE_ID;
476
+ style.textContent = GLOBAL_CSS;
477
+ document.head.appendChild(style);
478
+ }
479
+ function applyThemeOverrides(theme) {
480
+ if (typeof document === "undefined") return;
481
+ const root = document.documentElement;
482
+ Object.keys(THEME_VAR).forEach((key) => {
483
+ const value = theme?.[key];
484
+ if (value) {
485
+ root.style.setProperty(THEME_VAR[key], value);
486
+ } else {
487
+ root.style.removeProperty(THEME_VAR[key]);
488
+ }
489
+ });
490
+ }
324
491
  var CollabContext = React4.createContext(null);
325
492
  function useCollab() {
326
493
  const context = React4.useContext(CollabContext);
@@ -339,6 +506,9 @@ function CollabProvider({ config, apiBaseUrl, children }) {
339
506
  const pendingResolvers = React4.useRef(/* @__PURE__ */ new Map());
340
507
  const previewCache = React4.useRef(/* @__PURE__ */ new Map());
341
508
  const batchScheduled = React4.useRef(false);
509
+ React4.useEffect(() => {
510
+ applyThemeOverrides(config.theme);
511
+ }, [config.theme]);
342
512
  React4.useEffect(() => {
343
513
  if (typeof window === "undefined") return;
344
514
  let s = socket;
@@ -554,6 +724,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
554
724
  config,
555
725
  apiBaseUrl,
556
726
  totalUnread,
727
+ unreadCounts,
557
728
  requestPreview,
558
729
  invalidatePreview,
559
730
  fetchMessages,
@@ -731,26 +902,22 @@ function useConversation({
731
902
  setConversation(existingConv);
732
903
  setParticipants(preview.participants);
733
904
  joinChannel(existingConv);
734
- setMessages(preview.lastMessages);
735
- setHasMore(preview.messageCount > preview.lastMessages.length);
736
- setPinnedMessages(preview.lastMessages.filter((m) => m.isPinned));
737
- setIsLoading(false);
738
905
  if (loadHistory) {
739
906
  fetchMessages(preview.conversationId).then((history) => {
740
907
  if (cancelled) return;
741
- setMessages((prev) => {
742
- const seen = new Set(prev.map((m) => m.id));
743
- const older = history.filter((m) => !seen.has(m.id));
744
- return [...older, ...prev];
745
- });
746
- setHasMore(history.length > 0);
747
- setPinnedMessages((prev) => {
748
- const fromHistory = history.filter((m) => m.isPinned);
749
- const seen = new Set(prev.map((m) => m.id));
750
- return [...fromHistory.filter((m) => !seen.has(m.id)), ...prev];
751
- });
908
+ setMessages(history);
909
+ setHasMore(history.length >= MESSAGES_PAGE_SIZE);
910
+ setPinnedMessages(history.filter((m) => m.isPinned));
911
+ setIsLoading(false);
752
912
  }).catch(() => {
913
+ if (cancelled) return;
914
+ setIsLoading(false);
753
915
  });
916
+ } else {
917
+ setMessages([]);
918
+ setHasMore(false);
919
+ setPinnedMessages([]);
920
+ setIsLoading(false);
754
921
  }
755
922
  } catch (err) {
756
923
  if (cancelled) return;
@@ -831,7 +998,7 @@ function useConversation({
831
998
  const payload = {
832
999
  body,
833
1000
  type: "text",
834
- ...replyTo ? { replyToId: replyTo.id } : {}
1001
+ ...replyTo ? { reply_to_id: replyTo.id } : {}
835
1002
  };
836
1003
  const { conv, persistedByCreate } = await ensureConversation(payload);
837
1004
  setReplyTo(null);
@@ -884,7 +1051,7 @@ function useConversation({
884
1051
  const payload = {
885
1052
  body: target.body,
886
1053
  type: "text",
887
- ...target.replyToId ? { replyToId: target.replyToId } : {}
1054
+ ...target.replyToId ? { reply_to_id: target.replyToId } : {}
888
1055
  };
889
1056
  try {
890
1057
  await socket.sendMessage(conversation.id, payload);
@@ -1209,102 +1376,6 @@ function BackIcon({ size = 22, color = "currentColor" }) {
1209
1376
  }
1210
1377
  );
1211
1378
  }
1212
-
1213
- // src/core/theme.ts
1214
- var FONT_SIZE = {
1215
- xs: "12px",
1216
- sm: "13px",
1217
- md: "15px",
1218
- lg: "17px",
1219
- xxl: "28px"
1220
- };
1221
- var FONT_WEIGHT = {
1222
- regular: 400,
1223
- medium: 500,
1224
- semibold: 600,
1225
- bold: 700
1226
- };
1227
- var LINE_HEIGHT = {
1228
- tight: 1.3,
1229
- normal: 1.45};
1230
- var SPACE = {
1231
- S1: "4px",
1232
- S2: "8px",
1233
- S3: "12px",
1234
- S4: "16px",
1235
- S5: "20px",
1236
- S6: "24px",
1237
- S12: "48px"
1238
- };
1239
- var RADIUS = {
1240
- sm: "4px",
1241
- md: "8px",
1242
- lg: "12px",
1243
- pill: "9999px",
1244
- full: "50%"
1245
- };
1246
- var COLOR = {
1247
- // Brand
1248
- primary: "#2563eb",
1249
- /** Tinted surface for chips/cards on brand-coloured states. */
1250
- primaryBg: "#dbeafe",
1251
- primaryFg: "#1d4ed8",
1252
- // Neutral grayscale — taken from Tailwind's zinc-leaning slate, the
1253
- // existing dominant family in the package.
1254
- white: "#ffffff",
1255
- neutral50: "#f9fafb",
1256
- neutral100: "#f3f4f6",
1257
- neutral200: "#e5e7eb",
1258
- neutral300: "#d1d5db",
1259
- neutral400: "#9ca3af",
1260
- neutral500: "#6b7280",
1261
- neutral600: "#4b5563",
1262
- neutral700: "#374151",
1263
- neutral800: "#1f2937",
1264
- neutral900: "#111827",
1265
- // Slate (used by day dividers + dialog title bar)
1266
- slate200: "#e2e8f0",
1267
- slate700: "#334155",
1268
- slate800: "#1e293b",
1269
- // Semantic
1270
- success: "#059669",
1271
- warning: "#d97706",
1272
- danger: "#dc2626",
1273
- dangerBg: "#fef2f2",
1274
- dangerBorder: "#fecaca",
1275
- dangerFg: "#b91c1c"
1276
- };
1277
- var SIZE = {
1278
- /** Default interactive height (text buttons, list rows). */
1279
- control: "40px",
1280
- /** Square icon-only buttons inside chrome (popup title, menu trigger). */
1281
- controlIcon: "36px",
1282
- /** Primary input controls (textarea send/attach). 44px = WCAG min. */
1283
- controlPrimary: "44px",
1284
- /** Avatar in the inbox row. */
1285
- avatar: "40px",
1286
- /** Unread dot. */
1287
- dot: "10px"
1288
- };
1289
- var SHADOW = {
1290
- /** Cards / floating popovers (message-actions menu). */
1291
- popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
1292
- /** Dialog (CollabPopup). */
1293
- dialog: "0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08)",
1294
- /** Subtle separator shadow (sticky day divider, toast). */
1295
- subtle: "0 1px 2px rgba(0, 0, 0, 0.04)",
1296
- toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
1297
- };
1298
- var Z_INDEX = {
1299
- /** Sticky day divider — above bubbles, below interactive popovers. */
1300
- sticky: 1,
1301
- /** Toasts inside a panel. */
1302
- toast: 20,
1303
- /** Floating CollabPopup dialog. */
1304
- dialog: 9999,
1305
- /** Portaled message-actions menu — must sit above the dialog. */
1306
- menu: 1e4
1307
- };
1308
1379
  function resolveDisplayName(patientData, displayName) {
1309
1380
  const localComplete = !!patientData.labName && !!patientData.displayOrderId;
1310
1381
  if (localComplete) return buildChannelName(patientData);
@@ -1321,15 +1392,28 @@ function PatientHeader({
1321
1392
  }) {
1322
1393
  const hasDicom = !!(patientData.studyId && patientData.storageId);
1323
1394
  const meta = [];
1324
- if (patientData.patientAge) meta.push({ label: "Age", value: String(patientData.patientAge) });
1325
- if (patientData.patientSex) meta.push({ label: "Sex", value: String(patientData.patientSex) });
1326
- if (patientData.studyType) meta.push({ label: "Modality", value: patientData.studyType });
1327
- if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1328
- meta.push({ label: "Body part", value: patientData.bodyParts.join(", ") });
1395
+ if (patientData.patientAge && patientData.patientSex) {
1396
+ meta.push({
1397
+ key: "ageSex",
1398
+ label: "Age / Sex",
1399
+ value: `${patientData.patientAge} \xB7 ${patientData.patientSex}`
1400
+ });
1401
+ } else if (patientData.patientAge) {
1402
+ meta.push({ key: "age", label: "Age", value: String(patientData.patientAge) });
1403
+ } else if (patientData.patientSex) {
1404
+ meta.push({ key: "sex", label: "Sex", value: String(patientData.patientSex) });
1405
+ }
1406
+ if (patientData.studyType) {
1407
+ meta.push({ key: "modality", label: "Modality", value: patientData.studyType });
1329
1408
  }
1330
- if (patientData.referringPhysician) {
1331
- meta.push({ label: "Ref", value: patientData.referringPhysician });
1409
+ if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1410
+ meta.push({
1411
+ key: "body",
1412
+ label: "Body part",
1413
+ value: patientData.bodyParts.join(", ")
1414
+ });
1332
1415
  }
1416
+ const hasActions = hasDicom && onOpenDicom || onOpenSettings;
1333
1417
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles.container, children: [
1334
1418
  !hideName && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.nameRow, children: [
1335
1419
  onBack && /* @__PURE__ */ jsxRuntime.jsx(
@@ -1345,54 +1429,55 @@ function PatientHeader({
1345
1429
  ),
1346
1430
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.nameText, children: resolveDisplayName(patientData, displayName) })
1347
1431
  ] }),
1348
- meta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.metaItem, children: [
1349
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles.metaLabel, children: [
1350
- item.label,
1351
- ":"
1352
- ] }),
1353
- " ",
1354
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.metaValue, children: item.value })
1355
- ] }, item.label)) }),
1356
- hasDicom && onOpenDicom || onOpenSettings ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
1357
- hasDicom && onOpenDicom && /* @__PURE__ */ jsxRuntime.jsxs(
1358
- "button",
1359
- {
1360
- onClick: onOpenDicom,
1361
- style: styles.dicomButton,
1362
- type: "button",
1363
- "aria-label": "View DICOM study",
1364
- children: [
1365
- /* @__PURE__ */ jsxRuntime.jsx(DicomIcon, { size: 18, color: COLOR.white }),
1366
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "View DICOM" })
1367
- ]
1368
- }
1369
- ),
1370
- onOpenSettings && /* @__PURE__ */ jsxRuntime.jsxs(
1371
- "button",
1372
- {
1373
- onClick: onOpenSettings,
1374
- style: styles.settingsButton,
1375
- type: "button",
1376
- "aria-label": "Open channel settings",
1377
- children: [
1378
- /* @__PURE__ */ jsxRuntime.jsx(SettingsIcon, { size: 18, color: COLOR.neutral700 }),
1379
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Settings" })
1380
- ]
1381
- }
1382
- )
1383
- ] }) : null
1432
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.caseCard, children: [
1433
+ meta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.metaCol, children: [
1434
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.metaLabel, children: item.label }),
1435
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.metaValue, children: item.value })
1436
+ ] }, item.key)) }),
1437
+ hasActions && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.actions, children: [
1438
+ hasDicom && onOpenDicom && /* @__PURE__ */ jsxRuntime.jsxs(
1439
+ "button",
1440
+ {
1441
+ onClick: onOpenDicom,
1442
+ style: styles.dicomButton,
1443
+ type: "button",
1444
+ "aria-label": "View DICOM study",
1445
+ children: [
1446
+ /* @__PURE__ */ jsxRuntime.jsx(DicomIcon, { size: 18, color: COLOR.primary }),
1447
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "View DICOM" })
1448
+ ]
1449
+ }
1450
+ ),
1451
+ onOpenSettings && /* @__PURE__ */ jsxRuntime.jsx(
1452
+ "button",
1453
+ {
1454
+ onClick: onOpenSettings,
1455
+ style: styles.settingsIconButton,
1456
+ type: "button",
1457
+ "aria-label": "Open channel settings",
1458
+ title: "Settings",
1459
+ children: /* @__PURE__ */ jsxRuntime.jsx(SettingsIcon, { size: 18, color: COLOR.neutral600 })
1460
+ }
1461
+ )
1462
+ ] })
1463
+ ] })
1384
1464
  ] });
1385
1465
  }
1386
1466
  var styles = {
1387
1467
  container: {
1388
- padding: `${SPACE.S3} ${SPACE.S4}`,
1389
- borderBottom: `1px solid ${COLOR.neutral200}`,
1390
- backgroundColor: COLOR.neutral50
1468
+ display: "flex",
1469
+ flexDirection: "column",
1470
+ backgroundColor: COLOR.primaryBg,
1471
+ borderBottom: `1px solid ${COLOR.neutral200}`
1391
1472
  },
1473
+ // Optional name row, only when `hideName` is false (compact-inbox path)
1392
1474
  nameRow: {
1393
1475
  display: "flex",
1394
1476
  alignItems: "center",
1395
- gap: SPACE.S2
1477
+ gap: SPACE.S2,
1478
+ padding: `${SPACE.S3} ${SPACE.S4} ${SPACE.S2}`,
1479
+ borderBottom: `1px solid ${COLOR.neutral200}`,
1480
+ backgroundColor: COLOR.white
1396
1481
  },
1397
1482
  backButton: {
1398
1483
  width: SIZE.controlIcon,
@@ -1409,65 +1494,90 @@ var styles = {
1409
1494
  },
1410
1495
  nameText: {
1411
1496
  fontSize: FONT_SIZE.lg,
1412
- fontWeight: FONT_WEIGHT.semibold,
1497
+ fontWeight: FONT_WEIGHT.bold,
1413
1498
  color: COLOR.neutral900,
1414
1499
  lineHeight: LINE_HEIGHT.tight,
1500
+ letterSpacing: "-0.01em",
1415
1501
  minWidth: 0,
1416
1502
  overflow: "hidden",
1417
1503
  textOverflow: "ellipsis",
1418
1504
  whiteSpace: "nowrap"
1419
1505
  },
1506
+ // Case card: meta columns flowing in a wrap-row, actions stacked
1507
+ // below. Each meta column is a small label-above-value pair so the
1508
+ // user can scan field names quickly without a legend.
1509
+ caseCard: {
1510
+ display: "flex",
1511
+ flexDirection: "column",
1512
+ gap: SPACE.S3,
1513
+ padding: `${SPACE.S3} ${SPACE.S5}`
1514
+ },
1515
+ // Wrap-row of stacked label/value columns. Compact column gap keeps
1516
+ // the row dense without crowding; row gap kicks in when columns
1517
+ // wrap to a second line on narrow popups.
1420
1518
  metaRow: {
1421
1519
  display: "flex",
1422
1520
  flexWrap: "wrap",
1423
- columnGap: SPACE.S3,
1424
- rowGap: SPACE.S1,
1425
- marginTop: SPACE.S1
1521
+ columnGap: SPACE.S5,
1522
+ rowGap: SPACE.S2,
1523
+ minWidth: 0
1426
1524
  },
1427
- metaItem: {
1428
- fontSize: FONT_SIZE.sm,
1429
- color: COLOR.neutral700,
1430
- whiteSpace: "nowrap"
1525
+ metaCol: {
1526
+ display: "flex",
1527
+ flexDirection: "column",
1528
+ gap: "2px",
1529
+ minWidth: 0
1431
1530
  },
1432
1531
  metaLabel: {
1532
+ fontSize: "11px",
1533
+ fontWeight: FONT_WEIGHT.bold,
1534
+ letterSpacing: "0.06em",
1535
+ textTransform: "uppercase",
1433
1536
  color: COLOR.neutral500,
1434
- fontWeight: FONT_WEIGHT.medium
1537
+ whiteSpace: "nowrap"
1435
1538
  },
1436
1539
  metaValue: {
1437
- color: COLOR.neutral900
1540
+ fontSize: FONT_SIZE.sm,
1541
+ fontWeight: FONT_WEIGHT.semibold,
1542
+ color: COLOR.neutral900,
1543
+ whiteSpace: "nowrap",
1544
+ overflow: "hidden",
1545
+ textOverflow: "ellipsis",
1546
+ maxWidth: "180px"
1438
1547
  },
1439
1548
  actions: {
1440
1549
  display: "flex",
1441
1550
  gap: SPACE.S2,
1442
- marginTop: SPACE.S3
1551
+ flexShrink: 0
1443
1552
  },
1444
1553
  dicomButton: {
1445
1554
  display: "inline-flex",
1446
1555
  alignItems: "center",
1447
1556
  gap: SPACE.S2,
1448
1557
  minHeight: SIZE.control,
1449
- padding: `${SPACE.S2} ${SPACE.S4}`,
1558
+ padding: `${SPACE.S2} 14px`,
1450
1559
  fontSize: FONT_SIZE.sm,
1451
- fontWeight: FONT_WEIGHT.medium,
1452
- color: COLOR.white,
1453
- backgroundColor: COLOR.primary,
1454
- border: "none",
1455
- borderRadius: RADIUS.md,
1560
+ fontWeight: FONT_WEIGHT.semibold,
1561
+ color: COLOR.primary,
1562
+ backgroundColor: COLOR.white,
1563
+ border: `1.5px solid ${COLOR.primary}`,
1564
+ borderRadius: RADIUS.lg,
1456
1565
  cursor: "pointer"
1457
1566
  },
1458
- settingsButton: {
1567
+ // Settings is icon-only (40x40 tile) since it's a secondary action
1568
+ // and View DICOM already carries a label.
1569
+ settingsIconButton: {
1570
+ width: SIZE.control,
1571
+ height: SIZE.control,
1459
1572
  display: "inline-flex",
1460
1573
  alignItems: "center",
1461
- gap: SPACE.S2,
1462
- minHeight: SIZE.control,
1463
- padding: `${SPACE.S2} ${SPACE.S4}`,
1464
- fontSize: FONT_SIZE.sm,
1465
- fontWeight: FONT_WEIGHT.medium,
1466
- color: COLOR.neutral700,
1574
+ justifyContent: "center",
1467
1575
  backgroundColor: COLOR.white,
1468
- border: `1px solid ${COLOR.neutral300}`,
1469
- borderRadius: RADIUS.md,
1470
- cursor: "pointer"
1576
+ border: `1px solid ${COLOR.neutral200}`,
1577
+ borderRadius: RADIUS.lg,
1578
+ color: COLOR.neutral600,
1579
+ cursor: "pointer",
1580
+ flexShrink: 0
1471
1581
  }
1472
1582
  };
1473
1583
  function ReplyQuoteBlock({
@@ -1476,10 +1586,10 @@ function ReplyQuoteBlock({
1476
1586
  onClick,
1477
1587
  className
1478
1588
  }) {
1479
- const baseBg = inOwnBubble ? "rgba(255,255,255,0.18)" : COLOR.neutral100;
1589
+ const baseBg = inOwnBubble ? "rgba(255,255,255,0.22)" : COLOR.white;
1480
1590
  const barColor = inOwnBubble ? COLOR.white : COLOR.primary;
1481
1591
  const nameColor = inOwnBubble ? COLOR.white : COLOR.primary;
1482
- const bodyColor = inOwnBubble ? "rgba(255,255,255,0.85)" : COLOR.neutral600;
1592
+ const bodyColor = inOwnBubble ? "rgba(255,255,255,0.9)" : COLOR.neutral700;
1483
1593
  const preview = renderSnapshotPreview(snapshot);
1484
1594
  return /* @__PURE__ */ jsxRuntime.jsxs(
1485
1595
  "div",
@@ -1851,43 +1961,50 @@ var styles4 = {
1851
1961
  position: "relative",
1852
1962
  display: "inline-block"
1853
1963
  },
1964
+ // Pill-shaped trigger matching the prototype's `.nc-msg-act` style —
1965
+ // always visible (touch-friendly) rather than hover-only.
1854
1966
  trigger: {
1855
- width: SIZE.controlIcon,
1856
- height: SIZE.controlIcon,
1857
- display: "flex",
1967
+ display: "inline-flex",
1858
1968
  alignItems: "center",
1859
1969
  justifyContent: "center",
1860
- backgroundColor: "transparent",
1861
- border: "none",
1862
- borderRadius: RADIUS.full,
1970
+ width: "32px",
1971
+ height: "32px",
1972
+ backgroundColor: COLOR.white,
1973
+ border: `1px solid ${COLOR.neutral200}`,
1974
+ borderRadius: "999px",
1863
1975
  cursor: "pointer",
1864
- color: COLOR.neutral600
1976
+ color: COLOR.neutral600,
1977
+ transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
1865
1978
  },
1866
1979
  menu: {
1867
1980
  position: "fixed",
1868
- minWidth: "160px",
1981
+ minWidth: "200px",
1869
1982
  backgroundColor: COLOR.white,
1870
- border: `1px solid ${COLOR.neutral200}`,
1871
- borderRadius: RADIUS.md,
1983
+ border: `1px solid ${COLOR.neutral300}`,
1984
+ borderRadius: RADIUS.lg,
1872
1985
  boxShadow: SHADOW.popover,
1873
- padding: SPACE.S1,
1986
+ padding: "6px",
1987
+ display: "flex",
1988
+ flexDirection: "column",
1989
+ gap: "2px",
1874
1990
  // Above the floating popup (z-index dialog) but below any future modal.
1875
1991
  zIndex: Z_INDEX.menu
1876
1992
  },
1877
1993
  item: {
1878
1994
  display: "flex",
1879
1995
  alignItems: "center",
1880
- gap: SPACE.S3,
1996
+ gap: SPACE.S2,
1881
1997
  width: "100%",
1882
1998
  minHeight: SIZE.control,
1883
- padding: `${SPACE.S2} ${SPACE.S3}`,
1999
+ padding: `10px ${SPACE.S3}`,
1884
2000
  fontSize: FONT_SIZE.sm,
1885
- color: COLOR.neutral900,
2001
+ color: COLOR.neutral700,
1886
2002
  backgroundColor: "transparent",
1887
2003
  border: "none",
1888
- borderRadius: RADIUS.sm,
2004
+ borderRadius: RADIUS.md,
1889
2005
  cursor: "pointer",
1890
- textAlign: "left"
2006
+ textAlign: "left",
2007
+ fontFamily: "inherit"
1891
2008
  },
1892
2009
  itemDisabled: {
1893
2010
  color: COLOR.neutral400,
@@ -2001,71 +2118,116 @@ function MessageBubble({
2001
2118
  },
2002
2119
  children: [
2003
2120
  isOwn && actionsSlot,
2004
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.bubbleWrapper, children: /* @__PURE__ */ jsxRuntime.jsxs(
2005
- "div",
2006
- {
2007
- style: {
2008
- ...styles5.bubble,
2009
- ...isOwn ? styles5.ownBubble : styles5.otherBubble
2010
- },
2011
- children: [
2012
- message.isPinned && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.pinnedBadge, children: [
2013
- /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 12 }),
2014
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Pinned" })
2015
- ] }),
2016
- message.replyToSnapshot && /* @__PURE__ */ jsxRuntime.jsx(
2017
- ReplyQuoteBlock,
2018
- {
2019
- snapshot: message.replyToSnapshot,
2020
- inOwnBubble: isOwn,
2021
- onClick: onReplyJumpTo
2022
- }
2023
- ),
2024
- !isOwn && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.senderRow, children: [
2025
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.senderName, children: message.senderName }),
2026
- /* @__PURE__ */ jsxRuntime.jsx(RoleBadge, { role: message.senderRole })
2027
- ] }),
2028
- message.type === "text" && /* @__PURE__ */ jsxRuntime.jsx(TextContent, { body: message.body, onDeepLinkClick }),
2029
- message.type === "audio" && /* @__PURE__ */ jsxRuntime.jsx(AudioContent, { mediaUrl: message.mediaUrl, duration: message.mediaDuration }),
2030
- message.type === "image" && /* @__PURE__ */ jsxRuntime.jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2031
- message.type === "file" && /* @__PURE__ */ jsxRuntime.jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2032
- message.type === "deep_link" && /* @__PURE__ */ jsxRuntime.jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
2033
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
2034
- isOwn && message.status === "sending" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.statusRow, children: [
2035
- /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 12, color: "rgba(255,255,255,0.85)" }),
2036
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.statusText, children: "Sending\u2026" })
2037
- ] }),
2038
- isOwn && message.status === "failed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.statusRow, children: [
2039
- /* @__PURE__ */ jsxRuntime.jsx(AlertIcon, { size: 12, color: "#fecaca" }),
2040
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.statusText, children: "Not sent" }),
2041
- onRetry && /* @__PURE__ */ jsxRuntime.jsx(
2042
- "button",
2121
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.bubbleWrapper, children: [
2122
+ !isOwn && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.authorRow, children: [
2123
+ /* @__PURE__ */ jsxRuntime.jsx(
2124
+ "span",
2125
+ {
2126
+ style: {
2127
+ ...styles5.authorAvatar,
2128
+ backgroundColor: roleColor(message.senderRole)
2129
+ },
2130
+ "aria-hidden": "true",
2131
+ children: computeInitials(message.senderName)
2132
+ }
2133
+ ),
2134
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.authorName, children: message.senderName }),
2135
+ /* @__PURE__ */ jsxRuntime.jsx(RoleBadge, { role: message.senderRole })
2136
+ ] }),
2137
+ /* @__PURE__ */ jsxRuntime.jsxs(
2138
+ "div",
2139
+ {
2140
+ style: {
2141
+ ...styles5.bubble,
2142
+ ...isOwn ? styles5.ownBubble : styles5.otherBubble
2143
+ },
2144
+ children: [
2145
+ message.isPinned && /* @__PURE__ */ jsxRuntime.jsxs(
2146
+ "div",
2043
2147
  {
2044
- type: "button",
2045
- onClick: () => onRetry(message.id),
2046
- style: styles5.retryButton,
2047
- "aria-label": "Retry sending message",
2048
- children: "Retry"
2148
+ style: {
2149
+ ...styles5.pinnedBadge,
2150
+ ...isOwn ? styles5.pinnedBadgeOwn : styles5.pinnedBadgeOther
2151
+ },
2152
+ children: [
2153
+ /* @__PURE__ */ jsxRuntime.jsx(PinIcon, { size: 12 }),
2154
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Pinned" })
2155
+ ]
2049
2156
  }
2050
- )
2051
- ] }),
2052
- showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsxRuntime.jsx(
2053
- SeenByIndicator,
2054
- {
2055
- readBy: message.readBy ?? [],
2056
- participants,
2057
- currentUserId,
2058
- senderId: message.senderId
2059
- }
2060
- )
2061
- ]
2062
- }
2063
- ) }),
2157
+ ),
2158
+ message.replyToSnapshot && /* @__PURE__ */ jsxRuntime.jsx(
2159
+ ReplyQuoteBlock,
2160
+ {
2161
+ snapshot: message.replyToSnapshot,
2162
+ inOwnBubble: isOwn,
2163
+ onClick: onReplyJumpTo
2164
+ }
2165
+ ),
2166
+ message.type === "text" && /* @__PURE__ */ jsxRuntime.jsx(TextContent, { body: message.body, onDeepLinkClick }),
2167
+ message.type === "audio" && /* @__PURE__ */ jsxRuntime.jsx(AudioContent, { mediaUrl: message.mediaUrl, duration: message.mediaDuration }),
2168
+ message.type === "image" && /* @__PURE__ */ jsxRuntime.jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2169
+ message.type === "file" && /* @__PURE__ */ jsxRuntime.jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2170
+ message.type === "deep_link" && /* @__PURE__ */ jsxRuntime.jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
2171
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.bubbleFoot, children: [
2172
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
2173
+ isOwn && message.status === "sending" && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles5.footStatus, children: [
2174
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 11, color: "rgba(255,255,255,0.85)" }),
2175
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Sending\u2026" })
2176
+ ] }),
2177
+ isOwn && message.status === "failed" && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles5.footStatusFailed, children: [
2178
+ /* @__PURE__ */ jsxRuntime.jsx(AlertIcon, { size: 11, color: "#fecaca" }),
2179
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Not sent" }),
2180
+ onRetry && /* @__PURE__ */ jsxRuntime.jsx(
2181
+ "button",
2182
+ {
2183
+ type: "button",
2184
+ onClick: () => onRetry(message.id),
2185
+ style: styles5.retryButton,
2186
+ "aria-label": "Retry sending message",
2187
+ children: "Retry"
2188
+ }
2189
+ )
2190
+ ] }),
2191
+ showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsxRuntime.jsx(
2192
+ SeenByIndicator,
2193
+ {
2194
+ readBy: message.readBy ?? [],
2195
+ participants,
2196
+ currentUserId,
2197
+ senderId: message.senderId
2198
+ }
2199
+ )
2200
+ ] })
2201
+ ]
2202
+ }
2203
+ )
2204
+ ] }),
2064
2205
  !isOwn && actionsSlot
2065
2206
  ]
2066
2207
  }
2067
2208
  );
2068
2209
  }
2210
+ function computeInitials(name) {
2211
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
2212
+ }
2213
+ function roleColor(role) {
2214
+ switch (role) {
2215
+ case "radiologist":
2216
+ return "#4f46e5";
2217
+ // indigo (blue-leaning)
2218
+ case "lab":
2219
+ return "#2563eb";
2220
+ // brand blue
2221
+ case "physician":
2222
+ return "#0284c7";
2223
+ // sky
2224
+ case "admin":
2225
+ return "#64748b";
2226
+ // slate (muted)
2227
+ default:
2228
+ return "#6b7280";
2229
+ }
2230
+ }
2069
2231
  function TextContent({ body, onDeepLinkClick }) {
2070
2232
  if (body.includes(DEEP_LINK_PREFIX)) {
2071
2233
  const parts = body.split(new RegExp(`(${escapeRegex2(DEEP_LINK_PREFIX)}[\\w/.-]+)`, "g"));
@@ -2127,19 +2289,13 @@ function DeepLinkContent({
2127
2289
  );
2128
2290
  }
2129
2291
  function SystemBubble({ message }) {
2130
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles5.systemMessage, children: [
2292
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles5.systemWrapper, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles5.systemPill, children: [
2131
2293
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: message.body }),
2132
2294
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles5.systemTime, children: formatTime(message.insertedAt) })
2133
- ] });
2295
+ ] }) });
2134
2296
  }
2135
2297
  function RoleBadge({ role }) {
2136
- const colors = {
2137
- radiologist: "#7c3aed",
2138
- lab: "#2563eb",
2139
- physician: "#059669",
2140
- admin: "#dc2626"
2141
- };
2142
- return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: ROLE_LABELS[role] ?? role });
2298
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles5.roleBadge, backgroundColor: roleColor(role) }, children: ROLE_LABELS[role] ?? role });
2143
2299
  }
2144
2300
  function formatTime(iso) {
2145
2301
  try {
@@ -2161,89 +2317,134 @@ var styles5 = {
2161
2317
  display: "flex",
2162
2318
  alignItems: "flex-end",
2163
2319
  gap: SPACE.S1,
2164
- marginBottom: SPACE.S2,
2320
+ marginBottom: SPACE.S3,
2165
2321
  paddingLeft: SPACE.S4,
2166
2322
  paddingRight: SPACE.S4
2167
2323
  },
2168
2324
  bubbleWrapper: {
2169
2325
  position: "relative",
2170
- maxWidth: "75%"
2326
+ maxWidth: "78%",
2327
+ display: "flex",
2328
+ flexDirection: "column",
2329
+ gap: "4px"
2171
2330
  },
2172
2331
  actionsSlot: {
2173
2332
  flexShrink: 0,
2174
2333
  alignSelf: "flex-end"
2175
2334
  },
2335
+ // Author row above the bubble — only rendered for others' messages.
2336
+ authorRow: {
2337
+ display: "flex",
2338
+ alignItems: "center",
2339
+ gap: SPACE.S2
2340
+ },
2341
+ authorAvatar: {
2342
+ width: "36px",
2343
+ height: "36px",
2344
+ borderRadius: "50%",
2345
+ color: COLOR.white,
2346
+ fontSize: FONT_SIZE.sm,
2347
+ fontWeight: FONT_WEIGHT.bold,
2348
+ display: "flex",
2349
+ alignItems: "center",
2350
+ justifyContent: "center",
2351
+ flexShrink: 0,
2352
+ letterSpacing: "0.02em"
2353
+ },
2354
+ authorName: {
2355
+ fontSize: FONT_SIZE.sm,
2356
+ fontWeight: FONT_WEIGHT.semibold,
2357
+ color: COLOR.neutral700,
2358
+ overflow: "hidden",
2359
+ textOverflow: "ellipsis",
2360
+ whiteSpace: "nowrap",
2361
+ minWidth: 0
2362
+ },
2176
2363
  bubble: {
2177
2364
  padding: `${SPACE.S3} ${SPACE.S4}`,
2178
- borderRadius: RADIUS.lg,
2179
- wordBreak: "break-word"
2365
+ borderRadius: "18px",
2366
+ wordBreak: "break-word",
2367
+ border: "1px solid transparent"
2180
2368
  },
2181
2369
  ownBubble: {
2182
2370
  backgroundColor: COLOR.primary,
2183
2371
  color: COLOR.white,
2184
- borderBottomRightRadius: RADIUS.sm
2372
+ borderColor: COLOR.primary,
2373
+ borderBottomRightRadius: "6px"
2185
2374
  },
2186
2375
  otherBubble: {
2187
2376
  backgroundColor: COLOR.neutral100,
2188
2377
  color: COLOR.neutral900,
2189
- borderBottomLeftRadius: RADIUS.sm
2378
+ borderColor: COLOR.neutral200,
2379
+ borderBottomLeftRadius: "6px"
2190
2380
  },
2191
2381
  pinnedBadge: {
2192
2382
  display: "inline-flex",
2193
2383
  alignItems: "center",
2194
- gap: SPACE.S1,
2195
- fontSize: FONT_SIZE.xs,
2196
- fontWeight: FONT_WEIGHT.semibold,
2197
- marginBottom: SPACE.S1,
2198
- color: COLOR.neutral500
2384
+ gap: "4px",
2385
+ fontSize: "11px",
2386
+ fontWeight: FONT_WEIGHT.bold,
2387
+ marginBottom: SPACE.S2,
2388
+ padding: `2px ${SPACE.S2}`,
2389
+ borderRadius: "999px",
2390
+ textTransform: "uppercase",
2391
+ letterSpacing: "0.05em"
2199
2392
  },
2200
- senderRow: {
2201
- display: "flex",
2202
- alignItems: "center",
2203
- gap: SPACE.S2,
2204
- marginBottom: SPACE.S1
2393
+ pinnedBadgeOther: {
2394
+ color: "#b8680f",
2395
+ backgroundColor: "#fff7e0"
2205
2396
  },
2206
- senderName: {
2207
- fontSize: FONT_SIZE.sm,
2208
- fontWeight: FONT_WEIGHT.semibold,
2209
- color: COLOR.neutral700
2397
+ pinnedBadgeOwn: {
2398
+ color: "#fff4d6",
2399
+ backgroundColor: "rgba(255, 255, 255, 0.2)"
2210
2400
  },
2211
2401
  roleBadge: {
2212
- fontSize: FONT_SIZE.xs,
2213
- fontWeight: FONT_WEIGHT.semibold,
2214
- padding: `2px ${SPACE.S2}`,
2215
- borderRadius: RADIUS.sm
2402
+ fontSize: "10px",
2403
+ fontWeight: FONT_WEIGHT.bold,
2404
+ padding: "2px 7px",
2405
+ borderRadius: RADIUS.sm,
2406
+ textTransform: "uppercase",
2407
+ letterSpacing: "0.06em",
2408
+ color: COLOR.white,
2409
+ flexShrink: 0
2216
2410
  },
2217
2411
  textBody: {
2218
2412
  margin: 0,
2219
2413
  fontSize: FONT_SIZE.md,
2220
- lineHeight: LINE_HEIGHT.normal
2414
+ lineHeight: 1.5
2415
+ },
2416
+ bubbleFoot: {
2417
+ display: "flex",
2418
+ alignItems: "center",
2419
+ justifyContent: "flex-end",
2420
+ gap: SPACE.S2,
2421
+ marginTop: SPACE.S2,
2422
+ fontSize: "11px",
2423
+ opacity: 0.85
2221
2424
  },
2222
2425
  timestamp: {
2223
- fontSize: FONT_SIZE.xs,
2426
+ fontSize: "11px",
2224
2427
  color: "inherit",
2225
- opacity: 0.8,
2226
- marginTop: SPACE.S1,
2227
- textAlign: "right"
2428
+ fontVariantNumeric: "tabular-nums"
2228
2429
  },
2229
- statusRow: {
2230
- display: "flex",
2430
+ footStatus: {
2431
+ display: "inline-flex",
2231
2432
  alignItems: "center",
2232
- gap: SPACE.S2,
2233
- marginTop: SPACE.S1,
2234
- fontSize: FONT_SIZE.xs,
2235
- justifyContent: "flex-end",
2236
- color: "rgba(255, 255, 255, 0.9)"
2433
+ gap: "4px",
2434
+ fontSize: "11px"
2237
2435
  },
2238
- statusText: {
2239
- opacity: 0.9
2436
+ footStatusFailed: {
2437
+ display: "inline-flex",
2438
+ alignItems: "center",
2439
+ gap: "4px",
2440
+ fontSize: "11px"
2240
2441
  },
2241
2442
  retryButton: {
2242
2443
  background: "transparent",
2243
2444
  border: "none",
2244
2445
  color: COLOR.white,
2245
2446
  fontWeight: FONT_WEIGHT.semibold,
2246
- fontSize: FONT_SIZE.xs,
2447
+ fontSize: "11px",
2247
2448
  textDecoration: "underline",
2248
2449
  cursor: "pointer",
2249
2450
  padding: `0 ${SPACE.S1}`
@@ -2306,18 +2507,25 @@ var styles5 = {
2306
2507
  color: COLOR.neutral500,
2307
2508
  fontFamily: "monospace"
2308
2509
  },
2309
- systemMessage: {
2510
+ systemWrapper: {
2310
2511
  display: "flex",
2311
2512
  justifyContent: "center",
2513
+ margin: `${SPACE.S2} 0`
2514
+ },
2515
+ systemPill: {
2516
+ display: "inline-flex",
2312
2517
  alignItems: "center",
2313
2518
  gap: SPACE.S2,
2314
- padding: `${SPACE.S1} ${SPACE.S4}`,
2315
- fontSize: FONT_SIZE.sm,
2519
+ padding: `5px ${SPACE.S3}`,
2520
+ backgroundColor: COLOR.neutral100,
2521
+ borderRadius: "999px",
2522
+ fontSize: FONT_SIZE.xs,
2316
2523
  color: COLOR.neutral500
2317
2524
  },
2318
2525
  systemTime: {
2319
- fontSize: FONT_SIZE.xs,
2320
- color: COLOR.neutral400
2526
+ fontSize: "11px",
2527
+ color: COLOR.neutral400,
2528
+ fontVariantNumeric: "tabular-nums"
2321
2529
  }
2322
2530
  };
2323
2531
  function ChatIllustration({ size = 96 }) {
@@ -2456,6 +2664,7 @@ var MessageList = React4.forwardRef(function MessageList2({
2456
2664
  const containerRef = React4.useRef(null);
2457
2665
  const bottomRef = React4.useRef(null);
2458
2666
  const prevMessageCount = React4.useRef(messages.length);
2667
+ const hasInitiallyScrolledRef = React4.useRef(false);
2459
2668
  React4.useImperativeHandle(
2460
2669
  ref,
2461
2670
  () => ({
@@ -2473,7 +2682,31 @@ var MessageList = React4.forwardRef(function MessageList2({
2473
2682
  }),
2474
2683
  []
2475
2684
  );
2685
+ React4.useLayoutEffect(() => {
2686
+ const container = containerRef.current;
2687
+ if (!container || messages.length === 0) return;
2688
+ if (hasInitiallyScrolledRef.current) return;
2689
+ hasInitiallyScrolledRef.current = true;
2690
+ prevMessageCount.current = messages.length;
2691
+ const firstUnread = messages.find(
2692
+ (m) => m.type !== "system" && m.senderId !== currentUserId && !(m.readBy ?? []).includes(currentUserId)
2693
+ );
2694
+ if (firstUnread) {
2695
+ const el = container.querySelector(
2696
+ `[data-message-id="${firstUnread.id}"]`
2697
+ );
2698
+ if (el) {
2699
+ const containerRect = container.getBoundingClientRect();
2700
+ const elRect = el.getBoundingClientRect();
2701
+ const offset = elRect.top - containerRect.top + container.scrollTop;
2702
+ container.scrollTop = Math.max(0, offset - 16);
2703
+ return;
2704
+ }
2705
+ }
2706
+ container.scrollTop = container.scrollHeight;
2707
+ }, [messages, currentUserId]);
2476
2708
  React4.useEffect(() => {
2709
+ if (!hasInitiallyScrolledRef.current) return;
2477
2710
  if (messages.length > prevMessageCount.current) {
2478
2711
  const lastMessage = messages[messages.length - 1];
2479
2712
  const isOwnMessage = lastMessage?.senderId === currentUserId;
@@ -2532,7 +2765,11 @@ var MessageList = React4.forwardRef(function MessageList2({
2532
2765
  const showDivider = !!currentBucket && currentBucket !== lastValidBucket;
2533
2766
  if (currentBucket) lastValidBucket = currentBucket;
2534
2767
  return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
2535
- 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) }) }),
2768
+ showDivider && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: [
2769
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayHr, "aria-hidden": "true" }),
2770
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayLabel, children: formatDayDivider(message.insertedAt) }),
2771
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles6.dayHr, "aria-hidden": "true" })
2772
+ ] }),
2536
2773
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-message-id": message.id, "data-natoe-message-bubble": true, style: styles6.bubbleSlot, children: /* @__PURE__ */ jsxRuntime.jsx(
2537
2774
  MessageBubble,
2538
2775
  {
@@ -2631,23 +2868,32 @@ var styles6 = {
2631
2868
  },
2632
2869
  dayDivider: {
2633
2870
  display: "flex",
2634
- justifyContent: "center",
2635
- margin: `${SPACE.S3} 0 ${SPACE.S2}`,
2871
+ alignItems: "center",
2872
+ gap: SPACE.S3,
2873
+ margin: `${SPACE.S4} 0 ${SPACE.S3}`,
2636
2874
  position: "sticky",
2637
2875
  top: SPACE.S1,
2638
2876
  zIndex: Z_INDEX.sticky,
2639
2877
  pointerEvents: "none"
2640
2878
  },
2641
- dayDividerPill: {
2642
- display: "inline-block",
2643
- padding: `${SPACE.S1} ${SPACE.S3}`,
2644
- fontSize: FONT_SIZE.sm,
2645
- fontWeight: FONT_WEIGHT.medium,
2646
- color: COLOR.slate700,
2647
- backgroundColor: COLOR.slate200,
2648
- borderRadius: RADIUS.lg,
2649
- letterSpacing: "0.2px",
2650
- boxShadow: SHADOW.subtle
2879
+ dayHr: {
2880
+ flex: 1,
2881
+ height: "1px",
2882
+ backgroundColor: COLOR.neutral200
2883
+ },
2884
+ dayLabel: {
2885
+ flexShrink: 0,
2886
+ fontSize: FONT_SIZE.xs,
2887
+ fontWeight: FONT_WEIGHT.semibold,
2888
+ color: COLOR.neutral500,
2889
+ textTransform: "uppercase",
2890
+ letterSpacing: "0.06em",
2891
+ // Subtle pill background so the label is readable when it overlays
2892
+ // bubbles passing under it during sticky scroll. Without this the
2893
+ // hairlines visually run through the text on tinted backgrounds.
2894
+ padding: `2px ${SPACE.S2}`,
2895
+ backgroundColor: COLOR.white,
2896
+ borderRadius: RADIUS.sm
2651
2897
  },
2652
2898
  bubbleSlot: {
2653
2899
  animation: "natoe-colab-message-in 180ms ease-out"
@@ -2901,11 +3147,11 @@ function MessageInput({
2901
3147
  {
2902
3148
  onClick: () => fileInputRef.current?.click(),
2903
3149
  disabled: disabled || isSending,
2904
- style: styles8.iconButton,
3150
+ style: styles8.attachButton,
2905
3151
  "aria-label": "Attach file",
2906
3152
  title: "Attach file",
2907
3153
  type: "button",
2908
- children: /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, { size: 22, color: "#374151" })
3154
+ children: /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, { size: 22, color: COLOR.neutral700 })
2909
3155
  }
2910
3156
  ),
2911
3157
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2944,7 +3190,7 @@ function MessageInput({
2944
3190
  style: styles8.textarea
2945
3191
  }
2946
3192
  ),
2947
- /* @__PURE__ */ jsxRuntime.jsx(
3193
+ /* @__PURE__ */ jsxRuntime.jsxs(
2948
3194
  "button",
2949
3195
  {
2950
3196
  onClick: handleSendText,
@@ -2953,10 +3199,13 @@ function MessageInput({
2953
3199
  title: "Send message",
2954
3200
  style: {
2955
3201
  ...styles8.sendButton,
2956
- opacity: text.trim() ? 1 : 0.4
3202
+ opacity: text.trim() ? 1 : 0.5
2957
3203
  },
2958
3204
  type: "button",
2959
- children: /* @__PURE__ */ jsxRuntime.jsx(SendIcon, { size: 20, color: COLOR.white })
3205
+ children: [
3206
+ /* @__PURE__ */ jsxRuntime.jsx(SendIcon, { size: 20, color: COLOR.white }),
3207
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles8.sendLabel, children: "Send" })
3208
+ ]
2960
3209
  }
2961
3210
  )
2962
3211
  ] })
@@ -3018,46 +3267,64 @@ var styles8 = {
3018
3267
  inputBar: {
3019
3268
  display: "flex",
3020
3269
  alignItems: "flex-end",
3021
- gap: SPACE.S2,
3022
- padding: `${SPACE.S3} ${SPACE.S3}`
3023
- },
3024
- iconButton: {
3025
- width: SIZE.controlPrimary,
3026
- height: SIZE.controlPrimary,
3270
+ gap: "10px",
3271
+ padding: `${SPACE.S3} ${SPACE.S4} 14px`
3272
+ },
3273
+ // 48px square tile with a 14px corner radius — visually grounds the
3274
+ // attach affordance as part of the composer chrome rather than a stray
3275
+ // round icon button.
3276
+ attachButton: {
3277
+ width: "48px",
3278
+ height: "48px",
3027
3279
  display: "flex",
3028
3280
  alignItems: "center",
3029
3281
  justifyContent: "center",
3030
- backgroundColor: "transparent",
3031
- border: "none",
3032
- borderRadius: RADIUS.full,
3282
+ backgroundColor: COLOR.neutral100,
3283
+ border: `1.5px solid ${COLOR.neutral200}`,
3284
+ borderRadius: "14px",
3033
3285
  cursor: "pointer",
3034
- flexShrink: 0
3286
+ flexShrink: 0,
3287
+ color: COLOR.neutral700,
3288
+ transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
3035
3289
  },
3036
3290
  textarea: {
3037
3291
  flex: 1,
3038
- padding: `${SPACE.S3} ${SPACE.S4}`,
3292
+ minHeight: "48px",
3293
+ maxHeight: "140px",
3294
+ padding: "13px 16px",
3039
3295
  fontSize: FONT_SIZE.md,
3040
3296
  lineHeight: LINE_HEIGHT.normal,
3041
- border: `1px solid ${COLOR.neutral300}`,
3042
- borderRadius: RADIUS.pill,
3297
+ color: COLOR.neutral900,
3298
+ backgroundColor: COLOR.neutral100,
3299
+ border: `1.5px solid ${COLOR.neutral200}`,
3300
+ borderRadius: "14px",
3043
3301
  resize: "none",
3044
3302
  outline: "none",
3045
3303
  fontFamily: "inherit",
3046
- maxHeight: "120px",
3047
- minHeight: SIZE.controlPrimary
3304
+ transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
3048
3305
  },
3306
+ // Pill-shaped send: icon + "Send" label. Always labelled (never icon-
3307
+ // only) so the affordance is obvious to first-time users.
3049
3308
  sendButton: {
3050
- width: SIZE.controlPrimary,
3051
- height: SIZE.controlPrimary,
3052
- display: "flex",
3309
+ display: "inline-flex",
3053
3310
  alignItems: "center",
3054
- justifyContent: "center",
3311
+ gap: "6px",
3312
+ minWidth: "48px",
3313
+ height: "48px",
3314
+ padding: `0 ${SPACE.S4}`,
3055
3315
  backgroundColor: COLOR.primary,
3056
3316
  color: COLOR.white,
3057
3317
  border: "none",
3058
- borderRadius: RADIUS.full,
3318
+ borderRadius: "14px",
3059
3319
  cursor: "pointer",
3060
- flexShrink: 0
3320
+ flexShrink: 0,
3321
+ fontFamily: "inherit",
3322
+ fontSize: "15px",
3323
+ fontWeight: FONT_WEIGHT.semibold,
3324
+ transition: "opacity 120ms ease, background-color 120ms ease"
3325
+ },
3326
+ sendLabel: {
3327
+ lineHeight: 1
3061
3328
  }};
3062
3329
  function ParticipantsList({
3063
3330
  participants,
@@ -3114,10 +3381,14 @@ function fallbackName(role) {
3114
3381
  }
3115
3382
  function RoleBadge2({ role }) {
3116
3383
  const colors = {
3117
- radiologist: { bg: "#f5f3ff", text: "#7c3aed" },
3384
+ radiologist: { bg: "#eef2ff", text: "#4f46e5" },
3385
+ // indigo
3118
3386
  lab: { bg: "#eff6ff", text: "#2563eb" },
3119
- physician: { bg: "#ecfdf5", text: "#059669" },
3120
- admin: { bg: "#fef2f2", text: "#dc2626" }
3387
+ // brand blue
3388
+ physician: { bg: "#e0f2fe", text: "#0284c7" },
3389
+ // sky
3390
+ admin: { bg: "#f1f5f9", text: "#64748b" }
3391
+ // slate
3121
3392
  };
3122
3393
  const color = colors[role];
3123
3394
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles9.roleBadge, backgroundColor: color.bg, color: color.text }, children: role });
@@ -3726,45 +3997,6 @@ var styles12 = {
3726
3997
  letterSpacing: "0.4px"
3727
3998
  }
3728
3999
  };
3729
-
3730
- // src/core/styles.ts
3731
- var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
3732
- var ROOT_CLASS = "natoe-colab-root";
3733
- var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
3734
- var GLOBAL_CSS = `
3735
- @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
3736
-
3737
- @keyframes natoe-colab-message-in {
3738
- from { opacity: 0; transform: translateY(4px); }
3739
- to { opacity: 1; transform: translateY(0); }
3740
- }
3741
-
3742
- /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
3743
- get heavier borders and stronger text without losing the package look. */
3744
- @media (prefers-contrast: more) {
3745
- .${ROOT_CLASS} {
3746
- color: #000000;
3747
- }
3748
- .${ROOT_CLASS} button {
3749
- outline: 1px solid currentColor;
3750
- }
3751
- }
3752
-
3753
- /* Honour reduced-motion preferences by killing entry animations. */
3754
- @media (prefers-reduced-motion: reduce) {
3755
- .${ROOT_CLASS} [data-natoe-message-bubble] {
3756
- animation: none !important;
3757
- }
3758
- }
3759
- `;
3760
- function ensureGlobalStyles() {
3761
- if (typeof document === "undefined") return;
3762
- if (document.getElementById(GLOBAL_STYLE_ID)) return;
3763
- const style = document.createElement("style");
3764
- style.id = GLOBAL_STYLE_ID;
3765
- style.textContent = GLOBAL_CSS;
3766
- document.head.appendChild(style);
3767
- }
3768
4000
  ensureGlobalStyles();
3769
4001
  function CollabPanel({
3770
4002
  orderId,
@@ -4013,6 +4245,7 @@ function CollabPopup({
4013
4245
  isOpen,
4014
4246
  onClose,
4015
4247
  onBack,
4248
+ onMinimize,
4016
4249
  initialPosition,
4017
4250
  width = 380,
4018
4251
  height = 520,
@@ -4128,35 +4361,48 @@ function CollabPopup({
4128
4361
  "button",
4129
4362
  {
4130
4363
  onClick: onBack,
4131
- style: styles13.titleButton,
4132
- "aria-label": "Go back",
4364
+ style: styles13.titleSquare,
4365
+ "aria-label": "Back to messages",
4133
4366
  title: "Back",
4134
4367
  type: "button",
4135
- children: "\u2190"
4368
+ children: /* @__PURE__ */ jsxRuntime.jsx(BackIcon, { size: 18, color: COLOR.neutral700 })
4136
4369
  }
4137
4370
  ),
4138
- /* @__PURE__ */ jsxRuntime.jsx("span", { id: titleId, style: { ...styles13.titleText, flex: 1 }, children: titleText }),
4371
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.titleCenter, children: [
4372
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, style: styles13.patientName, children: patientData.patientName || titleText }),
4373
+ (patientData.labName || patientData.displayOrderId) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.subRow, children: [
4374
+ patientData.labName && /* @__PURE__ */ jsxRuntime.jsx("span", { children: patientData.labName }),
4375
+ patientData.labName && patientData.displayOrderId && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles13.subDot, "aria-hidden": "true", children: "\xB7" }),
4376
+ patientData.displayOrderId && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles13.mono, children: [
4377
+ "#",
4378
+ patientData.displayOrderId.slice(-4)
4379
+ ] })
4380
+ ] })
4381
+ ] }),
4139
4382
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles13.titleActions, children: [
4140
4383
  /* @__PURE__ */ jsxRuntime.jsx(
4141
4384
  "button",
4142
4385
  {
4143
- onClick: () => setIsMinimized(!isMinimized),
4144
- style: styles13.titleButton,
4145
- "aria-label": isMinimized ? "Expand chat" : "Minimize chat",
4146
- title: isMinimized ? "Expand" : "Minimize",
4386
+ onClick: () => {
4387
+ if (onMinimize) onMinimize();
4388
+ else setIsMinimized(!isMinimized);
4389
+ },
4390
+ style: styles13.titleSquare,
4391
+ "aria-label": onMinimize ? "Minimize chat" : isMinimized ? "Expand chat" : "Minimize chat",
4392
+ title: onMinimize ? "Minimize" : isMinimized ? "Expand" : "Minimize",
4147
4393
  type: "button",
4148
- children: isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, { size: 16, color: COLOR.slate200 }) : /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, { size: 16, color: COLOR.slate200 })
4394
+ children: onMinimize || !isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, { size: 18, color: COLOR.neutral700 }) : /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, { size: 18, color: COLOR.neutral700 })
4149
4395
  }
4150
4396
  ),
4151
4397
  /* @__PURE__ */ jsxRuntime.jsx(
4152
4398
  "button",
4153
4399
  {
4154
4400
  onClick: onClose,
4155
- style: styles13.titleButton,
4401
+ style: styles13.titleSquare,
4156
4402
  "aria-label": "Close chat",
4157
4403
  title: "Close",
4158
4404
  type: "button",
4159
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 16, color: COLOR.slate200 })
4405
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 18, color: COLOR.neutral700 })
4160
4406
  }
4161
4407
  )
4162
4408
  ] })
@@ -4179,51 +4425,78 @@ var styles13 = {
4179
4425
  container: {
4180
4426
  position: "fixed",
4181
4427
  zIndex: Z_INDEX.dialog,
4182
- borderRadius: RADIUS.lg,
4183
- boxShadow: SHADOW.dialog,
4428
+ borderRadius: "20px",
4429
+ boxShadow: "0 20px 48px rgba(20, 21, 28, 0.16), 0 6px 16px rgba(20, 21, 28, 0.08)",
4184
4430
  overflow: "hidden",
4185
4431
  display: "flex",
4186
4432
  flexDirection: "column",
4187
4433
  backgroundColor: COLOR.white,
4188
- border: `1px solid ${COLOR.neutral200}`,
4434
+ border: `1px solid ${COLOR.neutral300}`,
4189
4435
  transition: "height 0.2s ease",
4190
4436
  fontFamily: FONT_STACK
4191
4437
  },
4192
4438
  titleBar: {
4193
4439
  display: "flex",
4194
- justifyContent: "space-between",
4195
4440
  alignItems: "center",
4196
- padding: `0 ${SPACE.S2} 0 ${SPACE.S4}`,
4197
- height: "52px",
4198
- backgroundColor: COLOR.slate800,
4441
+ gap: SPACE.S2,
4442
+ padding: `10px ${SPACE.S3}`,
4443
+ backgroundColor: COLOR.white,
4444
+ borderBottom: `1px solid ${COLOR.neutral200}`,
4199
4445
  cursor: "grab",
4200
4446
  userSelect: "none",
4201
4447
  flexShrink: 0
4202
4448
  },
4203
- titleText: {
4204
- fontSize: FONT_SIZE.md,
4205
- fontWeight: FONT_WEIGHT.medium,
4206
- color: COLOR.white,
4449
+ titleCenter: {
4450
+ flex: 1,
4451
+ minWidth: 0,
4452
+ display: "flex",
4453
+ flexDirection: "column",
4454
+ gap: "2px"
4455
+ },
4456
+ patientName: {
4457
+ margin: 0,
4458
+ fontSize: FONT_SIZE.lg,
4459
+ fontWeight: FONT_WEIGHT.bold,
4460
+ letterSpacing: "-0.01em",
4461
+ color: COLOR.neutral900,
4207
4462
  overflow: "hidden",
4208
4463
  textOverflow: "ellipsis",
4209
4464
  whiteSpace: "nowrap"
4210
4465
  },
4466
+ subRow: {
4467
+ display: "flex",
4468
+ alignItems: "center",
4469
+ gap: SPACE.S2,
4470
+ fontSize: FONT_SIZE.sm,
4471
+ color: COLOR.neutral500,
4472
+ overflow: "hidden",
4473
+ textOverflow: "ellipsis",
4474
+ whiteSpace: "nowrap"
4475
+ },
4476
+ subDot: {
4477
+ color: COLOR.neutral400
4478
+ },
4479
+ mono: {
4480
+ fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
4481
+ fontVariantNumeric: "tabular-nums"
4482
+ },
4211
4483
  titleActions: {
4212
4484
  display: "flex",
4213
4485
  gap: SPACE.S1,
4214
4486
  flexShrink: 0
4215
4487
  },
4216
- titleButton: {
4217
- width: SIZE.control,
4218
- height: SIZE.control,
4488
+ titleSquare: {
4489
+ width: "36px",
4490
+ height: "36px",
4219
4491
  display: "flex",
4220
4492
  alignItems: "center",
4221
4493
  justifyContent: "center",
4222
- backgroundColor: "transparent",
4223
- border: "none",
4494
+ backgroundColor: COLOR.neutral100,
4495
+ border: `1px solid ${COLOR.neutral200}`,
4224
4496
  borderRadius: RADIUS.md,
4225
4497
  cursor: "pointer",
4226
- color: COLOR.slate200
4498
+ color: COLOR.neutral700,
4499
+ flexShrink: 0
4227
4500
  },
4228
4501
  panelWrapper: {
4229
4502
  flex: 1,
@@ -4527,10 +4800,14 @@ function InlineMessageRow({ message }) {
4527
4800
  }
4528
4801
  function RoleDot({ role }) {
4529
4802
  const colors = {
4530
- radiologist: "#7c3aed",
4803
+ radiologist: "#4f46e5",
4804
+ // indigo
4531
4805
  lab: "#2563eb",
4532
- physician: "#059669",
4533
- admin: "#dc2626"
4806
+ // brand blue
4807
+ physician: "#0284c7",
4808
+ // sky
4809
+ admin: "#64748b"
4810
+ // slate
4534
4811
  };
4535
4812
  return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.roleDot, backgroundColor: colors[role] } });
4536
4813
  }
@@ -4952,6 +5229,10 @@ function ConversationListItem({
4952
5229
  }) {
4953
5230
  const hasUnread = item.unreadCount > 0;
4954
5231
  const displayName = item.name || item.patientSnapshot?.patientName || "Unknown";
5232
+ const studyType = item.patientSnapshot?.studyType;
5233
+ const patientId = item.patientSnapshot?.patientId;
5234
+ const showStudyRow = !!(studyType || patientId);
5235
+ const initials = computeInitials2(item.patientSnapshot?.patientName || displayName);
4955
5236
  return /* @__PURE__ */ jsxRuntime.jsxs(
4956
5237
  "button",
4957
5238
  {
@@ -4959,15 +5240,11 @@ function ConversationListItem({
4959
5240
  onClick,
4960
5241
  style: {
4961
5242
  ...styles15.container,
4962
- ...isSelected ? styles15.selected : {},
4963
- ...hasUnread ? styles15.unread : {}
5243
+ ...isSelected ? styles15.selected : {}
4964
5244
  },
4965
5245
  type: "button",
4966
5246
  children: [
4967
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.avatarWrapper, children: [
4968
- item.picture ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.picture, alt: "", style: styles15.avatar }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles15.avatarFallback, children: (displayName).charAt(0).toUpperCase() }),
4969
- hasUnread && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.unreadDot })
4970
- ] }),
5247
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles15.avatarWrapper, children: item.picture ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.picture, alt: "", style: styles15.avatarImg }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles15.avatarTile, "aria-hidden": "true", children: initials }) }),
4971
5248
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.content, children: [
4972
5249
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.topRow, children: [
4973
5250
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4991,101 +5268,120 @@ function ConversationListItem({
4991
5268
  }
4992
5269
  )
4993
5270
  ] }),
5271
+ showStudyRow && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.studyRow, children: [
5272
+ studyType && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.studyType, children: studyType }),
5273
+ studyType && patientId && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.studyDivider, children: "\xB7" }),
5274
+ patientId && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles15.studyId, children: [
5275
+ "MRN ",
5276
+ patientId
5277
+ ] })
5278
+ ] }),
4994
5279
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles15.bottomRow, children: [
4995
- /* @__PURE__ */ jsxRuntime.jsx(
4996
- "span",
4997
- {
4998
- style: {
4999
- ...styles15.preview,
5000
- ...hasUnread ? styles15.previewUnread : {}
5001
- },
5002
- children: renderLastMessagePreview(item.lastMessage)
5003
- }
5004
- ),
5005
- hasUnread && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.badge, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
5280
+ /* @__PURE__ */ jsxRuntime.jsx(PreviewLine, { message: item.lastMessage, hasUnread }),
5281
+ hasUnread && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.badge, "aria-label": `${item.unreadCount} unread`, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
5006
5282
  ] })
5007
5283
  ] })
5008
5284
  ]
5009
5285
  }
5010
5286
  );
5011
5287
  }
5012
- function renderLastMessagePreview(message) {
5013
- if (!message) return "No messages yet";
5014
- const prefix = message.senderName ? `${message.senderName}: ` : "";
5288
+ function PreviewLine({ message, hasUnread }) {
5289
+ if (!message) {
5290
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.preview, children: "No messages yet" });
5291
+ }
5292
+ if (message.type === "system") {
5293
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles15.preview, children: message.body });
5294
+ }
5295
+ const text = previewText(message);
5296
+ const sender = message.senderName ? `${message.senderName.split(" ")[0]}:` : "";
5297
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5298
+ "span",
5299
+ {
5300
+ style: {
5301
+ ...styles15.preview,
5302
+ ...hasUnread ? styles15.previewUnread : {}
5303
+ },
5304
+ children: [
5305
+ sender && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles15.previewSender, children: [
5306
+ sender,
5307
+ " "
5308
+ ] }),
5309
+ text
5310
+ ]
5311
+ }
5312
+ );
5313
+ }
5314
+ function previewText(message) {
5015
5315
  switch (message.type) {
5016
5316
  case "audio":
5017
- return `${prefix}\u{1F3A4} Voice message`;
5317
+ return "Voice message";
5018
5318
  case "image":
5019
- return `${prefix}\u{1F4F7} ${message.fileName || "Image"}`;
5319
+ return message.fileName || "Image";
5020
5320
  case "file":
5021
- return `${prefix}\u{1F4CE} ${message.fileName || "File"}`;
5321
+ return message.fileName || "File";
5022
5322
  case "deep_link":
5023
- return `${prefix}\u{1F517} ${message.body}`;
5024
- case "system":
5025
- return message.body;
5323
+ return message.body || "Shared link";
5026
5324
  default: {
5027
5325
  const body = message.body || "";
5028
- const preview = body.length > 60 ? `${body.slice(0, 60)}\u2026` : body;
5029
- return `${prefix}${preview}`;
5326
+ return body.length > 60 ? `${body.slice(0, 60)}\u2026` : body;
5030
5327
  }
5031
5328
  }
5032
5329
  }
5330
+ function computeInitials2(name) {
5331
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("") || "?";
5332
+ }
5033
5333
  var styles15 = {
5034
5334
  container: {
5035
5335
  display: "flex",
5036
- alignItems: "center",
5037
- gap: SPACE.S3,
5038
- padding: `${SPACE.S3} ${SPACE.S3}`,
5336
+ alignItems: "flex-start",
5337
+ gap: "14px",
5039
5338
  width: "100%",
5339
+ padding: `14px ${SPACE.S5}`,
5340
+ minHeight: "84px",
5040
5341
  backgroundColor: "transparent",
5041
5342
  border: "none",
5042
- borderBottom: `1px solid ${COLOR.neutral100}`,
5343
+ borderLeft: "4px solid transparent",
5043
5344
  cursor: "pointer",
5044
- textAlign: "left"
5345
+ textAlign: "left",
5346
+ color: "inherit",
5347
+ fontFamily: "inherit",
5348
+ transition: "background-color 120ms ease"
5045
5349
  },
5046
5350
  selected: {
5047
- backgroundColor: COLOR.primaryBg
5351
+ backgroundColor: COLOR.primaryBg,
5352
+ borderLeftColor: COLOR.primary
5048
5353
  },
5049
- unread: {},
5050
5354
  avatarWrapper: {
5051
- position: "relative",
5052
- width: SIZE.avatar,
5053
- height: SIZE.avatar,
5355
+ width: "52px",
5356
+ height: "52px",
5054
5357
  flexShrink: 0
5055
5358
  },
5056
- avatar: {
5057
- width: SIZE.avatar,
5058
- height: SIZE.avatar,
5059
- borderRadius: RADIUS.full,
5060
- objectFit: "cover"
5359
+ avatarImg: {
5360
+ width: "52px",
5361
+ height: "52px",
5362
+ borderRadius: RADIUS.xl,
5363
+ objectFit: "cover",
5364
+ boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
5061
5365
  },
5062
- avatarFallback: {
5063
- width: SIZE.avatar,
5064
- height: SIZE.avatar,
5065
- borderRadius: RADIUS.full,
5066
- backgroundColor: COLOR.neutral200,
5366
+ avatarTile: {
5367
+ width: "52px",
5368
+ height: "52px",
5369
+ borderRadius: RADIUS.xl,
5370
+ backgroundColor: COLOR.primary,
5371
+ color: COLOR.white,
5067
5372
  display: "flex",
5068
5373
  alignItems: "center",
5069
5374
  justifyContent: "center",
5070
- fontSize: FONT_SIZE.md,
5071
- fontWeight: FONT_WEIGHT.semibold,
5072
- color: COLOR.neutral500
5073
- },
5074
- unreadDot: {
5075
- position: "absolute",
5076
- top: "0",
5077
- left: "0",
5078
- width: SIZE.dot,
5079
- height: SIZE.dot,
5080
- borderRadius: RADIUS.full,
5081
- backgroundColor: COLOR.primary,
5082
- border: `2px solid ${COLOR.white}`
5375
+ fontSize: FONT_SIZE.lg,
5376
+ fontWeight: FONT_WEIGHT.bold,
5377
+ letterSpacing: "0.02em",
5378
+ boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
5083
5379
  },
5084
5380
  content: {
5085
5381
  flex: 1,
5086
5382
  display: "flex",
5087
5383
  flexDirection: "column",
5088
- gap: "2px",
5384
+ gap: "3px",
5089
5385
  minWidth: 0
5090
5386
  },
5091
5387
  topRow: {
@@ -5110,123 +5406,191 @@ var styles15 = {
5110
5406
  time: {
5111
5407
  fontSize: FONT_SIZE.xs,
5112
5408
  color: COLOR.neutral500,
5409
+ fontVariantNumeric: "tabular-nums",
5113
5410
  flexShrink: 0
5114
5411
  },
5115
5412
  timeUnread: {
5116
5413
  color: COLOR.primary,
5117
5414
  fontWeight: FONT_WEIGHT.semibold
5118
5415
  },
5416
+ studyRow: {
5417
+ display: "flex",
5418
+ alignItems: "center",
5419
+ gap: SPACE.S2,
5420
+ minWidth: 0
5421
+ },
5422
+ studyType: {
5423
+ fontSize: FONT_SIZE.sm,
5424
+ color: COLOR.neutral500,
5425
+ overflow: "hidden",
5426
+ textOverflow: "ellipsis",
5427
+ whiteSpace: "nowrap"
5428
+ },
5429
+ studyDivider: {
5430
+ fontSize: FONT_SIZE.sm,
5431
+ color: COLOR.neutral400,
5432
+ flexShrink: 0
5433
+ },
5434
+ studyId: {
5435
+ fontSize: FONT_SIZE.sm,
5436
+ color: COLOR.neutral500,
5437
+ fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
5438
+ fontVariantNumeric: "tabular-nums",
5439
+ flexShrink: 0
5440
+ },
5119
5441
  bottomRow: {
5120
5442
  display: "flex",
5121
5443
  justifyContent: "space-between",
5122
5444
  alignItems: "center",
5123
- gap: SPACE.S2
5445
+ gap: SPACE.S2,
5446
+ marginTop: "2px"
5124
5447
  },
5125
5448
  preview: {
5126
5449
  fontSize: FONT_SIZE.sm,
5127
- color: COLOR.neutral600,
5450
+ color: COLOR.neutral500,
5128
5451
  overflow: "hidden",
5129
5452
  textOverflow: "ellipsis",
5130
5453
  whiteSpace: "nowrap",
5131
5454
  flex: 1,
5132
5455
  minWidth: 0
5133
5456
  },
5457
+ previewSender: {
5458
+ color: COLOR.neutral700,
5459
+ fontWeight: FONT_WEIGHT.semibold
5460
+ },
5134
5461
  previewUnread: {
5135
5462
  color: COLOR.neutral900,
5136
5463
  fontWeight: FONT_WEIGHT.medium
5137
5464
  },
5138
5465
  badge: {
5139
- minWidth: "20px",
5140
- height: "20px",
5141
- padding: `0 ${SPACE.S2}`,
5466
+ minWidth: "22px",
5467
+ height: "22px",
5468
+ padding: "0 9px",
5142
5469
  fontSize: FONT_SIZE.xs,
5143
- fontWeight: FONT_WEIGHT.semibold,
5470
+ fontWeight: FONT_WEIGHT.bold,
5144
5471
  color: COLOR.white,
5145
- backgroundColor: COLOR.primary,
5472
+ backgroundColor: COLOR.danger,
5146
5473
  borderRadius: RADIUS.pill,
5147
5474
  display: "flex",
5148
5475
  alignItems: "center",
5149
5476
  justifyContent: "center",
5477
+ fontVariantNumeric: "tabular-nums",
5150
5478
  flexShrink: 0
5151
5479
  }
5152
5480
  };
5481
+ function SearchIcon({ size = 20, color = "currentColor" }) {
5482
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5483
+ "svg",
5484
+ {
5485
+ xmlns: "http://www.w3.org/2000/svg",
5486
+ width: size,
5487
+ height: size,
5488
+ viewBox: "0 0 24 24",
5489
+ fill: "none",
5490
+ stroke: color,
5491
+ strokeWidth: "2",
5492
+ strokeLinecap: "round",
5493
+ strokeLinejoin: "round",
5494
+ "aria-hidden": "true",
5495
+ children: [
5496
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
5497
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 20l-4-4" })
5498
+ ]
5499
+ }
5500
+ );
5501
+ }
5153
5502
  function ConversationList({
5154
5503
  conversations,
5155
5504
  selectedId,
5156
5505
  isLoading,
5157
5506
  error,
5158
5507
  onSelect,
5159
- title = "Conversations",
5160
5508
  className
5161
5509
  }) {
5162
5510
  const [query, setQuery] = React4.useState("");
5163
- const [showUnreadOnly, setShowUnreadOnly] = React4.useState(false);
5511
+ const [filter, setFilter] = React4.useState("all");
5512
+ const [searchFocused, setSearchFocused] = React4.useState(false);
5513
+ const unreadCount = React4.useMemo(
5514
+ () => conversations.filter((c) => c.unreadCount > 0).length,
5515
+ [conversations]
5516
+ );
5517
+ const autoDefaultedRef = React4.useRef(false);
5518
+ React4.useEffect(() => {
5519
+ if (autoDefaultedRef.current) return;
5520
+ if (conversations.length === 0) return;
5521
+ autoDefaultedRef.current = true;
5522
+ if (unreadCount > 0) setFilter("unread");
5523
+ }, [conversations.length, unreadCount]);
5164
5524
  const filtered = React4.useMemo(() => {
5165
5525
  let result = conversations;
5166
- if (showUnreadOnly) {
5526
+ if (filter === "unread") {
5167
5527
  result = result.filter((c) => c.unreadCount > 0);
5168
5528
  }
5169
5529
  const q = query.trim().toLowerCase();
5170
5530
  if (q) {
5171
5531
  result = result.filter((c) => {
5172
- const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
5173
5532
  const name = c.name.toLowerCase();
5174
- return name.includes(q) || patient.includes(q);
5533
+ const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
5534
+ const studyType = c.patientSnapshot?.studyType?.toLowerCase() || "";
5535
+ const patientId = c.patientSnapshot?.patientId?.toLowerCase() || "";
5536
+ return name.includes(q) || patient.includes(q) || studyType.includes(q) || patientId.includes(q);
5175
5537
  });
5176
5538
  }
5177
5539
  return result;
5178
- }, [conversations, query, showUnreadOnly]);
5179
- const totalUnread = conversations.reduce((sum, c) => sum + c.unreadCount, 0);
5540
+ }, [conversations, query, filter]);
5180
5541
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: styles16.container, children: [
5181
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.header, children: [
5182
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.titleRow, children: [
5183
- /* @__PURE__ */ jsxRuntime.jsx("h3", { style: styles16.title, children: title }),
5184
- totalUnread > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles16.totalBadge, children: totalUnread })
5185
- ] }),
5186
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.searchRow, children: /* @__PURE__ */ jsxRuntime.jsx(
5187
- "input",
5542
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.tabs, children: [
5543
+ /* @__PURE__ */ jsxRuntime.jsx(
5544
+ TabButton,
5188
5545
  {
5189
- type: "text",
5190
- value: query,
5191
- onChange: (e) => setQuery(e.target.value),
5192
- placeholder: "Search patients or channels...",
5193
- style: styles16.searchInput
5546
+ label: "Unread",
5547
+ count: unreadCount,
5548
+ isActive: filter === "unread",
5549
+ onClick: () => setFilter("unread")
5194
5550
  }
5195
- ) }),
5196
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.filterRow, children: [
5197
- /* @__PURE__ */ jsxRuntime.jsx(
5198
- "button",
5199
- {
5200
- onClick: () => setShowUnreadOnly(false),
5201
- style: {
5202
- ...styles16.filterButton,
5203
- ...!showUnreadOnly ? styles16.filterButtonActive : {}
5204
- },
5205
- type: "button",
5206
- children: "All"
5207
- }
5208
- ),
5209
- /* @__PURE__ */ jsxRuntime.jsxs(
5210
- "button",
5211
- {
5212
- onClick: () => setShowUnreadOnly(true),
5213
- style: {
5214
- ...styles16.filterButton,
5215
- ...showUnreadOnly ? styles16.filterButtonActive : {}
5216
- },
5217
- type: "button",
5218
- children: [
5219
- "Unread ",
5220
- totalUnread > 0 && `(${totalUnread})`
5221
- ]
5222
- }
5223
- )
5224
- ] })
5551
+ ),
5552
+ /* @__PURE__ */ jsxRuntime.jsx(
5553
+ TabButton,
5554
+ {
5555
+ label: "All",
5556
+ count: conversations.length,
5557
+ isActive: filter === "all",
5558
+ onClick: () => setFilter("all")
5559
+ }
5560
+ )
5225
5561
  ] }),
5562
+ /* @__PURE__ */ jsxRuntime.jsxs(
5563
+ "div",
5564
+ {
5565
+ style: {
5566
+ ...styles16.search,
5567
+ ...searchFocused ? styles16.searchFocused : {}
5568
+ },
5569
+ children: [
5570
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles16.searchIcon, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, { size: 20, color: COLOR.neutral500 }) }),
5571
+ /* @__PURE__ */ jsxRuntime.jsx(
5572
+ "input",
5573
+ {
5574
+ type: "text",
5575
+ value: query,
5576
+ onChange: (e) => setQuery(e.target.value),
5577
+ onFocus: () => setSearchFocused(true),
5578
+ onBlur: () => setSearchFocused(false),
5579
+ placeholder: "Search by patient name or MRN",
5580
+ "aria-label": "Search conversations",
5581
+ style: styles16.searchInput
5582
+ }
5583
+ )
5584
+ ]
5585
+ }
5586
+ ),
5226
5587
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.list, children: [
5227
- isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.state, children: "Loading conversations..." }),
5228
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.errorState, children: error }),
5229
- !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.state, children: query ? "No matching conversations" : "No conversations yet" }),
5588
+ isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.loading, children: [
5589
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16 }),
5590
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Loading conversations\u2026" })
5591
+ ] }),
5592
+ error && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles16.errorState, children: error }),
5593
+ !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { filter, hasQuery: query.trim().length > 0 }),
5230
5594
  filtered.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
5231
5595
  ConversationListItem,
5232
5596
  {
@@ -5239,95 +5603,178 @@ function ConversationList({
5239
5603
  ] })
5240
5604
  ] });
5241
5605
  }
5606
+ function TabButton({
5607
+ label,
5608
+ count,
5609
+ isActive,
5610
+ onClick
5611
+ }) {
5612
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5613
+ "button",
5614
+ {
5615
+ type: "button",
5616
+ onClick,
5617
+ style: {
5618
+ ...styles16.tab,
5619
+ ...isActive ? styles16.tabActive : {}
5620
+ },
5621
+ children: [
5622
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
5623
+ /* @__PURE__ */ jsxRuntime.jsx(
5624
+ "span",
5625
+ {
5626
+ style: {
5627
+ ...styles16.tabCount,
5628
+ ...isActive ? styles16.tabCountActive : {}
5629
+ },
5630
+ children: count
5631
+ }
5632
+ )
5633
+ ]
5634
+ }
5635
+ );
5636
+ }
5637
+ function EmptyState({ filter, hasQuery }) {
5638
+ let title = "No conversations yet";
5639
+ let subtitle = "New chats will show up here as cases come in.";
5640
+ if (filter === "unread") {
5641
+ title = "No unread messages";
5642
+ subtitle = "You're all caught up.";
5643
+ } else if (hasQuery) {
5644
+ title = "No conversations match";
5645
+ subtitle = "Try a different search.";
5646
+ }
5647
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles16.empty, children: [
5648
+ /* @__PURE__ */ jsxRuntime.jsx(ChatIllustration, { size: 88 }),
5649
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles16.emptyTitle, children: title }),
5650
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles16.emptySubtitle, children: subtitle })
5651
+ ] });
5652
+ }
5242
5653
  var styles16 = {
5243
5654
  container: {
5244
5655
  display: "flex",
5245
5656
  flexDirection: "column",
5246
5657
  height: "100%",
5247
- backgroundColor: COLOR.white,
5248
- borderRight: `1px solid ${COLOR.neutral200}`
5658
+ backgroundColor: COLOR.white
5249
5659
  },
5250
- header: {
5251
- padding: SPACE.S3,
5660
+ // Tabs sit at the top of the panel now (header section removed).
5661
+ // A bit more top padding so they don't crowd the top edge.
5662
+ tabs: {
5663
+ display: "flex",
5664
+ gap: SPACE.S2,
5665
+ padding: `${SPACE.S5} ${SPACE.S5} ${SPACE.S2}`,
5252
5666
  borderBottom: `1px solid ${COLOR.neutral200}`,
5253
- backgroundColor: COLOR.neutral50,
5254
5667
  flexShrink: 0
5255
5668
  },
5256
- titleRow: {
5257
- display: "flex",
5669
+ tab: {
5670
+ display: "inline-flex",
5258
5671
  alignItems: "center",
5259
5672
  gap: SPACE.S2,
5260
- marginBottom: SPACE.S2
5261
- },
5262
- title: {
5263
- margin: 0,
5264
- fontSize: FONT_SIZE.md,
5673
+ padding: `${SPACE.S2} ${SPACE.S4}`,
5674
+ backgroundColor: "transparent",
5675
+ border: "none",
5676
+ borderRadius: RADIUS.lg,
5677
+ cursor: "pointer",
5678
+ color: COLOR.neutral500,
5679
+ fontFamily: "inherit",
5680
+ fontSize: FONT_SIZE.sm,
5265
5681
  fontWeight: FONT_WEIGHT.semibold,
5266
- color: COLOR.neutral900
5682
+ transition: "background-color 120ms ease, color 120ms ease"
5267
5683
  },
5268
- totalBadge: {
5269
- minWidth: "20px",
5270
- height: "20px",
5271
- padding: `0 ${SPACE.S2}`,
5684
+ tabActive: {
5685
+ backgroundColor: COLOR.primaryBg,
5686
+ color: COLOR.primary
5687
+ },
5688
+ tabCount: {
5272
5689
  fontSize: FONT_SIZE.xs,
5273
- fontWeight: FONT_WEIGHT.semibold,
5274
- color: COLOR.white,
5275
- backgroundColor: COLOR.primary,
5690
+ fontVariantNumeric: "tabular-nums",
5691
+ padding: "1px 8px",
5276
5692
  borderRadius: RADIUS.pill,
5693
+ backgroundColor: COLOR.neutral200,
5694
+ color: COLOR.neutral500,
5695
+ fontWeight: FONT_WEIGHT.semibold
5696
+ },
5697
+ tabCountActive: {
5698
+ backgroundColor: COLOR.primary,
5699
+ color: COLOR.white
5700
+ },
5701
+ search: {
5277
5702
  display: "flex",
5278
5703
  alignItems: "center",
5279
- justifyContent: "center"
5280
- },
5281
- searchRow: {
5282
- marginBottom: SPACE.S2
5704
+ gap: SPACE.S2,
5705
+ margin: `14px ${SPACE.S5} ${SPACE.S3}`,
5706
+ padding: `${SPACE.S3} 14px`,
5707
+ backgroundColor: COLOR.neutral100,
5708
+ border: `1.5px solid ${COLOR.neutral200}`,
5709
+ borderRadius: "14px",
5710
+ transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
5283
5711
  },
5284
- searchInput: {
5285
- width: "100%",
5286
- padding: `${SPACE.S2} ${SPACE.S3}`,
5287
- fontSize: FONT_SIZE.sm,
5288
- border: `1px solid ${COLOR.neutral200}`,
5289
- borderRadius: RADIUS.md,
5290
- outline: "none",
5291
- fontFamily: "inherit"
5712
+ searchFocused: {
5713
+ backgroundColor: COLOR.white,
5714
+ borderColor: COLOR.primary,
5715
+ boxShadow: `0 0 0 3px ${COLOR.primaryBg}`
5292
5716
  },
5293
- filterRow: {
5717
+ searchIcon: {
5294
5718
  display: "flex",
5295
- gap: SPACE.S1
5719
+ alignItems: "center",
5720
+ justifyContent: "center",
5721
+ flexShrink: 0
5296
5722
  },
5297
- filterButton: {
5723
+ searchInput: {
5298
5724
  flex: 1,
5299
- padding: `${SPACE.S1} ${SPACE.S2}`,
5300
- fontSize: FONT_SIZE.xs,
5301
- color: COLOR.neutral500,
5302
- backgroundColor: COLOR.white,
5303
- border: `1px solid ${COLOR.neutral200}`,
5304
- borderRadius: RADIUS.md,
5305
- cursor: "pointer"
5306
- },
5307
- filterButtonActive: {
5308
- color: COLOR.white,
5309
- backgroundColor: COLOR.primary,
5310
- borderColor: COLOR.primary,
5311
- fontWeight: FONT_WEIGHT.medium
5725
+ minWidth: 0,
5726
+ border: "none",
5727
+ background: "transparent",
5728
+ outline: "none",
5729
+ fontFamily: "inherit",
5730
+ fontSize: FONT_SIZE.md,
5731
+ color: COLOR.neutral900
5312
5732
  },
5313
5733
  list: {
5314
5734
  flex: 1,
5315
- overflowY: "auto"
5735
+ overflowY: "auto",
5736
+ padding: "4px 0 12px"
5316
5737
  },
5317
- state: {
5318
- padding: `${SPACE.S6} ${SPACE.S4}`,
5738
+ loading: {
5739
+ display: "flex",
5740
+ alignItems: "center",
5741
+ justifyContent: "center",
5742
+ gap: SPACE.S2,
5743
+ padding: `44px ${SPACE.S5}`,
5319
5744
  fontSize: FONT_SIZE.sm,
5320
- color: COLOR.neutral500,
5321
- textAlign: "center"
5745
+ color: COLOR.neutral500
5322
5746
  },
5323
5747
  errorState: {
5324
5748
  padding: SPACE.S4,
5749
+ margin: SPACE.S3,
5325
5750
  fontSize: FONT_SIZE.sm,
5326
5751
  color: COLOR.danger,
5327
5752
  backgroundColor: COLOR.dangerBg,
5328
- margin: SPACE.S3,
5753
+ border: `1px solid ${COLOR.dangerBorder}`,
5329
5754
  borderRadius: RADIUS.md,
5330
5755
  textAlign: "center"
5756
+ },
5757
+ empty: {
5758
+ display: "flex",
5759
+ flexDirection: "column",
5760
+ alignItems: "center",
5761
+ justifyContent: "center",
5762
+ padding: "44px 24px",
5763
+ textAlign: "center",
5764
+ color: COLOR.neutral500
5765
+ },
5766
+ emptyTitle: {
5767
+ margin: "12px 0 4px",
5768
+ fontSize: FONT_SIZE.md,
5769
+ fontWeight: FONT_WEIGHT.semibold,
5770
+ color: COLOR.neutral700
5771
+ },
5772
+ emptySubtitle: {
5773
+ margin: 0,
5774
+ fontSize: FONT_SIZE.sm,
5775
+ color: COLOR.neutral500,
5776
+ maxWidth: "280px",
5777
+ lineHeight: LINE_HEIGHT.normal
5331
5778
  }
5332
5779
  };
5333
5780
  ensureGlobalStyles();
@@ -5335,7 +5782,6 @@ var COMPACT_BREAKPOINT = 720;
5335
5782
  function CollabInbox({
5336
5783
  initialConversationId,
5337
5784
  onSelectConversation,
5338
- title,
5339
5785
  className,
5340
5786
  style
5341
5787
  }) {
@@ -5372,8 +5818,7 @@ function CollabInbox({
5372
5818
  selectedId,
5373
5819
  isLoading,
5374
5820
  error,
5375
- onSelect: handleSelect,
5376
- title
5821
+ onSelect: handleSelect
5377
5822
  }
5378
5823
  ) }),
5379
5824
  showDetail && /* @__PURE__ */ jsxRuntime.jsx("div", { style: isCompact ? styles17.mainCompact : styles17.main, children: selected ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -5386,7 +5831,7 @@ function CollabInbox({
5386
5831
  onBack: isCompact ? () => setSelectedId(null) : void 0
5387
5832
  },
5388
5833
  selected.id
5389
- ) : /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { hasAny: conversations.length > 0 }) })
5834
+ ) : /* @__PURE__ */ jsxRuntime.jsx(EmptyState2, { hasAny: conversations.length > 0 }) })
5390
5835
  ]
5391
5836
  }
5392
5837
  );
@@ -5420,7 +5865,7 @@ function parseChannelName(name, orderId) {
5420
5865
  orderId
5421
5866
  };
5422
5867
  }
5423
- function EmptyState({ hasAny }) {
5868
+ function EmptyState2({ hasAny }) {
5424
5869
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles17.empty, children: [
5425
5870
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles17.emptyIcon, children: "\u{1F4AC}" }),
5426
5871
  /* @__PURE__ */ jsxRuntime.jsx("h3", { style: styles17.emptyTitle, children: hasAny ? "Select a conversation" : "No conversations yet" }),
@@ -5826,7 +6271,10 @@ exports.ReplyPreview = ReplyPreview;
5826
6271
  exports.ReplyQuoteBlock = ReplyQuoteBlock;
5827
6272
  exports.SUPPORTED_IMAGE_TYPES = SUPPORTED_IMAGE_TYPES;
5828
6273
  exports.SeenByIndicator = SeenByIndicator;
6274
+ exports.THEME_DEFAULTS = THEME_DEFAULTS;
6275
+ exports.THEME_VAR = THEME_VAR;
5829
6276
  exports.TYPING_DEBOUNCE_MS = TYPING_DEBOUNCE_MS;
6277
+ exports.applyThemeOverrides = applyThemeOverrides;
5830
6278
  exports.useAudioRecorder = useAudioRecorder;
5831
6279
  exports.useChannelSettings = useChannelSettings;
5832
6280
  exports.useCollab = useCollab;