chordia-ui 3.4.7 → 3.4.8

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.
@@ -0,0 +1,739 @@
1
+ "use client";
2
+
3
+ import React, { useState, useRef, useMemo, useEffect } from "react";
4
+ import {
5
+ MessageSquareMore,
6
+ MessagesSquare,
7
+ ChevronDown,
8
+ Ellipsis,
9
+ CircleCheck,
10
+ CornerDownRight,
11
+ Plus,
12
+ } from "lucide-react";
13
+
14
+ /* ============================================
15
+ Figma node: 237-5557 — Comments / Thread section
16
+ Parent layout (237-5555):
17
+ display: flex
18
+ flex-direction: column
19
+ align-items: flex-start
20
+ gap: 24px
21
+ background: var(--Grey-White, #FFF)
22
+ ============================================ */
23
+
24
+ /* ─── helpers ─── */
25
+
26
+ const roleColorMap = (role) => {
27
+ if (!role) return null;
28
+ const n = String(role).toLowerCase();
29
+ if (n === "supervisor") return "var(--rail-compliance, #C98A5A)";
30
+ if (n === "admin" || n === "administrator") return "var(--rail-discovery, #5E88B0)";
31
+ if (n === "agent") return "var(--rail-outcome, #6B7C93)";
32
+ return null;
33
+ };
34
+
35
+ const getInitials = (name) =>
36
+ (name || "")
37
+ .split(" ")
38
+ .map((w) => w[0])
39
+ .join("")
40
+ .toUpperCase() || "U";
41
+
42
+ const formatTimestamp = (value) => {
43
+ if (!value) return "";
44
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(value)) return value;
45
+ try {
46
+ const d = new Date(value);
47
+ if (Number.isNaN(d.getTime())) return value;
48
+ const now = new Date();
49
+ const diffMs = now.getTime() - d.getTime();
50
+ const diffMin = Math.floor(diffMs / 60000);
51
+ const diffHr = Math.floor(diffMs / 3600000);
52
+ if (diffMin < 1) return "Just now";
53
+ if (diffMin < 60) return `${diffMin} min`;
54
+ if (diffHr < 24) return `${diffHr} h`;
55
+ return `${Math.floor(diffHr / 24)} d`;
56
+ } catch {
57
+ return value;
58
+ }
59
+ };
60
+
61
+ /* ─── Avatar (36 × 36, circular, initials) ─── */
62
+ const Avatar = ({ name, initials, color, size = 36 }) => (
63
+ <div
64
+ style={{
65
+ width: size,
66
+ height: size,
67
+ borderRadius: "50%",
68
+ background: color || "#6B7C93",
69
+ color: "#FFF",
70
+ display: "flex",
71
+ alignItems: "center",
72
+ justifyContent: "center",
73
+ fontSize: size <= 32 ? "10px" : "11px",
74
+ fontWeight: 650,
75
+ flexShrink: 0,
76
+ fontFamily: "var(--default-font-family)",
77
+ letterSpacing: "0.02em",
78
+ }}
79
+ title={name}
80
+ >
81
+ {initials || getInitials(name)}
82
+ </div>
83
+ );
84
+
85
+ /* ============================================
86
+ THREAD CARD — Figma node 426-1904
87
+ Horizontal scroll card
88
+
89
+ Container row (each card):
90
+ display: flex; align-items: center; gap: 12px;
91
+ align-self: stretch;
92
+ border-bottom: 1px solid var(--Grey-absent, #D9D9D9)
93
+
94
+ Icons: circle-check 16×16 | Divider 0×48 | message-square-more 16×16
95
+ ============================================ */
96
+ const ThreadCard = ({ thread, isActive, onClick }) => {
97
+ const [hovered, setHovered] = useState(false);
98
+ const isResolved = thread.status === "resolved";
99
+ const commentCount = thread.comments?.length || thread.commentCount || 0;
100
+
101
+ return (
102
+ <div
103
+ onClick={onClick}
104
+ onMouseEnter={() => setHovered(true)}
105
+ onMouseLeave={() => setHovered(false)}
106
+ style={{
107
+ display: "flex",
108
+ width: 264,
109
+ padding: "12px 12px",
110
+ flexDirection: isActive ? "column" : "row",
111
+ justifyContent: isActive ? "center" : "flex-start",
112
+ alignItems: isActive ? "flex-start" : "center",
113
+ gap: isActive ? 6 : 8,
114
+ borderBottom: isActive
115
+ ? "3px solid var(--Grey-Strong, #2E3236)"
116
+ : "1px solid var(--Grey-absent, #D9D9D9)",
117
+ background: isActive
118
+ ? "var(--Grey-Hover, #F3F7F7)"
119
+ : hovered
120
+ ? "var(--surface-hover, #F3F7F7)"
121
+ : "var(--Grey-White, #FFF)",
122
+ cursor: "pointer",
123
+ flexShrink: 0,
124
+ transition: "all 0.15s ease",
125
+ boxSizing: "border-box",
126
+ }}
127
+ >
128
+ {/* Left: title + count stacked */}
129
+ <div style={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 4 }}>
130
+ {/* Title — 13px / 500 / #2E3236, truncated */}
131
+ <span
132
+ style={{
133
+ fontSize: 13,
134
+ fontWeight: 500,
135
+ color: "var(--Grey-Strong, #2E3236)",
136
+ lineHeight: "150%",
137
+ overflow: "hidden",
138
+ textOverflow: "ellipsis",
139
+ whiteSpace: "nowrap",
140
+ fontFamily: "var(--default-font-family)",
141
+ }}
142
+ >
143
+ {thread.title || "Untitled Thread"}
144
+ </span>
145
+ <span
146
+ style={{
147
+ fontSize: 12,
148
+ fontWeight: 400,
149
+ color: "var(--Grey-Muted, #808183)",
150
+ lineHeight: "150%",
151
+ fontFamily: "var(--default-font-family)",
152
+ }}
153
+ >
154
+ {commentCount} Comment{commentCount !== 1 ? "s" : ""}
155
+ </span>
156
+ </div>
157
+
158
+ {/* Divider — Figma: 0 × 48 (scaled down to match card height) */}
159
+ <div
160
+ style={{
161
+ width: 0,
162
+ height: 32,
163
+ borderLeft: "1px solid var(--Grey-absent, #D9D9D9)",
164
+ flexShrink: 0,
165
+ }}
166
+ />
167
+
168
+ {/* Right: status icon 16×16 + label */}
169
+ <div style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
170
+ {isResolved ? (
171
+ <>
172
+ <CircleCheck size={16} color="var(--Grey-Muted, #808183)" strokeWidth={1.5} />
173
+ <span
174
+ style={{
175
+ fontSize: 12,
176
+ fontWeight: 500,
177
+ color: "var(--Grey-Muted, #808183)",
178
+ fontFamily: "var(--default-font-family)",
179
+ }}
180
+ >
181
+ Resolved
182
+ </span>
183
+ </>
184
+ ) : (
185
+ <>
186
+ <MessageSquareMore size={16} color="var(--Grey-Strong, #2E3236)" strokeWidth={1.5} />
187
+ <span
188
+ style={{
189
+ fontSize: 12,
190
+ fontWeight: 500,
191
+ color: "var(--Grey-Strong, #2E3236)",
192
+ fontFamily: "var(--default-font-family)",
193
+ }}
194
+ >
195
+ Open
196
+ </span>
197
+ </>
198
+ )}
199
+ </div>
200
+ </div>
201
+ );
202
+ };
203
+
204
+ /* ============================================
205
+ COMMENT BUBBLE — single message
206
+ Figma: flex row, gap: 12
207
+ Avatar 36×36 + content column
208
+ ============================================ */
209
+ const CommentBubble = ({
210
+ comment,
211
+ isReply = false,
212
+ onReply,
213
+ onMenuClick,
214
+ showReply = true,
215
+ }) => {
216
+ const [hovered, setHovered] = useState(false);
217
+ const author = comment.author || {};
218
+ const authorName = author.name || comment.author_name || "Unknown";
219
+ const initials = author.initials || getInitials(authorName);
220
+ const role = author.role || comment.author_role;
221
+ const avatarColor = author.color || roleColorMap(role) || "#6B7C93";
222
+ const timestamp = formatTimestamp(comment.timestamp || comment.created_at);
223
+
224
+ const paragraphs = Array.isArray(comment.content)
225
+ ? comment.content
226
+ : [comment.content || comment.text || ""];
227
+
228
+ return (
229
+ <div
230
+ onMouseEnter={() => setHovered(true)}
231
+ onMouseLeave={() => setHovered(false)}
232
+ style={{
233
+ display: "flex",
234
+ gap: 12,
235
+ alignItems: "flex-start",
236
+ marginLeft: isReply ? 48 : 0,
237
+ position: "relative",
238
+ }}
239
+ >
240
+ {/* Avatar — 36 × 36 */}
241
+ <Avatar name={authorName} initials={initials} color={avatarColor} />
242
+
243
+ {/* Content column */}
244
+ <div style={{ flex: 1, minWidth: 0 }}>
245
+ {/* Header: name + time + ••• */}
246
+ <div
247
+ style={{
248
+ display: "flex",
249
+ alignItems: "center",
250
+ justifyContent: "space-between",
251
+ marginBottom: 6,
252
+ }}
253
+ >
254
+ <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
255
+ {/* Name — 13px / 650 */}
256
+ <span
257
+ style={{
258
+ fontSize: 13,
259
+ fontWeight: 650,
260
+ color: "var(--text-ink, #1E2125)",
261
+ lineHeight: "150%",
262
+ fontFamily: "var(--default-font-family)",
263
+ }}
264
+ >
265
+ {authorName}
266
+ </span>
267
+ {/* Timestamp — 12px / 400 */}
268
+ <span
269
+ style={{
270
+ fontSize: 12,
271
+ fontWeight: 400,
272
+ color: "var(--Grey-Muted, #808183)",
273
+ lineHeight: "150%",
274
+ fontFamily: "var(--default-font-family)",
275
+ }}
276
+ >
277
+ {timestamp}
278
+ </span>
279
+ </div>
280
+
281
+ {/* Three-dot menu — Ellipsis 24 × 24 */}
282
+ <button
283
+ type="button"
284
+ onClick={() => onMenuClick?.(comment)}
285
+ style={{
286
+ padding: 2,
287
+ border: "none",
288
+ background: "transparent",
289
+ cursor: "pointer",
290
+ borderRadius: 4,
291
+ display: "flex",
292
+ alignItems: "center",
293
+ justifyContent: "center",
294
+ opacity: hovered ? 1 : 0,
295
+ transition: "opacity 0.15s",
296
+ flexShrink: 0,
297
+ }}
298
+ >
299
+ <Ellipsis size={24} color="var(--Grey-Muted, #808183)" />
300
+ </button>
301
+ </div>
302
+
303
+ {/* Message bubbles — stacked, gap: 4px */}
304
+ <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
305
+ {paragraphs.map((paragraph, idx) => (
306
+ <div
307
+ key={idx}
308
+ style={{
309
+ fontSize: 13,
310
+ fontWeight: 400,
311
+ color: "var(--text-ink, #1E2125)",
312
+ lineHeight: "160%",
313
+ fontFamily: "var(--default-font-family)",
314
+ padding: "8px 12px",
315
+ background: isReply
316
+ ? "var(--surface-hover, #F3F7F7)"
317
+ : "var(--surface-warm-40, rgba(243, 241, 229, 0.40))",
318
+ borderRadius: 8,
319
+ wordBreak: "break-word",
320
+ }}
321
+ >
322
+ {paragraph}
323
+ </div>
324
+ ))}
325
+ </div>
326
+
327
+ {/* Reply action — CornerDownRight 20×20 + "Reply" */}
328
+ {showReply && onReply && (
329
+ <button
330
+ type="button"
331
+ onClick={() => onReply(comment)}
332
+ style={{
333
+ display: "flex",
334
+ alignItems: "center",
335
+ gap: 6,
336
+ marginTop: 8,
337
+ padding: 0,
338
+ border: "none",
339
+ background: "transparent",
340
+ cursor: "pointer",
341
+ fontSize: 13,
342
+ fontWeight: 400,
343
+ color: "var(--Grey-Muted, #808183)",
344
+ fontFamily: "var(--default-font-family)",
345
+ transition: "color 0.15s",
346
+ }}
347
+ >
348
+ <CornerDownRight size={20} color="var(--Grey-Muted, #808183)" strokeWidth={1.5} />
349
+ Reply
350
+ </button>
351
+ )}
352
+ </div>
353
+ </div>
354
+ );
355
+ };
356
+
357
+
358
+ /* ============================================
359
+ COMMENT INPUT
360
+ Figma: Avatar + bordered box
361
+ ┌── toolbar (B I S Link | ⊕) ──┐
362
+ │ "Add a comment" [Post] │
363
+ └──────────────────────────────┘
364
+ ============================================ */
365
+ const CommentInput = ({ currentUser, onSubmit }) => {
366
+ const [value, setValue] = useState("");
367
+ const [isFocused, setIsFocused] = useState(false);
368
+ const textareaRef = useRef(null);
369
+
370
+ const handleSubmit = () => {
371
+ const trimmed = value.trim();
372
+ if (!trimmed) return;
373
+ onSubmit?.(trimmed);
374
+ setValue("");
375
+ };
376
+
377
+ const handleKeyDown = (e) => {
378
+ if (e.key === "Enter" && !e.shiftKey) {
379
+ e.preventDefault();
380
+ handleSubmit();
381
+ }
382
+ };
383
+
384
+ const initials = currentUser?.initials || getInitials(currentUser?.name);
385
+ const color =
386
+ currentUser?.color || roleColorMap(currentUser?.role) || "#6B7C93";
387
+
388
+ return (
389
+ <div style={{ display: "flex", gap: 12, alignItems: "flex-start", width: "100%" }}>
390
+ {/* Current user avatar */}
391
+ <Avatar name={currentUser?.name} initials={initials} color={color} />
392
+
393
+ {/* Input container */}
394
+ <div
395
+ style={{
396
+ flex: 1,
397
+ border: isFocused
398
+ ? "1px solid var(--Grey-Strong, #2E3236)"
399
+ : "1px solid var(--Grey-absent, #D9D9D9)",
400
+ borderRadius: 8,
401
+ background: "var(--Grey-White, #FFF)",
402
+ transition: "border-color 0.15s",
403
+ overflow: "hidden",
404
+ }}
405
+ >
406
+ {/* Textarea row + Post button */}
407
+ <div
408
+ style={{
409
+ display: "flex",
410
+ alignItems: "flex-end",
411
+ padding: "10px 12px",
412
+ gap: 8,
413
+ }}
414
+ >
415
+ <textarea
416
+ ref={textareaRef}
417
+ value={value}
418
+ onChange={(e) => setValue(e.target.value)}
419
+ onFocus={() => setIsFocused(true)}
420
+ onBlur={() => setIsFocused(false)}
421
+ onKeyDown={handleKeyDown}
422
+ placeholder="Add a comment"
423
+ rows={1}
424
+ style={{
425
+ flex: 1,
426
+ border: "none",
427
+ outline: "none",
428
+ resize: "none",
429
+ fontSize: 13,
430
+ fontWeight: 400,
431
+ color: "var(--text-ink, #1E2125)",
432
+ fontFamily: "var(--default-font-family)",
433
+ lineHeight: "160%",
434
+ background: "transparent",
435
+ padding: 0,
436
+ minHeight: 24,
437
+ }}
438
+ />
439
+ <button
440
+ type="button"
441
+ onClick={handleSubmit}
442
+ disabled={!value.trim()}
443
+ style={{
444
+ display: "flex",
445
+ padding: "6px 16px",
446
+ justifyContent: "center",
447
+ alignItems: "center",
448
+ fontSize: 13,
449
+ fontWeight: 600,
450
+ color: value.trim()
451
+ ? "var(--Grey-White, #FFF)"
452
+ : "var(--Grey-Muted, #808183)",
453
+ background: value.trim()
454
+ ? "var(--Grey-Strong, #2E3236)"
455
+ : "var(--grey-light, #e9e8e8a1)",
456
+ border: "none",
457
+ borderRadius: 6,
458
+ cursor: value.trim() ? "pointer" : "not-allowed",
459
+ fontFamily: "var(--default-font-family)",
460
+ transition: "all 0.15s",
461
+ flexShrink: 0,
462
+ lineHeight: "150%",
463
+ }}
464
+ >
465
+ Post
466
+ </button>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ );
471
+ };
472
+
473
+ /* CommentsIcon — lucide/messages-square 24×24 orange */
474
+
475
+ /* ============================================
476
+ MAIN COMPONENT: UpdatedThreads
477
+ Figma node 237-5557 — Thread conversation area
478
+
479
+ Layout:
480
+ display: flex
481
+ flex-direction: column
482
+ align-items: flex-start
483
+ gap: 24px
484
+ background: var(--Grey-White, #FFF)
485
+ ============================================ */
486
+ const UpdatedThreads = ({
487
+ threads = [],
488
+ activeThreadId,
489
+ onThreadSelect,
490
+ onNewThread,
491
+ onSendComment,
492
+ onReply,
493
+ onMenuClick,
494
+ onMarkResolved,
495
+ onViewPreviousComments,
496
+ currentUser,
497
+ filterValue = "all",
498
+ onFilterChange,
499
+ }) => {
500
+ const [internalActiveId, setInternalActiveId] = useState(
501
+ activeThreadId || threads[0]?.id || null
502
+ );
503
+ const [filterOpen, setFilterOpen] = useState(false);
504
+ const filterRef = useRef(null);
505
+
506
+ const currentActiveId = activeThreadId ?? internalActiveId;
507
+
508
+ const activeThread = useMemo(
509
+ () => threads.find((t) => t.id === currentActiveId) || threads[0],
510
+ [threads, currentActiveId]
511
+ );
512
+
513
+ const comments = activeThread?.comments || [];
514
+
515
+ // Group comments: top-level + their replies
516
+ const groupedComments = useMemo(() => {
517
+ const topLevel = comments.filter((c) => !c.parentId);
518
+ const repliesByParent = {};
519
+ comments.forEach((c) => {
520
+ if (c.parentId) {
521
+ if (!repliesByParent[c.parentId]) repliesByParent[c.parentId] = [];
522
+ repliesByParent[c.parentId].push(c);
523
+ }
524
+ });
525
+ return topLevel.map((c) => ({
526
+ ...c,
527
+ replies: repliesByParent[c.id] || [],
528
+ }));
529
+ }, [comments]);
530
+
531
+ useEffect(() => {
532
+ if (activeThreadId) setInternalActiveId(activeThreadId);
533
+ }, [activeThreadId]);
534
+
535
+ // Close filter dropdown on outside click
536
+ useEffect(() => {
537
+ if (!filterOpen) return;
538
+ const handler = (e) => {
539
+ if (filterRef.current && !filterRef.current.contains(e.target)) {
540
+ setFilterOpen(false);
541
+ }
542
+ };
543
+ document.addEventListener("mousedown", handler);
544
+ return () => document.removeEventListener("mousedown", handler);
545
+ }, [filterOpen]);
546
+
547
+ const handleThreadClick = (threadId) => {
548
+ setInternalActiveId(threadId);
549
+ onThreadSelect?.(threadId);
550
+ };
551
+
552
+ const displayCurrentUser = useMemo(() => {
553
+ if (!currentUser) return { name: "You", initials: "YO", color: "#6B7C93" };
554
+ const name = currentUser.name || "You";
555
+ const initials = currentUser.initials || getInitials(name);
556
+ const role = currentUser.role && String(currentUser.role).toLowerCase();
557
+ const color = currentUser.color || roleColorMap(role) || "#6B7C93";
558
+ return { ...currentUser, name, initials, color };
559
+ }, [currentUser]);
560
+
561
+ const filterOptions = [
562
+ { label: "All", value: "all" },
563
+ { label: "Open", value: "open" },
564
+ { label: "Resolved", value: "resolved" },
565
+ ];
566
+
567
+ return (
568
+ <div
569
+ style={{
570
+ display: "flex",
571
+ flexDirection: "column",
572
+ alignItems: "flex-start",
573
+ gap: 24,
574
+ background: "var(--Grey-White, #FFF)",
575
+ width: "100%",
576
+ fontFamily: "var(--default-font-family)",
577
+ }}
578
+ >
579
+ {/* ═══════════════════════════════════════════
580
+ HEADER — icon + "COMMENTS" | All ▾ | + New Thread
581
+ Figma: flex row, space-between, center
582
+ ═══════════════════════════════════════════ */}
583
+ <div
584
+ style={{
585
+ display: "flex",
586
+ alignItems: "center",
587
+ justifyContent: "space-between",
588
+ width: "100%",
589
+ }}
590
+ >
591
+ {/* Left: Icon (24×24 orange) + COMMENTS title */}
592
+ <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
593
+ <MessagesSquare size={24} color="var(--rail-orange, #C98A5A)" strokeWidth={1.5} />
594
+ <span
595
+ style={{
596
+ fontSize: 14,
597
+ fontWeight: 650,
598
+ color: "var(--Grey-Strong, #2E3236)",
599
+ letterSpacing: "0.08em",
600
+ textTransform: "uppercase",
601
+ lineHeight: "150%",
602
+ }}
603
+ >
604
+ COMMENTS
605
+ </span>
606
+ </div>
607
+
608
+ </div>
609
+
610
+ {/* ═══════════════════════════════════════════
611
+ THREAD CARDS — horizontal scroll row
612
+ ═══════════════════════════════════════════ */}
613
+ {threads.length > 0 && (
614
+ <div
615
+ style={{
616
+ display: "flex",
617
+ gap: 0,
618
+ width: "100%",
619
+ overflowX: "auto",
620
+ }}
621
+ className="custom-thin-scrollbar-library"
622
+ >
623
+ {threads.map((thread) => (
624
+ <ThreadCard
625
+ key={thread.id}
626
+ thread={thread}
627
+ isActive={thread.id === currentActiveId}
628
+ onClick={() => handleThreadClick(thread.id)}
629
+ />
630
+ ))}
631
+ </div>
632
+ )}
633
+
634
+ {/* ═══════════════════════════════════════════
635
+ VIEW PREVIOUS COMMENTS — bordered button
636
+ Figma: padding 8px 16px, border-radius 8px
637
+ ═══════════════════════════════════════════ */}
638
+ {onViewPreviousComments && (
639
+ <button
640
+ type="button"
641
+ onClick={onViewPreviousComments}
642
+ style={{
643
+ display: "flex",
644
+ alignItems: "center",
645
+ gap: 6,
646
+ padding: "8px 16px",
647
+ fontSize: 13,
648
+ fontWeight: 500,
649
+ color: "var(--Grey-Strong, #2E3236)",
650
+ background: "var(--Grey-White, #FFF)",
651
+ border: "1px solid var(--Grey-absent, #D9D9D9)",
652
+ borderRadius: 8,
653
+ cursor: "pointer",
654
+ fontFamily: "var(--default-font-family)",
655
+ transition: "background 0.15s",
656
+ lineHeight: "150%",
657
+ }}
658
+ >
659
+ View Previous Comments
660
+ </button>
661
+ )}
662
+
663
+ {/* ═══════════════════════════════════════════
664
+ COMMENTS LIST — grouped by parent/reply
665
+ Figma: flex column, gap: 20px
666
+ ═══════════════════════════════════════════ */}
667
+ <div
668
+ style={{
669
+ display: "flex",
670
+ flexDirection: "column",
671
+ gap: 20,
672
+ width: "100%",
673
+ }}
674
+ >
675
+ {groupedComments.map((comment) => (
676
+ <div key={comment.id} style={{ display: "flex", flexDirection: "column", gap: 20 }}>
677
+ {/* Top-level comment — no Reply button when it has replies below */}
678
+ <CommentBubble
679
+ comment={comment}
680
+ isReply={false}
681
+ onReply={onReply}
682
+ onMenuClick={onMenuClick}
683
+ showReply={comment.replies.length === 0}
684
+ />
685
+
686
+ {/* Replies — indented, last reply shows Reply button */}
687
+ {comment.replies.map((reply, idx) => (
688
+ <CommentBubble
689
+ key={reply.id}
690
+ comment={reply}
691
+ isReply
692
+ onReply={onReply}
693
+ onMenuClick={onMenuClick}
694
+ showReply={idx === comment.replies.length - 1}
695
+ />
696
+ ))}
697
+ </div>
698
+ ))}
699
+ </div>
700
+
701
+ {/* ═══════════════════════════════════════════
702
+ COMMENT INPUT — avatar + rich text box
703
+ ═══════════════════════════════════════════ */}
704
+ <CommentInput currentUser={displayCurrentUser} onSubmit={onSendComment} />
705
+
706
+ {/* ═══════════════════════════════════════════
707
+ MARK AS RESOLVED — pill button
708
+ Figma: CircleCheck 16×16 + text, border-radius 20px
709
+ ═══════════════════════════════════════════ */}
710
+ {onMarkResolved && activeThread && activeThread.status !== "resolved" && (
711
+ <button
712
+ type="button"
713
+ onClick={onMarkResolved}
714
+ style={{
715
+ display: "flex",
716
+ alignItems: "center",
717
+ gap: 8,
718
+ padding: "8px 16px",
719
+ fontSize: 13,
720
+ fontWeight: 500,
721
+ color: "var(--Grey-Strong, #2E3236)",
722
+ background: "var(--Grey-White, #FFF)",
723
+ border: "1px solid var(--Grey-absent, #D9D9D9)",
724
+ borderRadius: 20,
725
+ cursor: "pointer",
726
+ fontFamily: "var(--default-font-family)",
727
+ transition: "background 0.15s",
728
+ lineHeight: "150%",
729
+ }}
730
+ >
731
+ <CircleCheck size={16} color="var(--Grey-Strong, #2E3236)" strokeWidth={1.5} />
732
+ Mark as Resolved
733
+ </button>
734
+ )}
735
+ </div>
736
+ );
737
+ };
738
+
739
+ export default UpdatedThreads;