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