@handled-ai/design-system 0.20.11 → 0.20.12

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.
@@ -101,7 +101,7 @@ describe("DetailView case-panel-v2 section layout", () => {
101
101
  screen.getByText("Cash movement"),
102
102
  screen.getByText("Approve action"),
103
103
  screen.getByText("After-score marker"),
104
- screen.getByText("Activity timeline"),
104
+ screen.getByText(/activity timeline/i),
105
105
  screen.getByText("Legacy detail extra marker"),
106
106
  )
107
107
  })
@@ -125,7 +125,7 @@ describe("DetailView case-panel-v2 section layout", () => {
125
125
  screen.getByText("Opportunity marker"),
126
126
  screen.getByText("Primary action marker"),
127
127
  screen.getByText("Comment area marker"),
128
- screen.getByText("Activity timeline"),
128
+ screen.getByText(/activity timeline/i),
129
129
  )
130
130
  })
131
131
 
@@ -148,7 +148,7 @@ describe("DetailView case-panel-v2 section layout", () => {
148
148
 
149
149
  expectInDocumentOrder(
150
150
  screen.getByText("Comment composer marker"),
151
- screen.getByText("Activity timeline"),
151
+ screen.getByText(/activity timeline/i),
152
152
  )
153
153
  })
154
154
 
@@ -209,20 +209,22 @@ describe("DetailView timeline system-events toggle", () => {
209
209
  const badge = container.querySelector('[data-testid="hidden-count-badge"]')
210
210
  expect(badge).not.toBeNull()
211
211
  expect(badge?.textContent).toBe("2")
212
- expect(badge).toHaveClass("min-w-[18px]")
212
+ expect(badge).toHaveClass("min-w-[22px]")
213
213
  })
214
214
 
215
- it("calls localStorage.setItem when toggle changes and shows a stronger pressed style", () => {
215
+ it("calls localStorage.setItem when toggle changes and shows the active pill style", () => {
216
216
  const { container } = render(<DetailView {...baseProps()} />)
217
217
  expandTimeline(container)
218
218
  const toggle = container.querySelector(
219
219
  '[data-testid="system-events-toggle"]',
220
220
  ) as HTMLElement
221
221
  expect(toggle).toHaveAttribute("aria-pressed", "false")
222
+ expect(toggle).toHaveAttribute("title", "Score changes are hidden.")
222
223
  fireEvent.click(toggle)
223
224
  expect(toggle).toHaveAttribute("aria-pressed", "true")
224
- expect(toggle.className).toContain("border-primary/40")
225
- expect(toggle.className).toContain("bg-primary/10")
225
+ expect(toggle).toHaveClass("border-foreground")
226
+ expect(toggle).toHaveClass("bg-foreground")
227
+ expect(toggle).toHaveAttribute("title", "Showing 2 score changes.")
226
228
  expect(localStorageMock.setItem).toHaveBeenCalledWith(
227
229
  "test-show-score-changes",
228
230
  "true",
@@ -312,18 +314,19 @@ describe("DetailView timeline system-events toggle", () => {
312
314
  expect(toggle).toBeNull()
313
315
  })
314
316
 
315
- it("shows footer hint below the case-panel timeline when timeline is expanded and system events are hidden", () => {
317
+ it("does not render a footer hint and uses the hidden hint as toggle help", () => {
316
318
  const { container } = render(<DetailView {...baseProps()} />)
317
319
  expandTimeline(container)
318
320
  const timeline = container.querySelector('[data-variant="case-panel"]')
319
321
  const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
322
+ const toggle = container.querySelector('[data-testid="system-events-toggle"]')
320
323
  expect(timeline).not.toBeNull()
321
- expect(hint).not.toBeNull()
322
- expect(hint?.textContent).toBe("Score changes are hidden.")
323
- expect(timeline?.compareDocumentPosition(hint as Node)).toBe(Node.DOCUMENT_POSITION_FOLLOWING)
324
+ expect(hint).toBeNull()
325
+ expect(toggle).toHaveAttribute("title", "Score changes are hidden.")
326
+ expect(toggle).toHaveAttribute("aria-label", "Score changes are hidden.")
324
327
  })
325
328
 
326
- it("shows visible footer hint with count when system events are shown", () => {
329
+ it("uses visible footer hint text as toggle help with count when system events are shown", () => {
327
330
  const { container } = render(<DetailView {...baseProps()} />)
328
331
  expandTimeline(container)
329
332
  // Toggle on
@@ -332,8 +335,8 @@ describe("DetailView timeline system-events toggle", () => {
332
335
  ) as HTMLElement
333
336
  fireEvent.click(toggle)
334
337
  const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
335
- expect(hint).not.toBeNull()
336
- expect(hint?.textContent).toBe("Showing 2 score changes.")
338
+ expect(hint).toBeNull()
339
+ expect(toggle).toHaveAttribute("title", "Showing 2 score changes.")
337
340
  })
338
341
 
339
342
  // --- Toggle always renders when system-noise events exist (review fix #1) ---
@@ -414,10 +417,10 @@ describe("DetailView timeline system-events toggle", () => {
414
417
  const toggle = container.querySelector('[data-testid="system-events-toggle"]')
415
418
  expect(toggle).not.toBeNull()
416
419
  expect(toggle?.textContent).toContain("Legacy label")
417
- // Footer hint should work too
420
+ // Deprecated hint props are accepted and exposed as toggle help.
418
421
  expandTimeline(container)
419
422
  const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
420
- expect(hint).not.toBeNull()
421
- expect(hint?.textContent).toBe("Legacy hidden hint.")
423
+ expect(hint).toBeNull()
424
+ expect(toggle).toHaveAttribute("title", "Legacy hidden hint.")
422
425
  })
423
426
  })
@@ -222,6 +222,7 @@ function TimelineSection({
222
222
  attentionCount,
223
223
  sysEvtConfig,
224
224
  lastActivityTime,
225
+ isCasePanel = false,
225
226
  }: {
226
227
  timelineEvents: TimelineEvent[]
227
228
  showTimeline: boolean
@@ -231,6 +232,7 @@ function TimelineSection({
231
232
  attentionCount?: number
232
233
  sysEvtConfig?: TimelineSystemEventsConfig
233
234
  lastActivityTime?: string
235
+ isCasePanel?: boolean
234
236
  }) {
235
237
  // Single-pass partition: compute visibleEvents and hiddenCount together
236
238
  const visibleEvents: TimelineEvent[] = []
@@ -245,6 +247,9 @@ function TimelineSection({
245
247
  // config was provided — so consumers that emit `isSystemNoise: true` always
246
248
  // give users a way to reveal those events.
247
249
  const toggleLabel = sysEvtConfig?.toggleLabel ?? "System events"
250
+ const toggleHelp = showSystemEvents
251
+ ? sysEvtConfig?.visibleHint?.replace("{count}", String(hiddenCount)) ?? "Hide system events"
252
+ : sysEvtConfig?.hiddenHint ?? "Show system events"
248
253
 
249
254
  // Derive "Last activity" from the first *visible* event so the collapsed
250
255
  // header never points at a hidden score-update. The caller-supplied
@@ -262,84 +267,106 @@ function TimelineSection({
262
267
  const eventCountLabel = `${visibleCount} ${visibleCount === 1 ? "event" : "events"}`
263
268
 
264
269
  return (
265
- <div className="mb-8">
270
+ <div
271
+ className={cn(
272
+ isCasePanel ? "mt-8 border-t border-border pt-8 pb-8" : "mb-8"
273
+ )}
274
+ >
266
275
  {/* Header — outer non-interactive container */}
267
276
  <div
268
- className="group/timeline flex w-full items-center justify-between gap-2 py-2 rounded-md transition-colors hover:bg-muted/40 -mx-2 px-2"
277
+ className={cn(
278
+ "flex w-full items-center justify-between",
279
+ isCasePanel
280
+ ? "gap-4 border-b border-border pb-5"
281
+ : "group/timeline gap-2 rounded-md py-2 transition-colors hover:bg-muted/40 -mx-2 px-2"
282
+ )}
269
283
  data-testid="timeline-header"
270
284
  >
271
285
  {/* Left: collapse/expand button */}
272
286
  <button
273
287
  type="button"
274
288
  onClick={() => setShowTimeline((prev) => !prev)}
275
- className="flex items-center gap-2 cursor-pointer bg-transparent border-0 p-0"
289
+ className="flex min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left"
276
290
  data-testid="timeline-collapse-btn"
277
291
  >
278
- <h3 className="text-xs font-bold text-muted-foreground uppercase tracking-wider group-hover/timeline:text-foreground transition-colors">Activity timeline</h3>
292
+ <h3 className="text-xs font-bold uppercase tracking-[0.16em] text-muted-foreground transition-colors group-hover/timeline:text-foreground">ACTIVITY TIMELINE</h3>
279
293
  {!showTimeline && attentionCount != null && attentionCount > 0 && (
280
294
  <span className="inline-flex items-center gap-1 rounded-full bg-destructive/10 px-1.5 py-0.5 text-[10px] font-semibold text-destructive border border-destructive/20">
281
295
  {attentionCount} new
282
296
  </span>
283
297
  )}
284
- {!showTimeline && firstVisibleTime && (
298
+ {!isCasePanel && !showTimeline && firstVisibleTime && (
285
299
  <span className="text-[11px] text-muted-foreground/60" data-testid="last-activity-hint">
286
300
  &middot; Last activity {firstVisibleTime}
287
301
  </span>
288
302
  )}
289
- <div className="flex items-center gap-1.5">
290
- <span className="text-[11px] font-medium text-muted-foreground" data-testid="event-count">{eventCountLabel}</span>
291
- <ChevronDown className={`h-3.5 w-3.5 text-muted-foreground transition-transform duration-200 ${showTimeline ? "rotate-180" : ""}`} />
292
- </div>
293
303
  </button>
294
304
 
295
- {/* Right: system-events toggle always rendered when noise events exist */}
296
- {hasSystemNoise && (
305
+ {/* Right: system-events toggle, event count, and collapse affordance */}
306
+ <div className="flex shrink-0 items-center gap-4">
307
+ {hasSystemNoise && (
308
+ <button
309
+ type="button"
310
+ onClick={() => setShowSystemEvents((prev) => !prev)}
311
+ className={cn(
312
+ "inline-flex shrink-0 cursor-pointer items-center gap-3 rounded-full border px-3.5 py-2 text-sm font-semibold transition-colors",
313
+ showSystemEvents
314
+ ? "border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90"
315
+ : "border-border bg-background text-muted-foreground shadow-sm hover:bg-muted/40 hover:text-foreground"
316
+ )}
317
+ aria-pressed={showSystemEvents}
318
+ aria-label={toggleHelp}
319
+ title={toggleHelp}
320
+ data-testid="system-events-toggle"
321
+ >
322
+ <span
323
+ className={cn(
324
+ "relative inline-flex h-4 w-8 shrink-0 items-center rounded-full p-0.5 transition-colors",
325
+ showSystemEvents ? "bg-teal-600" : "bg-muted-foreground/30"
326
+ )}
327
+ aria-hidden="true"
328
+ data-testid="system-events-indicator"
329
+ >
330
+ <span
331
+ className={cn(
332
+ "block h-3 w-3 rounded-full bg-white shadow-sm transition-transform",
333
+ showSystemEvents ? "translate-x-4" : "translate-x-0"
334
+ )}
335
+ />
336
+ </span>
337
+ <span>{toggleLabel}</span>
338
+ {!showSystemEvents ? (
339
+ <span
340
+ className="inline-flex min-w-[22px] items-center justify-center rounded-full bg-muted px-1.5 text-xs font-bold tabular-nums text-muted-foreground"
341
+ data-testid="hidden-count-badge"
342
+ >
343
+ {hiddenCount}
344
+ </span>
345
+ ) : null}
346
+ </button>
347
+ )}
348
+
297
349
  <button
298
350
  type="button"
299
- onClick={() => setShowSystemEvents((prev) => !prev)}
351
+ onClick={() => setShowTimeline((prev) => !prev)}
300
352
  className={cn(
301
- "flex shrink-0 cursor-pointer items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors hover:text-foreground",
302
- showSystemEvents
303
- ? "border-primary/40 bg-primary/10 text-primary shadow-sm hover:bg-primary/15"
304
- : "border-border bg-background text-muted-foreground hover:bg-muted/40"
353
+ "inline-flex shrink-0 cursor-pointer items-center border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground",
354
+ isCasePanel ? "gap-3 text-sm" : "gap-1.5 text-[11px]"
305
355
  )}
306
- aria-pressed={showSystemEvents}
307
- data-testid="system-events-toggle"
356
+ aria-label={showTimeline ? "Collapse activity timeline" : "Expand activity timeline"}
308
357
  >
309
- {toggleLabel}
310
- <span
311
- className={cn(
312
- "inline-flex min-w-[18px] items-center justify-center rounded-full px-1.5 text-[10px] font-semibold tabular-nums",
313
- showSystemEvents
314
- ? "bg-primary/15 text-primary ring-1 ring-primary/30"
315
- : "bg-muted text-muted-foreground ring-1 ring-border/70"
316
- )}
317
- data-testid="hidden-count-badge"
318
- >
319
- {hiddenCount}
320
- </span>
358
+ <span className="font-medium" data-testid="event-count">{eventCountLabel}</span>
359
+ <ChevronDown className={`h-3.5 w-3.5 transition-transform duration-200 ${showTimeline ? "rotate-180" : ""}`} />
321
360
  </button>
322
- )}
361
+ </div>
323
362
  </div>
324
363
 
325
364
  {/* Timeline body */}
326
365
  {showTimeline && visibleEvents.length > 0 && (
327
- <div className="mt-3">
366
+ <div className="mt-6">
328
367
  <TimelineActivity events={visibleEvents} variant="case-panel" />
329
368
  </div>
330
369
  )}
331
-
332
- {/* Footer hint */}
333
- {showTimeline && !showSystemEvents && sysEvtConfig?.hiddenHint && hasSystemNoise && (
334
- <p className="mt-2 text-[11px] text-muted-foreground/60 border-t border-dashed border-border pt-2" data-testid="timeline-footer-hint">
335
- {sysEvtConfig.hiddenHint}
336
- </p>
337
- )}
338
- {showTimeline && showSystemEvents && sysEvtConfig?.visibleHint && hasSystemNoise && (
339
- <p className="mt-2 text-[11px] text-muted-foreground/60 border-t border-dashed border-border pt-2" data-testid="timeline-footer-hint">
340
- {sysEvtConfig.visibleHint.replace("{count}", String(hiddenCount))}
341
- </p>
342
- )}
343
370
  </div>
344
371
  )
345
372
  }
@@ -583,6 +610,7 @@ export function DetailView({
583
610
  attentionCount={attentionCount}
584
611
  sysEvtConfig={sysEvtConfig}
585
612
  lastActivityTime={lastActivityTime}
613
+ isCasePanel={isCasePanelV2}
586
614
  />
587
615
  ) : null
588
616