@natoe/colab 0.1.12 → 0.1.14
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.d.mts +225 -23
- package/dist/index.d.ts +225 -23
- package/dist/index.js +1325 -688
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1324 -690
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React4, { createContext, forwardRef, useRef, useImperativeHandle, useEffect, useCallback, useState,
|
|
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';
|
|
@@ -60,6 +60,13 @@ function toCamelKey(key) {
|
|
|
60
60
|
var CollabSocket = class {
|
|
61
61
|
constructor() {
|
|
62
62
|
this.socket = null;
|
|
63
|
+
/**
|
|
64
|
+
* Conversation channels keyed by id. Each entry is reference-counted so
|
|
65
|
+
* multiple surfaces (e.g. inline chat + expanded panel mounted at once
|
|
66
|
+
* for the same conversation) can coexist without one's `leaveConversation`
|
|
67
|
+
* tearing the channel out from under the other. See `joinConversation`
|
|
68
|
+
* and the returned `ChannelSubscription.release`.
|
|
69
|
+
*/
|
|
63
70
|
this.channels = /* @__PURE__ */ new Map();
|
|
64
71
|
this.presences = /* @__PURE__ */ new Map();
|
|
65
72
|
this.userChannel = null;
|
|
@@ -130,59 +137,85 @@ var CollabSocket = class {
|
|
|
130
137
|
onUnreadCountUpdate(callback) {
|
|
131
138
|
this.onUnreadUpdate = callback;
|
|
132
139
|
}
|
|
133
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Join a conversation channel and subscribe to events.
|
|
142
|
+
*
|
|
143
|
+
* Reference-counted: multiple callers can join the same conversation
|
|
144
|
+
* (e.g. inline preview + expanded panel mounted side-by-side). Each
|
|
145
|
+
* call binds its own listeners and gets back a `ChannelSubscription`.
|
|
146
|
+
* The underlying channel only `.leave()`s the server when the LAST
|
|
147
|
+
* subscriber calls `release()`.
|
|
148
|
+
*/
|
|
134
149
|
joinConversation(conversationId, callbacks) {
|
|
135
150
|
if (!this.socket) return null;
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
let entry = this.channels.get(conversationId);
|
|
152
|
+
if (!entry) {
|
|
153
|
+
const channel2 = this.socket.channel(`conversation:${conversationId}`, {});
|
|
154
|
+
channel2.join().receive("ok", () => {
|
|
155
|
+
}).receive("error", (reason) => {
|
|
156
|
+
this.config?.onError?.({
|
|
157
|
+
code: "CHANNEL_JOIN_ERROR",
|
|
158
|
+
message: `Failed to join conversation ${conversationId}`,
|
|
159
|
+
details: reason
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
entry = { channel: channel2, subscribers: 0, presence: null };
|
|
163
|
+
this.channels.set(conversationId, entry);
|
|
138
164
|
}
|
|
139
|
-
const channel =
|
|
165
|
+
const channel = entry.channel;
|
|
166
|
+
const refs = [];
|
|
167
|
+
const bind = (event, fn) => {
|
|
168
|
+
const ref = channel.on(event, fn);
|
|
169
|
+
refs.push({ event, ref });
|
|
170
|
+
};
|
|
140
171
|
if (callbacks.onMessage) {
|
|
141
|
-
|
|
172
|
+
bind(EVENTS.MESSAGE_NEW, (payload) => {
|
|
142
173
|
callbacks.onMessage(snakeToCamel(payload));
|
|
143
174
|
});
|
|
144
175
|
}
|
|
145
176
|
if (callbacks.onTyping) {
|
|
146
|
-
|
|
177
|
+
bind(EVENTS.USER_TYPING, (payload) => {
|
|
147
178
|
callbacks.onTyping(snakeToCamel(payload));
|
|
148
179
|
});
|
|
149
180
|
}
|
|
150
181
|
if (callbacks.onUserJoined) {
|
|
151
|
-
|
|
182
|
+
bind(EVENTS.USER_JOINED, (payload) => {
|
|
152
183
|
callbacks.onUserJoined(snakeToCamel(payload));
|
|
153
184
|
});
|
|
154
185
|
}
|
|
155
186
|
if (callbacks.onUserLeft) {
|
|
156
|
-
|
|
187
|
+
bind(EVENTS.USER_LEFT, (payload) => {
|
|
157
188
|
callbacks.onUserLeft(snakeToCamel(payload));
|
|
158
189
|
});
|
|
159
190
|
}
|
|
160
191
|
if (callbacks.onChannelUpdated) {
|
|
161
|
-
|
|
192
|
+
bind(EVENTS.CHANNEL_UPDATED, (payload) => {
|
|
162
193
|
callbacks.onChannelUpdated(snakeToCamel(payload));
|
|
163
194
|
});
|
|
164
195
|
}
|
|
165
196
|
if (callbacks.onChannelDeleted) {
|
|
166
|
-
|
|
197
|
+
bind(EVENTS.CHANNEL_DELETED, () => {
|
|
167
198
|
callbacks.onChannelDeleted();
|
|
168
199
|
});
|
|
169
200
|
}
|
|
170
201
|
if (callbacks.onMessageRead) {
|
|
171
|
-
|
|
172
|
-
callbacks.onMessageRead(
|
|
202
|
+
bind(EVENTS.MESSAGE_READ, (payload) => {
|
|
203
|
+
callbacks.onMessageRead(
|
|
204
|
+
snakeToCamel(payload)
|
|
205
|
+
);
|
|
173
206
|
});
|
|
174
207
|
}
|
|
175
208
|
if (callbacks.onMessagePinned) {
|
|
176
|
-
|
|
209
|
+
bind(EVENTS.MESSAGE_PINNED, (payload) => {
|
|
177
210
|
callbacks.onMessagePinned(snakeToCamel(payload));
|
|
178
211
|
});
|
|
179
212
|
}
|
|
180
213
|
if (callbacks.onMessageUnpinned) {
|
|
181
|
-
|
|
214
|
+
bind(EVENTS.MESSAGE_UNPINNED, (payload) => {
|
|
182
215
|
callbacks.onMessageUnpinned(snakeToCamel(payload));
|
|
183
216
|
});
|
|
184
217
|
}
|
|
185
|
-
if (callbacks.onPresence) {
|
|
218
|
+
if (callbacks.onPresence && !entry.presence) {
|
|
186
219
|
const presence = new Presence(channel);
|
|
187
220
|
presence.onSync(() => {
|
|
188
221
|
const online = {};
|
|
@@ -191,27 +224,45 @@ var CollabSocket = class {
|
|
|
191
224
|
});
|
|
192
225
|
callbacks.onPresence(online);
|
|
193
226
|
});
|
|
227
|
+
entry.presence = presence;
|
|
194
228
|
this.presences.set(conversationId, presence);
|
|
195
229
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
230
|
+
entry.subscribers += 1;
|
|
231
|
+
let released = false;
|
|
232
|
+
const release = () => {
|
|
233
|
+
if (released) return;
|
|
234
|
+
released = true;
|
|
235
|
+
const current = this.channels.get(conversationId);
|
|
236
|
+
if (!current) return;
|
|
237
|
+
for (const { event, ref } of refs) {
|
|
238
|
+
current.channel.off(event, ref);
|
|
239
|
+
}
|
|
240
|
+
current.subscribers -= 1;
|
|
241
|
+
if (current.subscribers <= 0) {
|
|
242
|
+
current.channel.leave();
|
|
243
|
+
this.channels.delete(conversationId);
|
|
244
|
+
this.presences.delete(conversationId);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
return { channel, release };
|
|
206
248
|
}
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
/**
|
|
250
|
+
* @deprecated Use the `release()` method returned by `joinConversation()`.
|
|
251
|
+
* Kept as a no-op so older callers don't throw — but it cannot identify
|
|
252
|
+
* which subscriber should leave, so it silently does nothing. Any code
|
|
253
|
+
* still calling this will leak listeners and prevent the channel from
|
|
254
|
+
* ever being torn down. Migrate to the subscription handle.
|
|
255
|
+
*/
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
257
|
+
leaveConversation(_conversationId) {
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Look up the underlying Phoenix Channel for a conversation, if any
|
|
261
|
+
* subscriber is still holding it. All send/push paths go through this
|
|
262
|
+
* helper so the refcounted entry shape is contained to joinConversation.
|
|
263
|
+
*/
|
|
264
|
+
getChannel(conversationId) {
|
|
265
|
+
return this.channels.get(conversationId)?.channel ?? null;
|
|
215
266
|
}
|
|
216
267
|
/** Send a message to a conversation.
|
|
217
268
|
*
|
|
@@ -223,7 +274,7 @@ var CollabSocket = class {
|
|
|
223
274
|
*/
|
|
224
275
|
sendMessage(conversationId, payload) {
|
|
225
276
|
return new Promise((resolve, reject) => {
|
|
226
|
-
const channel = this.
|
|
277
|
+
const channel = this.getChannel(conversationId);
|
|
227
278
|
if (!channel) {
|
|
228
279
|
reject({ code: "NOT_JOINED", message: "Not joined to this conversation" });
|
|
229
280
|
return;
|
|
@@ -246,7 +297,7 @@ var CollabSocket = class {
|
|
|
246
297
|
}
|
|
247
298
|
/** Broadcast typing indicator */
|
|
248
299
|
sendTyping(conversationId, isTyping) {
|
|
249
|
-
const channel = this.
|
|
300
|
+
const channel = this.getChannel(conversationId);
|
|
250
301
|
channel?.push(EVENTS.USER_TYPING, {
|
|
251
302
|
userId: this.config?.userId,
|
|
252
303
|
userName: this.config?.userName,
|
|
@@ -255,7 +306,7 @@ var CollabSocket = class {
|
|
|
255
306
|
}
|
|
256
307
|
/** Mark messages as read */
|
|
257
308
|
markAsRead(conversationId, messageId) {
|
|
258
|
-
const channel = this.
|
|
309
|
+
const channel = this.getChannel(conversationId);
|
|
259
310
|
channel?.push(EVENTS.MESSAGE_READ, {
|
|
260
311
|
messageId,
|
|
261
312
|
userId: this.config?.userId
|
|
@@ -289,7 +340,7 @@ var CollabSocket = class {
|
|
|
289
340
|
/** Generic push with promise wrapper */
|
|
290
341
|
channelPush(conversationId, event, payload) {
|
|
291
342
|
return new Promise((resolve, reject) => {
|
|
292
|
-
const channel = this.
|
|
343
|
+
const channel = this.getChannel(conversationId);
|
|
293
344
|
if (!channel) {
|
|
294
345
|
reject({ code: "NOT_JOINED", message: "Not joined to this conversation" });
|
|
295
346
|
return;
|
|
@@ -306,7 +357,7 @@ var CollabSocket = class {
|
|
|
306
357
|
}
|
|
307
358
|
/** Disconnect socket and leave all channels */
|
|
308
359
|
disconnect() {
|
|
309
|
-
this.channels.forEach((
|
|
360
|
+
this.channels.forEach((entry) => entry.channel.leave());
|
|
310
361
|
this.channels.clear();
|
|
311
362
|
this.presences.clear();
|
|
312
363
|
this.userChannel?.leave();
|
|
@@ -315,6 +366,203 @@ var CollabSocket = class {
|
|
|
315
366
|
this.config = null;
|
|
316
367
|
}
|
|
317
368
|
};
|
|
369
|
+
|
|
370
|
+
// src/core/theme.ts
|
|
371
|
+
var FONT_SIZE = {
|
|
372
|
+
xs: "12px",
|
|
373
|
+
sm: "13px",
|
|
374
|
+
md: "15px",
|
|
375
|
+
lg: "17px",
|
|
376
|
+
xxl: "28px"
|
|
377
|
+
};
|
|
378
|
+
var FONT_WEIGHT = {
|
|
379
|
+
regular: 400,
|
|
380
|
+
medium: 500,
|
|
381
|
+
semibold: 600,
|
|
382
|
+
bold: 700
|
|
383
|
+
};
|
|
384
|
+
var LINE_HEIGHT = {
|
|
385
|
+
tight: 1.3,
|
|
386
|
+
normal: 1.45};
|
|
387
|
+
var SPACE = {
|
|
388
|
+
S1: "4px",
|
|
389
|
+
S2: "8px",
|
|
390
|
+
S3: "12px",
|
|
391
|
+
S4: "16px",
|
|
392
|
+
S5: "20px",
|
|
393
|
+
S6: "24px",
|
|
394
|
+
S12: "48px"
|
|
395
|
+
};
|
|
396
|
+
var RADIUS = {
|
|
397
|
+
sm: "4px",
|
|
398
|
+
md: "8px",
|
|
399
|
+
lg: "12px",
|
|
400
|
+
xl: "16px",
|
|
401
|
+
pill: "9999px",
|
|
402
|
+
full: "50%"
|
|
403
|
+
};
|
|
404
|
+
var THEME_VAR = {
|
|
405
|
+
primary: "--natoe-colab-primary",
|
|
406
|
+
primaryHover: "--natoe-colab-primary-hover",
|
|
407
|
+
primaryBg: "--natoe-colab-primary-bg",
|
|
408
|
+
primaryFg: "--natoe-colab-primary-fg",
|
|
409
|
+
success: "--natoe-colab-success",
|
|
410
|
+
warning: "--natoe-colab-warning",
|
|
411
|
+
danger: "--natoe-colab-danger",
|
|
412
|
+
dangerBg: "--natoe-colab-danger-bg",
|
|
413
|
+
dangerBorder: "--natoe-colab-danger-border",
|
|
414
|
+
dangerFg: "--natoe-colab-danger-fg",
|
|
415
|
+
fontStack: "--natoe-colab-font-stack",
|
|
416
|
+
// Neutral surface palette — promoted to CSS vars so consumers (e.g.
|
|
417
|
+
// CollabPanel themeMode='dark') can flip the whole grayscale at a subtree
|
|
418
|
+
// level without re-themeing every component.
|
|
419
|
+
white: "--natoe-colab-white",
|
|
420
|
+
neutral50: "--natoe-colab-neutral-50",
|
|
421
|
+
neutral100: "--natoe-colab-neutral-100",
|
|
422
|
+
neutral200: "--natoe-colab-neutral-200",
|
|
423
|
+
neutral300: "--natoe-colab-neutral-300",
|
|
424
|
+
neutral400: "--natoe-colab-neutral-400",
|
|
425
|
+
neutral500: "--natoe-colab-neutral-500",
|
|
426
|
+
neutral600: "--natoe-colab-neutral-600",
|
|
427
|
+
neutral700: "--natoe-colab-neutral-700",
|
|
428
|
+
neutral800: "--natoe-colab-neutral-800",
|
|
429
|
+
neutral900: "--natoe-colab-neutral-900"
|
|
430
|
+
};
|
|
431
|
+
var THEME_DEFAULTS = {
|
|
432
|
+
primary: "#2563eb",
|
|
433
|
+
primaryHover: "#1d4ed8",
|
|
434
|
+
primaryBg: "#dbeafe",
|
|
435
|
+
primaryFg: "#1d4ed8",
|
|
436
|
+
success: "#059669",
|
|
437
|
+
warning: "#d97706",
|
|
438
|
+
danger: "#dc2626",
|
|
439
|
+
dangerBg: "#fef2f2",
|
|
440
|
+
dangerBorder: "#fecaca",
|
|
441
|
+
dangerFg: "#b91c1c",
|
|
442
|
+
fontStack: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
443
|
+
// Neutral grayscale — these defaults are the same hex literals the
|
|
444
|
+
// package shipped with; they're now overridable per-subtree.
|
|
445
|
+
white: "#ffffff",
|
|
446
|
+
neutral50: "#f9fafb",
|
|
447
|
+
neutral100: "#f3f4f6",
|
|
448
|
+
neutral200: "#e5e7eb",
|
|
449
|
+
neutral300: "#d1d5db",
|
|
450
|
+
neutral400: "#9ca3af",
|
|
451
|
+
neutral500: "#6b7280",
|
|
452
|
+
neutral600: "#4b5563",
|
|
453
|
+
neutral700: "#374151",
|
|
454
|
+
neutral800: "#1f2937",
|
|
455
|
+
neutral900: "#111827"
|
|
456
|
+
};
|
|
457
|
+
var cssVar = (name, fallback) => `var(${name}, ${fallback})`;
|
|
458
|
+
var COLOR = {
|
|
459
|
+
// Brand — themeable
|
|
460
|
+
primary: cssVar(THEME_VAR.primary, THEME_DEFAULTS.primary),
|
|
461
|
+
primaryHover: cssVar(THEME_VAR.primaryHover, THEME_DEFAULTS.primaryHover),
|
|
462
|
+
/** Tinted surface for chips/cards on brand-coloured states. */
|
|
463
|
+
primaryBg: cssVar(THEME_VAR.primaryBg, THEME_DEFAULTS.primaryBg),
|
|
464
|
+
primaryFg: cssVar(THEME_VAR.primaryFg, THEME_DEFAULTS.primaryFg),
|
|
465
|
+
// Neutral grayscale — themeable via CSS variables. Light-mode defaults
|
|
466
|
+
// taken from Tailwind's zinc-leaning slate to match the package's
|
|
467
|
+
// existing tonal balance; a host can override the entire palette by
|
|
468
|
+
// setting --natoe-colab-* values on any ancestor element (used by
|
|
469
|
+
// CollabPanel themeMode='dark' for the viewer's left-panel surface).
|
|
470
|
+
white: cssVar(THEME_VAR.white, THEME_DEFAULTS.white),
|
|
471
|
+
neutral50: cssVar(THEME_VAR.neutral50, THEME_DEFAULTS.neutral50),
|
|
472
|
+
neutral100: cssVar(THEME_VAR.neutral100, THEME_DEFAULTS.neutral100),
|
|
473
|
+
neutral200: cssVar(THEME_VAR.neutral200, THEME_DEFAULTS.neutral200),
|
|
474
|
+
neutral300: cssVar(THEME_VAR.neutral300, THEME_DEFAULTS.neutral300),
|
|
475
|
+
neutral400: cssVar(THEME_VAR.neutral400, THEME_DEFAULTS.neutral400),
|
|
476
|
+
neutral500: cssVar(THEME_VAR.neutral500, THEME_DEFAULTS.neutral500),
|
|
477
|
+
neutral600: cssVar(THEME_VAR.neutral600, THEME_DEFAULTS.neutral600),
|
|
478
|
+
neutral700: cssVar(THEME_VAR.neutral700, THEME_DEFAULTS.neutral700),
|
|
479
|
+
neutral800: cssVar(THEME_VAR.neutral800, THEME_DEFAULTS.neutral800),
|
|
480
|
+
neutral900: cssVar(THEME_VAR.neutral900, THEME_DEFAULTS.neutral900),
|
|
481
|
+
slate800: "#1e293b",
|
|
482
|
+
// Semantic — themeable
|
|
483
|
+
success: cssVar(THEME_VAR.success, THEME_DEFAULTS.success),
|
|
484
|
+
warning: cssVar(THEME_VAR.warning, THEME_DEFAULTS.warning),
|
|
485
|
+
danger: cssVar(THEME_VAR.danger, THEME_DEFAULTS.danger),
|
|
486
|
+
dangerBg: cssVar(THEME_VAR.dangerBg, THEME_DEFAULTS.dangerBg),
|
|
487
|
+
dangerBorder: cssVar(THEME_VAR.dangerBorder, THEME_DEFAULTS.dangerBorder),
|
|
488
|
+
dangerFg: cssVar(THEME_VAR.dangerFg, THEME_DEFAULTS.dangerFg)
|
|
489
|
+
};
|
|
490
|
+
var SIZE = {
|
|
491
|
+
/** Default interactive height (text buttons, list rows). */
|
|
492
|
+
control: "40px",
|
|
493
|
+
/** Square icon-only buttons inside chrome (popup title, menu trigger). */
|
|
494
|
+
controlIcon: "36px"};
|
|
495
|
+
var SHADOW = {
|
|
496
|
+
/** Cards / floating popovers (message-actions menu). */
|
|
497
|
+
popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
|
|
498
|
+
toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
|
|
499
|
+
};
|
|
500
|
+
var Z_INDEX = {
|
|
501
|
+
/** Sticky day divider — above bubbles, below interactive popovers. */
|
|
502
|
+
sticky: 1,
|
|
503
|
+
/** Toasts inside a panel. */
|
|
504
|
+
toast: 20,
|
|
505
|
+
/** Floating CollabPopup dialog. */
|
|
506
|
+
dialog: 9999,
|
|
507
|
+
/** Portaled message-actions menu — must sit above the dialog. */
|
|
508
|
+
menu: 1e4
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// src/core/styles.ts
|
|
512
|
+
var FONT_STACK = `var(${THEME_VAR.fontStack}, ${THEME_DEFAULTS.fontStack})`;
|
|
513
|
+
var ROOT_CLASS = "natoe-colab-root";
|
|
514
|
+
var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
|
|
515
|
+
var ROOT_DEFAULTS_BLOCK = Object.keys(THEME_DEFAULTS).map((key) => ` ${THEME_VAR[key]}: ${THEME_DEFAULTS[key]};`).join("\n");
|
|
516
|
+
var GLOBAL_CSS = `
|
|
517
|
+
:root {
|
|
518
|
+
${ROOT_DEFAULTS_BLOCK}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
@keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
|
|
522
|
+
|
|
523
|
+
@keyframes natoe-colab-message-in {
|
|
524
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
525
|
+
to { opacity: 1; transform: translateY(0); }
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
|
|
529
|
+
get heavier borders and stronger text without losing the package look. */
|
|
530
|
+
@media (prefers-contrast: more) {
|
|
531
|
+
.${ROOT_CLASS} {
|
|
532
|
+
color: #000000;
|
|
533
|
+
}
|
|
534
|
+
.${ROOT_CLASS} button {
|
|
535
|
+
outline: 1px solid currentColor;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* Honour reduced-motion preferences by killing entry animations. */
|
|
540
|
+
@media (prefers-reduced-motion: reduce) {
|
|
541
|
+
.${ROOT_CLASS} [data-natoe-message-bubble] {
|
|
542
|
+
animation: none !important;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
`;
|
|
546
|
+
function ensureGlobalStyles() {
|
|
547
|
+
if (typeof document === "undefined") return;
|
|
548
|
+
if (document.getElementById(GLOBAL_STYLE_ID)) return;
|
|
549
|
+
const style = document.createElement("style");
|
|
550
|
+
style.id = GLOBAL_STYLE_ID;
|
|
551
|
+
style.textContent = GLOBAL_CSS;
|
|
552
|
+
document.head.appendChild(style);
|
|
553
|
+
}
|
|
554
|
+
function applyThemeOverrides(theme) {
|
|
555
|
+
if (typeof document === "undefined") return;
|
|
556
|
+
const root = document.documentElement;
|
|
557
|
+
Object.keys(THEME_VAR).forEach((key) => {
|
|
558
|
+
const value = theme?.[key];
|
|
559
|
+
if (value) {
|
|
560
|
+
root.style.setProperty(THEME_VAR[key], value);
|
|
561
|
+
} else {
|
|
562
|
+
root.style.removeProperty(THEME_VAR[key]);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
318
566
|
var CollabContext = createContext(null);
|
|
319
567
|
function useCollab() {
|
|
320
568
|
const context = useContext(CollabContext);
|
|
@@ -333,6 +581,9 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
333
581
|
const pendingResolvers = useRef(/* @__PURE__ */ new Map());
|
|
334
582
|
const previewCache = useRef(/* @__PURE__ */ new Map());
|
|
335
583
|
const batchScheduled = useRef(false);
|
|
584
|
+
useEffect(() => {
|
|
585
|
+
applyThemeOverrides(config.theme);
|
|
586
|
+
}, [config.theme]);
|
|
336
587
|
useEffect(() => {
|
|
337
588
|
if (typeof window === "undefined") return;
|
|
338
589
|
let s = socket;
|
|
@@ -548,6 +799,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
548
799
|
config,
|
|
549
800
|
apiBaseUrl,
|
|
550
801
|
totalUnread,
|
|
802
|
+
unreadCounts,
|
|
551
803
|
requestPreview,
|
|
552
804
|
invalidatePreview,
|
|
553
805
|
fetchMessages,
|
|
@@ -604,6 +856,7 @@ function useConversation({
|
|
|
604
856
|
const [isConnected, setIsConnected] = useState(false);
|
|
605
857
|
const [replyTo, setReplyTo] = useState(null);
|
|
606
858
|
const joinedConversationId = useRef(null);
|
|
859
|
+
const channelSubscription = useRef(null);
|
|
607
860
|
const typingTimers = useRef(/* @__PURE__ */ new Map());
|
|
608
861
|
const ensureConversationInFlight = useRef(null);
|
|
609
862
|
const markedReadIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -617,7 +870,9 @@ function useConversation({
|
|
|
617
870
|
const joinChannel = useCallback(
|
|
618
871
|
(conv) => {
|
|
619
872
|
if (joinedConversationId.current === conv.id) return;
|
|
620
|
-
|
|
873
|
+
channelSubscription.current?.release();
|
|
874
|
+
channelSubscription.current = null;
|
|
875
|
+
const subscription = socket.joinConversation(conv.id, {
|
|
621
876
|
// Dedupe by id so an optimistic message (added client-side on send)
|
|
622
877
|
// doesn't double up when the server's broadcast arrives.
|
|
623
878
|
onMessage: (msg) => setMessages((prev) => {
|
|
@@ -667,6 +922,8 @@ function useConversation({
|
|
|
667
922
|
onChannelDeleted: () => {
|
|
668
923
|
setConversation(null);
|
|
669
924
|
setMessages([]);
|
|
925
|
+
channelSubscription.current?.release();
|
|
926
|
+
channelSubscription.current = null;
|
|
670
927
|
joinedConversationId.current = null;
|
|
671
928
|
setIsConnected(false);
|
|
672
929
|
},
|
|
@@ -692,6 +949,7 @@ function useConversation({
|
|
|
692
949
|
);
|
|
693
950
|
}
|
|
694
951
|
});
|
|
952
|
+
channelSubscription.current = subscription;
|
|
695
953
|
joinedConversationId.current = conv.id;
|
|
696
954
|
setIsConnected(true);
|
|
697
955
|
},
|
|
@@ -725,26 +983,22 @@ function useConversation({
|
|
|
725
983
|
setConversation(existingConv);
|
|
726
984
|
setParticipants(preview.participants);
|
|
727
985
|
joinChannel(existingConv);
|
|
728
|
-
setMessages(preview.lastMessages);
|
|
729
|
-
setHasMore(preview.messageCount > preview.lastMessages.length);
|
|
730
|
-
setPinnedMessages(preview.lastMessages.filter((m) => m.isPinned));
|
|
731
|
-
setIsLoading(false);
|
|
732
986
|
if (loadHistory) {
|
|
733
987
|
fetchMessages(preview.conversationId).then((history) => {
|
|
734
988
|
if (cancelled) return;
|
|
735
|
-
setMessages(
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
});
|
|
740
|
-
setHasMore(history.length > 0);
|
|
741
|
-
setPinnedMessages((prev) => {
|
|
742
|
-
const fromHistory = history.filter((m) => m.isPinned);
|
|
743
|
-
const seen = new Set(prev.map((m) => m.id));
|
|
744
|
-
return [...fromHistory.filter((m) => !seen.has(m.id)), ...prev];
|
|
745
|
-
});
|
|
989
|
+
setMessages(history);
|
|
990
|
+
setHasMore(history.length >= MESSAGES_PAGE_SIZE);
|
|
991
|
+
setPinnedMessages(history.filter((m) => m.isPinned));
|
|
992
|
+
setIsLoading(false);
|
|
746
993
|
}).catch(() => {
|
|
994
|
+
if (cancelled) return;
|
|
995
|
+
setIsLoading(false);
|
|
747
996
|
});
|
|
997
|
+
} else {
|
|
998
|
+
setMessages([]);
|
|
999
|
+
setHasMore(false);
|
|
1000
|
+
setPinnedMessages([]);
|
|
1001
|
+
setIsLoading(false);
|
|
748
1002
|
}
|
|
749
1003
|
} catch (err) {
|
|
750
1004
|
if (cancelled) return;
|
|
@@ -756,10 +1010,9 @@ function useConversation({
|
|
|
756
1010
|
init();
|
|
757
1011
|
return () => {
|
|
758
1012
|
cancelled = true;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
1013
|
+
channelSubscription.current?.release();
|
|
1014
|
+
channelSubscription.current = null;
|
|
1015
|
+
joinedConversationId.current = null;
|
|
763
1016
|
typingTimers.current.forEach((timer) => clearTimeout(timer));
|
|
764
1017
|
typingTimers.current.clear();
|
|
765
1018
|
markedReadIdsRef.current.clear();
|
|
@@ -825,7 +1078,7 @@ function useConversation({
|
|
|
825
1078
|
const payload = {
|
|
826
1079
|
body,
|
|
827
1080
|
type: "text",
|
|
828
|
-
...replyTo ? {
|
|
1081
|
+
...replyTo ? { reply_to_id: replyTo.id } : {}
|
|
829
1082
|
};
|
|
830
1083
|
const { conv, persistedByCreate } = await ensureConversation(payload);
|
|
831
1084
|
setReplyTo(null);
|
|
@@ -878,7 +1131,7 @@ function useConversation({
|
|
|
878
1131
|
const payload = {
|
|
879
1132
|
body: target.body,
|
|
880
1133
|
type: "text",
|
|
881
|
-
...target.replyToId ? {
|
|
1134
|
+
...target.replyToId ? { reply_to_id: target.replyToId } : {}
|
|
882
1135
|
};
|
|
883
1136
|
try {
|
|
884
1137
|
await socket.sendMessage(conversation.id, payload);
|
|
@@ -1175,17 +1428,26 @@ function DicomIcon({ size = 18, color = "currentColor" }) {
|
|
|
1175
1428
|
}
|
|
1176
1429
|
);
|
|
1177
1430
|
}
|
|
1178
|
-
function
|
|
1179
|
-
return /* @__PURE__ */
|
|
1431
|
+
function PeopleIcon({ size = 18, color = "currentColor" }) {
|
|
1432
|
+
return /* @__PURE__ */ jsxs(
|
|
1180
1433
|
"svg",
|
|
1181
1434
|
{
|
|
1182
1435
|
xmlns: "http://www.w3.org/2000/svg",
|
|
1183
1436
|
width: size,
|
|
1184
1437
|
height: size,
|
|
1185
1438
|
viewBox: "0 0 24 24",
|
|
1186
|
-
fill:
|
|
1439
|
+
fill: "none",
|
|
1440
|
+
stroke: color,
|
|
1441
|
+
strokeWidth: "2",
|
|
1442
|
+
strokeLinecap: "round",
|
|
1443
|
+
strokeLinejoin: "round",
|
|
1187
1444
|
"aria-hidden": "true",
|
|
1188
|
-
children:
|
|
1445
|
+
children: [
|
|
1446
|
+
/* @__PURE__ */ jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
|
|
1447
|
+
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "7", r: "4" }),
|
|
1448
|
+
/* @__PURE__ */ jsx("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }),
|
|
1449
|
+
/* @__PURE__ */ jsx("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
|
|
1450
|
+
]
|
|
1189
1451
|
}
|
|
1190
1452
|
);
|
|
1191
1453
|
}
|
|
@@ -1203,102 +1465,6 @@ function BackIcon({ size = 22, color = "currentColor" }) {
|
|
|
1203
1465
|
}
|
|
1204
1466
|
);
|
|
1205
1467
|
}
|
|
1206
|
-
|
|
1207
|
-
// src/core/theme.ts
|
|
1208
|
-
var FONT_SIZE = {
|
|
1209
|
-
xs: "12px",
|
|
1210
|
-
sm: "13px",
|
|
1211
|
-
md: "15px",
|
|
1212
|
-
lg: "17px",
|
|
1213
|
-
xxl: "28px"
|
|
1214
|
-
};
|
|
1215
|
-
var FONT_WEIGHT = {
|
|
1216
|
-
regular: 400,
|
|
1217
|
-
medium: 500,
|
|
1218
|
-
semibold: 600,
|
|
1219
|
-
bold: 700
|
|
1220
|
-
};
|
|
1221
|
-
var LINE_HEIGHT = {
|
|
1222
|
-
tight: 1.3,
|
|
1223
|
-
normal: 1.45};
|
|
1224
|
-
var SPACE = {
|
|
1225
|
-
S1: "4px",
|
|
1226
|
-
S2: "8px",
|
|
1227
|
-
S3: "12px",
|
|
1228
|
-
S4: "16px",
|
|
1229
|
-
S5: "20px",
|
|
1230
|
-
S6: "24px",
|
|
1231
|
-
S12: "48px"
|
|
1232
|
-
};
|
|
1233
|
-
var RADIUS = {
|
|
1234
|
-
sm: "4px",
|
|
1235
|
-
md: "8px",
|
|
1236
|
-
lg: "12px",
|
|
1237
|
-
pill: "9999px",
|
|
1238
|
-
full: "50%"
|
|
1239
|
-
};
|
|
1240
|
-
var COLOR = {
|
|
1241
|
-
// Brand
|
|
1242
|
-
primary: "#2563eb",
|
|
1243
|
-
/** Tinted surface for chips/cards on brand-coloured states. */
|
|
1244
|
-
primaryBg: "#dbeafe",
|
|
1245
|
-
primaryFg: "#1d4ed8",
|
|
1246
|
-
// Neutral grayscale — taken from Tailwind's zinc-leaning slate, the
|
|
1247
|
-
// existing dominant family in the package.
|
|
1248
|
-
white: "#ffffff",
|
|
1249
|
-
neutral50: "#f9fafb",
|
|
1250
|
-
neutral100: "#f3f4f6",
|
|
1251
|
-
neutral200: "#e5e7eb",
|
|
1252
|
-
neutral300: "#d1d5db",
|
|
1253
|
-
neutral400: "#9ca3af",
|
|
1254
|
-
neutral500: "#6b7280",
|
|
1255
|
-
neutral600: "#4b5563",
|
|
1256
|
-
neutral700: "#374151",
|
|
1257
|
-
neutral800: "#1f2937",
|
|
1258
|
-
neutral900: "#111827",
|
|
1259
|
-
// Slate (used by day dividers + dialog title bar)
|
|
1260
|
-
slate200: "#e2e8f0",
|
|
1261
|
-
slate700: "#334155",
|
|
1262
|
-
slate800: "#1e293b",
|
|
1263
|
-
// Semantic
|
|
1264
|
-
success: "#059669",
|
|
1265
|
-
warning: "#d97706",
|
|
1266
|
-
danger: "#dc2626",
|
|
1267
|
-
dangerBg: "#fef2f2",
|
|
1268
|
-
dangerBorder: "#fecaca",
|
|
1269
|
-
dangerFg: "#b91c1c"
|
|
1270
|
-
};
|
|
1271
|
-
var SIZE = {
|
|
1272
|
-
/** Default interactive height (text buttons, list rows). */
|
|
1273
|
-
control: "40px",
|
|
1274
|
-
/** Square icon-only buttons inside chrome (popup title, menu trigger). */
|
|
1275
|
-
controlIcon: "36px",
|
|
1276
|
-
/** Primary input controls (textarea send/attach). 44px = WCAG min. */
|
|
1277
|
-
controlPrimary: "44px",
|
|
1278
|
-
/** Avatar in the inbox row. */
|
|
1279
|
-
avatar: "40px",
|
|
1280
|
-
/** Unread dot. */
|
|
1281
|
-
dot: "10px"
|
|
1282
|
-
};
|
|
1283
|
-
var SHADOW = {
|
|
1284
|
-
/** Cards / floating popovers (message-actions menu). */
|
|
1285
|
-
popover: "0 6px 18px rgba(0, 0, 0, 0.12)",
|
|
1286
|
-
/** Dialog (CollabPopup). */
|
|
1287
|
-
dialog: "0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08)",
|
|
1288
|
-
/** Subtle separator shadow (sticky day divider, toast). */
|
|
1289
|
-
subtle: "0 1px 2px rgba(0, 0, 0, 0.04)",
|
|
1290
|
-
toast: "0 4px 12px rgba(0, 0, 0, 0.18)"
|
|
1291
|
-
};
|
|
1292
|
-
var Z_INDEX = {
|
|
1293
|
-
/** Sticky day divider — above bubbles, below interactive popovers. */
|
|
1294
|
-
sticky: 1,
|
|
1295
|
-
/** Toasts inside a panel. */
|
|
1296
|
-
toast: 20,
|
|
1297
|
-
/** Floating CollabPopup dialog. */
|
|
1298
|
-
dialog: 9999,
|
|
1299
|
-
/** Portaled message-actions menu — must sit above the dialog. */
|
|
1300
|
-
menu: 1e4
|
|
1301
|
-
};
|
|
1302
1468
|
function resolveDisplayName(patientData, displayName) {
|
|
1303
1469
|
const localComplete = !!patientData.labName && !!patientData.displayOrderId;
|
|
1304
1470
|
if (localComplete) return buildChannelName(patientData);
|
|
@@ -1306,6 +1472,7 @@ function resolveDisplayName(patientData, displayName) {
|
|
|
1306
1472
|
}
|
|
1307
1473
|
function PatientHeader({
|
|
1308
1474
|
patientData,
|
|
1475
|
+
participants,
|
|
1309
1476
|
onOpenDicom,
|
|
1310
1477
|
onOpenSettings,
|
|
1311
1478
|
onBack,
|
|
@@ -1315,15 +1482,28 @@ function PatientHeader({
|
|
|
1315
1482
|
}) {
|
|
1316
1483
|
const hasDicom = !!(patientData.studyId && patientData.storageId);
|
|
1317
1484
|
const meta = [];
|
|
1318
|
-
if (patientData.patientAge
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1485
|
+
if (patientData.patientAge && patientData.patientSex) {
|
|
1486
|
+
meta.push({
|
|
1487
|
+
key: "ageSex",
|
|
1488
|
+
label: "Age / Sex",
|
|
1489
|
+
value: `${patientData.patientAge} \xB7 ${patientData.patientSex}`
|
|
1490
|
+
});
|
|
1491
|
+
} else if (patientData.patientAge) {
|
|
1492
|
+
meta.push({ key: "age", label: "Age", value: String(patientData.patientAge) });
|
|
1493
|
+
} else if (patientData.patientSex) {
|
|
1494
|
+
meta.push({ key: "sex", label: "Sex", value: String(patientData.patientSex) });
|
|
1495
|
+
}
|
|
1496
|
+
if (patientData.studyType) {
|
|
1497
|
+
meta.push({ key: "modality", label: "Modality", value: patientData.studyType });
|
|
1323
1498
|
}
|
|
1324
|
-
if (patientData.
|
|
1325
|
-
meta.push({
|
|
1499
|
+
if (patientData.bodyParts && patientData.bodyParts.length > 0) {
|
|
1500
|
+
meta.push({
|
|
1501
|
+
key: "body",
|
|
1502
|
+
label: "Body part",
|
|
1503
|
+
value: patientData.bodyParts.join(", ")
|
|
1504
|
+
});
|
|
1326
1505
|
}
|
|
1506
|
+
const hasActions = hasDicom && onOpenDicom || onOpenSettings;
|
|
1327
1507
|
return /* @__PURE__ */ jsxs("div", { className, style: styles.container, children: [
|
|
1328
1508
|
!hideName && /* @__PURE__ */ jsxs("div", { style: styles.nameRow, children: [
|
|
1329
1509
|
onBack && /* @__PURE__ */ jsx(
|
|
@@ -1339,54 +1519,58 @@ function PatientHeader({
|
|
|
1339
1519
|
),
|
|
1340
1520
|
/* @__PURE__ */ jsx("span", { style: styles.nameText, children: resolveDisplayName(patientData, displayName) })
|
|
1341
1521
|
] }),
|
|
1342
|
-
|
|
1343
|
-
/* @__PURE__ */ jsxs("
|
|
1344
|
-
item.label,
|
|
1345
|
-
":
|
|
1346
|
-
] }),
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
] }) : null
|
|
1522
|
+
/* @__PURE__ */ jsxs("div", { style: styles.caseCard, children: [
|
|
1523
|
+
meta.length > 0 && /* @__PURE__ */ jsx("div", { style: styles.metaRow, children: meta.map((item) => /* @__PURE__ */ jsxs("div", { style: styles.metaCol, children: [
|
|
1524
|
+
/* @__PURE__ */ jsx("span", { style: styles.metaLabel, children: item.label }),
|
|
1525
|
+
/* @__PURE__ */ jsx("span", { style: styles.metaValue, children: item.value })
|
|
1526
|
+
] }, item.key)) }),
|
|
1527
|
+
hasActions && /* @__PURE__ */ jsxs("div", { style: styles.actions, children: [
|
|
1528
|
+
hasDicom && onOpenDicom && /* @__PURE__ */ jsxs(
|
|
1529
|
+
"button",
|
|
1530
|
+
{
|
|
1531
|
+
onClick: onOpenDicom,
|
|
1532
|
+
style: styles.dicomButton,
|
|
1533
|
+
type: "button",
|
|
1534
|
+
"aria-label": "View DICOM study",
|
|
1535
|
+
children: [
|
|
1536
|
+
/* @__PURE__ */ jsx(DicomIcon, { size: 18, color: COLOR.primary }),
|
|
1537
|
+
/* @__PURE__ */ jsx("span", { children: "View DICOM" })
|
|
1538
|
+
]
|
|
1539
|
+
}
|
|
1540
|
+
),
|
|
1541
|
+
onOpenSettings && /* @__PURE__ */ jsxs(
|
|
1542
|
+
"button",
|
|
1543
|
+
{
|
|
1544
|
+
onClick: onOpenSettings,
|
|
1545
|
+
style: styles.settingsIconButton,
|
|
1546
|
+
type: "button",
|
|
1547
|
+
"aria-label": `Open channel settings (${participants.length} participants)`,
|
|
1548
|
+
title: "Channel participants",
|
|
1549
|
+
children: [
|
|
1550
|
+
/* @__PURE__ */ jsx(PeopleIcon, { size: 18, color: COLOR.neutral600 }),
|
|
1551
|
+
/* @__PURE__ */ jsx("span", { style: styles.participantCount, children: participants.length })
|
|
1552
|
+
]
|
|
1553
|
+
}
|
|
1554
|
+
)
|
|
1555
|
+
] })
|
|
1556
|
+
] })
|
|
1378
1557
|
] });
|
|
1379
1558
|
}
|
|
1380
1559
|
var styles = {
|
|
1381
1560
|
container: {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
backgroundColor: COLOR.
|
|
1561
|
+
display: "flex",
|
|
1562
|
+
flexDirection: "column",
|
|
1563
|
+
backgroundColor: COLOR.primaryBg,
|
|
1564
|
+
borderBottom: `1px solid ${COLOR.neutral200}`
|
|
1385
1565
|
},
|
|
1566
|
+
// Optional name row, only when `hideName` is false (compact-inbox path)
|
|
1386
1567
|
nameRow: {
|
|
1387
1568
|
display: "flex",
|
|
1388
1569
|
alignItems: "center",
|
|
1389
|
-
gap: SPACE.S2
|
|
1570
|
+
gap: SPACE.S2,
|
|
1571
|
+
padding: `${SPACE.S3} ${SPACE.S4} ${SPACE.S2}`,
|
|
1572
|
+
borderBottom: `1px solid ${COLOR.neutral200}`,
|
|
1573
|
+
backgroundColor: COLOR.white
|
|
1390
1574
|
},
|
|
1391
1575
|
backButton: {
|
|
1392
1576
|
width: SIZE.controlIcon,
|
|
@@ -1403,65 +1587,98 @@ var styles = {
|
|
|
1403
1587
|
},
|
|
1404
1588
|
nameText: {
|
|
1405
1589
|
fontSize: FONT_SIZE.lg,
|
|
1406
|
-
fontWeight: FONT_WEIGHT.
|
|
1590
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
1407
1591
|
color: COLOR.neutral900,
|
|
1408
1592
|
lineHeight: LINE_HEIGHT.tight,
|
|
1593
|
+
letterSpacing: "-0.01em",
|
|
1409
1594
|
minWidth: 0,
|
|
1410
1595
|
overflow: "hidden",
|
|
1411
1596
|
textOverflow: "ellipsis",
|
|
1412
1597
|
whiteSpace: "nowrap"
|
|
1413
1598
|
},
|
|
1599
|
+
// Case card: meta columns flowing in a wrap-row, actions stacked
|
|
1600
|
+
// below. Each meta column is a small label-above-value pair so the
|
|
1601
|
+
// user can scan field names quickly without a legend.
|
|
1602
|
+
caseCard: {
|
|
1603
|
+
display: "flex",
|
|
1604
|
+
flexDirection: "column",
|
|
1605
|
+
gap: SPACE.S3,
|
|
1606
|
+
padding: `${SPACE.S3} ${SPACE.S5}`
|
|
1607
|
+
},
|
|
1608
|
+
// Wrap-row of stacked label/value columns. Compact column gap keeps
|
|
1609
|
+
// the row dense without crowding; row gap kicks in when columns
|
|
1610
|
+
// wrap to a second line on narrow popups.
|
|
1414
1611
|
metaRow: {
|
|
1415
1612
|
display: "flex",
|
|
1416
1613
|
flexWrap: "wrap",
|
|
1417
|
-
columnGap: SPACE.
|
|
1418
|
-
rowGap: SPACE.
|
|
1419
|
-
|
|
1614
|
+
columnGap: SPACE.S5,
|
|
1615
|
+
rowGap: SPACE.S2,
|
|
1616
|
+
minWidth: 0
|
|
1420
1617
|
},
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1618
|
+
metaCol: {
|
|
1619
|
+
display: "flex",
|
|
1620
|
+
flexDirection: "column",
|
|
1621
|
+
gap: "2px",
|
|
1622
|
+
minWidth: 0
|
|
1425
1623
|
},
|
|
1426
1624
|
metaLabel: {
|
|
1625
|
+
fontSize: "11px",
|
|
1626
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
1627
|
+
letterSpacing: "0.06em",
|
|
1628
|
+
textTransform: "uppercase",
|
|
1427
1629
|
color: COLOR.neutral500,
|
|
1428
|
-
|
|
1630
|
+
whiteSpace: "nowrap"
|
|
1429
1631
|
},
|
|
1430
1632
|
metaValue: {
|
|
1431
|
-
|
|
1633
|
+
fontSize: FONT_SIZE.sm,
|
|
1634
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
1635
|
+
color: COLOR.neutral900,
|
|
1636
|
+
whiteSpace: "nowrap",
|
|
1637
|
+
overflow: "hidden",
|
|
1638
|
+
textOverflow: "ellipsis",
|
|
1639
|
+
maxWidth: "180px"
|
|
1432
1640
|
},
|
|
1433
1641
|
actions: {
|
|
1434
1642
|
display: "flex",
|
|
1435
1643
|
gap: SPACE.S2,
|
|
1436
|
-
|
|
1644
|
+
flexShrink: 0
|
|
1437
1645
|
},
|
|
1438
1646
|
dicomButton: {
|
|
1439
1647
|
display: "inline-flex",
|
|
1440
1648
|
alignItems: "center",
|
|
1441
1649
|
gap: SPACE.S2,
|
|
1442
1650
|
minHeight: SIZE.control,
|
|
1443
|
-
padding: `${SPACE.S2}
|
|
1651
|
+
padding: `${SPACE.S2} 14px`,
|
|
1444
1652
|
fontSize: FONT_SIZE.sm,
|
|
1445
|
-
fontWeight: FONT_WEIGHT.
|
|
1446
|
-
color: COLOR.
|
|
1447
|
-
backgroundColor: COLOR.
|
|
1448
|
-
border:
|
|
1449
|
-
borderRadius: RADIUS.
|
|
1653
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
1654
|
+
color: COLOR.primary,
|
|
1655
|
+
backgroundColor: COLOR.white,
|
|
1656
|
+
border: `1.5px solid ${COLOR.primary}`,
|
|
1657
|
+
borderRadius: RADIUS.lg,
|
|
1450
1658
|
cursor: "pointer"
|
|
1451
1659
|
},
|
|
1452
|
-
|
|
1660
|
+
// Channel-info button: people icon + participant count. Click opens the
|
|
1661
|
+
// channel settings overlay (kept on the same handler as before so hosts
|
|
1662
|
+
// don't have to re-wire — the affordance just looks like a "members"
|
|
1663
|
+
// pill now instead of a gear).
|
|
1664
|
+
settingsIconButton: {
|
|
1665
|
+
minHeight: SIZE.control,
|
|
1453
1666
|
display: "inline-flex",
|
|
1454
1667
|
alignItems: "center",
|
|
1455
1668
|
gap: SPACE.S2,
|
|
1456
|
-
|
|
1457
|
-
|
|
1669
|
+
padding: `0 ${SPACE.S3}`,
|
|
1670
|
+
backgroundColor: COLOR.white,
|
|
1671
|
+
border: `1px solid ${COLOR.neutral200}`,
|
|
1672
|
+
borderRadius: RADIUS.lg,
|
|
1673
|
+
color: COLOR.neutral600,
|
|
1674
|
+
cursor: "pointer",
|
|
1675
|
+
flexShrink: 0
|
|
1676
|
+
},
|
|
1677
|
+
participantCount: {
|
|
1458
1678
|
fontSize: FONT_SIZE.sm,
|
|
1459
|
-
fontWeight: FONT_WEIGHT.
|
|
1679
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
1460
1680
|
color: COLOR.neutral700,
|
|
1461
|
-
|
|
1462
|
-
border: `1px solid ${COLOR.neutral300}`,
|
|
1463
|
-
borderRadius: RADIUS.md,
|
|
1464
|
-
cursor: "pointer"
|
|
1681
|
+
fontVariantNumeric: "tabular-nums"
|
|
1465
1682
|
}
|
|
1466
1683
|
};
|
|
1467
1684
|
function ReplyQuoteBlock({
|
|
@@ -1470,10 +1687,10 @@ function ReplyQuoteBlock({
|
|
|
1470
1687
|
onClick,
|
|
1471
1688
|
className
|
|
1472
1689
|
}) {
|
|
1473
|
-
const baseBg = inOwnBubble ? "rgba(255,255,255,0.
|
|
1690
|
+
const baseBg = inOwnBubble ? "rgba(255,255,255,0.22)" : COLOR.white;
|
|
1474
1691
|
const barColor = inOwnBubble ? COLOR.white : COLOR.primary;
|
|
1475
1692
|
const nameColor = inOwnBubble ? COLOR.white : COLOR.primary;
|
|
1476
|
-
const bodyColor = inOwnBubble ? "rgba(255,255,255,0.
|
|
1693
|
+
const bodyColor = inOwnBubble ? "rgba(255,255,255,0.9)" : COLOR.neutral700;
|
|
1477
1694
|
const preview = renderSnapshotPreview(snapshot);
|
|
1478
1695
|
return /* @__PURE__ */ jsxs(
|
|
1479
1696
|
"div",
|
|
@@ -1845,43 +2062,50 @@ var styles4 = {
|
|
|
1845
2062
|
position: "relative",
|
|
1846
2063
|
display: "inline-block"
|
|
1847
2064
|
},
|
|
2065
|
+
// Pill-shaped trigger matching the prototype's `.nc-msg-act` style —
|
|
2066
|
+
// always visible (touch-friendly) rather than hover-only.
|
|
1848
2067
|
trigger: {
|
|
1849
|
-
|
|
1850
|
-
height: SIZE.controlIcon,
|
|
1851
|
-
display: "flex",
|
|
2068
|
+
display: "inline-flex",
|
|
1852
2069
|
alignItems: "center",
|
|
1853
2070
|
justifyContent: "center",
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2071
|
+
width: "32px",
|
|
2072
|
+
height: "32px",
|
|
2073
|
+
backgroundColor: COLOR.white,
|
|
2074
|
+
border: `1px solid ${COLOR.neutral200}`,
|
|
2075
|
+
borderRadius: "999px",
|
|
1857
2076
|
cursor: "pointer",
|
|
1858
|
-
color: COLOR.neutral600
|
|
2077
|
+
color: COLOR.neutral600,
|
|
2078
|
+
transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
|
|
1859
2079
|
},
|
|
1860
2080
|
menu: {
|
|
1861
2081
|
position: "fixed",
|
|
1862
|
-
minWidth: "
|
|
2082
|
+
minWidth: "200px",
|
|
1863
2083
|
backgroundColor: COLOR.white,
|
|
1864
|
-
border: `1px solid ${COLOR.
|
|
1865
|
-
borderRadius: RADIUS.
|
|
2084
|
+
border: `1px solid ${COLOR.neutral300}`,
|
|
2085
|
+
borderRadius: RADIUS.lg,
|
|
1866
2086
|
boxShadow: SHADOW.popover,
|
|
1867
|
-
padding:
|
|
2087
|
+
padding: "6px",
|
|
2088
|
+
display: "flex",
|
|
2089
|
+
flexDirection: "column",
|
|
2090
|
+
gap: "2px",
|
|
1868
2091
|
// Above the floating popup (z-index dialog) but below any future modal.
|
|
1869
2092
|
zIndex: Z_INDEX.menu
|
|
1870
2093
|
},
|
|
1871
2094
|
item: {
|
|
1872
2095
|
display: "flex",
|
|
1873
2096
|
alignItems: "center",
|
|
1874
|
-
gap: SPACE.
|
|
2097
|
+
gap: SPACE.S2,
|
|
1875
2098
|
width: "100%",
|
|
1876
2099
|
minHeight: SIZE.control,
|
|
1877
|
-
padding:
|
|
2100
|
+
padding: `10px ${SPACE.S3}`,
|
|
1878
2101
|
fontSize: FONT_SIZE.sm,
|
|
1879
|
-
color: COLOR.
|
|
2102
|
+
color: COLOR.neutral700,
|
|
1880
2103
|
backgroundColor: "transparent",
|
|
1881
2104
|
border: "none",
|
|
1882
|
-
borderRadius: RADIUS.
|
|
2105
|
+
borderRadius: RADIUS.md,
|
|
1883
2106
|
cursor: "pointer",
|
|
1884
|
-
textAlign: "left"
|
|
2107
|
+
textAlign: "left",
|
|
2108
|
+
fontFamily: "inherit"
|
|
1885
2109
|
},
|
|
1886
2110
|
itemDisabled: {
|
|
1887
2111
|
color: COLOR.neutral400,
|
|
@@ -1995,71 +2219,116 @@ function MessageBubble({
|
|
|
1995
2219
|
},
|
|
1996
2220
|
children: [
|
|
1997
2221
|
isOwn && actionsSlot,
|
|
1998
|
-
/* @__PURE__ */
|
|
1999
|
-
"div",
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
message.type === "image" && /* @__PURE__ */ jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
|
|
2025
|
-
message.type === "file" && /* @__PURE__ */ jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
|
|
2026
|
-
message.type === "deep_link" && /* @__PURE__ */ jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
|
|
2027
|
-
/* @__PURE__ */ jsx("div", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
|
|
2028
|
-
isOwn && message.status === "sending" && /* @__PURE__ */ jsxs("div", { style: styles5.statusRow, children: [
|
|
2029
|
-
/* @__PURE__ */ jsx(Spinner, { size: 12, color: "rgba(255,255,255,0.85)" }),
|
|
2030
|
-
/* @__PURE__ */ jsx("span", { style: styles5.statusText, children: "Sending\u2026" })
|
|
2031
|
-
] }),
|
|
2032
|
-
isOwn && message.status === "failed" && /* @__PURE__ */ jsxs("div", { style: styles5.statusRow, children: [
|
|
2033
|
-
/* @__PURE__ */ jsx(AlertIcon, { size: 12, color: "#fecaca" }),
|
|
2034
|
-
/* @__PURE__ */ jsx("span", { style: styles5.statusText, children: "Not sent" }),
|
|
2035
|
-
onRetry && /* @__PURE__ */ jsx(
|
|
2036
|
-
"button",
|
|
2222
|
+
/* @__PURE__ */ jsxs("div", { style: styles5.bubbleWrapper, children: [
|
|
2223
|
+
!isOwn && /* @__PURE__ */ jsxs("div", { style: styles5.authorRow, children: [
|
|
2224
|
+
/* @__PURE__ */ jsx(
|
|
2225
|
+
"span",
|
|
2226
|
+
{
|
|
2227
|
+
style: {
|
|
2228
|
+
...styles5.authorAvatar,
|
|
2229
|
+
backgroundColor: roleColor(message.senderRole)
|
|
2230
|
+
},
|
|
2231
|
+
"aria-hidden": "true",
|
|
2232
|
+
children: computeInitials(message.senderName)
|
|
2233
|
+
}
|
|
2234
|
+
),
|
|
2235
|
+
/* @__PURE__ */ jsx("span", { style: styles5.authorName, children: message.senderName }),
|
|
2236
|
+
/* @__PURE__ */ jsx(RoleBadge, { role: message.senderRole })
|
|
2237
|
+
] }),
|
|
2238
|
+
/* @__PURE__ */ jsxs(
|
|
2239
|
+
"div",
|
|
2240
|
+
{
|
|
2241
|
+
style: {
|
|
2242
|
+
...styles5.bubble,
|
|
2243
|
+
...isOwn ? styles5.ownBubble : styles5.otherBubble
|
|
2244
|
+
},
|
|
2245
|
+
children: [
|
|
2246
|
+
message.isPinned && /* @__PURE__ */ jsxs(
|
|
2247
|
+
"div",
|
|
2037
2248
|
{
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
children:
|
|
2249
|
+
style: {
|
|
2250
|
+
...styles5.pinnedBadge,
|
|
2251
|
+
...isOwn ? styles5.pinnedBadgeOwn : styles5.pinnedBadgeOther
|
|
2252
|
+
},
|
|
2253
|
+
children: [
|
|
2254
|
+
/* @__PURE__ */ jsx(PinIcon, { size: 12 }),
|
|
2255
|
+
/* @__PURE__ */ jsx("span", { children: "Pinned" })
|
|
2256
|
+
]
|
|
2043
2257
|
}
|
|
2044
|
-
)
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2258
|
+
),
|
|
2259
|
+
message.replyToSnapshot && /* @__PURE__ */ jsx(
|
|
2260
|
+
ReplyQuoteBlock,
|
|
2261
|
+
{
|
|
2262
|
+
snapshot: message.replyToSnapshot,
|
|
2263
|
+
inOwnBubble: isOwn,
|
|
2264
|
+
onClick: onReplyJumpTo
|
|
2265
|
+
}
|
|
2266
|
+
),
|
|
2267
|
+
message.type === "text" && /* @__PURE__ */ jsx(TextContent, { body: message.body, onDeepLinkClick }),
|
|
2268
|
+
message.type === "audio" && /* @__PURE__ */ jsx(AudioContent, { mediaUrl: message.mediaUrl, duration: message.mediaDuration }),
|
|
2269
|
+
message.type === "image" && /* @__PURE__ */ jsx(ImageContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
|
|
2270
|
+
message.type === "file" && /* @__PURE__ */ jsx(FileContent, { mediaUrl: message.mediaUrl, fileName: message.fileName }),
|
|
2271
|
+
message.type === "deep_link" && /* @__PURE__ */ jsx(DeepLinkContent, { body: message.body, metadata: message.metadata, onDeepLinkClick }),
|
|
2272
|
+
/* @__PURE__ */ jsxs("div", { style: styles5.bubbleFoot, children: [
|
|
2273
|
+
/* @__PURE__ */ jsx("span", { style: styles5.timestamp, children: formatTime(message.insertedAt) }),
|
|
2274
|
+
isOwn && message.status === "sending" && /* @__PURE__ */ jsxs("span", { style: styles5.footStatus, children: [
|
|
2275
|
+
/* @__PURE__ */ jsx(Spinner, { size: 11, color: "rgba(255,255,255,0.85)" }),
|
|
2276
|
+
/* @__PURE__ */ jsx("span", { children: "Sending\u2026" })
|
|
2277
|
+
] }),
|
|
2278
|
+
isOwn && message.status === "failed" && /* @__PURE__ */ jsxs("span", { style: styles5.footStatusFailed, children: [
|
|
2279
|
+
/* @__PURE__ */ jsx(AlertIcon, { size: 11, color: "#fecaca" }),
|
|
2280
|
+
/* @__PURE__ */ jsx("span", { children: "Not sent" }),
|
|
2281
|
+
onRetry && /* @__PURE__ */ jsx(
|
|
2282
|
+
"button",
|
|
2283
|
+
{
|
|
2284
|
+
type: "button",
|
|
2285
|
+
onClick: () => onRetry(message.id),
|
|
2286
|
+
style: styles5.retryButton,
|
|
2287
|
+
"aria-label": "Retry sending message",
|
|
2288
|
+
children: "Retry"
|
|
2289
|
+
}
|
|
2290
|
+
)
|
|
2291
|
+
] }),
|
|
2292
|
+
showSeenBy && isOwn && !message.status && /* @__PURE__ */ jsx(
|
|
2293
|
+
SeenByIndicator,
|
|
2294
|
+
{
|
|
2295
|
+
readBy: message.readBy ?? [],
|
|
2296
|
+
participants,
|
|
2297
|
+
currentUserId,
|
|
2298
|
+
senderId: message.senderId
|
|
2299
|
+
}
|
|
2300
|
+
)
|
|
2301
|
+
] })
|
|
2302
|
+
]
|
|
2303
|
+
}
|
|
2304
|
+
)
|
|
2305
|
+
] }),
|
|
2058
2306
|
!isOwn && actionsSlot
|
|
2059
2307
|
]
|
|
2060
2308
|
}
|
|
2061
2309
|
);
|
|
2062
2310
|
}
|
|
2311
|
+
function computeInitials(name) {
|
|
2312
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]?.toUpperCase() ?? "").join("") || "?";
|
|
2313
|
+
}
|
|
2314
|
+
function roleColor(role) {
|
|
2315
|
+
switch (role) {
|
|
2316
|
+
case "radiologist":
|
|
2317
|
+
return "#4f46e5";
|
|
2318
|
+
// indigo (blue-leaning)
|
|
2319
|
+
case "lab":
|
|
2320
|
+
return "#2563eb";
|
|
2321
|
+
// brand blue
|
|
2322
|
+
case "physician":
|
|
2323
|
+
return "#0284c7";
|
|
2324
|
+
// sky
|
|
2325
|
+
case "admin":
|
|
2326
|
+
return "#64748b";
|
|
2327
|
+
// slate (muted)
|
|
2328
|
+
default:
|
|
2329
|
+
return "#6b7280";
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2063
2332
|
function TextContent({ body, onDeepLinkClick }) {
|
|
2064
2333
|
if (body.includes(DEEP_LINK_PREFIX)) {
|
|
2065
2334
|
const parts = body.split(new RegExp(`(${escapeRegex2(DEEP_LINK_PREFIX)}[\\w/.-]+)`, "g"));
|
|
@@ -2121,19 +2390,13 @@ function DeepLinkContent({
|
|
|
2121
2390
|
);
|
|
2122
2391
|
}
|
|
2123
2392
|
function SystemBubble({ message }) {
|
|
2124
|
-
return /* @__PURE__ */
|
|
2393
|
+
return /* @__PURE__ */ jsx("div", { style: styles5.systemWrapper, children: /* @__PURE__ */ jsxs("span", { style: styles5.systemPill, children: [
|
|
2125
2394
|
/* @__PURE__ */ jsx("span", { children: message.body }),
|
|
2126
2395
|
/* @__PURE__ */ jsx("span", { style: styles5.systemTime, children: formatTime(message.insertedAt) })
|
|
2127
|
-
] });
|
|
2396
|
+
] }) });
|
|
2128
2397
|
}
|
|
2129
2398
|
function RoleBadge({ role }) {
|
|
2130
|
-
|
|
2131
|
-
radiologist: "#7c3aed",
|
|
2132
|
-
lab: "#2563eb",
|
|
2133
|
-
physician: "#059669",
|
|
2134
|
-
admin: "#dc2626"
|
|
2135
|
-
};
|
|
2136
|
-
return /* @__PURE__ */ jsx("span", { style: { ...styles5.roleBadge, backgroundColor: `${colors[role]}15`, color: colors[role] }, children: ROLE_LABELS[role] ?? role });
|
|
2399
|
+
return /* @__PURE__ */ jsx("span", { style: { ...styles5.roleBadge, backgroundColor: roleColor(role) }, children: ROLE_LABELS[role] ?? role });
|
|
2137
2400
|
}
|
|
2138
2401
|
function formatTime(iso) {
|
|
2139
2402
|
try {
|
|
@@ -2155,89 +2418,134 @@ var styles5 = {
|
|
|
2155
2418
|
display: "flex",
|
|
2156
2419
|
alignItems: "flex-end",
|
|
2157
2420
|
gap: SPACE.S1,
|
|
2158
|
-
marginBottom: SPACE.
|
|
2421
|
+
marginBottom: SPACE.S3,
|
|
2159
2422
|
paddingLeft: SPACE.S4,
|
|
2160
2423
|
paddingRight: SPACE.S4
|
|
2161
2424
|
},
|
|
2162
2425
|
bubbleWrapper: {
|
|
2163
2426
|
position: "relative",
|
|
2164
|
-
maxWidth: "
|
|
2427
|
+
maxWidth: "78%",
|
|
2428
|
+
display: "flex",
|
|
2429
|
+
flexDirection: "column",
|
|
2430
|
+
gap: "4px"
|
|
2165
2431
|
},
|
|
2166
2432
|
actionsSlot: {
|
|
2167
2433
|
flexShrink: 0,
|
|
2168
2434
|
alignSelf: "flex-end"
|
|
2169
2435
|
},
|
|
2436
|
+
// Author row above the bubble — only rendered for others' messages.
|
|
2437
|
+
authorRow: {
|
|
2438
|
+
display: "flex",
|
|
2439
|
+
alignItems: "center",
|
|
2440
|
+
gap: SPACE.S2
|
|
2441
|
+
},
|
|
2442
|
+
authorAvatar: {
|
|
2443
|
+
width: "36px",
|
|
2444
|
+
height: "36px",
|
|
2445
|
+
borderRadius: "50%",
|
|
2446
|
+
color: COLOR.white,
|
|
2447
|
+
fontSize: FONT_SIZE.sm,
|
|
2448
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
2449
|
+
display: "flex",
|
|
2450
|
+
alignItems: "center",
|
|
2451
|
+
justifyContent: "center",
|
|
2452
|
+
flexShrink: 0,
|
|
2453
|
+
letterSpacing: "0.02em"
|
|
2454
|
+
},
|
|
2455
|
+
authorName: {
|
|
2456
|
+
fontSize: FONT_SIZE.sm,
|
|
2457
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
2458
|
+
color: COLOR.neutral700,
|
|
2459
|
+
overflow: "hidden",
|
|
2460
|
+
textOverflow: "ellipsis",
|
|
2461
|
+
whiteSpace: "nowrap",
|
|
2462
|
+
minWidth: 0
|
|
2463
|
+
},
|
|
2170
2464
|
bubble: {
|
|
2171
2465
|
padding: `${SPACE.S3} ${SPACE.S4}`,
|
|
2172
|
-
borderRadius:
|
|
2173
|
-
wordBreak: "break-word"
|
|
2466
|
+
borderRadius: "18px",
|
|
2467
|
+
wordBreak: "break-word",
|
|
2468
|
+
border: "1px solid transparent"
|
|
2174
2469
|
},
|
|
2175
2470
|
ownBubble: {
|
|
2176
2471
|
backgroundColor: COLOR.primary,
|
|
2177
2472
|
color: COLOR.white,
|
|
2178
|
-
|
|
2473
|
+
borderColor: COLOR.primary,
|
|
2474
|
+
borderBottomRightRadius: "6px"
|
|
2179
2475
|
},
|
|
2180
2476
|
otherBubble: {
|
|
2181
2477
|
backgroundColor: COLOR.neutral100,
|
|
2182
2478
|
color: COLOR.neutral900,
|
|
2183
|
-
|
|
2479
|
+
borderColor: COLOR.neutral200,
|
|
2480
|
+
borderBottomLeftRadius: "6px"
|
|
2184
2481
|
},
|
|
2185
2482
|
pinnedBadge: {
|
|
2186
2483
|
display: "inline-flex",
|
|
2187
2484
|
alignItems: "center",
|
|
2188
|
-
gap:
|
|
2189
|
-
fontSize:
|
|
2190
|
-
fontWeight: FONT_WEIGHT.
|
|
2191
|
-
marginBottom: SPACE.
|
|
2192
|
-
|
|
2485
|
+
gap: "4px",
|
|
2486
|
+
fontSize: "11px",
|
|
2487
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
2488
|
+
marginBottom: SPACE.S2,
|
|
2489
|
+
padding: `2px ${SPACE.S2}`,
|
|
2490
|
+
borderRadius: "999px",
|
|
2491
|
+
textTransform: "uppercase",
|
|
2492
|
+
letterSpacing: "0.05em"
|
|
2193
2493
|
},
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
gap: SPACE.S2,
|
|
2198
|
-
marginBottom: SPACE.S1
|
|
2494
|
+
pinnedBadgeOther: {
|
|
2495
|
+
color: "#b8680f",
|
|
2496
|
+
backgroundColor: "#fff7e0"
|
|
2199
2497
|
},
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
color: COLOR.neutral700
|
|
2498
|
+
pinnedBadgeOwn: {
|
|
2499
|
+
color: "#fff4d6",
|
|
2500
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)"
|
|
2204
2501
|
},
|
|
2205
2502
|
roleBadge: {
|
|
2206
|
-
fontSize:
|
|
2207
|
-
fontWeight: FONT_WEIGHT.
|
|
2208
|
-
padding:
|
|
2209
|
-
borderRadius: RADIUS.sm
|
|
2503
|
+
fontSize: "10px",
|
|
2504
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
2505
|
+
padding: "2px 7px",
|
|
2506
|
+
borderRadius: RADIUS.sm,
|
|
2507
|
+
textTransform: "uppercase",
|
|
2508
|
+
letterSpacing: "0.06em",
|
|
2509
|
+
color: COLOR.white,
|
|
2510
|
+
flexShrink: 0
|
|
2210
2511
|
},
|
|
2211
2512
|
textBody: {
|
|
2212
2513
|
margin: 0,
|
|
2213
2514
|
fontSize: FONT_SIZE.md,
|
|
2214
|
-
lineHeight:
|
|
2515
|
+
lineHeight: 1.5
|
|
2516
|
+
},
|
|
2517
|
+
bubbleFoot: {
|
|
2518
|
+
display: "flex",
|
|
2519
|
+
alignItems: "center",
|
|
2520
|
+
justifyContent: "flex-end",
|
|
2521
|
+
gap: SPACE.S2,
|
|
2522
|
+
marginTop: SPACE.S2,
|
|
2523
|
+
fontSize: "11px",
|
|
2524
|
+
opacity: 0.85
|
|
2215
2525
|
},
|
|
2216
2526
|
timestamp: {
|
|
2217
|
-
fontSize:
|
|
2527
|
+
fontSize: "11px",
|
|
2218
2528
|
color: "inherit",
|
|
2219
|
-
|
|
2220
|
-
marginTop: SPACE.S1,
|
|
2221
|
-
textAlign: "right"
|
|
2529
|
+
fontVariantNumeric: "tabular-nums"
|
|
2222
2530
|
},
|
|
2223
|
-
|
|
2224
|
-
display: "flex",
|
|
2531
|
+
footStatus: {
|
|
2532
|
+
display: "inline-flex",
|
|
2225
2533
|
alignItems: "center",
|
|
2226
|
-
gap:
|
|
2227
|
-
|
|
2228
|
-
fontSize: FONT_SIZE.xs,
|
|
2229
|
-
justifyContent: "flex-end",
|
|
2230
|
-
color: "rgba(255, 255, 255, 0.9)"
|
|
2534
|
+
gap: "4px",
|
|
2535
|
+
fontSize: "11px"
|
|
2231
2536
|
},
|
|
2232
|
-
|
|
2233
|
-
|
|
2537
|
+
footStatusFailed: {
|
|
2538
|
+
display: "inline-flex",
|
|
2539
|
+
alignItems: "center",
|
|
2540
|
+
gap: "4px",
|
|
2541
|
+
fontSize: "11px"
|
|
2234
2542
|
},
|
|
2235
2543
|
retryButton: {
|
|
2236
2544
|
background: "transparent",
|
|
2237
2545
|
border: "none",
|
|
2238
2546
|
color: COLOR.white,
|
|
2239
2547
|
fontWeight: FONT_WEIGHT.semibold,
|
|
2240
|
-
fontSize:
|
|
2548
|
+
fontSize: "11px",
|
|
2241
2549
|
textDecoration: "underline",
|
|
2242
2550
|
cursor: "pointer",
|
|
2243
2551
|
padding: `0 ${SPACE.S1}`
|
|
@@ -2300,18 +2608,25 @@ var styles5 = {
|
|
|
2300
2608
|
color: COLOR.neutral500,
|
|
2301
2609
|
fontFamily: "monospace"
|
|
2302
2610
|
},
|
|
2303
|
-
|
|
2611
|
+
systemWrapper: {
|
|
2304
2612
|
display: "flex",
|
|
2305
2613
|
justifyContent: "center",
|
|
2614
|
+
margin: `${SPACE.S2} 0`
|
|
2615
|
+
},
|
|
2616
|
+
systemPill: {
|
|
2617
|
+
display: "inline-flex",
|
|
2306
2618
|
alignItems: "center",
|
|
2307
2619
|
gap: SPACE.S2,
|
|
2308
|
-
padding:
|
|
2309
|
-
|
|
2620
|
+
padding: `5px ${SPACE.S3}`,
|
|
2621
|
+
backgroundColor: COLOR.neutral100,
|
|
2622
|
+
borderRadius: "999px",
|
|
2623
|
+
fontSize: FONT_SIZE.xs,
|
|
2310
2624
|
color: COLOR.neutral500
|
|
2311
2625
|
},
|
|
2312
2626
|
systemTime: {
|
|
2313
|
-
fontSize:
|
|
2314
|
-
color: COLOR.neutral400
|
|
2627
|
+
fontSize: "11px",
|
|
2628
|
+
color: COLOR.neutral400,
|
|
2629
|
+
fontVariantNumeric: "tabular-nums"
|
|
2315
2630
|
}
|
|
2316
2631
|
};
|
|
2317
2632
|
function ChatIllustration({ size = 96 }) {
|
|
@@ -2450,6 +2765,7 @@ var MessageList = forwardRef(function MessageList2({
|
|
|
2450
2765
|
const containerRef = useRef(null);
|
|
2451
2766
|
const bottomRef = useRef(null);
|
|
2452
2767
|
const prevMessageCount = useRef(messages.length);
|
|
2768
|
+
const hasInitiallyScrolledRef = useRef(false);
|
|
2453
2769
|
useImperativeHandle(
|
|
2454
2770
|
ref,
|
|
2455
2771
|
() => ({
|
|
@@ -2467,7 +2783,31 @@ var MessageList = forwardRef(function MessageList2({
|
|
|
2467
2783
|
}),
|
|
2468
2784
|
[]
|
|
2469
2785
|
);
|
|
2786
|
+
useLayoutEffect(() => {
|
|
2787
|
+
const container = containerRef.current;
|
|
2788
|
+
if (!container || messages.length === 0) return;
|
|
2789
|
+
if (hasInitiallyScrolledRef.current) return;
|
|
2790
|
+
hasInitiallyScrolledRef.current = true;
|
|
2791
|
+
prevMessageCount.current = messages.length;
|
|
2792
|
+
const firstUnread = messages.find(
|
|
2793
|
+
(m) => m.type !== "system" && m.senderId !== currentUserId && !(m.readBy ?? []).includes(currentUserId)
|
|
2794
|
+
);
|
|
2795
|
+
if (firstUnread) {
|
|
2796
|
+
const el = container.querySelector(
|
|
2797
|
+
`[data-message-id="${firstUnread.id}"]`
|
|
2798
|
+
);
|
|
2799
|
+
if (el) {
|
|
2800
|
+
const containerRect = container.getBoundingClientRect();
|
|
2801
|
+
const elRect = el.getBoundingClientRect();
|
|
2802
|
+
const offset = elRect.top - containerRect.top + container.scrollTop;
|
|
2803
|
+
container.scrollTop = Math.max(0, offset - 16);
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
container.scrollTop = container.scrollHeight;
|
|
2808
|
+
}, [messages, currentUserId]);
|
|
2470
2809
|
useEffect(() => {
|
|
2810
|
+
if (!hasInitiallyScrolledRef.current) return;
|
|
2471
2811
|
if (messages.length > prevMessageCount.current) {
|
|
2472
2812
|
const lastMessage = messages[messages.length - 1];
|
|
2473
2813
|
const isOwnMessage = lastMessage?.senderId === currentUserId;
|
|
@@ -2526,7 +2866,11 @@ var MessageList = forwardRef(function MessageList2({
|
|
|
2526
2866
|
const showDivider = !!currentBucket && currentBucket !== lastValidBucket;
|
|
2527
2867
|
if (currentBucket) lastValidBucket = currentBucket;
|
|
2528
2868
|
return /* @__PURE__ */ jsxs(React4.Fragment, { children: [
|
|
2529
|
-
showDivider && /* @__PURE__ */
|
|
2869
|
+
showDivider && /* @__PURE__ */ jsxs("div", { style: styles6.dayDivider, role: "separator", "aria-label": "Date", children: [
|
|
2870
|
+
/* @__PURE__ */ jsx("span", { style: styles6.dayHr, "aria-hidden": "true" }),
|
|
2871
|
+
/* @__PURE__ */ jsx("span", { style: styles6.dayLabel, children: formatDayDivider(message.insertedAt) }),
|
|
2872
|
+
/* @__PURE__ */ jsx("span", { style: styles6.dayHr, "aria-hidden": "true" })
|
|
2873
|
+
] }),
|
|
2530
2874
|
/* @__PURE__ */ jsx("div", { "data-message-id": message.id, "data-natoe-message-bubble": true, style: styles6.bubbleSlot, children: /* @__PURE__ */ jsx(
|
|
2531
2875
|
MessageBubble,
|
|
2532
2876
|
{
|
|
@@ -2625,23 +2969,32 @@ var styles6 = {
|
|
|
2625
2969
|
},
|
|
2626
2970
|
dayDivider: {
|
|
2627
2971
|
display: "flex",
|
|
2628
|
-
|
|
2629
|
-
|
|
2972
|
+
alignItems: "center",
|
|
2973
|
+
gap: SPACE.S3,
|
|
2974
|
+
margin: `${SPACE.S4} 0 ${SPACE.S3}`,
|
|
2630
2975
|
position: "sticky",
|
|
2631
2976
|
top: SPACE.S1,
|
|
2632
2977
|
zIndex: Z_INDEX.sticky,
|
|
2633
2978
|
pointerEvents: "none"
|
|
2634
2979
|
},
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2980
|
+
dayHr: {
|
|
2981
|
+
flex: 1,
|
|
2982
|
+
height: "1px",
|
|
2983
|
+
backgroundColor: COLOR.neutral200
|
|
2984
|
+
},
|
|
2985
|
+
dayLabel: {
|
|
2986
|
+
flexShrink: 0,
|
|
2987
|
+
fontSize: FONT_SIZE.xs,
|
|
2988
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
2989
|
+
color: COLOR.neutral500,
|
|
2990
|
+
textTransform: "uppercase",
|
|
2991
|
+
letterSpacing: "0.06em",
|
|
2992
|
+
// Subtle pill background so the label is readable when it overlays
|
|
2993
|
+
// bubbles passing under it during sticky scroll. Without this the
|
|
2994
|
+
// hairlines visually run through the text on tinted backgrounds.
|
|
2995
|
+
padding: `2px ${SPACE.S2}`,
|
|
2996
|
+
backgroundColor: COLOR.white,
|
|
2997
|
+
borderRadius: RADIUS.sm
|
|
2645
2998
|
},
|
|
2646
2999
|
bubbleSlot: {
|
|
2647
3000
|
animation: "natoe-colab-message-in 180ms ease-out"
|
|
@@ -2895,11 +3248,11 @@ function MessageInput({
|
|
|
2895
3248
|
{
|
|
2896
3249
|
onClick: () => fileInputRef.current?.click(),
|
|
2897
3250
|
disabled: disabled || isSending,
|
|
2898
|
-
style: styles8.
|
|
3251
|
+
style: styles8.attachButton,
|
|
2899
3252
|
"aria-label": "Attach file",
|
|
2900
3253
|
title: "Attach file",
|
|
2901
3254
|
type: "button",
|
|
2902
|
-
children: /* @__PURE__ */ jsx(AttachIcon, { size: 22, color:
|
|
3255
|
+
children: /* @__PURE__ */ jsx(AttachIcon, { size: 22, color: COLOR.neutral700 })
|
|
2903
3256
|
}
|
|
2904
3257
|
),
|
|
2905
3258
|
/* @__PURE__ */ jsx(
|
|
@@ -2938,7 +3291,7 @@ function MessageInput({
|
|
|
2938
3291
|
style: styles8.textarea
|
|
2939
3292
|
}
|
|
2940
3293
|
),
|
|
2941
|
-
/* @__PURE__ */
|
|
3294
|
+
/* @__PURE__ */ jsxs(
|
|
2942
3295
|
"button",
|
|
2943
3296
|
{
|
|
2944
3297
|
onClick: handleSendText,
|
|
@@ -2947,10 +3300,13 @@ function MessageInput({
|
|
|
2947
3300
|
title: "Send message",
|
|
2948
3301
|
style: {
|
|
2949
3302
|
...styles8.sendButton,
|
|
2950
|
-
opacity: text.trim() ? 1 : 0.
|
|
3303
|
+
opacity: text.trim() ? 1 : 0.5
|
|
2951
3304
|
},
|
|
2952
3305
|
type: "button",
|
|
2953
|
-
children:
|
|
3306
|
+
children: [
|
|
3307
|
+
/* @__PURE__ */ jsx(SendIcon, { size: 20, color: COLOR.white }),
|
|
3308
|
+
/* @__PURE__ */ jsx("span", { style: styles8.sendLabel, children: "Send" })
|
|
3309
|
+
]
|
|
2954
3310
|
}
|
|
2955
3311
|
)
|
|
2956
3312
|
] })
|
|
@@ -3012,46 +3368,64 @@ var styles8 = {
|
|
|
3012
3368
|
inputBar: {
|
|
3013
3369
|
display: "flex",
|
|
3014
3370
|
alignItems: "flex-end",
|
|
3015
|
-
gap:
|
|
3016
|
-
padding: `${SPACE.S3} ${SPACE.
|
|
3017
|
-
},
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3371
|
+
gap: "10px",
|
|
3372
|
+
padding: `${SPACE.S3} ${SPACE.S4} 14px`
|
|
3373
|
+
},
|
|
3374
|
+
// 48px square tile with a 14px corner radius — visually grounds the
|
|
3375
|
+
// attach affordance as part of the composer chrome rather than a stray
|
|
3376
|
+
// round icon button.
|
|
3377
|
+
attachButton: {
|
|
3378
|
+
width: "48px",
|
|
3379
|
+
height: "48px",
|
|
3021
3380
|
display: "flex",
|
|
3022
3381
|
alignItems: "center",
|
|
3023
3382
|
justifyContent: "center",
|
|
3024
|
-
backgroundColor:
|
|
3025
|
-
border:
|
|
3026
|
-
borderRadius:
|
|
3383
|
+
backgroundColor: COLOR.neutral100,
|
|
3384
|
+
border: `1.5px solid ${COLOR.neutral200}`,
|
|
3385
|
+
borderRadius: "14px",
|
|
3027
3386
|
cursor: "pointer",
|
|
3028
|
-
flexShrink: 0
|
|
3387
|
+
flexShrink: 0,
|
|
3388
|
+
color: COLOR.neutral700,
|
|
3389
|
+
transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
|
|
3029
3390
|
},
|
|
3030
3391
|
textarea: {
|
|
3031
3392
|
flex: 1,
|
|
3032
|
-
|
|
3393
|
+
minHeight: "48px",
|
|
3394
|
+
maxHeight: "140px",
|
|
3395
|
+
padding: "13px 16px",
|
|
3033
3396
|
fontSize: FONT_SIZE.md,
|
|
3034
3397
|
lineHeight: LINE_HEIGHT.normal,
|
|
3035
|
-
|
|
3036
|
-
|
|
3398
|
+
color: COLOR.neutral900,
|
|
3399
|
+
backgroundColor: COLOR.neutral100,
|
|
3400
|
+
border: `1.5px solid ${COLOR.neutral200}`,
|
|
3401
|
+
borderRadius: "14px",
|
|
3037
3402
|
resize: "none",
|
|
3038
3403
|
outline: "none",
|
|
3039
3404
|
fontFamily: "inherit",
|
|
3040
|
-
|
|
3041
|
-
minHeight: SIZE.controlPrimary
|
|
3405
|
+
transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
|
|
3042
3406
|
},
|
|
3407
|
+
// Pill-shaped send: icon + "Send" label. Always labelled (never icon-
|
|
3408
|
+
// only) so the affordance is obvious to first-time users.
|
|
3043
3409
|
sendButton: {
|
|
3044
|
-
|
|
3045
|
-
height: SIZE.controlPrimary,
|
|
3046
|
-
display: "flex",
|
|
3410
|
+
display: "inline-flex",
|
|
3047
3411
|
alignItems: "center",
|
|
3048
|
-
|
|
3412
|
+
gap: "6px",
|
|
3413
|
+
minWidth: "48px",
|
|
3414
|
+
height: "48px",
|
|
3415
|
+
padding: `0 ${SPACE.S4}`,
|
|
3049
3416
|
backgroundColor: COLOR.primary,
|
|
3050
3417
|
color: COLOR.white,
|
|
3051
3418
|
border: "none",
|
|
3052
|
-
borderRadius:
|
|
3419
|
+
borderRadius: "14px",
|
|
3053
3420
|
cursor: "pointer",
|
|
3054
|
-
flexShrink: 0
|
|
3421
|
+
flexShrink: 0,
|
|
3422
|
+
fontFamily: "inherit",
|
|
3423
|
+
fontSize: "15px",
|
|
3424
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
3425
|
+
transition: "opacity 120ms ease, background-color 120ms ease"
|
|
3426
|
+
},
|
|
3427
|
+
sendLabel: {
|
|
3428
|
+
lineHeight: 1
|
|
3055
3429
|
}};
|
|
3056
3430
|
function ParticipantsList({
|
|
3057
3431
|
participants,
|
|
@@ -3108,10 +3482,14 @@ function fallbackName(role) {
|
|
|
3108
3482
|
}
|
|
3109
3483
|
function RoleBadge2({ role }) {
|
|
3110
3484
|
const colors = {
|
|
3111
|
-
radiologist: { bg: "#
|
|
3485
|
+
radiologist: { bg: "#eef2ff", text: "#4f46e5" },
|
|
3486
|
+
// indigo
|
|
3112
3487
|
lab: { bg: "#eff6ff", text: "#2563eb" },
|
|
3113
|
-
|
|
3114
|
-
|
|
3488
|
+
// brand blue
|
|
3489
|
+
physician: { bg: "#e0f2fe", text: "#0284c7" },
|
|
3490
|
+
// sky
|
|
3491
|
+
admin: { bg: "#f1f5f9", text: "#64748b" }
|
|
3492
|
+
// slate
|
|
3115
3493
|
};
|
|
3116
3494
|
const color = colors[role];
|
|
3117
3495
|
return /* @__PURE__ */ jsx("span", { style: { ...styles9.roleBadge, backgroundColor: color.bg, color: color.text }, children: role });
|
|
@@ -3720,46 +4098,21 @@ var styles12 = {
|
|
|
3720
4098
|
letterSpacing: "0.4px"
|
|
3721
4099
|
}
|
|
3722
4100
|
};
|
|
3723
|
-
|
|
3724
|
-
// src/core/styles.ts
|
|
3725
|
-
var FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
|
|
3726
|
-
var ROOT_CLASS = "natoe-colab-root";
|
|
3727
|
-
var GLOBAL_STYLE_ID = "natoe-colab-global-styles";
|
|
3728
|
-
var GLOBAL_CSS = `
|
|
3729
|
-
@keyframes natoe-colab-spin { to { transform: rotate(360deg); } }
|
|
3730
|
-
|
|
3731
|
-
@keyframes natoe-colab-message-in {
|
|
3732
|
-
from { opacity: 0; transform: translateY(4px); }
|
|
3733
|
-
to { opacity: 1; transform: translateY(0); }
|
|
3734
|
-
}
|
|
3735
|
-
|
|
3736
|
-
/* High-contrast adjustments \u2014 older users on OS-level high-contrast mode
|
|
3737
|
-
get heavier borders and stronger text without losing the package look. */
|
|
3738
|
-
@media (prefers-contrast: more) {
|
|
3739
|
-
.${ROOT_CLASS} {
|
|
3740
|
-
color: #000000;
|
|
3741
|
-
}
|
|
3742
|
-
.${ROOT_CLASS} button {
|
|
3743
|
-
outline: 1px solid currentColor;
|
|
3744
|
-
}
|
|
3745
|
-
}
|
|
3746
|
-
|
|
3747
|
-
/* Honour reduced-motion preferences by killing entry animations. */
|
|
3748
|
-
@media (prefers-reduced-motion: reduce) {
|
|
3749
|
-
.${ROOT_CLASS} [data-natoe-message-bubble] {
|
|
3750
|
-
animation: none !important;
|
|
3751
|
-
}
|
|
3752
|
-
}
|
|
3753
|
-
`;
|
|
3754
|
-
function ensureGlobalStyles() {
|
|
3755
|
-
if (typeof document === "undefined") return;
|
|
3756
|
-
if (document.getElementById(GLOBAL_STYLE_ID)) return;
|
|
3757
|
-
const style = document.createElement("style");
|
|
3758
|
-
style.id = GLOBAL_STYLE_ID;
|
|
3759
|
-
style.textContent = GLOBAL_CSS;
|
|
3760
|
-
document.head.appendChild(style);
|
|
3761
|
-
}
|
|
3762
4101
|
ensureGlobalStyles();
|
|
4102
|
+
var DARK_THEME_OVERRIDES = {
|
|
4103
|
+
["--natoe-colab-white"]: "#0b0b0c",
|
|
4104
|
+
["--natoe-colab-neutral-50"]: "#18181c",
|
|
4105
|
+
["--natoe-colab-neutral-100"]: "#1f1f23",
|
|
4106
|
+
["--natoe-colab-neutral-200"]: "#2a2a2e",
|
|
4107
|
+
["--natoe-colab-neutral-300"]: "#3f3f44",
|
|
4108
|
+
["--natoe-colab-neutral-400"]: "#6b7280",
|
|
4109
|
+
["--natoe-colab-neutral-500"]: "#9ca3af",
|
|
4110
|
+
["--natoe-colab-neutral-600"]: "#cbd5e1",
|
|
4111
|
+
["--natoe-colab-neutral-700"]: "#e5e7eb",
|
|
4112
|
+
["--natoe-colab-neutral-800"]: "#f3f4f6",
|
|
4113
|
+
["--natoe-colab-neutral-900"]: "#ffffff",
|
|
4114
|
+
["--natoe-colab-primary-bg"]: "rgba(37, 99, 235, 0.18)"
|
|
4115
|
+
};
|
|
3763
4116
|
function CollabPanel({
|
|
3764
4117
|
orderId,
|
|
3765
4118
|
patientData,
|
|
@@ -3768,6 +4121,7 @@ function CollabPanel({
|
|
|
3768
4121
|
onBack,
|
|
3769
4122
|
hidePatientName = false,
|
|
3770
4123
|
onConversationChange,
|
|
4124
|
+
themeMode = "light",
|
|
3771
4125
|
className,
|
|
3772
4126
|
style
|
|
3773
4127
|
}) {
|
|
@@ -3843,16 +4197,21 @@ function CollabPanel({
|
|
|
3843
4197
|
}
|
|
3844
4198
|
};
|
|
3845
4199
|
const pinDisabled = pinnedMessages.length >= MAX_PINNED_MESSAGES;
|
|
4200
|
+
const containerStyle = {
|
|
4201
|
+
...panelStyles.container,
|
|
4202
|
+
...themeMode === "dark" ? DARK_THEME_OVERRIDES : {},
|
|
4203
|
+
...style
|
|
4204
|
+
};
|
|
3846
4205
|
if (error) {
|
|
3847
|
-
return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4206
|
+
return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: /* @__PURE__ */ jsxs("div", { style: panelStyles.errorState, children: [
|
|
3848
4207
|
/* @__PURE__ */ jsx("p", { style: panelStyles.errorTitle, children: "Unable to load conversation" }),
|
|
3849
4208
|
/* @__PURE__ */ jsx("p", { style: panelStyles.errorMessage, children: error })
|
|
3850
4209
|
] }) });
|
|
3851
4210
|
}
|
|
3852
4211
|
if (isLoading && !conversation) {
|
|
3853
|
-
return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4212
|
+
return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: /* @__PURE__ */ jsx("div", { style: panelStyles.loadingState, children: "Loading conversation..." }) });
|
|
3854
4213
|
}
|
|
3855
|
-
return /* @__PURE__ */ jsxs("div", { className
|
|
4214
|
+
return /* @__PURE__ */ jsxs("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: [
|
|
3856
4215
|
showSettings && conversation ? /* @__PURE__ */ jsx(
|
|
3857
4216
|
ChannelSettings,
|
|
3858
4217
|
{
|
|
@@ -4007,6 +4366,7 @@ function CollabPopup({
|
|
|
4007
4366
|
isOpen,
|
|
4008
4367
|
onClose,
|
|
4009
4368
|
onBack,
|
|
4369
|
+
onMinimize,
|
|
4010
4370
|
initialPosition,
|
|
4011
4371
|
width = 380,
|
|
4012
4372
|
height = 520,
|
|
@@ -4122,35 +4482,48 @@ function CollabPopup({
|
|
|
4122
4482
|
"button",
|
|
4123
4483
|
{
|
|
4124
4484
|
onClick: onBack,
|
|
4125
|
-
style: styles13.
|
|
4126
|
-
"aria-label": "
|
|
4485
|
+
style: styles13.titleSquare,
|
|
4486
|
+
"aria-label": "Back to messages",
|
|
4127
4487
|
title: "Back",
|
|
4128
4488
|
type: "button",
|
|
4129
|
-
children:
|
|
4489
|
+
children: /* @__PURE__ */ jsx(BackIcon, { size: 18, color: COLOR.neutral700 })
|
|
4130
4490
|
}
|
|
4131
4491
|
),
|
|
4132
|
-
/* @__PURE__ */
|
|
4492
|
+
/* @__PURE__ */ jsxs("div", { style: styles13.titleCenter, children: [
|
|
4493
|
+
/* @__PURE__ */ jsx("h2", { id: titleId, style: styles13.patientName, children: patientData.patientName || titleText }),
|
|
4494
|
+
(patientData.labName || patientData.displayOrderId) && /* @__PURE__ */ jsxs("div", { style: styles13.subRow, children: [
|
|
4495
|
+
patientData.labName && /* @__PURE__ */ jsx("span", { children: patientData.labName }),
|
|
4496
|
+
patientData.labName && patientData.displayOrderId && /* @__PURE__ */ jsx("span", { style: styles13.subDot, "aria-hidden": "true", children: "\xB7" }),
|
|
4497
|
+
patientData.displayOrderId && /* @__PURE__ */ jsxs("span", { style: styles13.mono, children: [
|
|
4498
|
+
"#",
|
|
4499
|
+
patientData.displayOrderId.slice(-4)
|
|
4500
|
+
] })
|
|
4501
|
+
] })
|
|
4502
|
+
] }),
|
|
4133
4503
|
/* @__PURE__ */ jsxs("div", { style: styles13.titleActions, children: [
|
|
4134
4504
|
/* @__PURE__ */ jsx(
|
|
4135
4505
|
"button",
|
|
4136
4506
|
{
|
|
4137
|
-
onClick: () =>
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4507
|
+
onClick: () => {
|
|
4508
|
+
if (onMinimize) onMinimize();
|
|
4509
|
+
else setIsMinimized(!isMinimized);
|
|
4510
|
+
},
|
|
4511
|
+
style: styles13.titleSquare,
|
|
4512
|
+
"aria-label": onMinimize ? "Minimize chat" : isMinimized ? "Expand chat" : "Minimize chat",
|
|
4513
|
+
title: onMinimize ? "Minimize" : isMinimized ? "Expand" : "Minimize",
|
|
4141
4514
|
type: "button",
|
|
4142
|
-
children: isMinimized ? /* @__PURE__ */ jsx(
|
|
4515
|
+
children: onMinimize || !isMinimized ? /* @__PURE__ */ jsx(MinimizeIcon, { size: 18, color: COLOR.neutral700 }) : /* @__PURE__ */ jsx(ExpandIcon, { size: 18, color: COLOR.neutral700 })
|
|
4143
4516
|
}
|
|
4144
4517
|
),
|
|
4145
4518
|
/* @__PURE__ */ jsx(
|
|
4146
4519
|
"button",
|
|
4147
4520
|
{
|
|
4148
4521
|
onClick: onClose,
|
|
4149
|
-
style: styles13.
|
|
4522
|
+
style: styles13.titleSquare,
|
|
4150
4523
|
"aria-label": "Close chat",
|
|
4151
4524
|
title: "Close",
|
|
4152
4525
|
type: "button",
|
|
4153
|
-
children: /* @__PURE__ */ jsx(CloseIcon, { size:
|
|
4526
|
+
children: /* @__PURE__ */ jsx(CloseIcon, { size: 18, color: COLOR.neutral700 })
|
|
4154
4527
|
}
|
|
4155
4528
|
)
|
|
4156
4529
|
] })
|
|
@@ -4173,51 +4546,78 @@ var styles13 = {
|
|
|
4173
4546
|
container: {
|
|
4174
4547
|
position: "fixed",
|
|
4175
4548
|
zIndex: Z_INDEX.dialog,
|
|
4176
|
-
borderRadius:
|
|
4177
|
-
boxShadow:
|
|
4549
|
+
borderRadius: "20px",
|
|
4550
|
+
boxShadow: "0 20px 48px rgba(20, 21, 28, 0.16), 0 6px 16px rgba(20, 21, 28, 0.08)",
|
|
4178
4551
|
overflow: "hidden",
|
|
4179
4552
|
display: "flex",
|
|
4180
4553
|
flexDirection: "column",
|
|
4181
4554
|
backgroundColor: COLOR.white,
|
|
4182
|
-
border: `1px solid ${COLOR.
|
|
4555
|
+
border: `1px solid ${COLOR.neutral300}`,
|
|
4183
4556
|
transition: "height 0.2s ease",
|
|
4184
4557
|
fontFamily: FONT_STACK
|
|
4185
4558
|
},
|
|
4186
4559
|
titleBar: {
|
|
4187
4560
|
display: "flex",
|
|
4188
|
-
justifyContent: "space-between",
|
|
4189
4561
|
alignItems: "center",
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
backgroundColor: COLOR.
|
|
4562
|
+
gap: SPACE.S2,
|
|
4563
|
+
padding: `10px ${SPACE.S3}`,
|
|
4564
|
+
backgroundColor: COLOR.white,
|
|
4565
|
+
borderBottom: `1px solid ${COLOR.neutral200}`,
|
|
4193
4566
|
cursor: "grab",
|
|
4194
4567
|
userSelect: "none",
|
|
4195
4568
|
flexShrink: 0
|
|
4196
4569
|
},
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4570
|
+
titleCenter: {
|
|
4571
|
+
flex: 1,
|
|
4572
|
+
minWidth: 0,
|
|
4573
|
+
display: "flex",
|
|
4574
|
+
flexDirection: "column",
|
|
4575
|
+
gap: "2px"
|
|
4576
|
+
},
|
|
4577
|
+
patientName: {
|
|
4578
|
+
margin: 0,
|
|
4579
|
+
fontSize: FONT_SIZE.lg,
|
|
4580
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
4581
|
+
letterSpacing: "-0.01em",
|
|
4582
|
+
color: COLOR.neutral900,
|
|
4201
4583
|
overflow: "hidden",
|
|
4202
4584
|
textOverflow: "ellipsis",
|
|
4203
4585
|
whiteSpace: "nowrap"
|
|
4204
4586
|
},
|
|
4587
|
+
subRow: {
|
|
4588
|
+
display: "flex",
|
|
4589
|
+
alignItems: "center",
|
|
4590
|
+
gap: SPACE.S2,
|
|
4591
|
+
fontSize: FONT_SIZE.sm,
|
|
4592
|
+
color: COLOR.neutral500,
|
|
4593
|
+
overflow: "hidden",
|
|
4594
|
+
textOverflow: "ellipsis",
|
|
4595
|
+
whiteSpace: "nowrap"
|
|
4596
|
+
},
|
|
4597
|
+
subDot: {
|
|
4598
|
+
color: COLOR.neutral400
|
|
4599
|
+
},
|
|
4600
|
+
mono: {
|
|
4601
|
+
fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4602
|
+
fontVariantNumeric: "tabular-nums"
|
|
4603
|
+
},
|
|
4205
4604
|
titleActions: {
|
|
4206
4605
|
display: "flex",
|
|
4207
4606
|
gap: SPACE.S1,
|
|
4208
4607
|
flexShrink: 0
|
|
4209
4608
|
},
|
|
4210
|
-
|
|
4211
|
-
width:
|
|
4212
|
-
height:
|
|
4609
|
+
titleSquare: {
|
|
4610
|
+
width: "36px",
|
|
4611
|
+
height: "36px",
|
|
4213
4612
|
display: "flex",
|
|
4214
4613
|
alignItems: "center",
|
|
4215
4614
|
justifyContent: "center",
|
|
4216
|
-
backgroundColor:
|
|
4217
|
-
border:
|
|
4615
|
+
backgroundColor: COLOR.neutral100,
|
|
4616
|
+
border: `1px solid ${COLOR.neutral200}`,
|
|
4218
4617
|
borderRadius: RADIUS.md,
|
|
4219
4618
|
cursor: "pointer",
|
|
4220
|
-
color: COLOR.
|
|
4619
|
+
color: COLOR.neutral700,
|
|
4620
|
+
flexShrink: 0
|
|
4221
4621
|
},
|
|
4222
4622
|
panelWrapper: {
|
|
4223
4623
|
flex: 1,
|
|
@@ -4247,6 +4647,7 @@ function useInlineCollab({
|
|
|
4247
4647
|
const elementRef = useRef(null);
|
|
4248
4648
|
const observerRef = useRef(null);
|
|
4249
4649
|
const subscribedConversationIdRef = useRef(null);
|
|
4650
|
+
const channelSubscriptionRef = useRef(null);
|
|
4250
4651
|
const trimToLimit = useCallback(
|
|
4251
4652
|
(msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
|
|
4252
4653
|
[messageLimit]
|
|
@@ -4284,7 +4685,9 @@ function useInlineCollab({
|
|
|
4284
4685
|
const subscribe = useCallback(
|
|
4285
4686
|
(conversationId) => {
|
|
4286
4687
|
if (subscribedConversationIdRef.current === conversationId) return;
|
|
4287
|
-
|
|
4688
|
+
channelSubscriptionRef.current?.release();
|
|
4689
|
+
channelSubscriptionRef.current = null;
|
|
4690
|
+
const subscription = socket.joinConversation(conversationId, {
|
|
4288
4691
|
onMessage: (msg) => {
|
|
4289
4692
|
setMessages((prev) => trimToLimit([...prev, msg]));
|
|
4290
4693
|
if (msg.senderId !== config.userId) {
|
|
@@ -4300,19 +4703,18 @@ function useInlineCollab({
|
|
|
4300
4703
|
setParticipants((prev) => prev.filter((p) => p.userId !== participant.userId));
|
|
4301
4704
|
}
|
|
4302
4705
|
});
|
|
4706
|
+
channelSubscriptionRef.current = subscription;
|
|
4303
4707
|
subscribedConversationIdRef.current = conversationId;
|
|
4304
4708
|
setIsSubscribed(true);
|
|
4305
4709
|
},
|
|
4306
4710
|
[socket, trimToLimit, config.userId]
|
|
4307
4711
|
);
|
|
4308
4712
|
const unsubscribe = useCallback(() => {
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
}
|
|
4315
|
-
}, [socket]);
|
|
4713
|
+
channelSubscriptionRef.current?.release();
|
|
4714
|
+
channelSubscriptionRef.current = null;
|
|
4715
|
+
subscribedConversationIdRef.current = null;
|
|
4716
|
+
setIsSubscribed(false);
|
|
4717
|
+
}, []);
|
|
4316
4718
|
const containerRef = useCallback(
|
|
4317
4719
|
(element) => {
|
|
4318
4720
|
if (observerRef.current) {
|
|
@@ -4454,16 +4856,52 @@ function useInlineCollab({
|
|
|
4454
4856
|
};
|
|
4455
4857
|
}
|
|
4456
4858
|
ensureGlobalStyles();
|
|
4859
|
+
function palette(mode) {
|
|
4860
|
+
if (mode === "dark") {
|
|
4861
|
+
return {
|
|
4862
|
+
bg: "transparent",
|
|
4863
|
+
border: "transparent",
|
|
4864
|
+
previewBorder: "#1f1f23",
|
|
4865
|
+
senderName: "#f3f4f6",
|
|
4866
|
+
messageText: "#cbd5e1",
|
|
4867
|
+
systemText: "#6b7280",
|
|
4868
|
+
inputBg: "#18181c",
|
|
4869
|
+
inputBorder: "#2a2a2e",
|
|
4870
|
+
inputText: "#e5e7eb",
|
|
4871
|
+
audioBg: "rgba(37, 99, 235, 0.18)",
|
|
4872
|
+
audioFg: "#93c5fd",
|
|
4873
|
+
expandBorder: "#2a2a2e",
|
|
4874
|
+
expandColor: "#9ca3af"
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4877
|
+
return {
|
|
4878
|
+
bg: COLOR.white,
|
|
4879
|
+
border: COLOR.neutral200,
|
|
4880
|
+
previewBorder: COLOR.neutral100,
|
|
4881
|
+
senderName: COLOR.neutral700,
|
|
4882
|
+
messageText: COLOR.neutral600,
|
|
4883
|
+
systemText: COLOR.neutral400,
|
|
4884
|
+
inputBg: "transparent",
|
|
4885
|
+
inputBorder: COLOR.neutral200,
|
|
4886
|
+
inputText: COLOR.neutral900,
|
|
4887
|
+
audioBg: COLOR.primaryBg,
|
|
4888
|
+
audioFg: COLOR.primary,
|
|
4889
|
+
expandBorder: COLOR.neutral200,
|
|
4890
|
+
expandColor: COLOR.neutral500
|
|
4891
|
+
};
|
|
4892
|
+
}
|
|
4457
4893
|
function CollabInline({
|
|
4458
4894
|
orderId,
|
|
4459
4895
|
patientData,
|
|
4460
4896
|
participantIds,
|
|
4461
4897
|
onExpand,
|
|
4462
|
-
messageLimit =
|
|
4898
|
+
messageLimit = 1,
|
|
4463
4899
|
placeholder = "Type a message about this case...",
|
|
4900
|
+
mode = "light",
|
|
4464
4901
|
className,
|
|
4465
4902
|
style
|
|
4466
4903
|
}) {
|
|
4904
|
+
const pal = palette(mode);
|
|
4467
4905
|
const {
|
|
4468
4906
|
hasConversation,
|
|
4469
4907
|
messages,
|
|
@@ -4475,11 +4913,17 @@ function CollabInline({
|
|
|
4475
4913
|
sendAudioMessage,
|
|
4476
4914
|
containerRef
|
|
4477
4915
|
} = useInlineCollab({ orderId, patientData, participantIds, messageLimit });
|
|
4916
|
+
const containerStyle = {
|
|
4917
|
+
...styles14.container,
|
|
4918
|
+
backgroundColor: pal.bg,
|
|
4919
|
+
borderColor: pal.border,
|
|
4920
|
+
...style
|
|
4921
|
+
};
|
|
4478
4922
|
if (isLoading && !hasConversation) {
|
|
4479
|
-
return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4923
|
+
return /* @__PURE__ */ jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: /* @__PURE__ */ jsx("div", { style: styles14.loadingState, children: "Loading..." }) });
|
|
4480
4924
|
}
|
|
4481
|
-
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4482
|
-
hasConversation && messages.length > 0 && /* @__PURE__ */ jsx("div", { style: styles14.preview, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsx(InlineMessageRow, { message }, message.id)) }),
|
|
4925
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: [
|
|
4926
|
+
hasConversation && messages.length > 0 && /* @__PURE__ */ jsx("div", { style: { ...styles14.preview, borderBottomColor: pal.previewBorder }, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsx(InlineMessageRow, { message, pal }, message.id)) }),
|
|
4483
4927
|
error && /* @__PURE__ */ jsx("div", { style: styles14.error, children: error }),
|
|
4484
4928
|
/* @__PURE__ */ jsx(
|
|
4485
4929
|
InlineInputBar,
|
|
@@ -4490,45 +4934,54 @@ function CollabInline({
|
|
|
4490
4934
|
unreadCount: hasConversation ? unreadCount : 0,
|
|
4491
4935
|
isLive: isSubscribed,
|
|
4492
4936
|
showExpand: hasConversation && !!onExpand,
|
|
4493
|
-
onExpand
|
|
4937
|
+
onExpand,
|
|
4938
|
+
pal
|
|
4494
4939
|
}
|
|
4495
4940
|
)
|
|
4496
4941
|
] });
|
|
4497
4942
|
}
|
|
4498
|
-
function InlineMessageRow({ message }) {
|
|
4943
|
+
function InlineMessageRow({ message, pal }) {
|
|
4499
4944
|
if (message.type === "system") {
|
|
4500
|
-
return /* @__PURE__ */ jsx("div", { style: styles14.systemRow, children: /* @__PURE__ */ jsx("span", { style: styles14.systemText, children: message.body }) });
|
|
4945
|
+
return /* @__PURE__ */ jsx("div", { style: styles14.systemRow, children: /* @__PURE__ */ jsx("span", { style: { ...styles14.systemText, color: pal.systemText }, children: message.body }) });
|
|
4501
4946
|
}
|
|
4502
4947
|
if (message.type === "audio" && message.mediaUrl) {
|
|
4503
4948
|
return /* @__PURE__ */ jsxs("div", { style: styles14.messageRow, children: [
|
|
4504
4949
|
/* @__PURE__ */ jsx(RoleDot, { role: message.senderRole }),
|
|
4505
|
-
/* @__PURE__ */ jsxs("span", { style: styles14.senderName, children: [
|
|
4950
|
+
/* @__PURE__ */ jsxs("span", { style: { ...styles14.senderName, color: pal.senderName }, children: [
|
|
4506
4951
|
message.senderName,
|
|
4507
4952
|
":"
|
|
4508
4953
|
] }),
|
|
4509
|
-
/* @__PURE__ */ jsx(InlineAudioPlayer, { url: message.mediaUrl, duration: message.mediaDuration })
|
|
4954
|
+
/* @__PURE__ */ jsx(InlineAudioPlayer, { url: message.mediaUrl, duration: message.mediaDuration, pal })
|
|
4510
4955
|
] });
|
|
4511
4956
|
}
|
|
4512
4957
|
const preview = renderMessagePreview(message);
|
|
4513
4958
|
return /* @__PURE__ */ jsxs("div", { style: styles14.messageRow, children: [
|
|
4514
4959
|
/* @__PURE__ */ jsx(RoleDot, { role: message.senderRole }),
|
|
4515
|
-
/* @__PURE__ */ jsxs("span", { style: styles14.senderName, children: [
|
|
4960
|
+
/* @__PURE__ */ jsxs("span", { style: { ...styles14.senderName, color: pal.senderName }, children: [
|
|
4516
4961
|
message.senderName,
|
|
4517
4962
|
":"
|
|
4518
4963
|
] }),
|
|
4519
|
-
/* @__PURE__ */ jsx("span", { style: styles14.messageText, children: preview })
|
|
4964
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.messageText, color: pal.messageText }, children: preview })
|
|
4520
4965
|
] });
|
|
4521
4966
|
}
|
|
4522
4967
|
function RoleDot({ role }) {
|
|
4523
4968
|
const colors = {
|
|
4524
|
-
radiologist: "#
|
|
4969
|
+
radiologist: "#4f46e5",
|
|
4970
|
+
// indigo
|
|
4525
4971
|
lab: "#2563eb",
|
|
4526
|
-
|
|
4527
|
-
|
|
4972
|
+
// brand blue
|
|
4973
|
+
physician: "#0284c7",
|
|
4974
|
+
// sky
|
|
4975
|
+
admin: "#64748b"
|
|
4976
|
+
// slate
|
|
4528
4977
|
};
|
|
4529
4978
|
return /* @__PURE__ */ jsx("span", { style: { ...styles14.roleDot, backgroundColor: colors[role] } });
|
|
4530
4979
|
}
|
|
4531
|
-
function InlineAudioPlayer({
|
|
4980
|
+
function InlineAudioPlayer({
|
|
4981
|
+
url,
|
|
4982
|
+
duration,
|
|
4983
|
+
pal
|
|
4984
|
+
}) {
|
|
4532
4985
|
const audioRef = useRef(null);
|
|
4533
4986
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
4534
4987
|
const [currentTime, setCurrentTime] = useState(0);
|
|
@@ -4570,7 +5023,7 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
4570
5023
|
const total = duration ?? 0;
|
|
4571
5024
|
const remaining = Math.max(0, total - Math.floor(currentTime));
|
|
4572
5025
|
const displayTime = isPlaying ? remaining : total;
|
|
4573
|
-
return /* @__PURE__ */ jsxs("span", { style: styles14.audioPlayer, children: [
|
|
5026
|
+
return /* @__PURE__ */ jsxs("span", { style: { ...styles14.audioPlayer, backgroundColor: pal.audioBg, borderColor: pal.audioBg }, children: [
|
|
4574
5027
|
/* @__PURE__ */ jsx(
|
|
4575
5028
|
"button",
|
|
4576
5029
|
{
|
|
@@ -4582,15 +5035,15 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
4582
5035
|
}
|
|
4583
5036
|
),
|
|
4584
5037
|
/* @__PURE__ */ jsxs("span", { style: styles14.audioWaveform, children: [
|
|
4585
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "40%" } }),
|
|
4586
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "80%" } }),
|
|
4587
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "60%" } }),
|
|
4588
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "90%" } }),
|
|
4589
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "50%" } }),
|
|
4590
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "70%" } }),
|
|
4591
|
-
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "40%" } })
|
|
5038
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "40%", backgroundColor: pal.audioFg } }),
|
|
5039
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "80%", backgroundColor: pal.audioFg } }),
|
|
5040
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "60%", backgroundColor: pal.audioFg } }),
|
|
5041
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "90%", backgroundColor: pal.audioFg } }),
|
|
5042
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "50%", backgroundColor: pal.audioFg } }),
|
|
5043
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "70%", backgroundColor: pal.audioFg } }),
|
|
5044
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.waveBar, height: "40%", backgroundColor: pal.audioFg } })
|
|
4592
5045
|
] }),
|
|
4593
|
-
/* @__PURE__ */ jsx("span", { style: styles14.audioDuration, children: formatDuration2(displayTime) }),
|
|
5046
|
+
/* @__PURE__ */ jsx("span", { style: { ...styles14.audioDuration, color: pal.audioFg }, children: formatDuration2(displayTime) }),
|
|
4594
5047
|
/* @__PURE__ */ jsx("audio", { ref: audioRef, src: url, preload: "metadata" })
|
|
4595
5048
|
] });
|
|
4596
5049
|
}
|
|
@@ -4620,7 +5073,8 @@ function InlineInputBar({
|
|
|
4620
5073
|
unreadCount,
|
|
4621
5074
|
isLive,
|
|
4622
5075
|
showExpand,
|
|
4623
|
-
onExpand
|
|
5076
|
+
onExpand,
|
|
5077
|
+
pal
|
|
4624
5078
|
}) {
|
|
4625
5079
|
const [text, setText] = useState("");
|
|
4626
5080
|
const [isSending, setIsSending] = useState(false);
|
|
@@ -4657,7 +5111,12 @@ function InlineInputBar({
|
|
|
4657
5111
|
onKeyDown: handleKeyDown,
|
|
4658
5112
|
placeholder,
|
|
4659
5113
|
disabled: isSending,
|
|
4660
|
-
style:
|
|
5114
|
+
style: {
|
|
5115
|
+
...styles14.input,
|
|
5116
|
+
backgroundColor: pal.inputBg,
|
|
5117
|
+
borderColor: pal.inputBorder,
|
|
5118
|
+
color: pal.inputText
|
|
5119
|
+
}
|
|
4661
5120
|
}
|
|
4662
5121
|
),
|
|
4663
5122
|
unreadCount > 0 && /* @__PURE__ */ jsx("span", { style: styles14.unreadBadge, title: `${unreadCount} unread`, children: unreadCount > 99 ? "99+" : unreadCount }),
|
|
@@ -4677,7 +5136,11 @@ function InlineInputBar({
|
|
|
4677
5136
|
"button",
|
|
4678
5137
|
{
|
|
4679
5138
|
onClick: onExpand,
|
|
4680
|
-
style:
|
|
5139
|
+
style: {
|
|
5140
|
+
...styles14.expandButton,
|
|
5141
|
+
borderColor: pal.expandBorder,
|
|
5142
|
+
color: pal.expandColor
|
|
5143
|
+
},
|
|
4681
5144
|
title: "Expand to full chat",
|
|
4682
5145
|
type: "button",
|
|
4683
5146
|
children: "\u26F6"
|
|
@@ -4946,6 +5409,10 @@ function ConversationListItem({
|
|
|
4946
5409
|
}) {
|
|
4947
5410
|
const hasUnread = item.unreadCount > 0;
|
|
4948
5411
|
const displayName = item.name || item.patientSnapshot?.patientName || "Unknown";
|
|
5412
|
+
const studyType = item.patientSnapshot?.studyType;
|
|
5413
|
+
const patientId = item.patientSnapshot?.patientId;
|
|
5414
|
+
const showStudyRow = !!(studyType || patientId);
|
|
5415
|
+
const initials = computeInitials2(item.patientSnapshot?.patientName || displayName);
|
|
4949
5416
|
return /* @__PURE__ */ jsxs(
|
|
4950
5417
|
"button",
|
|
4951
5418
|
{
|
|
@@ -4953,15 +5420,11 @@ function ConversationListItem({
|
|
|
4953
5420
|
onClick,
|
|
4954
5421
|
style: {
|
|
4955
5422
|
...styles15.container,
|
|
4956
|
-
...isSelected ? styles15.selected : {}
|
|
4957
|
-
...hasUnread ? styles15.unread : {}
|
|
5423
|
+
...isSelected ? styles15.selected : {}
|
|
4958
5424
|
},
|
|
4959
5425
|
type: "button",
|
|
4960
5426
|
children: [
|
|
4961
|
-
/* @__PURE__ */
|
|
4962
|
-
item.picture ? /* @__PURE__ */ jsx("img", { src: item.picture, alt: "", style: styles15.avatar }) : /* @__PURE__ */ jsx("div", { style: styles15.avatarFallback, children: (displayName).charAt(0).toUpperCase() }),
|
|
4963
|
-
hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.unreadDot })
|
|
4964
|
-
] }),
|
|
5427
|
+
/* @__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 }) }),
|
|
4965
5428
|
/* @__PURE__ */ jsxs("div", { style: styles15.content, children: [
|
|
4966
5429
|
/* @__PURE__ */ jsxs("div", { style: styles15.topRow, children: [
|
|
4967
5430
|
/* @__PURE__ */ jsx(
|
|
@@ -4985,101 +5448,120 @@ function ConversationListItem({
|
|
|
4985
5448
|
}
|
|
4986
5449
|
)
|
|
4987
5450
|
] }),
|
|
5451
|
+
showStudyRow && /* @__PURE__ */ jsxs("div", { style: styles15.studyRow, children: [
|
|
5452
|
+
studyType && /* @__PURE__ */ jsx("span", { style: styles15.studyType, children: studyType }),
|
|
5453
|
+
studyType && patientId && /* @__PURE__ */ jsx("span", { style: styles15.studyDivider, children: "\xB7" }),
|
|
5454
|
+
patientId && /* @__PURE__ */ jsxs("span", { style: styles15.studyId, children: [
|
|
5455
|
+
"MRN ",
|
|
5456
|
+
patientId
|
|
5457
|
+
] })
|
|
5458
|
+
] }),
|
|
4988
5459
|
/* @__PURE__ */ jsxs("div", { style: styles15.bottomRow, children: [
|
|
4989
|
-
/* @__PURE__ */ jsx(
|
|
4990
|
-
|
|
4991
|
-
{
|
|
4992
|
-
style: {
|
|
4993
|
-
...styles15.preview,
|
|
4994
|
-
...hasUnread ? styles15.previewUnread : {}
|
|
4995
|
-
},
|
|
4996
|
-
children: renderLastMessagePreview(item.lastMessage)
|
|
4997
|
-
}
|
|
4998
|
-
),
|
|
4999
|
-
hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.badge, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
|
|
5460
|
+
/* @__PURE__ */ jsx(PreviewLine, { message: item.lastMessage, hasUnread }),
|
|
5461
|
+
hasUnread && /* @__PURE__ */ jsx("span", { style: styles15.badge, "aria-label": `${item.unreadCount} unread`, children: item.unreadCount > 99 ? "99+" : item.unreadCount })
|
|
5000
5462
|
] })
|
|
5001
5463
|
] })
|
|
5002
5464
|
]
|
|
5003
5465
|
}
|
|
5004
5466
|
);
|
|
5005
5467
|
}
|
|
5006
|
-
function
|
|
5007
|
-
if (!message)
|
|
5008
|
-
|
|
5468
|
+
function PreviewLine({ message, hasUnread }) {
|
|
5469
|
+
if (!message) {
|
|
5470
|
+
return /* @__PURE__ */ jsx("span", { style: styles15.preview, children: "No messages yet" });
|
|
5471
|
+
}
|
|
5472
|
+
if (message.type === "system") {
|
|
5473
|
+
return /* @__PURE__ */ jsx("span", { style: styles15.preview, children: message.body });
|
|
5474
|
+
}
|
|
5475
|
+
const text = previewText(message);
|
|
5476
|
+
const sender = message.senderName ? `${message.senderName.split(" ")[0]}:` : "";
|
|
5477
|
+
return /* @__PURE__ */ jsxs(
|
|
5478
|
+
"span",
|
|
5479
|
+
{
|
|
5480
|
+
style: {
|
|
5481
|
+
...styles15.preview,
|
|
5482
|
+
...hasUnread ? styles15.previewUnread : {}
|
|
5483
|
+
},
|
|
5484
|
+
children: [
|
|
5485
|
+
sender && /* @__PURE__ */ jsxs("span", { style: styles15.previewSender, children: [
|
|
5486
|
+
sender,
|
|
5487
|
+
" "
|
|
5488
|
+
] }),
|
|
5489
|
+
text
|
|
5490
|
+
]
|
|
5491
|
+
}
|
|
5492
|
+
);
|
|
5493
|
+
}
|
|
5494
|
+
function previewText(message) {
|
|
5009
5495
|
switch (message.type) {
|
|
5010
5496
|
case "audio":
|
|
5011
|
-
return
|
|
5497
|
+
return "Voice message";
|
|
5012
5498
|
case "image":
|
|
5013
|
-
return
|
|
5499
|
+
return message.fileName || "Image";
|
|
5014
5500
|
case "file":
|
|
5015
|
-
return
|
|
5501
|
+
return message.fileName || "File";
|
|
5016
5502
|
case "deep_link":
|
|
5017
|
-
return
|
|
5018
|
-
case "system":
|
|
5019
|
-
return message.body;
|
|
5503
|
+
return message.body || "Shared link";
|
|
5020
5504
|
default: {
|
|
5021
5505
|
const body = message.body || "";
|
|
5022
|
-
|
|
5023
|
-
return `${prefix}${preview}`;
|
|
5506
|
+
return body.length > 60 ? `${body.slice(0, 60)}\u2026` : body;
|
|
5024
5507
|
}
|
|
5025
5508
|
}
|
|
5026
5509
|
}
|
|
5510
|
+
function computeInitials2(name) {
|
|
5511
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("") || "?";
|
|
5512
|
+
}
|
|
5027
5513
|
var styles15 = {
|
|
5028
5514
|
container: {
|
|
5029
5515
|
display: "flex",
|
|
5030
|
-
alignItems: "
|
|
5031
|
-
gap:
|
|
5032
|
-
padding: `${SPACE.S3} ${SPACE.S3}`,
|
|
5516
|
+
alignItems: "flex-start",
|
|
5517
|
+
gap: "14px",
|
|
5033
5518
|
width: "100%",
|
|
5519
|
+
padding: `14px ${SPACE.S5}`,
|
|
5520
|
+
minHeight: "84px",
|
|
5034
5521
|
backgroundColor: "transparent",
|
|
5035
5522
|
border: "none",
|
|
5036
|
-
|
|
5523
|
+
borderLeft: "4px solid transparent",
|
|
5037
5524
|
cursor: "pointer",
|
|
5038
|
-
textAlign: "left"
|
|
5525
|
+
textAlign: "left",
|
|
5526
|
+
color: "inherit",
|
|
5527
|
+
fontFamily: "inherit",
|
|
5528
|
+
transition: "background-color 120ms ease"
|
|
5039
5529
|
},
|
|
5040
5530
|
selected: {
|
|
5041
|
-
backgroundColor: COLOR.primaryBg
|
|
5531
|
+
backgroundColor: COLOR.primaryBg,
|
|
5532
|
+
borderLeftColor: COLOR.primary
|
|
5042
5533
|
},
|
|
5043
|
-
unread: {},
|
|
5044
5534
|
avatarWrapper: {
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
height: SIZE.avatar,
|
|
5535
|
+
width: "52px",
|
|
5536
|
+
height: "52px",
|
|
5048
5537
|
flexShrink: 0
|
|
5049
5538
|
},
|
|
5050
|
-
|
|
5051
|
-
width:
|
|
5052
|
-
height:
|
|
5053
|
-
borderRadius: RADIUS.
|
|
5054
|
-
objectFit: "cover"
|
|
5539
|
+
avatarImg: {
|
|
5540
|
+
width: "52px",
|
|
5541
|
+
height: "52px",
|
|
5542
|
+
borderRadius: RADIUS.xl,
|
|
5543
|
+
objectFit: "cover",
|
|
5544
|
+
boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
|
|
5055
5545
|
},
|
|
5056
|
-
|
|
5057
|
-
width:
|
|
5058
|
-
height:
|
|
5059
|
-
borderRadius: RADIUS.
|
|
5060
|
-
backgroundColor: COLOR.
|
|
5546
|
+
avatarTile: {
|
|
5547
|
+
width: "52px",
|
|
5548
|
+
height: "52px",
|
|
5549
|
+
borderRadius: RADIUS.xl,
|
|
5550
|
+
backgroundColor: COLOR.primary,
|
|
5551
|
+
color: COLOR.white,
|
|
5061
5552
|
display: "flex",
|
|
5062
5553
|
alignItems: "center",
|
|
5063
5554
|
justifyContent: "center",
|
|
5064
|
-
fontSize: FONT_SIZE.
|
|
5065
|
-
fontWeight: FONT_WEIGHT.
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
unreadDot: {
|
|
5069
|
-
position: "absolute",
|
|
5070
|
-
top: "0",
|
|
5071
|
-
left: "0",
|
|
5072
|
-
width: SIZE.dot,
|
|
5073
|
-
height: SIZE.dot,
|
|
5074
|
-
borderRadius: RADIUS.full,
|
|
5075
|
-
backgroundColor: COLOR.primary,
|
|
5076
|
-
border: `2px solid ${COLOR.white}`
|
|
5555
|
+
fontSize: FONT_SIZE.lg,
|
|
5556
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
5557
|
+
letterSpacing: "0.02em",
|
|
5558
|
+
boxShadow: "inset 0 -2px 0 rgba(0, 0, 0, 0.08)"
|
|
5077
5559
|
},
|
|
5078
5560
|
content: {
|
|
5079
5561
|
flex: 1,
|
|
5080
5562
|
display: "flex",
|
|
5081
5563
|
flexDirection: "column",
|
|
5082
|
-
gap: "
|
|
5564
|
+
gap: "3px",
|
|
5083
5565
|
minWidth: 0
|
|
5084
5566
|
},
|
|
5085
5567
|
topRow: {
|
|
@@ -5104,123 +5586,194 @@ var styles15 = {
|
|
|
5104
5586
|
time: {
|
|
5105
5587
|
fontSize: FONT_SIZE.xs,
|
|
5106
5588
|
color: COLOR.neutral500,
|
|
5589
|
+
fontVariantNumeric: "tabular-nums",
|
|
5107
5590
|
flexShrink: 0
|
|
5108
5591
|
},
|
|
5109
5592
|
timeUnread: {
|
|
5110
5593
|
color: COLOR.primary,
|
|
5111
5594
|
fontWeight: FONT_WEIGHT.semibold
|
|
5112
5595
|
},
|
|
5596
|
+
studyRow: {
|
|
5597
|
+
display: "flex",
|
|
5598
|
+
alignItems: "center",
|
|
5599
|
+
gap: SPACE.S2,
|
|
5600
|
+
minWidth: 0
|
|
5601
|
+
},
|
|
5602
|
+
studyType: {
|
|
5603
|
+
fontSize: FONT_SIZE.sm,
|
|
5604
|
+
color: COLOR.neutral500,
|
|
5605
|
+
overflow: "hidden",
|
|
5606
|
+
textOverflow: "ellipsis",
|
|
5607
|
+
whiteSpace: "nowrap"
|
|
5608
|
+
},
|
|
5609
|
+
studyDivider: {
|
|
5610
|
+
fontSize: FONT_SIZE.sm,
|
|
5611
|
+
color: COLOR.neutral400,
|
|
5612
|
+
flexShrink: 0
|
|
5613
|
+
},
|
|
5614
|
+
studyId: {
|
|
5615
|
+
fontSize: FONT_SIZE.sm,
|
|
5616
|
+
color: COLOR.neutral500,
|
|
5617
|
+
fontFamily: "'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
5618
|
+
fontVariantNumeric: "tabular-nums",
|
|
5619
|
+
flexShrink: 0
|
|
5620
|
+
},
|
|
5113
5621
|
bottomRow: {
|
|
5114
5622
|
display: "flex",
|
|
5115
5623
|
justifyContent: "space-between",
|
|
5116
5624
|
alignItems: "center",
|
|
5117
|
-
gap: SPACE.S2
|
|
5625
|
+
gap: SPACE.S2,
|
|
5626
|
+
marginTop: "2px"
|
|
5118
5627
|
},
|
|
5119
5628
|
preview: {
|
|
5120
5629
|
fontSize: FONT_SIZE.sm,
|
|
5121
|
-
color: COLOR.
|
|
5630
|
+
color: COLOR.neutral500,
|
|
5122
5631
|
overflow: "hidden",
|
|
5123
5632
|
textOverflow: "ellipsis",
|
|
5124
5633
|
whiteSpace: "nowrap",
|
|
5125
5634
|
flex: 1,
|
|
5126
5635
|
minWidth: 0
|
|
5127
5636
|
},
|
|
5637
|
+
previewSender: {
|
|
5638
|
+
color: COLOR.neutral700,
|
|
5639
|
+
fontWeight: FONT_WEIGHT.semibold
|
|
5640
|
+
},
|
|
5128
5641
|
previewUnread: {
|
|
5129
5642
|
color: COLOR.neutral900,
|
|
5130
5643
|
fontWeight: FONT_WEIGHT.medium
|
|
5131
5644
|
},
|
|
5132
5645
|
badge: {
|
|
5133
|
-
minWidth: "
|
|
5134
|
-
height: "
|
|
5135
|
-
padding:
|
|
5646
|
+
minWidth: "22px",
|
|
5647
|
+
height: "22px",
|
|
5648
|
+
padding: "0 9px",
|
|
5136
5649
|
fontSize: FONT_SIZE.xs,
|
|
5137
|
-
fontWeight: FONT_WEIGHT.
|
|
5650
|
+
fontWeight: FONT_WEIGHT.bold,
|
|
5138
5651
|
color: COLOR.white,
|
|
5139
|
-
backgroundColor: COLOR.
|
|
5652
|
+
backgroundColor: COLOR.danger,
|
|
5140
5653
|
borderRadius: RADIUS.pill,
|
|
5141
5654
|
display: "flex",
|
|
5142
5655
|
alignItems: "center",
|
|
5143
5656
|
justifyContent: "center",
|
|
5657
|
+
fontVariantNumeric: "tabular-nums",
|
|
5144
5658
|
flexShrink: 0
|
|
5145
5659
|
}
|
|
5146
5660
|
};
|
|
5661
|
+
function SearchIcon({ size = 20, color = "currentColor" }) {
|
|
5662
|
+
return /* @__PURE__ */ jsxs(
|
|
5663
|
+
"svg",
|
|
5664
|
+
{
|
|
5665
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
5666
|
+
width: size,
|
|
5667
|
+
height: size,
|
|
5668
|
+
viewBox: "0 0 24 24",
|
|
5669
|
+
fill: "none",
|
|
5670
|
+
stroke: color,
|
|
5671
|
+
strokeWidth: "2",
|
|
5672
|
+
strokeLinecap: "round",
|
|
5673
|
+
strokeLinejoin: "round",
|
|
5674
|
+
"aria-hidden": "true",
|
|
5675
|
+
children: [
|
|
5676
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
5677
|
+
/* @__PURE__ */ jsx("path", { d: "M20 20l-4-4" })
|
|
5678
|
+
]
|
|
5679
|
+
}
|
|
5680
|
+
);
|
|
5681
|
+
}
|
|
5147
5682
|
function ConversationList({
|
|
5148
5683
|
conversations,
|
|
5149
5684
|
selectedId,
|
|
5150
5685
|
isLoading,
|
|
5151
5686
|
error,
|
|
5152
5687
|
onSelect,
|
|
5153
|
-
title = "Conversations",
|
|
5154
5688
|
className
|
|
5155
5689
|
}) {
|
|
5690
|
+
const { totalUnread } = useCollab();
|
|
5156
5691
|
const [query, setQuery] = useState("");
|
|
5157
|
-
const [
|
|
5692
|
+
const [filter, setFilter] = useState(
|
|
5693
|
+
() => totalUnread > 0 ? "unread" : "all"
|
|
5694
|
+
);
|
|
5695
|
+
const [searchFocused, setSearchFocused] = useState(false);
|
|
5696
|
+
const unreadCount = useMemo(
|
|
5697
|
+
() => conversations.filter((c) => c.unreadCount > 0).length,
|
|
5698
|
+
[conversations]
|
|
5699
|
+
);
|
|
5700
|
+
const autoDefaultedRef = useRef(false);
|
|
5701
|
+
useEffect(() => {
|
|
5702
|
+
if (autoDefaultedRef.current) return;
|
|
5703
|
+
if (conversations.length === 0) return;
|
|
5704
|
+
autoDefaultedRef.current = true;
|
|
5705
|
+
if (unreadCount > 0) setFilter("unread");
|
|
5706
|
+
}, [conversations.length, unreadCount]);
|
|
5158
5707
|
const filtered = useMemo(() => {
|
|
5159
5708
|
let result = conversations;
|
|
5160
|
-
if (
|
|
5709
|
+
if (filter === "unread") {
|
|
5161
5710
|
result = result.filter((c) => c.unreadCount > 0);
|
|
5162
5711
|
}
|
|
5163
5712
|
const q = query.trim().toLowerCase();
|
|
5164
5713
|
if (q) {
|
|
5165
5714
|
result = result.filter((c) => {
|
|
5166
|
-
const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
|
|
5167
5715
|
const name = c.name.toLowerCase();
|
|
5168
|
-
|
|
5716
|
+
const patient = c.patientSnapshot?.patientName?.toLowerCase() || "";
|
|
5717
|
+
const studyType = c.patientSnapshot?.studyType?.toLowerCase() || "";
|
|
5718
|
+
const patientId = c.patientSnapshot?.patientId?.toLowerCase() || "";
|
|
5719
|
+
return name.includes(q) || patient.includes(q) || studyType.includes(q) || patientId.includes(q);
|
|
5169
5720
|
});
|
|
5170
5721
|
}
|
|
5171
5722
|
return result;
|
|
5172
|
-
}, [conversations, query,
|
|
5173
|
-
const totalUnread = conversations.reduce((sum, c) => sum + c.unreadCount, 0);
|
|
5723
|
+
}, [conversations, query, filter]);
|
|
5174
5724
|
return /* @__PURE__ */ jsxs("div", { className, style: styles16.container, children: [
|
|
5175
|
-
/* @__PURE__ */ jsxs("div", { style: styles16.
|
|
5176
|
-
/* @__PURE__ */
|
|
5177
|
-
|
|
5178
|
-
totalUnread > 0 && /* @__PURE__ */ jsx("span", { style: styles16.totalBadge, children: totalUnread })
|
|
5179
|
-
] }),
|
|
5180
|
-
/* @__PURE__ */ jsx("div", { style: styles16.searchRow, children: /* @__PURE__ */ jsx(
|
|
5181
|
-
"input",
|
|
5725
|
+
/* @__PURE__ */ jsxs("div", { style: styles16.tabs, children: [
|
|
5726
|
+
/* @__PURE__ */ jsx(
|
|
5727
|
+
TabButton,
|
|
5182
5728
|
{
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
style: styles16.searchInput
|
|
5729
|
+
label: "Unread",
|
|
5730
|
+
count: unreadCount,
|
|
5731
|
+
isActive: filter === "unread",
|
|
5732
|
+
onClick: () => setFilter("unread")
|
|
5188
5733
|
}
|
|
5189
|
-
)
|
|
5190
|
-
/* @__PURE__ */
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
type: "button",
|
|
5200
|
-
children: "All"
|
|
5201
|
-
}
|
|
5202
|
-
),
|
|
5203
|
-
/* @__PURE__ */ jsxs(
|
|
5204
|
-
"button",
|
|
5205
|
-
{
|
|
5206
|
-
onClick: () => setShowUnreadOnly(true),
|
|
5207
|
-
style: {
|
|
5208
|
-
...styles16.filterButton,
|
|
5209
|
-
...showUnreadOnly ? styles16.filterButtonActive : {}
|
|
5210
|
-
},
|
|
5211
|
-
type: "button",
|
|
5212
|
-
children: [
|
|
5213
|
-
"Unread ",
|
|
5214
|
-
totalUnread > 0 && `(${totalUnread})`
|
|
5215
|
-
]
|
|
5216
|
-
}
|
|
5217
|
-
)
|
|
5218
|
-
] })
|
|
5734
|
+
),
|
|
5735
|
+
/* @__PURE__ */ jsx(
|
|
5736
|
+
TabButton,
|
|
5737
|
+
{
|
|
5738
|
+
label: "All",
|
|
5739
|
+
count: conversations.length,
|
|
5740
|
+
isActive: filter === "all",
|
|
5741
|
+
onClick: () => setFilter("all")
|
|
5742
|
+
}
|
|
5743
|
+
)
|
|
5219
5744
|
] }),
|
|
5745
|
+
/* @__PURE__ */ jsxs(
|
|
5746
|
+
"div",
|
|
5747
|
+
{
|
|
5748
|
+
style: {
|
|
5749
|
+
...styles16.search,
|
|
5750
|
+
...searchFocused ? styles16.searchFocused : {}
|
|
5751
|
+
},
|
|
5752
|
+
children: [
|
|
5753
|
+
/* @__PURE__ */ jsx("span", { style: styles16.searchIcon, "aria-hidden": "true", children: /* @__PURE__ */ jsx(SearchIcon, { size: 20, color: COLOR.neutral500 }) }),
|
|
5754
|
+
/* @__PURE__ */ jsx(
|
|
5755
|
+
"input",
|
|
5756
|
+
{
|
|
5757
|
+
type: "text",
|
|
5758
|
+
value: query,
|
|
5759
|
+
onChange: (e) => setQuery(e.target.value),
|
|
5760
|
+
onFocus: () => setSearchFocused(true),
|
|
5761
|
+
onBlur: () => setSearchFocused(false),
|
|
5762
|
+
placeholder: "Search by patient name or MRN",
|
|
5763
|
+
"aria-label": "Search conversations",
|
|
5764
|
+
style: styles16.searchInput
|
|
5765
|
+
}
|
|
5766
|
+
)
|
|
5767
|
+
]
|
|
5768
|
+
}
|
|
5769
|
+
),
|
|
5220
5770
|
/* @__PURE__ */ jsxs("div", { style: styles16.list, children: [
|
|
5221
|
-
isLoading && /* @__PURE__ */
|
|
5222
|
-
|
|
5223
|
-
|
|
5771
|
+
isLoading && /* @__PURE__ */ jsxs("div", { style: styles16.loading, children: [
|
|
5772
|
+
/* @__PURE__ */ jsx(Spinner, { size: 16 }),
|
|
5773
|
+
/* @__PURE__ */ jsx("span", { children: "Loading conversations\u2026" })
|
|
5774
|
+
] }),
|
|
5775
|
+
error && !isLoading && /* @__PURE__ */ jsx("div", { style: styles16.errorState, children: error }),
|
|
5776
|
+
!isLoading && !error && filtered.length === 0 && /* @__PURE__ */ jsx(EmptyState, { filter, hasQuery: query.trim().length > 0 }),
|
|
5224
5777
|
filtered.map((item) => /* @__PURE__ */ jsx(
|
|
5225
5778
|
ConversationListItem,
|
|
5226
5779
|
{
|
|
@@ -5233,95 +5786,178 @@ function ConversationList({
|
|
|
5233
5786
|
] })
|
|
5234
5787
|
] });
|
|
5235
5788
|
}
|
|
5789
|
+
function TabButton({
|
|
5790
|
+
label,
|
|
5791
|
+
count,
|
|
5792
|
+
isActive,
|
|
5793
|
+
onClick
|
|
5794
|
+
}) {
|
|
5795
|
+
return /* @__PURE__ */ jsxs(
|
|
5796
|
+
"button",
|
|
5797
|
+
{
|
|
5798
|
+
type: "button",
|
|
5799
|
+
onClick,
|
|
5800
|
+
style: {
|
|
5801
|
+
...styles16.tab,
|
|
5802
|
+
...isActive ? styles16.tabActive : {}
|
|
5803
|
+
},
|
|
5804
|
+
children: [
|
|
5805
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
5806
|
+
/* @__PURE__ */ jsx(
|
|
5807
|
+
"span",
|
|
5808
|
+
{
|
|
5809
|
+
style: {
|
|
5810
|
+
...styles16.tabCount,
|
|
5811
|
+
...isActive ? styles16.tabCountActive : {}
|
|
5812
|
+
},
|
|
5813
|
+
children: count
|
|
5814
|
+
}
|
|
5815
|
+
)
|
|
5816
|
+
]
|
|
5817
|
+
}
|
|
5818
|
+
);
|
|
5819
|
+
}
|
|
5820
|
+
function EmptyState({ filter, hasQuery }) {
|
|
5821
|
+
let title = "No conversations yet";
|
|
5822
|
+
let subtitle = "New chats will show up here as cases come in.";
|
|
5823
|
+
if (filter === "unread") {
|
|
5824
|
+
title = "No unread messages";
|
|
5825
|
+
subtitle = "You're all caught up.";
|
|
5826
|
+
} else if (hasQuery) {
|
|
5827
|
+
title = "No conversations match";
|
|
5828
|
+
subtitle = "Try a different search.";
|
|
5829
|
+
}
|
|
5830
|
+
return /* @__PURE__ */ jsxs("div", { style: styles16.empty, children: [
|
|
5831
|
+
/* @__PURE__ */ jsx(ChatIllustration, { size: 88 }),
|
|
5832
|
+
/* @__PURE__ */ jsx("p", { style: styles16.emptyTitle, children: title }),
|
|
5833
|
+
/* @__PURE__ */ jsx("p", { style: styles16.emptySubtitle, children: subtitle })
|
|
5834
|
+
] });
|
|
5835
|
+
}
|
|
5236
5836
|
var styles16 = {
|
|
5237
5837
|
container: {
|
|
5238
5838
|
display: "flex",
|
|
5239
5839
|
flexDirection: "column",
|
|
5240
5840
|
height: "100%",
|
|
5241
|
-
backgroundColor: COLOR.white
|
|
5242
|
-
borderRight: `1px solid ${COLOR.neutral200}`
|
|
5841
|
+
backgroundColor: COLOR.white
|
|
5243
5842
|
},
|
|
5244
|
-
header
|
|
5245
|
-
|
|
5843
|
+
// Tabs sit at the top of the panel now (header section removed).
|
|
5844
|
+
// A bit more top padding so they don't crowd the top edge.
|
|
5845
|
+
tabs: {
|
|
5846
|
+
display: "flex",
|
|
5847
|
+
gap: SPACE.S2,
|
|
5848
|
+
padding: `${SPACE.S5} ${SPACE.S5} ${SPACE.S2}`,
|
|
5246
5849
|
borderBottom: `1px solid ${COLOR.neutral200}`,
|
|
5247
|
-
backgroundColor: COLOR.neutral50,
|
|
5248
5850
|
flexShrink: 0
|
|
5249
5851
|
},
|
|
5250
|
-
|
|
5251
|
-
display: "flex",
|
|
5852
|
+
tab: {
|
|
5853
|
+
display: "inline-flex",
|
|
5252
5854
|
alignItems: "center",
|
|
5253
5855
|
gap: SPACE.S2,
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5856
|
+
padding: `${SPACE.S2} ${SPACE.S4}`,
|
|
5857
|
+
backgroundColor: "transparent",
|
|
5858
|
+
border: "none",
|
|
5859
|
+
borderRadius: RADIUS.lg,
|
|
5860
|
+
cursor: "pointer",
|
|
5861
|
+
color: COLOR.neutral500,
|
|
5862
|
+
fontFamily: "inherit",
|
|
5863
|
+
fontSize: FONT_SIZE.sm,
|
|
5259
5864
|
fontWeight: FONT_WEIGHT.semibold,
|
|
5260
|
-
|
|
5865
|
+
transition: "background-color 120ms ease, color 120ms ease"
|
|
5261
5866
|
},
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5867
|
+
tabActive: {
|
|
5868
|
+
backgroundColor: COLOR.primaryBg,
|
|
5869
|
+
color: COLOR.primary
|
|
5870
|
+
},
|
|
5871
|
+
tabCount: {
|
|
5266
5872
|
fontSize: FONT_SIZE.xs,
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
backgroundColor: COLOR.primary,
|
|
5873
|
+
fontVariantNumeric: "tabular-nums",
|
|
5874
|
+
padding: "1px 8px",
|
|
5270
5875
|
borderRadius: RADIUS.pill,
|
|
5876
|
+
backgroundColor: COLOR.neutral200,
|
|
5877
|
+
color: COLOR.neutral500,
|
|
5878
|
+
fontWeight: FONT_WEIGHT.semibold
|
|
5879
|
+
},
|
|
5880
|
+
tabCountActive: {
|
|
5881
|
+
backgroundColor: COLOR.primary,
|
|
5882
|
+
color: COLOR.white
|
|
5883
|
+
},
|
|
5884
|
+
search: {
|
|
5271
5885
|
display: "flex",
|
|
5272
5886
|
alignItems: "center",
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5887
|
+
gap: SPACE.S2,
|
|
5888
|
+
margin: `14px ${SPACE.S5} ${SPACE.S3}`,
|
|
5889
|
+
padding: `${SPACE.S3} 14px`,
|
|
5890
|
+
backgroundColor: COLOR.neutral100,
|
|
5891
|
+
border: `1.5px solid ${COLOR.neutral200}`,
|
|
5892
|
+
borderRadius: "14px",
|
|
5893
|
+
transition: "background-color 120ms ease, border-color 120ms ease, box-shadow 120ms ease"
|
|
5277
5894
|
},
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
border: `1px solid ${COLOR.neutral200}`,
|
|
5283
|
-
borderRadius: RADIUS.md,
|
|
5284
|
-
outline: "none",
|
|
5285
|
-
fontFamily: "inherit"
|
|
5895
|
+
searchFocused: {
|
|
5896
|
+
backgroundColor: COLOR.white,
|
|
5897
|
+
borderColor: COLOR.primary,
|
|
5898
|
+
boxShadow: `0 0 0 3px ${COLOR.primaryBg}`
|
|
5286
5899
|
},
|
|
5287
|
-
|
|
5900
|
+
searchIcon: {
|
|
5288
5901
|
display: "flex",
|
|
5289
|
-
|
|
5902
|
+
alignItems: "center",
|
|
5903
|
+
justifyContent: "center",
|
|
5904
|
+
flexShrink: 0
|
|
5290
5905
|
},
|
|
5291
|
-
|
|
5906
|
+
searchInput: {
|
|
5292
5907
|
flex: 1,
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
},
|
|
5301
|
-
filterButtonActive: {
|
|
5302
|
-
color: COLOR.white,
|
|
5303
|
-
backgroundColor: COLOR.primary,
|
|
5304
|
-
borderColor: COLOR.primary,
|
|
5305
|
-
fontWeight: FONT_WEIGHT.medium
|
|
5908
|
+
minWidth: 0,
|
|
5909
|
+
border: "none",
|
|
5910
|
+
background: "transparent",
|
|
5911
|
+
outline: "none",
|
|
5912
|
+
fontFamily: "inherit",
|
|
5913
|
+
fontSize: FONT_SIZE.md,
|
|
5914
|
+
color: COLOR.neutral900
|
|
5306
5915
|
},
|
|
5307
5916
|
list: {
|
|
5308
5917
|
flex: 1,
|
|
5309
|
-
overflowY: "auto"
|
|
5918
|
+
overflowY: "auto",
|
|
5919
|
+
padding: "4px 0 12px"
|
|
5310
5920
|
},
|
|
5311
|
-
|
|
5312
|
-
|
|
5921
|
+
loading: {
|
|
5922
|
+
display: "flex",
|
|
5923
|
+
alignItems: "center",
|
|
5924
|
+
justifyContent: "center",
|
|
5925
|
+
gap: SPACE.S2,
|
|
5926
|
+
padding: `44px ${SPACE.S5}`,
|
|
5313
5927
|
fontSize: FONT_SIZE.sm,
|
|
5314
|
-
color: COLOR.neutral500
|
|
5315
|
-
textAlign: "center"
|
|
5928
|
+
color: COLOR.neutral500
|
|
5316
5929
|
},
|
|
5317
5930
|
errorState: {
|
|
5318
5931
|
padding: SPACE.S4,
|
|
5932
|
+
margin: SPACE.S3,
|
|
5319
5933
|
fontSize: FONT_SIZE.sm,
|
|
5320
5934
|
color: COLOR.danger,
|
|
5321
5935
|
backgroundColor: COLOR.dangerBg,
|
|
5322
|
-
|
|
5936
|
+
border: `1px solid ${COLOR.dangerBorder}`,
|
|
5323
5937
|
borderRadius: RADIUS.md,
|
|
5324
5938
|
textAlign: "center"
|
|
5939
|
+
},
|
|
5940
|
+
empty: {
|
|
5941
|
+
display: "flex",
|
|
5942
|
+
flexDirection: "column",
|
|
5943
|
+
alignItems: "center",
|
|
5944
|
+
justifyContent: "center",
|
|
5945
|
+
padding: "44px 24px",
|
|
5946
|
+
textAlign: "center",
|
|
5947
|
+
color: COLOR.neutral500
|
|
5948
|
+
},
|
|
5949
|
+
emptyTitle: {
|
|
5950
|
+
margin: "12px 0 4px",
|
|
5951
|
+
fontSize: FONT_SIZE.md,
|
|
5952
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
5953
|
+
color: COLOR.neutral700
|
|
5954
|
+
},
|
|
5955
|
+
emptySubtitle: {
|
|
5956
|
+
margin: 0,
|
|
5957
|
+
fontSize: FONT_SIZE.sm,
|
|
5958
|
+
color: COLOR.neutral500,
|
|
5959
|
+
maxWidth: "280px",
|
|
5960
|
+
lineHeight: LINE_HEIGHT.normal
|
|
5325
5961
|
}
|
|
5326
5962
|
};
|
|
5327
5963
|
ensureGlobalStyles();
|
|
@@ -5329,7 +5965,6 @@ var COMPACT_BREAKPOINT = 720;
|
|
|
5329
5965
|
function CollabInbox({
|
|
5330
5966
|
initialConversationId,
|
|
5331
5967
|
onSelectConversation,
|
|
5332
|
-
title,
|
|
5333
5968
|
className,
|
|
5334
5969
|
style
|
|
5335
5970
|
}) {
|
|
@@ -5366,8 +6001,7 @@ function CollabInbox({
|
|
|
5366
6001
|
selectedId,
|
|
5367
6002
|
isLoading,
|
|
5368
6003
|
error,
|
|
5369
|
-
onSelect: handleSelect
|
|
5370
|
-
title
|
|
6004
|
+
onSelect: handleSelect
|
|
5371
6005
|
}
|
|
5372
6006
|
) }),
|
|
5373
6007
|
showDetail && /* @__PURE__ */ jsx("div", { style: isCompact ? styles17.mainCompact : styles17.main, children: selected ? /* @__PURE__ */ jsx(
|
|
@@ -5380,7 +6014,7 @@ function CollabInbox({
|
|
|
5380
6014
|
onBack: isCompact ? () => setSelectedId(null) : void 0
|
|
5381
6015
|
},
|
|
5382
6016
|
selected.id
|
|
5383
|
-
) : /* @__PURE__ */ jsx(
|
|
6017
|
+
) : /* @__PURE__ */ jsx(EmptyState2, { hasAny: conversations.length > 0 }) })
|
|
5384
6018
|
]
|
|
5385
6019
|
}
|
|
5386
6020
|
);
|
|
@@ -5414,7 +6048,7 @@ function parseChannelName(name, orderId) {
|
|
|
5414
6048
|
orderId
|
|
5415
6049
|
};
|
|
5416
6050
|
}
|
|
5417
|
-
function
|
|
6051
|
+
function EmptyState2({ hasAny }) {
|
|
5418
6052
|
return /* @__PURE__ */ jsxs("div", { style: styles17.empty, children: [
|
|
5419
6053
|
/* @__PURE__ */ jsx("div", { style: styles17.emptyIcon, children: "\u{1F4AC}" }),
|
|
5420
6054
|
/* @__PURE__ */ jsx("h3", { style: styles17.emptyTitle, children: hasAny ? "Select a conversation" : "No conversations yet" }),
|
|
@@ -5793,6 +6427,6 @@ function usePinnedMessages({
|
|
|
5793
6427
|
};
|
|
5794
6428
|
}
|
|
5795
6429
|
|
|
5796
|
-
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 };
|
|
6430
|
+
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 };
|
|
5797
6431
|
//# sourceMappingURL=index.mjs.map
|
|
5798
6432
|
//# sourceMappingURL=index.mjs.map
|