chordia-ui 3.4.4 → 3.4.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chordia-ui",
3
- "version": "3.4.4",
3
+ "version": "3.4.6",
4
4
  "description": "Chordia Design System - UI components, tokens, and Tailwind preset",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -60,7 +60,7 @@ const GaugeMeter = ({ score = 4, maxScore = 10 }) => {
60
60
  const gap = 2; // degrees gap between segments
61
61
 
62
62
  return (
63
- <svg width={svgW} height={svgH} viewBox={`0 0 ${svgW} ${svgH}`} fill="none">
63
+ <svg width="100%" viewBox={`0 0 ${svgW} ${svgH}`} fill="none" style={{ maxWidth: svgW }}>
64
64
  {/* Segments from left (180°) to right (0°) */}
65
65
  {Array.from({ length: totalSeg }, (_, i) => {
66
66
  const fromA = 180 - i * segDeg - (i > 0 ? gap / 2 : 0);
@@ -201,6 +201,7 @@ const UpdatedCompassScore = ({
201
201
  border: `1px solid ${COLORS.absent}`,
202
202
  background: COLORS.white,
203
203
  alignSelf: 'stretch',
204
+ flex: 1,
204
205
  gap: 24,
205
206
  }}>
206
207
  {/* Section Title — left-aligned, vertical, gap: 8 */}
@@ -234,24 +235,48 @@ const UpdatedCompassScore = ({
234
235
  display: 'flex',
235
236
  alignItems: 'center',
236
237
  justifyContent: 'center',
237
- gap: 48,
238
+ gap: 24,
238
239
  flex: 1,
240
+ overflow: 'hidden',
239
241
  }}>
240
242
  {/* Left: Gauge + Pin + Score number */}
241
243
  <div style={{
242
- display: 'flex',
243
- width: 243,
244
+ position: 'relative',
245
+ maxWidth: 243,
246
+ minWidth: 160,
247
+ flex: '0 1 243px',
244
248
  flexDirection: 'column',
245
249
  alignItems: 'center',
246
- flexShrink: 0,
250
+ display: 'flex',
247
251
  }}>
248
252
  <GaugeMeter score={score} maxScore={maxScore} />
249
253
 
250
- {/* Compass pin icon exact Figma export */}
251
- <svg width="34" height="35" viewBox="0 0 34 35" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ marginTop: -24 }}>
252
- <path d="M16 25.2169C19.958 25.2169 23.1667 22.0083 23.1667 18.0503C23.1667 14.0922 19.958 10.8836 16 10.8836C12.042 10.8836 8.83334 14.0922 8.83334 18.0503C8.83334 22.0083 12.042 25.2169 16 25.2169Z" fill="var(--rail-orange, #C98A5A)" />
253
- <path fillRule="evenodd" clipRule="evenodd" d="M30.4791 11.2328L33.4351 0L21.6888 3.09113C19.9212 2.41855 18.0036 2.05025 16 2.05025C7.16344 2.05025 0 9.2137 0 18.0503C0 26.8868 7.16344 34.0503 16 34.0503C24.8366 34.0503 32 26.8868 32 18.0503C32 15.6119 31.4546 13.301 30.4791 11.2328ZM16 30.5503C22.9036 30.5503 28.5 24.9538 28.5 18.0503C28.5 11.1467 22.9036 5.55025 16 5.55025C9.09644 5.55025 3.5 11.1467 3.5 18.0503C3.5 24.9538 9.09644 30.5503 16 30.5503Z" fill="var(--rail-orange, #C98A5A)" />
254
- </svg>
254
+ {/* Compass pin — centered below gauge, needle rotates to point at score */}
255
+ {(() => {
256
+ // Pin sits at gauge center; needle rotates from 180° (score=0) to (score=max)
257
+ const pct = maxScore > 0 ? Math.min(score / maxScore, 1) : 0;
258
+ // SVG pin's default needle points to top-right (~45°)
259
+ // We need: score=0 → needle points left (180°), score=max → needle points right (0°)
260
+ // Target angle on gauge: 180 - pct*180
261
+ // Pin default orientation: needle at ~45° from top → offset is 45
262
+ // Rotation needed: -(targetAngle - 45) to align needle with gauge position
263
+ const targetAngle = 180 - pct * 180;
264
+ const rotateDeg = -(targetAngle - 45);
265
+ return (
266
+ <svg
267
+ width="34" height="35" viewBox="0 0 34 35" fill="none"
268
+ xmlns="http://www.w3.org/2000/svg"
269
+ style={{
270
+ marginTop: -24,
271
+ transform: `rotate(${rotateDeg}deg)`,
272
+ transformOrigin: 'center center',
273
+ }}
274
+ >
275
+ <path d="M16 25.2169C19.958 25.2169 23.1667 22.0083 23.1667 18.0503C23.1667 14.0922 19.958 10.8836 16 10.8836C12.042 10.8836 8.83334 14.0922 8.83334 18.0503C8.83334 22.0083 12.042 25.2169 16 25.2169Z" fill="var(--rail-orange, #C98A5A)" />
276
+ <path fillRule="evenodd" clipRule="evenodd" d="M30.4791 11.2328L33.4351 0L21.6888 3.09113C19.9212 2.41855 18.0036 2.05025 16 2.05025C7.16344 2.05025 0 9.2137 0 18.0503C0 26.8868 7.16344 34.0503 16 34.0503C24.8366 34.0503 32 26.8868 32 18.0503C32 15.6119 31.4546 13.301 30.4791 11.2328ZM16 30.5503C22.9036 30.5503 28.5 24.9538 28.5 18.0503C28.5 11.1467 22.9036 5.55025 16 5.55025C9.09644 5.55025 3.5 11.1467 3.5 18.0503C3.5 24.9538 9.09644 30.5503 16 30.5503Z" fill="var(--rail-orange, #C98A5A)" />
277
+ </svg>
278
+ );
279
+ })()}
255
280
 
256
281
  {/* Score number below gauge */}
257
282
  <div style={{
@@ -71,7 +71,7 @@ const UpdatedInteractionContext = ({
71
71
  messages != null && { label: 'Messages', value: messages },
72
72
  paradigm != null && { label: 'Paradigm', value: paradigm },
73
73
  ...dimensions.filter((_, i) => i % 2 === 1).map((d) => ({ label: d.label || d.key, value: d.value })),
74
- { label: 'More Details', isLink: true },
74
+ // { label: 'More Details', isLink: true },
75
75
  ].filter(Boolean);
76
76
 
77
77
  const renderRow = (item) => {
@@ -46,9 +46,16 @@ const UpdatedInteractionDetails = ({
46
46
  // Signal evidence playback — host app can provide to play audio segments from signals
47
47
  onPlayEvidence,
48
48
  onHighlightTurns,
49
+ // Context props — pass from host app to override block-derived defaults
50
+ callPurpose: externalCallPurpose,
51
+ classification: externalClassification,
52
+ outcomeQuality: externalOutcomeQuality,
49
53
  // Compass score props
50
- compassScore,
51
- predictedCsat,
54
+ compassScore, // gauge meter value (e.g. outcomeLift.p_full) — drives meter fill, range 1-10
55
+ compassMaxScore = 10,
56
+ predictedCsat, // predicted objective number (e.g. compass_score) — shown as "03" top-right
57
+ predictedLabel = 'Predicted Objective',
58
+ compassLegends,
52
59
  // Customer session dropdown
53
60
  customerSessions: customerSessionsList,
54
61
  customerSessionCount,
@@ -57,8 +64,10 @@ const UpdatedInteractionDetails = ({
57
64
  // Footer navigation
58
65
  prevSessionTitle,
59
66
  prevSessionDesc,
67
+ prevSessionDisabled = false,
60
68
  nextSessionTitle,
61
69
  nextSessionDesc,
70
+ nextSessionDisabled = false,
62
71
  onPreviousSession,
63
72
  onNextSession,
64
73
  }) => {
@@ -199,7 +208,9 @@ const UpdatedInteractionDetails = ({
199
208
  const direction = demoCallPurpose.interaction_direction === 'inbound' ? 'Inbound' : 'Outbound';
200
209
 
201
210
  return (
202
- <div style={{ display: 'flex', flexDirection: 'column', width: '100%', background: 'var(--Grey-White, #FFF)' }}>
211
+ <div style={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%', background: 'var(--Grey-White, #FFF)' }}>
212
+ {/* Sticky Header + Tabs */}
213
+ <div style={{ position: 'sticky', top: 0, zIndex: 20, background: 'var(--Grey-White, #FFF)' }}>
203
214
  {/* Header */}
204
215
  <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '24px 24px 0' }}>
205
216
  <button
@@ -267,6 +278,7 @@ const UpdatedInteractionDetails = ({
267
278
  </button>
268
279
  ))}
269
280
  </div>
281
+ </div>{/* end sticky header + tabs */}
270
282
 
271
283
  {/* All sections rendered — tabs scroll to them */}
272
284
  <div style={{ padding: 24, flex: 1, overflowY: 'auto' }}>
@@ -396,7 +408,7 @@ const UpdatedInteractionDetails = ({
396
408
  </div>
397
409
 
398
410
  {/* Session list — Figma: 264px, pad 12 16, space-between, center */}
399
- {sessionHistory.map((session, i) => (
411
+ {sessionHistory.slice(0, 5).map((session, i) => (
400
412
  <div
401
413
  key={session.id || i}
402
414
  onClick={() => { if (onSessionClick) onSessionClick(session); setShowSessionDropdown(false); }}
@@ -482,17 +494,18 @@ const UpdatedInteractionDetails = ({
482
494
  */}
483
495
  <div style={{
484
496
  display: 'flex',
485
- alignItems: 'flex-start',
497
+ alignItems: 'stretch',
486
498
  gap: 24,
487
499
  alignSelf: 'stretch',
488
500
  }}>
489
501
  {/* Left — Compass Score */}
490
- <div style={{ flex: 1, minWidth: 0 }}>
502
+ <div style={{ flex: 1, minWidth: 0, display: 'flex' }}>
491
503
  <UpdatedCompassScore
492
- score={compassScore ?? 0}
504
+ score={compassScore != null ? Math.round(compassScore * 100) : 0}
493
505
  maxScore={100}
494
506
  predictedScore={predictedCsat ?? 0}
495
- predictedLabel="Predicted Objective"
507
+ predictedLabel={predictedLabel}
508
+ legends={compassLegends}
496
509
  />
497
510
  </div>
498
511
 
@@ -508,9 +521,9 @@ const UpdatedInteractionDetails = ({
508
521
  }}>
509
522
  <UpdatedInteractionContext
510
523
  meta={demoMeta}
511
- callPurpose={demoCallPurpose}
512
- classification={demoClassification}
513
- outcomeQuality="Neutral"
524
+ callPurpose={externalCallPurpose || demoCallPurpose}
525
+ classification={externalClassification || demoClassification}
526
+ outcomeQuality={externalOutcomeQuality || 'Neutral'}
514
527
  />
515
528
  <UpdatedInteractionScores
516
529
  outcomeLift={demoOutcomeLift}
@@ -618,7 +631,8 @@ const UpdatedInteractionDetails = ({
618
631
  }}>
619
632
  {/* Previous Session */}
620
633
  <button
621
- onClick={onPreviousSession}
634
+ onClick={prevSessionDisabled ? undefined : onPreviousSession}
635
+ disabled={prevSessionDisabled}
622
636
  style={{
623
637
  display: 'flex',
624
638
  alignItems: 'center',
@@ -627,7 +641,9 @@ const UpdatedInteractionDetails = ({
627
641
  flex: 1,
628
642
  background: 'var(--Grey-White, #FFF)',
629
643
  border: '1px solid var(--Grey-absent, #D9D9D9)',
630
- cursor: 'pointer',
644
+ cursor: prevSessionDisabled ? 'default' : 'pointer',
645
+ opacity: prevSessionDisabled ? 0.4 : 1,
646
+ pointerEvents: prevSessionDisabled ? 'none' : 'auto',
631
647
  }}
632
648
  >
633
649
  <ArrowLeft size={24} color="var(--Grey-Muted, #808183)" strokeWidth={2} />
@@ -654,7 +670,8 @@ const UpdatedInteractionDetails = ({
654
670
 
655
671
  {/* Next Session */}
656
672
  <button
657
- onClick={onNextSession}
673
+ onClick={nextSessionDisabled ? undefined : onNextSession}
674
+ disabled={nextSessionDisabled}
658
675
  style={{
659
676
  display: 'flex',
660
677
  alignItems: 'center',
@@ -664,7 +681,9 @@ const UpdatedInteractionDetails = ({
664
681
  flex: 1,
665
682
  background: 'var(--Grey-White, #FFF)',
666
683
  border: '1px solid var(--Grey-absent, #D9D9D9)',
667
- cursor: 'pointer',
684
+ cursor: nextSessionDisabled ? 'default' : 'pointer',
685
+ opacity: nextSessionDisabled ? 0.4 : 1,
686
+ pointerEvents: nextSessionDisabled ? 'none' : 'auto',
668
687
  }}
669
688
  >
670
689
  <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
@@ -311,6 +311,19 @@ const UpdatedInteractionRecording = forwardRef(function UpdatedInteractionRecord
311
311
  }))
312
312
  : null;
313
313
 
314
+ // Auto-scroll transcript to active card during playback
315
+ useEffect(() => {
316
+ if (!activePlaying) return;
317
+ const activeIdx = turns ? activeTurnIndex : activeDemoIdx;
318
+ if (activeIdx == null || activeIdx < 0) return;
319
+ const container = scrollRef.current;
320
+ if (!container) return;
321
+ const cards = container.children;
322
+ if (cards?.[activeIdx]) {
323
+ cards[activeIdx].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
324
+ }
325
+ }, [activeTurnIndex, activeDemoIdx, activePlaying, turns]);
326
+
314
327
  // Computed display values
315
328
  const effectiveTime = isDemo ? (demoSeekTime ?? 0) : activeCurrentTime;
316
329
  const effectiveDuration = activeDuration || 156;
@@ -266,13 +266,14 @@ const UpdatedInteractionSignals = ({
266
266
  </div>
267
267
 
268
268
  {/* Evidence quotes — Frame 39 */}
269
- {obs.evidence?.map((ev, evIdx) => (
270
- ev.text && (
269
+ {obs.evidence?.map((ev, evIdx) => {
270
+ const hasTimestamps = ev.start_ms != null && ev.end_ms != null;
271
+ return ev.text && (
271
272
  <div
272
273
  key={evIdx}
273
274
  onClick={(e) => {
274
275
  e.stopPropagation();
275
- playEvidence(ev);
276
+ if (hasTimestamps) playEvidence(ev);
276
277
  }}
277
278
  onMouseEnter={() => highlightTurns(ev.turn_ids ?? [])}
278
279
  onMouseLeave={() => highlightTurns([])}
@@ -281,24 +282,26 @@ const UpdatedInteractionSignals = ({
281
282
  alignItems: 'center',
282
283
  gap: 8,
283
284
  padding: '8px 0',
284
- cursor: 'pointer',
285
+ cursor: hasTimestamps ? 'pointer' : 'default',
285
286
  }}
286
287
  >
287
- <HoverIcon size={28}>
288
- {isEvPlaying(ev) ? (
289
- <PauseCircle
290
- size={17}
291
- color="var(--Grey-Muted, #808183)"
292
- strokeWidth={1.5}
293
- />
294
- ) : (
295
- <PlayCircle
296
- size={17}
297
- color="var(--Grey-Muted, #808183)"
298
- strokeWidth={1}
299
- />
300
- )}
301
- </HoverIcon>
288
+ {hasTimestamps && (
289
+ <HoverIcon size={28}>
290
+ {isEvPlaying(ev) ? (
291
+ <PauseCircle
292
+ size={17}
293
+ color="var(--Grey-Muted, #808183)"
294
+ strokeWidth={1.5}
295
+ />
296
+ ) : (
297
+ <PlayCircle
298
+ size={17}
299
+ color="var(--Grey-Muted, #808183)"
300
+ strokeWidth={1}
301
+ />
302
+ )}
303
+ </HoverIcon>
304
+ )}
302
305
  <span style={{
303
306
  fontSize: 13,
304
307
  fontWeight: 400,
@@ -308,8 +311,8 @@ const UpdatedInteractionSignals = ({
308
311
  &ldquo;{ev.text}&rdquo;
309
312
  </span>
310
313
  </div>
311
- )
312
- ))}
314
+ );
315
+ })}
313
316
  </div>
314
317
  ))}
315
318
  </div>