@natoe/colab 0.1.11 → 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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import React4, { createContext, forwardRef, useRef, useImperativeHandle, useEffect, useCallback, useState, useLayoutEffect, useContext, useId, useMemo } from 'react';
1
+ import React4, { createContext, forwardRef, useRef, useImperativeHandle, useLayoutEffect, useEffect, useCallback, useState, useContext, useId, useMemo } from 'react';
2
2
  import { Socket, Presence } from 'phoenix';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { createPortal } from 'react-dom';
@@ -315,6 +315,173 @@ var CollabSocket = class {
315
315
  this.config = null;
316
316
  }
317
317
  };
318
+
319
+ // src/core/theme.ts
320
+ var FONT_SIZE = {
321
+ xs: "12px",
322
+ sm: "13px",
323
+ md: "15px",
324
+ lg: "17px",
325
+ xxl: "28px"
326
+ };
327
+ var FONT_WEIGHT = {
328
+ regular: 400,
329
+ medium: 500,
330
+ semibold: 600,
331
+ bold: 700
332
+ };
333
+ var LINE_HEIGHT = {
334
+ tight: 1.3,
335
+ normal: 1.45};
336
+ var SPACE = {
337
+ S1: "4px",
338
+ S2: "8px",
339
+ S3: "12px",
340
+ S4: "16px",
341
+ S5: "20px",
342
+ S6: "24px",
343
+ S12: "48px"
344
+ };
345
+ var RADIUS = {
346
+ sm: "4px",
347
+ md: "8px",
348
+ lg: "12px",
349
+ xl: "16px",
350
+ pill: "9999px",
351
+ full: "50%"
352
+ };
353
+ var THEME_VAR = {
354
+ primary: "--natoe-colab-primary",
355
+ primaryHover: "--natoe-colab-primary-hover",
356
+ primaryBg: "--natoe-colab-primary-bg",
357
+ primaryFg: "--natoe-colab-primary-fg",
358
+ success: "--natoe-colab-success",
359
+ warning: "--natoe-colab-warning",
360
+ danger: "--natoe-colab-danger",
361
+ dangerBg: "--natoe-colab-danger-bg",
362
+ dangerBorder: "--natoe-colab-danger-border",
363
+ dangerFg: "--natoe-colab-danger-fg",
364
+ fontStack: "--natoe-colab-font-stack"
365
+ };
366
+ var THEME_DEFAULTS = {
367
+ primary: "#2563eb",
368
+ primaryHover: "#1d4ed8",
369
+ primaryBg: "#dbeafe",
370
+ primaryFg: "#1d4ed8",
371
+ success: "#059669",
372
+ warning: "#d97706",
373
+ danger: "#dc2626",
374
+ dangerBg: "#fef2f2",
375
+ dangerBorder: "#fecaca",
376
+ dangerFg: "#b91c1c",
377
+ fontStack: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
378
+ };
379
+ var cssVar = (name, fallback) => `var(${name}, ${fallback})`;
380
+ var COLOR = {
381
+ // Brand — themeable
382
+ primary: cssVar(THEME_VAR.primary, THEME_DEFAULTS.primary),
383
+ primaryHover: cssVar(THEME_VAR.primaryHover, THEME_DEFAULTS.primaryHover),
384
+ /** Tinted surface for chips/cards on brand-coloured states. */
385
+ primaryBg: cssVar(THEME_VAR.primaryBg, THEME_DEFAULTS.primaryBg),
386
+ primaryFg: cssVar(THEME_VAR.primaryFg, THEME_DEFAULTS.primaryFg),
387
+ // Neutral grayscale — not themeable, taken from Tailwind's zinc-leaning
388
+ // slate to match the package's existing tonal balance.
389
+ white: "#ffffff",
390
+ neutral50: "#f9fafb",
391
+ neutral100: "#f3f4f6",
392
+ neutral200: "#e5e7eb",
393
+ neutral300: "#d1d5db",
394
+ neutral400: "#9ca3af",
395
+ neutral500: "#6b7280",
396
+ neutral600: "#4b5563",
397
+ neutral700: "#374151",
398
+ neutral800: "#1f2937",
399
+ neutral900: "#111827",
400
+ slate800: "#1e293b",
401
+ // Semantic — themeable
402
+ success: cssVar(THEME_VAR.success, THEME_DEFAULTS.success),
403
+ warning: cssVar(THEME_VAR.warning, THEME_DEFAULTS.warning),
404
+ danger: cssVar(THEME_VAR.danger, THEME_DEFAULTS.danger),
405
+ dangerBg: cssVar(THEME_VAR.dangerBg, THEME_DEFAULTS.dangerBg),
406
+ dangerBorder: cssVar(THEME_VAR.dangerBorder, THEME_DEFAULTS.dangerBorder),
407
+ dangerFg: cssVar(THEME_VAR.dangerFg, THEME_DEFAULTS.dangerFg)
408
+ };
409
+ var SIZE = {
410
+ /** Default interactive height (text buttons, list rows). */
411
+ control: "40px",
412
+ /** Square icon-only buttons inside chrome (popup title, menu trigger). */
413
+ controlIcon: "36px"};
414
+ var SHADOW = {
415
+ /** Cards / floating popovers (message-actions menu). */
416
+ popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
417
+ toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
418
+ };
419
+ var Z_INDEX = {
420
+ /** Sticky day divider — above bubbles, below interactive popovers. */
421
+ sticky: 1,
422
+ /** Toasts inside a panel. */
423
+ toast: 20,
424
+ /** Floating CollabPopup dialog. */
425
+ dialog: 9999,
426
+ /** Portaled message-actions menu — must sit above the dialog. */
427
+ menu: 1e4
428
+ };
429
+
430
+ // src/core/styles.ts
431
+ var FONT_STACK = `var(${THEME_VAR.fontStack}, ${THEME_DEFAULTS.fontStack})`;
432
+ var ROOT_CLASS = "natoe-colab-root";
433
+ var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
434
+ var ROOT_DEFAULTS_BLOCK = Object.keys(THEME_DEFAULTS).map((key) => ` ${THEME_VAR[key]}: ${THEME_DEFAULTS[key]};`).join("\n");
435
+ var GLOBAL_CSS = `
436
+ :root {
437
+ ${ROOT_DEFAULTS_BLOCK}
438
+ }
439
+
440
+ @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
441
+
442
+ @keyframes natoe-colab-message-in {
443
+ from { opacity: 0; transform: translateY(4px); }
444
+ to { opacity: 1; transform: translateY(0); }
445
+ }
446
+
447
+ /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
448
+ get heavier borders and stronger text without losing the package look. */
449
+ @media (prefers-contrast: more) {
450
+ .${ROOT_CLASS} {
451
+ color: #000000;
452
+ }
453
+ .${ROOT_CLASS} button {
454
+ outline: 1px solid currentColor;
455
+ }
456
+ }
457
+
458
+ /* Honour reduced-motion preferences by killing entry animations. */
459
+ @media (prefers-reduced-motion: reduce) {
460
+ .${ROOT_CLASS} [data-natoe-message-bubble] {
461
+ animation: none !important;
462
+ }
463
+ }
464
+ `;
465
+ function ensureGlobalStyles() {
466
+ if (typeof document === "undefined") return;
467
+ if (document.getElementById(GLOBAL_STYLE_ID)) return;
468
+ const style = document.createElement("style");
469
+ style.id = GLOBAL_STYLE_ID;
470
+ style.textContent = GLOBAL_CSS;
471
+ document.head.appendChild(style);
472
+ }
473
+ function applyThemeOverrides(theme) {
474
+ if (typeof document === "undefined") return;
475
+ const root = document.documentElement;
476
+ Object.keys(THEME_VAR).forEach((key) => {
477
+ const value = theme?.[key];
478
+ if (value) {
479
+ root.style.setProperty(THEME_VAR[key], value);
480
+ } else {
481
+ root.style.removeProperty(THEME_VAR[key]);
482
+ }
483
+ });
484
+ }
318
485
  var CollabContext = createContext(null);
319
486
  function useCollab() {
320
487
  const context = useContext(CollabContext);
@@ -333,6 +500,9 @@ function CollabProvider({ config, apiBaseUrl, children }) {
333
500
  const pendingResolvers = useRef(/* @__PURE__ */ new Map());
334
501
  const previewCache = useRef(/* @__PURE__ */ new Map());
335
502
  const batchScheduled = useRef(false);
503
+ useEffect(() => {
504
+ applyThemeOverrides(config.theme);
505
+ }, [config.theme]);
336
506
  useEffect(() => {
337
507
  if (typeof window === "undefined") return;
338
508
  let s = socket;
@@ -548,6 +718,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
548
718
  config,
549
719
  apiBaseUrl,
550
720
  totalUnread,
721
+ unreadCounts,
551
722
  requestPreview,
552
723
  invalidatePreview,
553
724
  fetchMessages,
@@ -569,7 +740,11 @@ function buildChannelName(patientData) {
569
740
  const patientPart = patientData.patientName?.trim() || "Unknown";
570
741
  const labPart = patientData.labName?.trim()?.slice(0, 24) || "";
571
742
  const last4 = (patientData.displayOrderId ?? "").trim().slice(-4) || "0000";
572
- return `${patientPart} - ${labPart} - ${last4}`;
743
+ return labPart ? `${patientPart} - ${labPart} - ${last4}` : `${patientPart} - ${last4}`;
744
+ }
745
+ function cleanChannelName(name) {
746
+ if (!name) return "";
747
+ return name.replace(/\s+-\s+-\s+/g, " - ").trim();
573
748
  }
574
749
 
575
750
  // src/hooks/useConversation.ts
@@ -604,8 +779,11 @@ function useConversation({
604
779
  const ensureConversationInFlight = useRef(null);
605
780
  const markedReadIdsRef = useRef(/* @__PURE__ */ new Set());
606
781
  const buildName = useCallback(
607
- () => buildChannelName(patientData),
608
- [patientData]
782
+ () => {
783
+ const labName = patientData.labName ?? (config.userRole === "lab" ? config.userName : void 0);
784
+ return buildChannelName({ ...patientData, labName });
785
+ },
786
+ [patientData, config.userRole, config.userName]
609
787
  );
610
788
  const joinChannel = useCallback(
611
789
  (conv) => {
@@ -718,26 +896,22 @@ function useConversation({
718
896
  setConversation(existingConv);
719
897
  setParticipants(preview.participants);
720
898
  joinChannel(existingConv);
721
- setMessages(preview.lastMessages);
722
- setHasMore(preview.messageCount > preview.lastMessages.length);
723
- setPinnedMessages(preview.lastMessages.filter((m) => m.isPinned));
724
- setIsLoading(false);
725
899
  if (loadHistory) {
726
900
  fetchMessages(preview.conversationId).then((history) => {
727
901
  if (cancelled) return;
728
- setMessages((prev) => {
729
- const seen = new Set(prev.map((m) => m.id));
730
- const older = history.filter((m) => !seen.has(m.id));
731
- return [...older, ...prev];
732
- });
733
- setHasMore(history.length > 0);
734
- setPinnedMessages((prev) => {
735
- const fromHistory = history.filter((m) => m.isPinned);
736
- const seen = new Set(prev.map((m) => m.id));
737
- return [...fromHistory.filter((m) => !seen.has(m.id)), ...prev];
738
- });
902
+ setMessages(history);
903
+ setHasMore(history.length >= MESSAGES_PAGE_SIZE);
904
+ setPinnedMessages(history.filter((m) => m.isPinned));
905
+ setIsLoading(false);
739
906
  }).catch(() => {
907
+ if (cancelled) return;
908
+ setIsLoading(false);
740
909
  });
910
+ } else {
911
+ setMessages([]);
912
+ setHasMore(false);
913
+ setPinnedMessages([]);
914
+ setIsLoading(false);
741
915
  }
742
916
  } catch (err) {
743
917
  if (cancelled) return;
@@ -818,7 +992,7 @@ function useConversation({
818
992
  const payload = {
819
993
  body,
820
994
  type: "text",
821
- ...replyTo ? { replyToId: replyTo.id } : {}
995
+ ...replyTo ? { reply_to_id: replyTo.id } : {}
822
996
  };
823
997
  const { conv, persistedByCreate } = await ensureConversation(payload);
824
998
  setReplyTo(null);
@@ -871,7 +1045,7 @@ function useConversation({
871
1045
  const payload = {
872
1046
  body: target.body,
873
1047
  type: "text",
874
- ...target.replyToId ? { replyToId: target.replyToId } : {}
1048
+ ...target.replyToId ? { reply_to_id: target.replyToId } : {}
875
1049
  };
876
1050
  try {
877
1051
  await socket.sendMessage(conversation.id, payload);
@@ -1196,106 +1370,10 @@ function BackIcon({ size = 22, color = "currentColor" }) {
1196
1370
  }
1197
1371
  );
1198
1372
  }
1199
-
1200
- // src/core/theme.ts
1201
- var FONT_SIZE = {
1202
- xs: "12px",
1203
- sm: "13px",
1204
- md: "15px",
1205
- lg: "17px",
1206
- xxl: "28px"
1207
- };
1208
- var FONT_WEIGHT = {
1209
- regular: 400,
1210
- medium: 500,
1211
- semibold: 600,
1212
- bold: 700
1213
- };
1214
- var LINE_HEIGHT = {
1215
- tight: 1.3,
1216
- normal: 1.45};
1217
- var SPACE = {
1218
- S1: "4px",
1219
- S2: "8px",
1220
- S3: "12px",
1221
- S4: "16px",
1222
- S5: "20px",
1223
- S6: "24px",
1224
- S12: "48px"
1225
- };
1226
- var RADIUS = {
1227
- sm: "4px",
1228
- md: "8px",
1229
- lg: "12px",
1230
- pill: "9999px",
1231
- full: "50%"
1232
- };
1233
- var COLOR = {
1234
- // Brand
1235
- primary: "#2563eb",
1236
- /** Tinted surface for chips/cards on brand-coloured states. */
1237
- primaryBg: "#dbeafe",
1238
- primaryFg: "#1d4ed8",
1239
- // Neutral grayscale — taken from Tailwind's zinc-leaning slate, the
1240
- // existing dominant family in the package.
1241
- white: "#ffffff",
1242
- neutral50: "#f9fafb",
1243
- neutral100: "#f3f4f6",
1244
- neutral200: "#e5e7eb",
1245
- neutral300: "#d1d5db",
1246
- neutral400: "#9ca3af",
1247
- neutral500: "#6b7280",
1248
- neutral600: "#4b5563",
1249
- neutral700: "#374151",
1250
- neutral800: "#1f2937",
1251
- neutral900: "#111827",
1252
- // Slate (used by day dividers + dialog title bar)
1253
- slate200: "#e2e8f0",
1254
- slate700: "#334155",
1255
- slate800: "#1e293b",
1256
- // Semantic
1257
- success: "#059669",
1258
- warning: "#d97706",
1259
- danger: "#dc2626",
1260
- dangerBg: "#fef2f2",
1261
- dangerBorder: "#fecaca",
1262
- dangerFg: "#b91c1c"
1263
- };
1264
- var SIZE = {
1265
- /** Default interactive height (text buttons, list rows). */
1266
- control: "40px",
1267
- /** Square icon-only buttons inside chrome (popup title, menu trigger). */
1268
- controlIcon: "36px",
1269
- /** Primary input controls (textarea send/attach). 44px = WCAG min. */
1270
- controlPrimary: "44px",
1271
- /** Avatar in the inbox row. */
1272
- avatar: "40px",
1273
- /** Unread dot. */
1274
- dot: "10px"
1275
- };
1276
- var SHADOW = {
1277
- /** Cards / floating popovers (message-actions menu). */
1278
- popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
1279
- /** Dialog (CollabPopup). */
1280
- dialog: "0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08)",
1281
- /** Subtle separator shadow (sticky day divider, toast). */
1282
- subtle: "0 1px 2px rgba(0, 0, 0, 0.04)",
1283
- toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
1284
- };
1285
- var Z_INDEX = {
1286
- /** Sticky day divider — above bubbles, below interactive popovers. */
1287
- sticky: 1,
1288
- /** Toasts inside a panel. */
1289
- toast: 20,
1290
- /** Floating CollabPopup dialog. */
1291
- dialog: 9999,
1292
- /** Portaled message-actions menu — must sit above the dialog. */
1293
- menu: 1e4
1294
- };
1295
1373
  function resolveDisplayName(patientData, displayName) {
1296
1374
  const localComplete = !!patientData.labName && !!patientData.displayOrderId;
1297
1375
  if (localComplete) return buildChannelName(patientData);
1298
- return displayName ?? buildChannelName(patientData);
1376
+ return cleanChannelName(displayName) || buildChannelName(patientData);
1299
1377
  }
1300
1378
  function PatientHeader({
1301
1379
  patientData,
@@ -1308,15 +1386,28 @@ function PatientHeader({
1308
1386
  }) {
1309
1387
  const hasDicom = !!(patientData.studyId && patientData.storageId);
1310
1388
  const meta = [];
1311
- if (patientData.patientAge) meta.push({ label: "Age", value: String(patientData.patientAge) });
1312
- if (patientData.patientSex) meta.push({ label: "Sex", value: String(patientData.patientSex) });
1313
- if (patientData.studyType) meta.push({ label: "Modality", value: patientData.studyType });
1314
- if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1315
- meta.push({ label: "Body part", value: patientData.bodyParts.join(", ") });
1389
+ if (patientData.patientAge && patientData.patientSex) {
1390
+ meta.push({
1391
+ key: "ageSex",
1392
+ label: "Age / Sex",
1393
+ value: `${patientData.patientAge} \xB7 ${patientData.patientSex}`
1394
+ });
1395
+ } else if (patientData.patientAge) {
1396
+ meta.push({ key: "age", label: "Age", value: String(patientData.patientAge) });
1397
+ } else if (patientData.patientSex) {
1398
+ meta.push({ key: "sex", label: "Sex", value: String(patientData.patientSex) });
1399
+ }
1400
+ if (patientData.studyType) {
1401
+ meta.push({ key: "modality", label: "Modality", value: patientData.studyType });
1316
1402
  }
1317
- if (patientData.referringPhysician) {
1318
- meta.push({ label: "Ref", value: patientData.referringPhysician });
1403
+ if (patientData.bodyParts && patientData.bodyParts.length > 0) {
1404
+ meta.push({
1405
+ key: "body",
1406
+ label: "Body part",
1407
+ value: patientData.bodyParts.join(", ")
1408
+ });
1319
1409
  }
1410
+ const hasActions = hasDicom && onOpenDicom || onOpenSettings;
1320
1411
  return /* @__PURE__ */ jsxs("div", { className, style: styles.container, children: [
1321
1412
  !hideName && /* @__PURE__ */ jsxs("div", { style: styles.nameRow, children: [
1322
1413
  onBack && /* @__PURE__ */ jsx(
@@ -1332,54 +1423,55 @@ function PatientHeader({
1332
1423
  ),
1333
1424
  /* @__PURE__ */ jsx("span", { style: styles.nameText, children: resolveDisplayName(patientData, displayName) })
1334
1425
  ] }),
1335
- meta.length > 0 && /* @__PURE__ */ jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxs("span", { style: styles.metaItem, children: [
1336
- /* @__PURE__ */ jsxs("span", { style: styles.metaLabel, children: [
1337
- item.label,
1338
- ":"
1339
- ] }),
1340
- " ",
1341
- /* @__PURE__ */ jsx("span", { style: styles.metaValue, children: item.value })
1342
- ] }, item.label)) }),
1343
- hasDicom && onOpenDicom || onOpenSettings ? /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
1344
- hasDicom && onOpenDicom && /* @__PURE__ */ jsxs(
1345
- "button",
1346
- {
1347
- onClick: onOpenDicom,
1348
- style: styles.dicomButton,
1349
- type: "button",
1350
- "aria-label": "View DICOM study",
1351
- children: [
1352
- /* @__PURE__ */ jsx(DicomIcon, { size: 18, color: COLOR.white }),
1353
- /* @__PURE__ */ jsx("span", { children: "View DICOM" })
1354
- ]
1355
- }
1356
- ),
1357
- onOpenSettings && /* @__PURE__ */ jsxs(
1358
- "button",
1359
- {
1360
- onClick: onOpenSettings,
1361
- style: styles.settingsButton,
1362
- type: "button",
1363
- "aria-label": "Open channel settings",
1364
- children: [
1365
- /* @__PURE__ */ jsx(SettingsIcon, { size: 18, color: COLOR.neutral700 }),
1366
- /* @__PURE__ */ jsx("span", { children: "Settings" })
1367
- ]
1368
- }
1369
- )
1370
- ] }) : null
1426
+ /* @__PURE__ */ jsxs("div", { style: styles.caseCard, children: [
1427
+ meta.length > 0 && /* @__PURE__ */ jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxs("div", { style: styles.metaCol, children: [
1428
+ /* @__PURE__ */ jsx("span", { style: styles.metaLabel, children: item.label }),
1429
+ /* @__PURE__ */ jsx("span", { style: styles.metaValue, children: item.value })
1430
+ ] }, item.key)) }),
1431
+ hasActions && /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
1432
+ hasDicom && onOpenDicom && /* @__PURE__ */ jsxs(
1433
+ "button",
1434
+ {
1435
+ onClick: onOpenDicom,
1436
+ style: styles.dicomButton,
1437
+ type: "button",
1438
+ "aria-label": "View DICOM study",
1439
+ children: [
1440
+ /* @__PURE__ */ jsx(DicomIcon, { size: 18, color: COLOR.primary }),
1441
+ /* @__PURE__ */ jsx("span", { children: "View DICOM" })
1442
+ ]
1443
+ }
1444
+ ),
1445
+ onOpenSettings && /* @__PURE__ */ jsx(
1446
+ "button",
1447
+ {
1448
+ onClick: onOpenSettings,
1449
+ style: styles.settingsIconButton,
1450
+ type: "button",
1451
+ "aria-label": "Open channel settings",
1452
+ title: "Settings",
1453
+ children: /* @__PURE__ */ jsx(SettingsIcon, { size: 18, color: COLOR.neutral600 })
1454
+ }
1455
+ )
1456
+ ] })
1457
+ ] })
1371
1458
  ] });
1372
1459
  }
1373
1460
  var styles = {
1374
1461
  container: {
1375
- padding: `${SPACE.S3} ${SPACE.S4}`,
1376
- borderBottom: `1px solid ${COLOR.neutral200}`,
1377
- backgroundColor: COLOR.neutral50
1462
+ display: "flex",
1463
+ flexDirection: "column",
1464
+ backgroundColor: COLOR.primaryBg,
1465
+ borderBottom: `1px solid ${COLOR.neutral200}`
1378
1466
  },
1467
+ // Optional name row, only when `hideName` is false (compact-inbox path)
1379
1468
  nameRow: {
1380
1469
  display: "flex",
1381
1470
  alignItems: "center",
1382
- gap: SPACE.S2
1471
+ gap: SPACE.S2,
1472
+ padding: `${SPACE.S3} ${SPACE.S4} ${SPACE.S2}`,
1473
+ borderBottom: `1px solid ${COLOR.neutral200}`,
1474
+ backgroundColor: COLOR.white
1383
1475
  },
1384
1476
  backButton: {
1385
1477
  width: SIZE.controlIcon,
@@ -1396,65 +1488,90 @@ var styles = {
1396
1488
  },
1397
1489
  nameText: {
1398
1490
  fontSize: FONT_SIZE.lg,
1399
- fontWeight: FONT_WEIGHT.semibold,
1491
+ fontWeight: FONT_WEIGHT.bold,
1400
1492
  color: COLOR.neutral900,
1401
1493
  lineHeight: LINE_HEIGHT.tight,
1494
+ letterSpacing: "-0.01em",
1402
1495
  minWidth: 0,
1403
1496
  overflow: "hidden",
1404
1497
  textOverflow: "ellipsis",
1405
1498
  whiteSpace: "nowrap"
1406
1499
  },
1500
+ // Case card: meta columns flowing in a wrap-row, actions stacked
1501
+ // below. Each meta column is a small label-above-value pair so the
1502
+ // user can scan field names quickly without a legend.
1503
+ caseCard: {
1504
+ display: "flex",
1505
+ flexDirection: "column",
1506
+ gap: SPACE.S3,
1507
+ padding: `${SPACE.S3} ${SPACE.S5}`
1508
+ },
1509
+ // Wrap-row of stacked label/value columns. Compact column gap keeps
1510
+ // the row dense without crowding; row gap kicks in when columns
1511
+ // wrap to a second line on narrow popups.
1407
1512
  metaRow: {
1408
1513
  display: "flex",
1409
1514
  flexWrap: "wrap",
1410
- columnGap: SPACE.S3,
1411
- rowGap: SPACE.S1,
1412
- marginTop: SPACE.S1
1515
+ columnGap: SPACE.S5,
1516
+ rowGap: SPACE.S2,
1517
+ minWidth: 0
1413
1518
  },
1414
- metaItem: {
1415
- fontSize: FONT_SIZE.sm,
1416
- color: COLOR.neutral700,
1417
- whiteSpace: "nowrap"
1519
+ metaCol: {
1520
+ display: "flex",
1521
+ flexDirection: "column",
1522
+ gap: "2px",
1523
+ minWidth: 0
1418
1524
  },
1419
1525
  metaLabel: {
1526
+ fontSize: "11px",
1527
+ fontWeight: FONT_WEIGHT.bold,
1528
+ letterSpacing: "0.06em",
1529
+ textTransform: "uppercase",
1420
1530
  color: COLOR.neutral500,
1421
- fontWeight: FONT_WEIGHT.medium
1531
+ whiteSpace: "nowrap"
1422
1532
  },
1423
1533
  metaValue: {
1424
- color: COLOR.neutral900
1534
+ fontSize: FONT_SIZE.sm,
1535
+ fontWeight: FONT_WEIGHT.semibold,
1536
+ color: COLOR.neutral900,
1537
+ whiteSpace: "nowrap",
1538
+ overflow: "hidden",
1539
+ textOverflow: "ellipsis",
1540
+ maxWidth: "180px"
1425
1541
  },
1426
1542
  actions: {
1427
1543
  display: "flex",
1428
1544
  gap: SPACE.S2,
1429
- marginTop: SPACE.S3
1545
+ flexShrink: 0
1430
1546
  },
1431
1547
  dicomButton: {
1432
1548
  display: "inline-flex",
1433
1549
  alignItems: "center",
1434
1550
  gap: SPACE.S2,
1435
1551
  minHeight: SIZE.control,
1436
- padding: `${SPACE.S2} ${SPACE.S4}`,
1552
+ padding: `${SPACE.S2} 14px`,
1437
1553
  fontSize: FONT_SIZE.sm,
1438
- fontWeight: FONT_WEIGHT.medium,
1439
- color: COLOR.white,
1440
- backgroundColor: COLOR.primary,
1441
- border: "none",
1442
- borderRadius: RADIUS.md,
1554
+ fontWeight: FONT_WEIGHT.semibold,
1555
+ color: COLOR.primary,
1556
+ backgroundColor: COLOR.white,
1557
+ border: `1.5px solid ${COLOR.primary}`,
1558
+ borderRadius: RADIUS.lg,
1443
1559
  cursor: "pointer"
1444
1560
  },
1445
- settingsButton: {
1561
+ // Settings is icon-only (40x40 tile) since it's a secondary action
1562
+ // and View DICOM already carries a label.
1563
+ settingsIconButton: {
1564
+ width: SIZE.control,
1565
+ height: SIZE.control,
1446
1566
  display: "inline-flex",
1447
1567
  alignItems: "center",
1448
- gap: SPACE.S2,
1449
- minHeight: SIZE.control,
1450
- padding: `${SPACE.S2} ${SPACE.S4}`,
1451
- fontSize: FONT_SIZE.sm,
1452
- fontWeight: FONT_WEIGHT.medium,
1453
- color: COLOR.neutral700,
1568
+ justifyContent: "center",
1454
1569
  backgroundColor: COLOR.white,
1455
- border: `1px solid ${COLOR.neutral300}`,
1456
- borderRadius: RADIUS.md,
1457
- cursor: "pointer"
1570
+ border: `1px solid ${COLOR.neutral200}`,
1571
+ borderRadius: RADIUS.lg,
1572
+ color: COLOR.neutral600,
1573
+ cursor: "pointer",
1574
+ flexShrink: 0
1458
1575
  }
1459
1576
  };
1460
1577
  function ReplyQuoteBlock({
@@ -1463,10 +1580,10 @@ function ReplyQuoteBlock({
1463
1580
  onClick,
1464
1581
  className
1465
1582
  }) {
1466
- const baseBg = inOwnBubble ? "rgba(255,255,255,0.18)" : COLOR.neutral100;
1583
+ const baseBg = inOwnBubble ? "rgba(255,255,255,0.22)" : COLOR.white;
1467
1584
  const barColor = inOwnBubble ? COLOR.white : COLOR.primary;
1468
1585
  const nameColor = inOwnBubble ? COLOR.white : COLOR.primary;
1469
- const bodyColor = inOwnBubble ? "rgba(255,255,255,0.85)" : COLOR.neutral600;
1586
+ const bodyColor = inOwnBubble ? "rgba(255,255,255,0.9)" : COLOR.neutral700;
1470
1587
  const preview = renderSnapshotPreview(snapshot);
1471
1588
  return /* @__PURE__ */ jsxs(
1472
1589
  "div",
@@ -1838,43 +1955,50 @@ var styles4 = {
1838
1955
  position: "relative",
1839
1956
  display: "inline-block"
1840
1957
  },
1958
+ // Pill-shaped trigger matching the prototype's `.nc-msg-act` style —
1959
+ // always visible (touch-friendly) rather than hover-only.
1841
1960
  trigger: {
1842
- width: SIZE.controlIcon,
1843
- height: SIZE.controlIcon,
1844
- display: "flex",
1961
+ display: "inline-flex",
1845
1962
  alignItems: "center",
1846
1963
  justifyContent: "center",
1847
- backgroundColor: "transparent",
1848
- border: "none",
1849
- borderRadius: RADIUS.full,
1964
+ width: "32px",
1965
+ height: "32px",
1966
+ backgroundColor: COLOR.white,
1967
+ border: `1px solid ${COLOR.neutral200}`,
1968
+ borderRadius: "999px",
1850
1969
  cursor: "pointer",
1851
- color: COLOR.neutral600
1970
+ color: COLOR.neutral600,
1971
+ transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
1852
1972
  },
1853
1973
  menu: {
1854
1974
  position: "fixed",
1855
- minWidth: "160px",
1975
+ minWidth: "200px",
1856
1976
  backgroundColor: COLOR.white,
1857
- border: `1px solid ${COLOR.neutral200}`,
1858
- borderRadius: RADIUS.md,
1977
+ border: `1px solid ${COLOR.neutral300}`,
1978
+ borderRadius: RADIUS.lg,
1859
1979
  boxShadow: SHADOW.popover,
1860
- padding: SPACE.S1,
1980
+ padding: "6px",
1981
+ display: "flex",
1982
+ flexDirection: "column",
1983
+ gap: "2px",
1861
1984
  // Above the floating popup (z-index dialog) but below any future modal.
1862
1985
  zIndex: Z_INDEX.menu
1863
1986
  },
1864
1987
  item: {
1865
1988
  display: "flex",
1866
1989
  alignItems: "center",
1867
- gap: SPACE.S3,
1990
+ gap: SPACE.S2,
1868
1991
  width: "100%",
1869
1992
  minHeight: SIZE.control,
1870
- padding: `${SPACE.S2} ${SPACE.S3}`,
1993
+ padding: `10px ${SPACE.S3}`,
1871
1994
  fontSize: FONT_SIZE.sm,
1872
- color: COLOR.neutral900,
1995
+ color: COLOR.neutral700,
1873
1996
  backgroundColor: "transparent",
1874
1997
  border: "none",
1875
- borderRadius: RADIUS.sm,
1998
+ borderRadius: RADIUS.md,
1876
1999
  cursor: "pointer",
1877
- textAlign: "left"
2000
+ textAlign: "left",
2001
+ fontFamily: "inherit"
1878
2002
  },
1879
2003
  itemDisabled: {
1880
2004
  color: COLOR.neutral400,
@@ -1988,71 +2112,116 @@ function MessageBubble({
1988
2112
  },
1989
2113
  children: [
1990
2114
  isOwn && actionsSlot,
1991
- /* @__PURE__ */ jsx("div", { style: styles5.bubbleWrapper, children: /* @__PURE__ */ jsxs(
1992
- "div",
1993
- {
1994
- style: {
1995
- ...styles5.bubble,
1996
- ...isOwn ? styles5.ownBubble : styles5.otherBubble
1997
- },
1998
- children: [
1999
- message.isPinned && /* @__PURE__ */ jsxs("div", { style: styles5.pinnedBadge, children: [
2000
- /* @__PURE__ */ jsx(PinIcon, { size: 12 }),
2001
- /* @__PURE__ */ jsx("span", { children: "Pinned" })
2002
- ] }),
2003
- message.replyToSnapshot && /* @__PURE__ */ jsx(
2004
- ReplyQuoteBlock,
2005
- {
2006
- snapshot: message.replyToSnapshot,
2007
- inOwnBubble: isOwn,
2008
- onClick: onReplyJumpTo
2009
- }
2010
- ),
2011
- !isOwn && /* @__PURE__ */ jsxs("div", { style: styles5.senderRow, children: [
2012
- /* @__PURE__ */ jsx("span", { style: styles5.senderName, children: message.senderName }),
2013
- /* @__PURE__ */ jsx(RoleBadge, { role: message.senderRole })
2014
- ] }),
2015
- message.type === "text" && /* @__PURE__ */ jsx(TextContent, { body: message.body, onDeepLinkClick }),
2016
- message.type === "audio" && /* @__PURE__ */ jsx(AudioContent, { mediaUrl: message.mediaUrl, duration: message.mediaDuration }),
2017
- message.type === "image" && /* @__PURE__ */ jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2018
- message.type === "file" && /* @__PURE__ */ jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2019
- message.type === "deep_link" && /* @__PURE__ */ jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
2020
- /* @__PURE__ */ jsx("div", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
2021
- isOwn && message.status === "sending" && /* @__PURE__ */ jsxs("div", { style: styles5.statusRow, children: [
2022
- /* @__PURE__ */ jsx(Spinner, { size: 12, color: "rgba(255,255,255,0.85)" }),
2023
- /* @__PURE__ */ jsx("span", { style: styles5.statusText, children: "Sending\u2026" })
2024
- ] }),
2025
- isOwn && message.status === "failed" && /* @__PURE__ */ jsxs("div", { style: styles5.statusRow, children: [
2026
- /* @__PURE__ */ jsx(AlertIcon, { size: 12, color: "#fecaca" }),
2027
- /* @__PURE__ */ jsx("span", { style: styles5.statusText, children: "Not sent" }),
2028
- onRetry && /* @__PURE__ */ jsx(
2029
- "button",
2115
+ /* @__PURE__ */ jsxs("div", { style: styles5.bubbleWrapper, children: [
2116
+ !isOwn && /* @__PURE__ */ jsxs("div", { style: styles5.authorRow, children: [
2117
+ /* @__PURE__ */ jsx(
2118
+ "span",
2119
+ {
2120
+ style: {
2121
+ ...styles5.authorAvatar,
2122
+ backgroundColor: roleColor(message.senderRole)
2123
+ },
2124
+ "aria-hidden": "true",
2125
+ children: computeInitials(message.senderName)
2126
+ }
2127
+ ),
2128
+ /* @__PURE__ */ jsx("span", { style: styles5.authorName, children: message.senderName }),
2129
+ /* @__PURE__ */ jsx(RoleBadge, { role: message.senderRole })
2130
+ ] }),
2131
+ /* @__PURE__ */ jsxs(
2132
+ "div",
2133
+ {
2134
+ style: {
2135
+ ...styles5.bubble,
2136
+ ...isOwn ? styles5.ownBubble : styles5.otherBubble
2137
+ },
2138
+ children: [
2139
+ message.isPinned && /* @__PURE__ */ jsxs(
2140
+ "div",
2030
2141
  {
2031
- type: "button",
2032
- onClick: () => onRetry(message.id),
2033
- style: styles5.retryButton,
2034
- "aria-label": "Retry sending message",
2035
- children: "Retry"
2142
+ style: {
2143
+ ...styles5.pinnedBadge,
2144
+ ...isOwn ? styles5.pinnedBadgeOwn : styles5.pinnedBadgeOther
2145
+ },
2146
+ children: [
2147
+ /* @__PURE__ */ jsx(PinIcon, { size: 12 }),
2148
+ /* @__PURE__ */ jsx("span", { children: "Pinned" })
2149
+ ]
2036
2150
  }
2037
- )
2038
- ] }),
2039
- showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsx(
2040
- SeenByIndicator,
2041
- {
2042
- readBy: message.readBy ?? [],
2043
- participants,
2044
- currentUserId,
2045
- senderId: message.senderId
2046
- }
2047
- )
2048
- ]
2049
- }
2050
- ) }),
2151
+ ),
2152
+ message.replyToSnapshot && /* @__PURE__ */ jsx(
2153
+ ReplyQuoteBlock,
2154
+ {
2155
+ snapshot: message.replyToSnapshot,
2156
+ inOwnBubble: isOwn,
2157
+ onClick: onReplyJumpTo
2158
+ }
2159
+ ),
2160
+ message.type === "text" && /* @__PURE__ */ jsx(TextContent, { body: message.body, onDeepLinkClick }),
2161
+ message.type === "audio" && /* @__PURE__ */ jsx(AudioContent, { mediaUrl: message.mediaUrl, duration: message.mediaDuration }),
2162
+ message.type === "image" && /* @__PURE__ */ jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2163
+ message.type === "file" && /* @__PURE__ */ jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
2164
+ message.type === "deep_link" && /* @__PURE__ */ jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
2165
+ /* @__PURE__ */ jsxs("div", { style: styles5.bubbleFoot, children: [
2166
+ /* @__PURE__ */ jsx("span", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
2167
+ isOwn && message.status === "sending" && /* @__PURE__ */ jsxs("span", { style: styles5.footStatus, children: [
2168
+ /* @__PURE__ */ jsx(Spinner, { size: 11, color: "rgba(255,255,255,0.85)" }),
2169
+ /* @__PURE__ */ jsx("span", { children: "Sending\u2026" })
2170
+ ] }),
2171
+ isOwn && message.status === "failed" && /* @__PURE__ */ jsxs("span", { style: styles5.footStatusFailed, children: [
2172
+ /* @__PURE__ */ jsx(AlertIcon, { size: 11, color: "#fecaca" }),
2173
+ /* @__PURE__ */ jsx("span", { children: "Not sent" }),
2174
+ onRetry && /* @__PURE__ */ jsx(
2175
+ "button",
2176
+ {
2177
+ type: "button",
2178
+ onClick: () => onRetry(message.id),
2179
+ style: styles5.retryButton,
2180
+ "aria-label": "Retry sending message",
2181
+ children: "Retry"
2182
+ }
2183
+ )
2184
+ ] }),
2185
+ showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsx(
2186
+ SeenByIndicator,
2187
+ {
2188
+ readBy: message.readBy ?? [],
2189
+ participants,
2190
+ currentUserId,
2191
+ senderId: message.senderId
2192
+ }
2193
+ )
2194
+ ] })
2195
+ ]
2196
+ }
2197
+ )
2198
+ ] }),
2051
2199
  !isOwn && actionsSlot
2052
2200
  ]
2053
2201
  }
2054
2202
  );
2055
2203
  }
2204
+ function computeInitials(name) {
2205
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
2206
+ }
2207
+ function roleColor(role) {
2208
+ switch (role) {
2209
+ case "radiologist":
2210
+ return "#4f46e5";
2211
+ // indigo (blue-leaning)
2212
+ case "lab":
2213
+ return "#2563eb";
2214
+ // brand blue
2215
+ case "physician":
2216
+ return "#0284c7";
2217
+ // sky
2218
+ case "admin":
2219
+ return "#64748b";
2220
+ // slate (muted)
2221
+ default:
2222
+ return "#6b7280";
2223
+ }
2224
+ }
2056
2225
  function TextContent({ body, onDeepLinkClick }) {
2057
2226
  if (body.includes(DEEP_LINK_PREFIX)) {
2058
2227
  const parts = body.split(new RegExp(`(${escapeRegex2(DEEP_LINK_PREFIX)}[\\w/.-]+)`, "g"));
@@ -2114,19 +2283,13 @@ function DeepLinkContent({
2114
2283
  );
2115
2284
  }
2116
2285
  function SystemBubble({ message }) {
2117
- return /* @__PURE__ */ jsxs("div", { style: styles5.systemMessage, children: [
2286
+ return /* @__PURE__ */ jsx("div", { style: styles5.systemWrapper, children: /* @__PURE__ */ jsxs("span", { style: styles5.systemPill, children: [
2118
2287
  /* @__PURE__ */ jsx("span", { children: message.body }),
2119
2288
  /* @__PURE__ */ jsx("span", { style: styles5.systemTime, children: formatTime(message.insertedAt) })
2120
- ] });
2289
+ ] }) });
2121
2290
  }
2122
2291
  function RoleBadge({ role }) {
2123
- const colors = {
2124
- radiologist: "#7c3aed",
2125
- lab: "#2563eb",
2126
- physician: "#059669",
2127
- admin: "#dc2626"
2128
- };
2129
- return /* @__PURE__ */ jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: ROLE_LABELS[role] ?? role });
2292
+ return /* @__PURE__ */ jsx("span", { style: { ...styles5.roleBadge, backgroundColor: roleColor(role) }, children: ROLE_LABELS[role] ?? role });
2130
2293
  }
2131
2294
  function formatTime(iso) {
2132
2295
  try {
@@ -2148,89 +2311,134 @@ var styles5 = {
2148
2311
  display: "flex",
2149
2312
  alignItems: "flex-end",
2150
2313
  gap: SPACE.S1,
2151
- marginBottom: SPACE.S2,
2314
+ marginBottom: SPACE.S3,
2152
2315
  paddingLeft: SPACE.S4,
2153
2316
  paddingRight: SPACE.S4
2154
2317
  },
2155
2318
  bubbleWrapper: {
2156
2319
  position: "relative",
2157
- maxWidth: "75%"
2320
+ maxWidth: "78%",
2321
+ display: "flex",
2322
+ flexDirection: "column",
2323
+ gap: "4px"
2158
2324
  },
2159
2325
  actionsSlot: {
2160
2326
  flexShrink: 0,
2161
2327
  alignSelf: "flex-end"
2162
2328
  },
2329
+ // Author row above the bubble — only rendered for others' messages.
2330
+ authorRow: {
2331
+ display: "flex",
2332
+ alignItems: "center",
2333
+ gap: SPACE.S2
2334
+ },
2335
+ authorAvatar: {
2336
+ width: "36px",
2337
+ height: "36px",
2338
+ borderRadius: "50%",
2339
+ color: COLOR.white,
2340
+ fontSize: FONT_SIZE.sm,
2341
+ fontWeight: FONT_WEIGHT.bold,
2342
+ display: "flex",
2343
+ alignItems: "center",
2344
+ justifyContent: "center",
2345
+ flexShrink: 0,
2346
+ letterSpacing: "0.02em"
2347
+ },
2348
+ authorName: {
2349
+ fontSize: FONT_SIZE.sm,
2350
+ fontWeight: FONT_WEIGHT.semibold,
2351
+ color: COLOR.neutral700,
2352
+ overflow: "hidden",
2353
+ textOverflow: "ellipsis",
2354
+ whiteSpace: "nowrap",
2355
+ minWidth: 0
2356
+ },
2163
2357
  bubble: {
2164
2358
  padding: `${SPACE.S3} ${SPACE.S4}`,
2165
- borderRadius: RADIUS.lg,
2166
- wordBreak: "break-word"
2359
+ borderRadius: "18px",
2360
+ wordBreak: "break-word",
2361
+ border: "1px solid transparent"
2167
2362
  },
2168
2363
  ownBubble: {
2169
2364
  backgroundColor: COLOR.primary,
2170
2365
  color: COLOR.white,
2171
- borderBottomRightRadius: RADIUS.sm
2366
+ borderColor: COLOR.primary,
2367
+ borderBottomRightRadius: "6px"
2172
2368
  },
2173
2369
  otherBubble: {
2174
2370
  backgroundColor: COLOR.neutral100,
2175
2371
  color: COLOR.neutral900,
2176
- borderBottomLeftRadius: RADIUS.sm
2372
+ borderColor: COLOR.neutral200,
2373
+ borderBottomLeftRadius: "6px"
2177
2374
  },
2178
2375
  pinnedBadge: {
2179
2376
  display: "inline-flex",
2180
2377
  alignItems: "center",
2181
- gap: SPACE.S1,
2182
- fontSize: FONT_SIZE.xs,
2183
- fontWeight: FONT_WEIGHT.semibold,
2184
- marginBottom: SPACE.S1,
2185
- color: COLOR.neutral500
2378
+ gap: "4px",
2379
+ fontSize: "11px",
2380
+ fontWeight: FONT_WEIGHT.bold,
2381
+ marginBottom: SPACE.S2,
2382
+ padding: `2px ${SPACE.S2}`,
2383
+ borderRadius: "999px",
2384
+ textTransform: "uppercase",
2385
+ letterSpacing: "0.05em"
2186
2386
  },
2187
- senderRow: {
2188
- display: "flex",
2189
- alignItems: "center",
2190
- gap: SPACE.S2,
2191
- marginBottom: SPACE.S1
2387
+ pinnedBadgeOther: {
2388
+ color: "#b8680f",
2389
+ backgroundColor: "#fff7e0"
2192
2390
  },
2193
- senderName: {
2194
- fontSize: FONT_SIZE.sm,
2195
- fontWeight: FONT_WEIGHT.semibold,
2196
- color: COLOR.neutral700
2391
+ pinnedBadgeOwn: {
2392
+ color: "#fff4d6",
2393
+ backgroundColor: "rgba(255, 255, 255, 0.2)"
2197
2394
  },
2198
2395
  roleBadge: {
2199
- fontSize: FONT_SIZE.xs,
2200
- fontWeight: FONT_WEIGHT.semibold,
2201
- padding: `2px ${SPACE.S2}`,
2202
- borderRadius: RADIUS.sm
2396
+ fontSize: "10px",
2397
+ fontWeight: FONT_WEIGHT.bold,
2398
+ padding: "2px 7px",
2399
+ borderRadius: RADIUS.sm,
2400
+ textTransform: "uppercase",
2401
+ letterSpacing: "0.06em",
2402
+ color: COLOR.white,
2403
+ flexShrink: 0
2203
2404
  },
2204
2405
  textBody: {
2205
2406
  margin: 0,
2206
2407
  fontSize: FONT_SIZE.md,
2207
- lineHeight: LINE_HEIGHT.normal
2408
+ lineHeight: 1.5
2409
+ },
2410
+ bubbleFoot: {
2411
+ display: "flex",
2412
+ alignItems: "center",
2413
+ justifyContent: "flex-end",
2414
+ gap: SPACE.S2,
2415
+ marginTop: SPACE.S2,
2416
+ fontSize: "11px",
2417
+ opacity: 0.85
2208
2418
  },
2209
2419
  timestamp: {
2210
- fontSize: FONT_SIZE.xs,
2420
+ fontSize: "11px",
2211
2421
  color: "inherit",
2212
- opacity: 0.8,
2213
- marginTop: SPACE.S1,
2214
- textAlign: "right"
2422
+ fontVariantNumeric: "tabular-nums"
2215
2423
  },
2216
- statusRow: {
2217
- display: "flex",
2424
+ footStatus: {
2425
+ display: "inline-flex",
2218
2426
  alignItems: "center",
2219
- gap: SPACE.S2,
2220
- marginTop: SPACE.S1,
2221
- fontSize: FONT_SIZE.xs,
2222
- justifyContent: "flex-end",
2223
- color: "rgba(255, 255, 255, 0.9)"
2427
+ gap: "4px",
2428
+ fontSize: "11px"
2224
2429
  },
2225
- statusText: {
2226
- opacity: 0.9
2430
+ footStatusFailed: {
2431
+ display: "inline-flex",
2432
+ alignItems: "center",
2433
+ gap: "4px",
2434
+ fontSize: "11px"
2227
2435
  },
2228
2436
  retryButton: {
2229
2437
  background: "transparent",
2230
2438
  border: "none",
2231
2439
  color: COLOR.white,
2232
2440
  fontWeight: FONT_WEIGHT.semibold,
2233
- fontSize: FONT_SIZE.xs,
2441
+ fontSize: "11px",
2234
2442
  textDecoration: "underline",
2235
2443
  cursor: "pointer",
2236
2444
  padding: `0 ${SPACE.S1}`
@@ -2293,18 +2501,25 @@ var styles5 = {
2293
2501
  color: COLOR.neutral500,
2294
2502
  fontFamily: "monospace"
2295
2503
  },
2296
- systemMessage: {
2504
+ systemWrapper: {
2297
2505
  display: "flex",
2298
2506
  justifyContent: "center",
2507
+ margin: `${SPACE.S2} 0`
2508
+ },
2509
+ systemPill: {
2510
+ display: "inline-flex",
2299
2511
  alignItems: "center",
2300
2512
  gap: SPACE.S2,
2301
- padding: `${SPACE.S1} ${SPACE.S4}`,
2302
- fontSize: FONT_SIZE.sm,
2513
+ padding: `5px ${SPACE.S3}`,
2514
+ backgroundColor: COLOR.neutral100,
2515
+ borderRadius: "999px",
2516
+ fontSize: FONT_SIZE.xs,
2303
2517
  color: COLOR.neutral500
2304
2518
  },
2305
2519
  systemTime: {
2306
- fontSize: FONT_SIZE.xs,
2307
- color: COLOR.neutral400
2520
+ fontSize: "11px",
2521
+ color: COLOR.neutral400,
2522
+ fontVariantNumeric: "tabular-nums"
2308
2523
  }
2309
2524
  };
2310
2525
  function ChatIllustration({ size = 96 }) {
@@ -2443,6 +2658,7 @@ var MessageList = forwardRef(function MessageList2({
2443
2658
  const containerRef = useRef(null);
2444
2659
  const bottomRef = useRef(null);
2445
2660
  const prevMessageCount = useRef(messages.length);
2661
+ const hasInitiallyScrolledRef = useRef(false);
2446
2662
  useImperativeHandle(
2447
2663
  ref,
2448
2664
  () => ({
@@ -2460,7 +2676,31 @@ var MessageList = forwardRef(function MessageList2({
2460
2676
  }),
2461
2677
  []
2462
2678
  );
2679
+ useLayoutEffect(() => {
2680
+ const container = containerRef.current;
2681
+ if (!container || messages.length === 0) return;
2682
+ if (hasInitiallyScrolledRef.current) return;
2683
+ hasInitiallyScrolledRef.current = true;
2684
+ prevMessageCount.current = messages.length;
2685
+ const firstUnread = messages.find(
2686
+ (m) => m.type !== "system" && m.senderId !== currentUserId && !(m.readBy ?? []).includes(currentUserId)
2687
+ );
2688
+ if (firstUnread) {
2689
+ const el = container.querySelector(
2690
+ `[data-message-id="${firstUnread.id}"]`
2691
+ );
2692
+ if (el) {
2693
+ const containerRect = container.getBoundingClientRect();
2694
+ const elRect = el.getBoundingClientRect();
2695
+ const offset = elRect.top - containerRect.top + container.scrollTop;
2696
+ container.scrollTop = Math.max(0, offset - 16);
2697
+ return;
2698
+ }
2699
+ }
2700
+ container.scrollTop = container.scrollHeight;
2701
+ }, [messages, currentUserId]);
2463
2702
  useEffect(() => {
2703
+ if (!hasInitiallyScrolledRef.current) return;
2464
2704
  if (messages.length > prevMessageCount.current) {
2465
2705
  const lastMessage = messages[messages.length - 1];
2466
2706
  const isOwnMessage = lastMessage?.senderId === currentUserId;
@@ -2519,7 +2759,11 @@ var MessageList = forwardRef(function MessageList2({
2519
2759
  const showDivider = !!currentBucket && currentBucket !== lastValidBucket;
2520
2760
  if (currentBucket) lastValidBucket = currentBucket;
2521
2761
  return /* @__PURE__ */ jsxs(React4.Fragment, { children: [
2522
- showDivider && /* @__PURE__ */ jsx("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: /* @__PURE__ */ jsx("span", { style: styles6.dayDividerPill, children: formatDayDivider(message.insertedAt) }) }),
2762
+ showDivider && /* @__PURE__ */ jsxs("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: [
2763
+ /* @__PURE__ */ jsx("span", { style: styles6.dayHr, "aria-hidden": "true" }),
2764
+ /* @__PURE__ */ jsx("span", { style: styles6.dayLabel, children: formatDayDivider(message.insertedAt) }),
2765
+ /* @__PURE__ */ jsx("span", { style: styles6.dayHr, "aria-hidden": "true" })
2766
+ ] }),
2523
2767
  /* @__PURE__ */ jsx("div", { "data-message-id": message.id, "data-natoe-message-bubble": true, style: styles6.bubbleSlot, children: /* @__PURE__ */ jsx(
2524
2768
  MessageBubble,
2525
2769
  {
@@ -2618,23 +2862,32 @@ var styles6 = {
2618
2862
  },
2619
2863
  dayDivider: {
2620
2864
  display: "flex",
2621
- justifyContent: "center",
2622
- margin: `${SPACE.S3} 0 ${SPACE.S2}`,
2865
+ alignItems: "center",
2866
+ gap: SPACE.S3,
2867
+ margin: `${SPACE.S4} 0 ${SPACE.S3}`,
2623
2868
  position: "sticky",
2624
2869
  top: SPACE.S1,
2625
2870
  zIndex: Z_INDEX.sticky,
2626
2871
  pointerEvents: "none"
2627
2872
  },
2628
- dayDividerPill: {
2629
- display: "inline-block",
2630
- padding: `${SPACE.S1} ${SPACE.S3}`,
2631
- fontSize: FONT_SIZE.sm,
2632
- fontWeight: FONT_WEIGHT.medium,
2633
- color: COLOR.slate700,
2634
- backgroundColor: COLOR.slate200,
2635
- borderRadius: RADIUS.lg,
2636
- letterSpacing: "0.2px",
2637
- boxShadow: SHADOW.subtle
2873
+ dayHr: {
2874
+ flex: 1,
2875
+ height: "1px",
2876
+ backgroundColor: COLOR.neutral200
2877
+ },
2878
+ dayLabel: {
2879
+ flexShrink: 0,
2880
+ fontSize: FONT_SIZE.xs,
2881
+ fontWeight: FONT_WEIGHT.semibold,
2882
+ color: COLOR.neutral500,
2883
+ textTransform: "uppercase",
2884
+ letterSpacing: "0.06em",
2885
+ // Subtle pill background so the label is readable when it overlays
2886
+ // bubbles passing under it during sticky scroll. Without this the
2887
+ // hairlines visually run through the text on tinted backgrounds.
2888
+ padding: `2px ${SPACE.S2}`,
2889
+ backgroundColor: COLOR.white,
2890
+ borderRadius: RADIUS.sm
2638
2891
  },
2639
2892
  bubbleSlot: {
2640
2893
  animation: "natoe-colab-message-in 180ms ease-out"
@@ -2888,11 +3141,11 @@ function MessageInput({
2888
3141
  {
2889
3142
  onClick: () => fileInputRef.current?.click(),
2890
3143
  disabled: disabled || isSending,
2891
- style: styles8.iconButton,
3144
+ style: styles8.attachButton,
2892
3145
  "aria-label": "Attach file",
2893
3146
  title: "Attach file",
2894
3147
  type: "button",
2895
- children: /* @__PURE__ */ jsx(AttachIcon, { size: 22, color: "#374151" })
3148
+ children: /* @__PURE__ */ jsx(AttachIcon, { size: 22, color: COLOR.neutral700 })
2896
3149
  }
2897
3150
  ),
2898
3151
  /* @__PURE__ */ jsx(
@@ -2931,7 +3184,7 @@ function MessageInput({
2931
3184
  style: styles8.textarea
2932
3185
  }
2933
3186
  ),
2934
- /* @__PURE__ */ jsx(
3187
+ /* @__PURE__ */ jsxs(
2935
3188
  "button",
2936
3189
  {
2937
3190
  onClick: handleSendText,
@@ -2940,10 +3193,13 @@ function MessageInput({
2940
3193
  title: "Send message",
2941
3194
  style: {
2942
3195
  ...styles8.sendButton,
2943
- opacity: text.trim() ? 1 : 0.4
3196
+ opacity: text.trim() ? 1 : 0.5
2944
3197
  },
2945
3198
  type: "button",
2946
- children: /* @__PURE__ */ jsx(SendIcon, { size: 20, color: COLOR.white })
3199
+ children: [
3200
+ /* @__PURE__ */ jsx(SendIcon, { size: 20, color: COLOR.white }),
3201
+ /* @__PURE__ */ jsx("span", { style: styles8.sendLabel, children: "Send" })
3202
+ ]
2947
3203
  }
2948
3204
  )
2949
3205
  ] })
@@ -3005,49 +3261,67 @@ var styles8 = {
3005
3261
  inputBar: {
3006
3262
  display: "flex",
3007
3263
  alignItems: "flex-end",
3008
- gap: SPACE.S2,
3009
- padding: `${SPACE.S3} ${SPACE.S3}`
3010
- },
3011
- iconButton: {
3012
- width: SIZE.controlPrimary,
3013
- height: SIZE.controlPrimary,
3264
+ gap: "10px",
3265
+ padding: `${SPACE.S3} ${SPACE.S4} 14px`
3266
+ },
3267
+ // 48px square tile with a 14px corner radius — visually grounds the
3268
+ // attach affordance as part of the composer chrome rather than a stray
3269
+ // round icon button.
3270
+ attachButton: {
3271
+ width: "48px",
3272
+ height: "48px",
3014
3273
  display: "flex",
3015
3274
  alignItems: "center",
3016
3275
  justifyContent: "center",
3017
- backgroundColor: "transparent",
3018
- border: "none",
3019
- borderRadius: RADIUS.full,
3276
+ backgroundColor: COLOR.neutral100,
3277
+ border: `1.5px solid ${COLOR.neutral200}`,
3278
+ borderRadius: "14px",
3020
3279
  cursor: "pointer",
3021
- flexShrink: 0
3280
+ flexShrink: 0,
3281
+ color: COLOR.neutral700,
3282
+ transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
3022
3283
  },
3023
3284
  textarea: {
3024
3285
  flex: 1,
3025
- padding: `${SPACE.S3} ${SPACE.S4}`,
3286
+ minHeight: "48px",
3287
+ maxHeight: "140px",
3288
+ padding: "13px 16px",
3026
3289
  fontSize: FONT_SIZE.md,
3027
3290
  lineHeight: LINE_HEIGHT.normal,
3028
- border: `1px solid ${COLOR.neutral300}`,
3029
- borderRadius: RADIUS.pill,
3291
+ color: COLOR.neutral900,
3292
+ backgroundColor: COLOR.neutral100,
3293
+ border: `1.5px solid ${COLOR.neutral200}`,
3294
+ borderRadius: "14px",
3030
3295
  resize: "none",
3031
3296
  outline: "none",
3032
3297
  fontFamily: "inherit",
3033
- maxHeight: "120px",
3034
- minHeight: SIZE.controlPrimary
3298
+ transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
3035
3299
  },
3300
+ // Pill-shaped send: icon + "Send" label. Always labelled (never icon-
3301
+ // only) so the affordance is obvious to first-time users.
3036
3302
  sendButton: {
3037
- width: SIZE.controlPrimary,
3038
- height: SIZE.controlPrimary,
3039
- display: "flex",
3303
+ display: "inline-flex",
3040
3304
  alignItems: "center",
3041
- justifyContent: "center",
3305
+ gap: "6px",
3306
+ minWidth: "48px",
3307
+ height: "48px",
3308
+ padding: `0 ${SPACE.S4}`,
3042
3309
  backgroundColor: COLOR.primary,
3043
3310
  color: COLOR.white,
3044
3311
  border: "none",
3045
- borderRadius: RADIUS.full,
3312
+ borderRadius: "14px",
3046
3313
  cursor: "pointer",
3047
- flexShrink: 0
3048
- }};
3049
- function ParticipantsList({
3050
- participants,
3314
+ flexShrink: 0,
3315
+ fontFamily: "inherit",
3316
+ fontSize: "15px",
3317
+ fontWeight: FONT_WEIGHT.semibold,
3318
+ transition: "opacity 120ms ease, background-color 120ms ease"
3319
+ },
3320
+ sendLabel: {
3321
+ lineHeight: 1
3322
+ }};
3323
+ function ParticipantsList({
3324
+ participants,
3051
3325
  currentUserId,
3052
3326
  isAdmin,
3053
3327
  onRemoveUser,
@@ -3101,10 +3375,14 @@ function fallbackName(role) {
3101
3375
  }
3102
3376
  function RoleBadge2({ role }) {
3103
3377
  const colors = {
3104
- radiologist: { bg: "#f5f3ff", text: "#7c3aed" },
3378
+ radiologist: { bg: "#eef2ff", text: "#4f46e5" },
3379
+ // indigo
3105
3380
  lab: { bg: "#eff6ff", text: "#2563eb" },
3106
- physician: { bg: "#ecfdf5", text: "#059669" },
3107
- admin: { bg: "#fef2f2", text: "#dc2626" }
3381
+ // brand blue
3382
+ physician: { bg: "#e0f2fe", text: "#0284c7" },
3383
+ // sky
3384
+ admin: { bg: "#f1f5f9", text: "#64748b" }
3385
+ // slate
3108
3386
  };
3109
3387
  const color = colors[role];
3110
3388
  return /* @__PURE__ */ jsx("span", { style: { ...styles9.roleBadge, backgroundColor: color.bg, color: color.text }, children: role });
@@ -3713,45 +3991,6 @@ var styles12 = {
3713
3991
  letterSpacing: "0.4px"
3714
3992
  }
3715
3993
  };
3716
-
3717
- // src/core/styles.ts
3718
- var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
3719
- var ROOT_CLASS = "natoe-colab-root";
3720
- var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
3721
- var GLOBAL_CSS = `
3722
- @keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
3723
-
3724
- @keyframes natoe-colab-message-in {
3725
- from { opacity: 0; transform: translateY(4px); }
3726
- to { opacity: 1; transform: translateY(0); }
3727
- }
3728
-
3729
- /* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
3730
- get heavier borders and stronger text without losing the package look. */
3731
- @media (prefers-contrast: more) {
3732
- .${ROOT_CLASS} {
3733
- color: #000000;
3734
- }
3735
- .${ROOT_CLASS} button {
3736
- outline: 1px solid currentColor;
3737
- }
3738
- }
3739
-
3740
- /* Honour reduced-motion preferences by killing entry animations. */
3741
- @media (prefers-reduced-motion: reduce) {
3742
- .${ROOT_CLASS} [data-natoe-message-bubble] {
3743
- animation: none !important;
3744
- }
3745
- }
3746
- `;
3747
- function ensureGlobalStyles() {
3748
- if (typeof document === "undefined") return;
3749
- if (document.getElementById(GLOBAL_STYLE_ID)) return;
3750
- const style = document.createElement("style");
3751
- style.id = GLOBAL_STYLE_ID;
3752
- style.textContent = GLOBAL_CSS;
3753
- document.head.appendChild(style);
3754
- }
3755
3994
  ensureGlobalStyles();
3756
3995
  function CollabPanel({
3757
3996
  orderId,
@@ -3760,6 +3999,7 @@ function CollabPanel({
3760
3999
  showSeenBy = true,
3761
4000
  onBack,
3762
4001
  hidePatientName = false,
4002
+ onConversationChange,
3763
4003
  className,
3764
4004
  style
3765
4005
  }) {
@@ -3788,6 +4028,9 @@ function CollabPanel({
3788
4028
  pinMessage,
3789
4029
  unpinMessage
3790
4030
  } = useConversation({ orderId, patientData, participantIds });
4031
+ useEffect(() => {
4032
+ onConversationChange?.(conversation);
4033
+ }, [conversation, onConversationChange]);
3791
4034
  const channelSettings = useChannelSettings({
3792
4035
  conversationId: conversation?.id ?? null
3793
4036
  });
@@ -3996,6 +4239,7 @@ function CollabPopup({
3996
4239
  isOpen,
3997
4240
  onClose,
3998
4241
  onBack,
4242
+ onMinimize,
3999
4243
  initialPosition,
4000
4244
  width = 380,
4001
4245
  height = 520,
@@ -4009,10 +4253,12 @@ function CollabPopup({
4009
4253
  );
4010
4254
  const [isDragging, setIsDragging] = useState(false);
4011
4255
  const [isMinimized, setIsMinimized] = useState(false);
4256
+ const [loadedConversation, setLoadedConversation] = useState(null);
4012
4257
  const dragOffset = useRef({ x: 0, y: 0 });
4013
4258
  const containerRef = useRef(null);
4014
4259
  const previouslyFocused = useRef(null);
4015
4260
  const titleId = useId();
4261
+ const titleText = cleanChannelName(loadedConversation?.name) || buildChannelName(patientData);
4016
4262
  const handleMouseDown = useCallback(
4017
4263
  (e) => {
4018
4264
  if (!e.target.closest("[data-drag-handle]")) return;
@@ -4109,40 +4355,62 @@ function CollabPopup({
4109
4355
  "button",
4110
4356
  {
4111
4357
  onClick: onBack,
4112
- style: styles13.titleButton,
4113
- "aria-label": "Go back",
4358
+ style: styles13.titleSquare,
4359
+ "aria-label": "Back to messages",
4114
4360
  title: "Back",
4115
4361
  type: "button",
4116
- children: "\u2190"
4362
+ children: /* @__PURE__ */ jsx(BackIcon, { size: 18, color: COLOR.neutral700 })
4117
4363
  }
4118
4364
  ),
4119
- /* @__PURE__ */ jsx("span", { id: titleId, style: { ...styles13.titleText, flex: 1 }, children: buildChannelName(patientData) }),
4365
+ /* @__PURE__ */ jsxs("div", { style: styles13.titleCenter, children: [
4366
+ /* @__PURE__ */ jsx("h2", { id: titleId, style: styles13.patientName, children: patientData.patientName || titleText }),
4367
+ (patientData.labName || patientData.displayOrderId) && /* @__PURE__ */ jsxs("div", { style: styles13.subRow, children: [
4368
+ patientData.labName && /* @__PURE__ */ jsx("span", { children: patientData.labName }),
4369
+ patientData.labName && patientData.displayOrderId && /* @__PURE__ */ jsx("span", { style: styles13.subDot, "aria-hidden": "true", children: "\xB7" }),
4370
+ patientData.displayOrderId && /* @__PURE__ */ jsxs("span", { style: styles13.mono, children: [
4371
+ "#",
4372
+ patientData.displayOrderId.slice(-4)
4373
+ ] })
4374
+ ] })
4375
+ ] }),
4120
4376
  /* @__PURE__ */ jsxs("div", { style: styles13.titleActions, children: [
4121
4377
  /* @__PURE__ */ jsx(
4122
4378
  "button",
4123
4379
  {
4124
- onClick: () => setIsMinimized(!isMinimized),
4125
- style: styles13.titleButton,
4126
- "aria-label": isMinimized ? "Expand chat" : "Minimize chat",
4127
- title: isMinimized ? "Expand" : "Minimize",
4380
+ onClick: () => {
4381
+ if (onMinimize) onMinimize();
4382
+ else setIsMinimized(!isMinimized);
4383
+ },
4384
+ style: styles13.titleSquare,
4385
+ "aria-label": onMinimize ? "Minimize chat" : isMinimized ? "Expand chat" : "Minimize chat",
4386
+ title: onMinimize ? "Minimize" : isMinimized ? "Expand" : "Minimize",
4128
4387
  type: "button",
4129
- children: isMinimized ? /* @__PURE__ */ jsx(ExpandIcon, { size: 16, color: COLOR.slate200 }) : /* @__PURE__ */ jsx(MinimizeIcon, { size: 16, color: COLOR.slate200 })
4388
+ children: onMinimize || !isMinimized ? /* @__PURE__ */ jsx(MinimizeIcon, { size: 18, color: COLOR.neutral700 }) : /* @__PURE__ */ jsx(ExpandIcon, { size: 18, color: COLOR.neutral700 })
4130
4389
  }
4131
4390
  ),
4132
4391
  /* @__PURE__ */ jsx(
4133
4392
  "button",
4134
4393
  {
4135
4394
  onClick: onClose,
4136
- style: styles13.titleButton,
4395
+ style: styles13.titleSquare,
4137
4396
  "aria-label": "Close chat",
4138
4397
  title: "Close",
4139
4398
  type: "button",
4140
- children: /* @__PURE__ */ jsx(CloseIcon, { size: 16, color: COLOR.slate200 })
4399
+ children: /* @__PURE__ */ jsx(CloseIcon, { size: 18, color: COLOR.neutral700 })
4141
4400
  }
4142
4401
  )
4143
4402
  ] })
4144
4403
  ] }),
4145
- !isMinimized && /* @__PURE__ */ jsx("div", { style: styles13.panelWrapper, children: /* @__PURE__ */ jsx(CollabPanel, { orderId, patientData, participantIds, hidePatientName: true }) })
4404
+ !isMinimized && /* @__PURE__ */ jsx("div", { style: styles13.panelWrapper, children: /* @__PURE__ */ jsx(
4405
+ CollabPanel,
4406
+ {
4407
+ orderId,
4408
+ patientData,
4409
+ participantIds,
4410
+ hidePatientName: true,
4411
+ onConversationChange: setLoadedConversation
4412
+ }
4413
+ ) })
4146
4414
  ]
4147
4415
  }
4148
4416
  );
@@ -4151,51 +4419,78 @@ var styles13 = {
4151
4419
  container: {
4152
4420
  position: "fixed",
4153
4421
  zIndex: Z_INDEX.dialog,
4154
- borderRadius: RADIUS.lg,
4155
- boxShadow: SHADOW.dialog,
4422
+ borderRadius: "20px",
4423
+ boxShadow: "0 20px 48px rgba(20, 21, 28, 0.16), 0 6px 16px rgba(20, 21, 28, 0.08)",
4156
4424
  overflow: "hidden",
4157
4425
  display: "flex",
4158
4426
  flexDirection: "column",
4159
4427
  backgroundColor: COLOR.white,
4160
- border: `1px solid ${COLOR.neutral200}`,
4428
+ border: `1px solid ${COLOR.neutral300}`,
4161
4429
  transition: "height 0.2s ease",
4162
4430
  fontFamily: FONT_STACK
4163
4431
  },
4164
4432
  titleBar: {
4165
4433
  display: "flex",
4166
- justifyContent: "space-between",
4167
4434
  alignItems: "center",
4168
- padding: `0 ${SPACE.S2} 0 ${SPACE.S4}`,
4169
- height: "52px",
4170
- backgroundColor: COLOR.slate800,
4435
+ gap: SPACE.S2,
4436
+ padding: `10px ${SPACE.S3}`,
4437
+ backgroundColor: COLOR.white,
4438
+ borderBottom: `1px solid ${COLOR.neutral200}`,
4171
4439
  cursor: "grab",
4172
4440
  userSelect: "none",
4173
4441
  flexShrink: 0
4174
4442
  },
4175
- titleText: {
4176
- fontSize: FONT_SIZE.md,
4177
- fontWeight: FONT_WEIGHT.medium,
4178
- color: COLOR.white,
4443
+ titleCenter: {
4444
+ flex: 1,
4445
+ minWidth: 0,
4446
+ display: "flex",
4447
+ flexDirection: "column",
4448
+ gap: "2px"
4449
+ },
4450
+ patientName: {
4451
+ margin: 0,
4452
+ fontSize: FONT_SIZE.lg,
4453
+ fontWeight: FONT_WEIGHT.bold,
4454
+ letterSpacing: "-0.01em",
4455
+ color: COLOR.neutral900,
4456
+ overflow: "hidden",
4457
+ textOverflow: "ellipsis",
4458
+ whiteSpace: "nowrap"
4459
+ },
4460
+ subRow: {
4461
+ display: "flex",
4462
+ alignItems: "center",
4463
+ gap: SPACE.S2,
4464
+ fontSize: FONT_SIZE.sm,
4465
+ color: COLOR.neutral500,
4179
4466
  overflow: "hidden",
4180
4467
  textOverflow: "ellipsis",
4181
4468
  whiteSpace: "nowrap"
4182
4469
  },
4470
+ subDot: {
4471
+ color: COLOR.neutral400
4472
+ },
4473
+ mono: {
4474
+ fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
4475
+ fontVariantNumeric: "tabular-nums"
4476
+ },
4183
4477
  titleActions: {
4184
4478
  display: "flex",
4185
4479
  gap: SPACE.S1,
4186
4480
  flexShrink: 0
4187
4481
  },
4188
- titleButton: {
4189
- width: SIZE.control,
4190
- height: SIZE.control,
4482
+ titleSquare: {
4483
+ width: "36px",
4484
+ height: "36px",
4191
4485
  display: "flex",
4192
4486
  alignItems: "center",
4193
4487
  justifyContent: "center",
4194
- backgroundColor: "transparent",
4195
- border: "none",
4488
+ backgroundColor: COLOR.neutral100,
4489
+ border: `1px solid ${COLOR.neutral200}`,
4196
4490
  borderRadius: RADIUS.md,
4197
4491
  cursor: "pointer",
4198
- color: COLOR.slate200
4492
+ color: COLOR.neutral700,
4493
+ flexShrink: 0
4199
4494
  },
4200
4495
  panelWrapper: {
4201
4496
  flex: 1,
@@ -4499,10 +4794,14 @@ function InlineMessageRow({ message }) {
4499
4794
  }
4500
4795
  function RoleDot({ role }) {
4501
4796
  const colors = {
4502
- radiologist: "#7c3aed",
4797
+ radiologist: "#4f46e5",
4798
+ // indigo
4503
4799
  lab: "#2563eb",
4504
- physician: "#059669",
4505
- admin: "#dc2626"
4800
+ // brand blue
4801
+ physician: "#0284c7",
4802
+ // sky
4803
+ admin: "#64748b"
4804
+ // slate
4506
4805
  };
4507
4806
  return /* @__PURE__ */ jsx("span", { style: { ...styles14.roleDot, backgroundColor: colors[role] } });
4508
4807
  }
@@ -4924,6 +5223,10 @@ function ConversationListItem({
4924
5223
  }) {
4925
5224
  const hasUnread = item.unreadCount > 0;
4926
5225
  const displayName = item.name || item.patientSnapshot?.patientName || "Unknown";
5226
+ const studyType = item.patientSnapshot?.studyType;
5227
+ const patientId = item.patientSnapshot?.patientId;
5228
+ const showStudyRow = !!(studyType || patientId);
5229
+ const initials = computeInitials2(item.patientSnapshot?.patientName || displayName);
4927
5230
  return /* @__PURE__ */ jsxs(
4928
5231
  "button",
4929
5232
  {
@@ -4931,15 +5234,11 @@ function ConversationListItem({
4931
5234
  onClick,
4932
5235
  style: {
4933
5236
  ...styles15.container,
4934
- ...isSelected ? styles15.selected : {},
4935
- ...hasUnread ? styles15.unread : {}
5237
+ ...isSelected ? styles15.selected : {}
4936
5238
  },
4937
5239
  type: "button",
4938
5240
  children: [
4939
- /* @__PURE__ */ jsxs("div", { style: styles15.avatarWrapper, children: [
4940
- item.picture ? /* @__PURE__ */ jsx("img", { src: item.picture, alt: "", style: styles15.avatar }) : /* @__PURE__ */ jsx("div", { style: styles15.avatarFallback, children: (displayName).charAt(0).toUpperCase() }),
4941
- hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.unreadDot })
4942
- ] }),
5241
+ /* @__PURE__ */ jsx("div", { style: styles15.avatarWrapper, children: item.picture ? /* @__PURE__ */ jsx("img", { src: item.picture, alt: "", style: styles15.avatarImg }) : /* @__PURE__ */ jsx("div", { style: styles15.avatarTile, "aria-hidden": "true", children: initials }) }),
4943
5242
  /* @__PURE__ */ jsxs("div", { style: styles15.content, children: [
4944
5243
  /* @__PURE__ */ jsxs("div", { style: styles15.topRow, children: [
4945
5244
  /* @__PURE__ */ jsx(
@@ -4963,101 +5262,120 @@ function ConversationListItem({
4963
5262
  }
4964
5263
  )
4965
5264
  ] }),
5265
+ showStudyRow && /* @__PURE__ */ jsxs("div", { style: styles15.studyRow, children: [
5266
+ studyType && /* @__PURE__ */ jsx("span", { style: styles15.studyType, children: studyType }),
5267
+ studyType && patientId && /* @__PURE__ */ jsx("span", { style: styles15.studyDivider, children: "\xB7" }),
5268
+ patientId && /* @__PURE__ */ jsxs("span", { style: styles15.studyId, children: [
5269
+ "MRN ",
5270
+ patientId
5271
+ ] })
5272
+ ] }),
4966
5273
  /* @__PURE__ */ jsxs("div", { style: styles15.bottomRow, children: [
4967
- /* @__PURE__ */ jsx(
4968
- "span",
4969
- {
4970
- style: {
4971
- ...styles15.preview,
4972
- ...hasUnread ? styles15.previewUnread : {}
4973
- },
4974
- children: renderLastMessagePreview(item.lastMessage)
4975
- }
4976
- ),
4977
- hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.badge, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
5274
+ /* @__PURE__ */ jsx(PreviewLine, { message: item.lastMessage, hasUnread }),
5275
+ hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.badge, "aria-label": `${item.unreadCount} unread`, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
4978
5276
  ] })
4979
5277
  ] })
4980
5278
  ]
4981
5279
  }
4982
5280
  );
4983
5281
  }
4984
- function renderLastMessagePreview(message) {
4985
- if (!message) return "No messages yet";
4986
- const prefix = message.senderName ? `${message.senderName}: ` : "";
5282
+ function PreviewLine({ message, hasUnread }) {
5283
+ if (!message) {
5284
+ return /* @__PURE__ */ jsx("span", { style: styles15.preview, children: "No messages yet" });
5285
+ }
5286
+ if (message.type === "system") {
5287
+ return /* @__PURE__ */ jsx("span", { style: styles15.preview, children: message.body });
5288
+ }
5289
+ const text = previewText(message);
5290
+ const sender = message.senderName ? `${message.senderName.split(" ")[0]}:` : "";
5291
+ return /* @__PURE__ */ jsxs(
5292
+ "span",
5293
+ {
5294
+ style: {
5295
+ ...styles15.preview,
5296
+ ...hasUnread ? styles15.previewUnread : {}
5297
+ },
5298
+ children: [
5299
+ sender && /* @__PURE__ */ jsxs("span", { style: styles15.previewSender, children: [
5300
+ sender,
5301
+ " "
5302
+ ] }),
5303
+ text
5304
+ ]
5305
+ }
5306
+ );
5307
+ }
5308
+ function previewText(message) {
4987
5309
  switch (message.type) {
4988
5310
  case "audio":
4989
- return `${prefix}\u{1F3A4} Voice message`;
5311
+ return "Voice message";
4990
5312
  case "image":
4991
- return `${prefix}\u{1F4F7} ${message.fileName || "Image"}`;
5313
+ return message.fileName || "Image";
4992
5314
  case "file":
4993
- return `${prefix}\u{1F4CE} ${message.fileName || "File"}`;
5315
+ return message.fileName || "File";
4994
5316
  case "deep_link":
4995
- return `${prefix}\u{1F517} ${message.body}`;
4996
- case "system":
4997
- return message.body;
5317
+ return message.body || "Shared link";
4998
5318
  default: {
4999
5319
  const body = message.body || "";
5000
- const preview = body.length > 60 ? `${body.slice(0, 60)}\u2026` : body;
5001
- return `${prefix}${preview}`;
5320
+ return body.length > 60 ? `${body.slice(0, 60)}\u2026` : body;
5002
5321
  }
5003
5322
  }
5004
5323
  }
5324
+ function computeInitials2(name) {
5325
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("") || "?";
5326
+ }
5005
5327
  var styles15 = {
5006
5328
  container: {
5007
5329
  display: "flex",
5008
- alignItems: "center",
5009
- gap: SPACE.S3,
5010
- padding: `${SPACE.S3} ${SPACE.S3}`,
5330
+ alignItems: "flex-start",
5331
+ gap: "14px",
5011
5332
  width: "100%",
5333
+ padding: `14px ${SPACE.S5}`,
5334
+ minHeight: "84px",
5012
5335
  backgroundColor: "transparent",
5013
5336
  border: "none",
5014
- borderBottom: `1px solid ${COLOR.neutral100}`,
5337
+ borderLeft: "4px solid transparent",
5015
5338
  cursor: "pointer",
5016
- textAlign: "left"
5339
+ textAlign: "left",
5340
+ color: "inherit",
5341
+ fontFamily: "inherit",
5342
+ transition: "background-color 120ms ease"
5017
5343
  },
5018
5344
  selected: {
5019
- backgroundColor: COLOR.primaryBg
5345
+ backgroundColor: COLOR.primaryBg,
5346
+ borderLeftColor: COLOR.primary
5020
5347
  },
5021
- unread: {},
5022
5348
  avatarWrapper: {
5023
- position: "relative",
5024
- width: SIZE.avatar,
5025
- height: SIZE.avatar,
5349
+ width: "52px",
5350
+ height: "52px",
5026
5351
  flexShrink: 0
5027
5352
  },
5028
- avatar: {
5029
- width: SIZE.avatar,
5030
- height: SIZE.avatar,
5031
- borderRadius: RADIUS.full,
5032
- objectFit: "cover"
5353
+ avatarImg: {
5354
+ width: "52px",
5355
+ height: "52px",
5356
+ borderRadius: RADIUS.xl,
5357
+ objectFit: "cover",
5358
+ boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
5033
5359
  },
5034
- avatarFallback: {
5035
- width: SIZE.avatar,
5036
- height: SIZE.avatar,
5037
- borderRadius: RADIUS.full,
5038
- backgroundColor: COLOR.neutral200,
5360
+ avatarTile: {
5361
+ width: "52px",
5362
+ height: "52px",
5363
+ borderRadius: RADIUS.xl,
5364
+ backgroundColor: COLOR.primary,
5365
+ color: COLOR.white,
5039
5366
  display: "flex",
5040
5367
  alignItems: "center",
5041
5368
  justifyContent: "center",
5042
- fontSize: FONT_SIZE.md,
5043
- fontWeight: FONT_WEIGHT.semibold,
5044
- color: COLOR.neutral500
5045
- },
5046
- unreadDot: {
5047
- position: "absolute",
5048
- top: "0",
5049
- left: "0",
5050
- width: SIZE.dot,
5051
- height: SIZE.dot,
5052
- borderRadius: RADIUS.full,
5053
- backgroundColor: COLOR.primary,
5054
- border: `2px solid ${COLOR.white}`
5369
+ fontSize: FONT_SIZE.lg,
5370
+ fontWeight: FONT_WEIGHT.bold,
5371
+ letterSpacing: "0.02em",
5372
+ boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
5055
5373
  },
5056
5374
  content: {
5057
5375
  flex: 1,
5058
5376
  display: "flex",
5059
5377
  flexDirection: "column",
5060
- gap: "2px",
5378
+ gap: "3px",
5061
5379
  minWidth: 0
5062
5380
  },
5063
5381
  topRow: {
@@ -5082,123 +5400,191 @@ var styles15 = {
5082
5400
  time: {
5083
5401
  fontSize: FONT_SIZE.xs,
5084
5402
  color: COLOR.neutral500,
5403
+ fontVariantNumeric: "tabular-nums",
5085
5404
  flexShrink: 0
5086
5405
  },
5087
5406
  timeUnread: {
5088
5407
  color: COLOR.primary,
5089
5408
  fontWeight: FONT_WEIGHT.semibold
5090
5409
  },
5410
+ studyRow: {
5411
+ display: "flex",
5412
+ alignItems: "center",
5413
+ gap: SPACE.S2,
5414
+ minWidth: 0
5415
+ },
5416
+ studyType: {
5417
+ fontSize: FONT_SIZE.sm,
5418
+ color: COLOR.neutral500,
5419
+ overflow: "hidden",
5420
+ textOverflow: "ellipsis",
5421
+ whiteSpace: "nowrap"
5422
+ },
5423
+ studyDivider: {
5424
+ fontSize: FONT_SIZE.sm,
5425
+ color: COLOR.neutral400,
5426
+ flexShrink: 0
5427
+ },
5428
+ studyId: {
5429
+ fontSize: FONT_SIZE.sm,
5430
+ color: COLOR.neutral500,
5431
+ fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
5432
+ fontVariantNumeric: "tabular-nums",
5433
+ flexShrink: 0
5434
+ },
5091
5435
  bottomRow: {
5092
5436
  display: "flex",
5093
5437
  justifyContent: "space-between",
5094
5438
  alignItems: "center",
5095
- gap: SPACE.S2
5439
+ gap: SPACE.S2,
5440
+ marginTop: "2px"
5096
5441
  },
5097
5442
  preview: {
5098
5443
  fontSize: FONT_SIZE.sm,
5099
- color: COLOR.neutral600,
5444
+ color: COLOR.neutral500,
5100
5445
  overflow: "hidden",
5101
5446
  textOverflow: "ellipsis",
5102
5447
  whiteSpace: "nowrap",
5103
5448
  flex: 1,
5104
5449
  minWidth: 0
5105
5450
  },
5451
+ previewSender: {
5452
+ color: COLOR.neutral700,
5453
+ fontWeight: FONT_WEIGHT.semibold
5454
+ },
5106
5455
  previewUnread: {
5107
5456
  color: COLOR.neutral900,
5108
5457
  fontWeight: FONT_WEIGHT.medium
5109
5458
  },
5110
5459
  badge: {
5111
- minWidth: "20px",
5112
- height: "20px",
5113
- padding: `0 ${SPACE.S2}`,
5460
+ minWidth: "22px",
5461
+ height: "22px",
5462
+ padding: "0 9px",
5114
5463
  fontSize: FONT_SIZE.xs,
5115
- fontWeight: FONT_WEIGHT.semibold,
5464
+ fontWeight: FONT_WEIGHT.bold,
5116
5465
  color: COLOR.white,
5117
- backgroundColor: COLOR.primary,
5466
+ backgroundColor: COLOR.danger,
5118
5467
  borderRadius: RADIUS.pill,
5119
5468
  display: "flex",
5120
5469
  alignItems: "center",
5121
5470
  justifyContent: "center",
5471
+ fontVariantNumeric: "tabular-nums",
5122
5472
  flexShrink: 0
5123
5473
  }
5124
5474
  };
5475
+ function SearchIcon({ size = 20, color = "currentColor" }) {
5476
+ return /* @__PURE__ */ jsxs(
5477
+ "svg",
5478
+ {
5479
+ xmlns: "http://www.w3.org/2000/svg",
5480
+ width: size,
5481
+ height: size,
5482
+ viewBox: "0 0 24 24",
5483
+ fill: "none",
5484
+ stroke: color,
5485
+ strokeWidth: "2",
5486
+ strokeLinecap: "round",
5487
+ strokeLinejoin: "round",
5488
+ "aria-hidden": "true",
5489
+ children: [
5490
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
5491
+ /* @__PURE__ */ jsx("path", { d: "M20 20l-4-4" })
5492
+ ]
5493
+ }
5494
+ );
5495
+ }
5125
5496
  function ConversationList({
5126
5497
  conversations,
5127
5498
  selectedId,
5128
5499
  isLoading,
5129
5500
  error,
5130
5501
  onSelect,
5131
- title = "Conversations",
5132
5502
  className
5133
5503
  }) {
5134
5504
  const [query, setQuery] = useState("");
5135
- const [showUnreadOnly, setShowUnreadOnly] = useState(false);
5505
+ const [filter, setFilter] = useState("all");
5506
+ const [searchFocused, setSearchFocused] = useState(false);
5507
+ const unreadCount = useMemo(
5508
+ () => conversations.filter((c) => c.unreadCount > 0).length,
5509
+ [conversations]
5510
+ );
5511
+ const autoDefaultedRef = useRef(false);
5512
+ useEffect(() => {
5513
+ if (autoDefaultedRef.current) return;
5514
+ if (conversations.length === 0) return;
5515
+ autoDefaultedRef.current = true;
5516
+ if (unreadCount > 0) setFilter("unread");
5517
+ }, [conversations.length, unreadCount]);
5136
5518
  const filtered = useMemo(() => {
5137
5519
  let result = conversations;
5138
- if (showUnreadOnly) {
5520
+ if (filter === "unread") {
5139
5521
  result = result.filter((c) => c.unreadCount > 0);
5140
5522
  }
5141
5523
  const q = query.trim().toLowerCase();
5142
5524
  if (q) {
5143
5525
  result = result.filter((c) => {
5144
- const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
5145
5526
  const name = c.name.toLowerCase();
5146
- return name.includes(q) || patient.includes(q);
5527
+ const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
5528
+ const studyType = c.patientSnapshot?.studyType?.toLowerCase() || "";
5529
+ const patientId = c.patientSnapshot?.patientId?.toLowerCase() || "";
5530
+ return name.includes(q) || patient.includes(q) || studyType.includes(q) || patientId.includes(q);
5147
5531
  });
5148
5532
  }
5149
5533
  return result;
5150
- }, [conversations, query, showUnreadOnly]);
5151
- const totalUnread = conversations.reduce((sum, c) => sum + c.unreadCount, 0);
5534
+ }, [conversations, query, filter]);
5152
5535
  return /* @__PURE__ */ jsxs("div", { className, style: styles16.container, children: [
5153
- /* @__PURE__ */ jsxs("div", { style: styles16.header, children: [
5154
- /* @__PURE__ */ jsxs("div", { style: styles16.titleRow, children: [
5155
- /* @__PURE__ */ jsx("h3", { style: styles16.title, children: title }),
5156
- totalUnread > 0 && /* @__PURE__ */ jsx("span", { style: styles16.totalBadge, children: totalUnread })
5157
- ] }),
5158
- /* @__PURE__ */ jsx("div", { style: styles16.searchRow, children: /* @__PURE__ */ jsx(
5159
- "input",
5536
+ /* @__PURE__ */ jsxs("div", { style: styles16.tabs, children: [
5537
+ /* @__PURE__ */ jsx(
5538
+ TabButton,
5160
5539
  {
5161
- type: "text",
5162
- value: query,
5163
- onChange: (e) => setQuery(e.target.value),
5164
- placeholder: "Search patients or channels...",
5165
- style: styles16.searchInput
5540
+ label: "Unread",
5541
+ count: unreadCount,
5542
+ isActive: filter === "unread",
5543
+ onClick: () => setFilter("unread")
5166
5544
  }
5167
- ) }),
5168
- /* @__PURE__ */ jsxs("div", { style: styles16.filterRow, children: [
5169
- /* @__PURE__ */ jsx(
5170
- "button",
5171
- {
5172
- onClick: () => setShowUnreadOnly(false),
5173
- style: {
5174
- ...styles16.filterButton,
5175
- ...!showUnreadOnly ? styles16.filterButtonActive : {}
5176
- },
5177
- type: "button",
5178
- children: "All"
5179
- }
5180
- ),
5181
- /* @__PURE__ */ jsxs(
5182
- "button",
5183
- {
5184
- onClick: () => setShowUnreadOnly(true),
5185
- style: {
5186
- ...styles16.filterButton,
5187
- ...showUnreadOnly ? styles16.filterButtonActive : {}
5188
- },
5189
- type: "button",
5190
- children: [
5191
- "Unread ",
5192
- totalUnread > 0 && `(${totalUnread})`
5193
- ]
5194
- }
5195
- )
5196
- ] })
5545
+ ),
5546
+ /* @__PURE__ */ jsx(
5547
+ TabButton,
5548
+ {
5549
+ label: "All",
5550
+ count: conversations.length,
5551
+ isActive: filter === "all",
5552
+ onClick: () => setFilter("all")
5553
+ }
5554
+ )
5197
5555
  ] }),
5556
+ /* @__PURE__ */ jsxs(
5557
+ "div",
5558
+ {
5559
+ style: {
5560
+ ...styles16.search,
5561
+ ...searchFocused ? styles16.searchFocused : {}
5562
+ },
5563
+ children: [
5564
+ /* @__PURE__ */ jsx("span", { style: styles16.searchIcon, "aria-hidden": "true", children: /* @__PURE__ */ jsx(SearchIcon, { size: 20, color: COLOR.neutral500 }) }),
5565
+ /* @__PURE__ */ jsx(
5566
+ "input",
5567
+ {
5568
+ type: "text",
5569
+ value: query,
5570
+ onChange: (e) => setQuery(e.target.value),
5571
+ onFocus: () => setSearchFocused(true),
5572
+ onBlur: () => setSearchFocused(false),
5573
+ placeholder: "Search by patient name or MRN",
5574
+ "aria-label": "Search conversations",
5575
+ style: styles16.searchInput
5576
+ }
5577
+ )
5578
+ ]
5579
+ }
5580
+ ),
5198
5581
  /* @__PURE__ */ jsxs("div", { style: styles16.list, children: [
5199
- isLoading && /* @__PURE__ */ jsx("div", { style: styles16.state, children: "Loading conversations..." }),
5200
- error && /* @__PURE__ */ jsx("div", { style: styles16.errorState, children: error }),
5201
- !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsx("div", { style: styles16.state, children: query ? "No matching conversations" : "No conversations yet" }),
5582
+ isLoading && /* @__PURE__ */ jsxs("div", { style: styles16.loading, children: [
5583
+ /* @__PURE__ */ jsx(Spinner, { size: 16 }),
5584
+ /* @__PURE__ */ jsx("span", { children: "Loading conversations\u2026" })
5585
+ ] }),
5586
+ error && !isLoading && /* @__PURE__ */ jsx("div", { style: styles16.errorState, children: error }),
5587
+ !isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsx(EmptyState, { filter, hasQuery: query.trim().length > 0 }),
5202
5588
  filtered.map((item) => /* @__PURE__ */ jsx(
5203
5589
  ConversationListItem,
5204
5590
  {
@@ -5211,95 +5597,178 @@ function ConversationList({
5211
5597
  ] })
5212
5598
  ] });
5213
5599
  }
5600
+ function TabButton({
5601
+ label,
5602
+ count,
5603
+ isActive,
5604
+ onClick
5605
+ }) {
5606
+ return /* @__PURE__ */ jsxs(
5607
+ "button",
5608
+ {
5609
+ type: "button",
5610
+ onClick,
5611
+ style: {
5612
+ ...styles16.tab,
5613
+ ...isActive ? styles16.tabActive : {}
5614
+ },
5615
+ children: [
5616
+ /* @__PURE__ */ jsx("span", { children: label }),
5617
+ /* @__PURE__ */ jsx(
5618
+ "span",
5619
+ {
5620
+ style: {
5621
+ ...styles16.tabCount,
5622
+ ...isActive ? styles16.tabCountActive : {}
5623
+ },
5624
+ children: count
5625
+ }
5626
+ )
5627
+ ]
5628
+ }
5629
+ );
5630
+ }
5631
+ function EmptyState({ filter, hasQuery }) {
5632
+ let title = "No conversations yet";
5633
+ let subtitle = "New chats will show up here as cases come in.";
5634
+ if (filter === "unread") {
5635
+ title = "No unread messages";
5636
+ subtitle = "You're all caught up.";
5637
+ } else if (hasQuery) {
5638
+ title = "No conversations match";
5639
+ subtitle = "Try a different search.";
5640
+ }
5641
+ return /* @__PURE__ */ jsxs("div", { style: styles16.empty, children: [
5642
+ /* @__PURE__ */ jsx(ChatIllustration, { size: 88 }),
5643
+ /* @__PURE__ */ jsx("p", { style: styles16.emptyTitle, children: title }),
5644
+ /* @__PURE__ */ jsx("p", { style: styles16.emptySubtitle, children: subtitle })
5645
+ ] });
5646
+ }
5214
5647
  var styles16 = {
5215
5648
  container: {
5216
5649
  display: "flex",
5217
5650
  flexDirection: "column",
5218
5651
  height: "100%",
5219
- backgroundColor: COLOR.white,
5220
- borderRight: `1px solid ${COLOR.neutral200}`
5652
+ backgroundColor: COLOR.white
5221
5653
  },
5222
- header: {
5223
- padding: SPACE.S3,
5654
+ // Tabs sit at the top of the panel now (header section removed).
5655
+ // A bit more top padding so they don't crowd the top edge.
5656
+ tabs: {
5657
+ display: "flex",
5658
+ gap: SPACE.S2,
5659
+ padding: `${SPACE.S5} ${SPACE.S5} ${SPACE.S2}`,
5224
5660
  borderBottom: `1px solid ${COLOR.neutral200}`,
5225
- backgroundColor: COLOR.neutral50,
5226
5661
  flexShrink: 0
5227
5662
  },
5228
- titleRow: {
5229
- display: "flex",
5663
+ tab: {
5664
+ display: "inline-flex",
5230
5665
  alignItems: "center",
5231
5666
  gap: SPACE.S2,
5232
- marginBottom: SPACE.S2
5233
- },
5234
- title: {
5235
- margin: 0,
5236
- fontSize: FONT_SIZE.md,
5667
+ padding: `${SPACE.S2} ${SPACE.S4}`,
5668
+ backgroundColor: "transparent",
5669
+ border: "none",
5670
+ borderRadius: RADIUS.lg,
5671
+ cursor: "pointer",
5672
+ color: COLOR.neutral500,
5673
+ fontFamily: "inherit",
5674
+ fontSize: FONT_SIZE.sm,
5237
5675
  fontWeight: FONT_WEIGHT.semibold,
5238
- color: COLOR.neutral900
5676
+ transition: "background-color 120ms ease, color 120ms ease"
5239
5677
  },
5240
- totalBadge: {
5241
- minWidth: "20px",
5242
- height: "20px",
5243
- padding: `0 ${SPACE.S2}`,
5678
+ tabActive: {
5679
+ backgroundColor: COLOR.primaryBg,
5680
+ color: COLOR.primary
5681
+ },
5682
+ tabCount: {
5244
5683
  fontSize: FONT_SIZE.xs,
5245
- fontWeight: FONT_WEIGHT.semibold,
5246
- color: COLOR.white,
5247
- backgroundColor: COLOR.primary,
5684
+ fontVariantNumeric: "tabular-nums",
5685
+ padding: "1px 8px",
5248
5686
  borderRadius: RADIUS.pill,
5687
+ backgroundColor: COLOR.neutral200,
5688
+ color: COLOR.neutral500,
5689
+ fontWeight: FONT_WEIGHT.semibold
5690
+ },
5691
+ tabCountActive: {
5692
+ backgroundColor: COLOR.primary,
5693
+ color: COLOR.white
5694
+ },
5695
+ search: {
5249
5696
  display: "flex",
5250
5697
  alignItems: "center",
5251
- justifyContent: "center"
5252
- },
5253
- searchRow: {
5254
- marginBottom: SPACE.S2
5698
+ gap: SPACE.S2,
5699
+ margin: `14px ${SPACE.S5} ${SPACE.S3}`,
5700
+ padding: `${SPACE.S3} 14px`,
5701
+ backgroundColor: COLOR.neutral100,
5702
+ border: `1.5px solid ${COLOR.neutral200}`,
5703
+ borderRadius: "14px",
5704
+ transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
5255
5705
  },
5256
- searchInput: {
5257
- width: "100%",
5258
- padding: `${SPACE.S2} ${SPACE.S3}`,
5259
- fontSize: FONT_SIZE.sm,
5260
- border: `1px solid ${COLOR.neutral200}`,
5261
- borderRadius: RADIUS.md,
5262
- outline: "none",
5263
- fontFamily: "inherit"
5706
+ searchFocused: {
5707
+ backgroundColor: COLOR.white,
5708
+ borderColor: COLOR.primary,
5709
+ boxShadow: `0 0 0 3px ${COLOR.primaryBg}`
5264
5710
  },
5265
- filterRow: {
5711
+ searchIcon: {
5266
5712
  display: "flex",
5267
- gap: SPACE.S1
5713
+ alignItems: "center",
5714
+ justifyContent: "center",
5715
+ flexShrink: 0
5268
5716
  },
5269
- filterButton: {
5717
+ searchInput: {
5270
5718
  flex: 1,
5271
- padding: `${SPACE.S1} ${SPACE.S2}`,
5272
- fontSize: FONT_SIZE.xs,
5273
- color: COLOR.neutral500,
5274
- backgroundColor: COLOR.white,
5275
- border: `1px solid ${COLOR.neutral200}`,
5276
- borderRadius: RADIUS.md,
5277
- cursor: "pointer"
5278
- },
5279
- filterButtonActive: {
5280
- color: COLOR.white,
5281
- backgroundColor: COLOR.primary,
5282
- borderColor: COLOR.primary,
5283
- fontWeight: FONT_WEIGHT.medium
5719
+ minWidth: 0,
5720
+ border: "none",
5721
+ background: "transparent",
5722
+ outline: "none",
5723
+ fontFamily: "inherit",
5724
+ fontSize: FONT_SIZE.md,
5725
+ color: COLOR.neutral900
5284
5726
  },
5285
5727
  list: {
5286
5728
  flex: 1,
5287
- overflowY: "auto"
5729
+ overflowY: "auto",
5730
+ padding: "4px 0 12px"
5288
5731
  },
5289
- state: {
5290
- padding: `${SPACE.S6} ${SPACE.S4}`,
5732
+ loading: {
5733
+ display: "flex",
5734
+ alignItems: "center",
5735
+ justifyContent: "center",
5736
+ gap: SPACE.S2,
5737
+ padding: `44px ${SPACE.S5}`,
5291
5738
  fontSize: FONT_SIZE.sm,
5292
- color: COLOR.neutral500,
5293
- textAlign: "center"
5739
+ color: COLOR.neutral500
5294
5740
  },
5295
5741
  errorState: {
5296
5742
  padding: SPACE.S4,
5743
+ margin: SPACE.S3,
5297
5744
  fontSize: FONT_SIZE.sm,
5298
5745
  color: COLOR.danger,
5299
5746
  backgroundColor: COLOR.dangerBg,
5300
- margin: SPACE.S3,
5747
+ border: `1px solid ${COLOR.dangerBorder}`,
5301
5748
  borderRadius: RADIUS.md,
5302
5749
  textAlign: "center"
5750
+ },
5751
+ empty: {
5752
+ display: "flex",
5753
+ flexDirection: "column",
5754
+ alignItems: "center",
5755
+ justifyContent: "center",
5756
+ padding: "44px 24px",
5757
+ textAlign: "center",
5758
+ color: COLOR.neutral500
5759
+ },
5760
+ emptyTitle: {
5761
+ margin: "12px 0 4px",
5762
+ fontSize: FONT_SIZE.md,
5763
+ fontWeight: FONT_WEIGHT.semibold,
5764
+ color: COLOR.neutral700
5765
+ },
5766
+ emptySubtitle: {
5767
+ margin: 0,
5768
+ fontSize: FONT_SIZE.sm,
5769
+ color: COLOR.neutral500,
5770
+ maxWidth: "280px",
5771
+ lineHeight: LINE_HEIGHT.normal
5303
5772
  }
5304
5773
  };
5305
5774
  ensureGlobalStyles();
@@ -5307,7 +5776,6 @@ var COMPACT_BREAKPOINT = 720;
5307
5776
  function CollabInbox({
5308
5777
  initialConversationId,
5309
5778
  onSelectConversation,
5310
- title,
5311
5779
  className,
5312
5780
  style
5313
5781
  }) {
@@ -5344,8 +5812,7 @@ function CollabInbox({
5344
5812
  selectedId,
5345
5813
  isLoading,
5346
5814
  error,
5347
- onSelect: handleSelect,
5348
- title
5815
+ onSelect: handleSelect
5349
5816
  }
5350
5817
  ) }),
5351
5818
  showDetail && /* @__PURE__ */ jsx("div", { style: isCompact ? styles17.mainCompact : styles17.main, children: selected ? /* @__PURE__ */ jsx(
@@ -5358,7 +5825,7 @@ function CollabInbox({
5358
5825
  onBack: isCompact ? () => setSelectedId(null) : void 0
5359
5826
  },
5360
5827
  selected.id
5361
- ) : /* @__PURE__ */ jsx(EmptyState, { hasAny: conversations.length > 0 }) })
5828
+ ) : /* @__PURE__ */ jsx(EmptyState2, { hasAny: conversations.length > 0 }) })
5362
5829
  ]
5363
5830
  }
5364
5831
  );
@@ -5392,7 +5859,7 @@ function parseChannelName(name, orderId) {
5392
5859
  orderId
5393
5860
  };
5394
5861
  }
5395
- function EmptyState({ hasAny }) {
5862
+ function EmptyState2({ hasAny }) {
5396
5863
  return /* @__PURE__ */ jsxs("div", { style: styles17.empty, children: [
5397
5864
  /* @__PURE__ */ jsx("div", { style: styles17.emptyIcon, children: "\u{1F4AC}" }),
5398
5865
  /* @__PURE__ */ jsx("h3", { style: styles17.emptyTitle, children: hasAny ? "Select a conversation" : "No conversations yet" }),
@@ -5771,6 +6238,6 @@ function usePinnedMessages({
5771
6238
  };
5772
6239
  }
5773
6240
 
5774
- export { AUDIO_MIME_TYPE, ChannelSettings, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, ConversationList, ConversationListItem, DEEP_LINK_PREFIX, EVENTS, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, MessageActionsMenu, MessageBubble, MessageInput, MessageList, ParticipantsList, PatientHeader, PinnedMessagesBar, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, TYPING_DEBOUNCE_MS, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
6241
+ export { AUDIO_MIME_TYPE, ChannelSettings, CollabInbox, CollabInline, CollabPanel, CollabPopup, CollabProvider, CollabSocket, ConversationList, ConversationListItem, DEEP_LINK_PREFIX, EVENTS, MAX_FILE_SIZE, MAX_PINNED_MESSAGES, MESSAGES_PAGE_SIZE, MESSAGE_TYPES, MessageActionsMenu, MessageBubble, MessageInput, MessageList, ParticipantsList, PatientHeader, PinnedMessagesBar, ReplyPreview, ReplyQuoteBlock, SUPPORTED_IMAGE_TYPES, SeenByIndicator, THEME_DEFAULTS, THEME_VAR, TYPING_DEBOUNCE_MS, applyThemeOverrides, useAudioRecorder, useChannelSettings, useCollab, useConversation, useConversationList, useDeepLinks, useInlineCollab, useMessages, usePinnedMessages, useUnreadCount };
5775
6242
  //# sourceMappingURL=index.mjs.map
5776
6243
  //# sourceMappingURL=index.mjs.map