@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.
- package/dist/components/comment-composer.js +69 -49
- package/dist/components/comment-composer.js.map +1 -1
- package/dist/components/email-body.js +1 -39
- package/dist/components/email-body.js.map +1 -1
- package/dist/prototype/prototype-inbox-view.js +107 -62
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/comment-composer.test.tsx +7 -3
- package/src/components/__tests__/email-body.test.tsx +0 -32
- package/src/components/comment-composer.tsx +26 -14
- package/src/components/email-body.tsx +1 -46
- package/src/prototype/__tests__/detail-view-case-panel-v2.test.tsx +3 -3
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +17 -14
- package/src/prototype/prototype-inbox-view.tsx +72 -44
|
@@ -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(
|
|
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(
|
|
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(
|
|
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-[
|
|
212
|
+
expect(badge).toHaveClass("min-w-[22px]")
|
|
213
213
|
})
|
|
214
214
|
|
|
215
|
-
it("calls localStorage.setItem when toggle changes and shows
|
|
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
|
|
225
|
-
expect(toggle
|
|
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("
|
|
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).
|
|
322
|
-
expect(
|
|
323
|
-
expect(
|
|
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("
|
|
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).
|
|
336
|
-
expect(
|
|
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
|
-
//
|
|
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).
|
|
421
|
-
expect(
|
|
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
|
|
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=
|
|
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
|
|
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
|
|
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
|
· 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
|
|
296
|
-
|
|
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={() =>
|
|
351
|
+
onClick={() => setShowTimeline((prev) => !prev)}
|
|
300
352
|
className={cn(
|
|
301
|
-
"flex shrink-0 cursor-pointer items-center
|
|
302
|
-
|
|
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-
|
|
307
|
-
data-testid="system-events-toggle"
|
|
356
|
+
aria-label={showTimeline ? "Collapse activity timeline" : "Expand activity timeline"}
|
|
308
357
|
>
|
|
309
|
-
{
|
|
310
|
-
<
|
|
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-
|
|
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
|
|