@handled-ai/design-system 0.18.6 → 0.18.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.
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/data-table-filter.d.ts +8 -0
- package/dist/components/data-table-filter.js +8 -1
- package/dist/components/data-table-filter.js.map +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/timeline-activity.d.ts +16 -1
- package/dist/components/timeline-activity.js +69 -1
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/prototype/index.d.ts +1 -1
- package/dist/prototype/prototype-accounts-view.d.ts +1 -1
- package/dist/prototype/prototype-admin-view.d.ts +1 -1
- package/dist/prototype/prototype-config.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.d.ts +15 -3
- package/dist/prototype/prototype-inbox-view.js +156 -36
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +1 -1
- package/dist/prototype/prototype-shell.d.ts +1 -1
- package/dist/{signal-priority-popover-DWaAMhPI.d.ts → signal-priority-popover-BT6CPYNs.d.ts} +17 -1
- package/package.json +2 -1
- package/src/components/__tests__/data-table-filter.test.tsx +41 -0
- package/src/components/__tests__/timeline-activity.test.tsx +152 -0
- package/src/components/data-table-filter.tsx +17 -2
- package/src/components/timeline-activity.tsx +112 -1
- package/src/prototype/__tests__/detail-view-attention.test.tsx +2 -2
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +409 -0
- package/src/prototype/prototype-config.ts +21 -0
- package/src/prototype/prototype-inbox-view.tsx +227 -30
|
@@ -53,6 +53,7 @@ import type {
|
|
|
53
53
|
InboxDetailSections,
|
|
54
54
|
SignalScoreData,
|
|
55
55
|
BriefStyleVariant,
|
|
56
|
+
TimelineSystemEventsConfig,
|
|
56
57
|
} from "./prototype-config"
|
|
57
58
|
|
|
58
59
|
// ---------------------------------------------------------------------------
|
|
@@ -150,8 +151,151 @@ export interface DetailViewProps {
|
|
|
150
151
|
onRequestApproval?: () => Promise<void>
|
|
151
152
|
/** Number of important/attention-worthy events to highlight on the collapsed timeline header. */
|
|
152
153
|
attentionCount?: number
|
|
154
|
+
/** Configuration for the system-noise events toggle (score changes, etc.). */
|
|
155
|
+
timelineSystemEventsConfig?: TimelineSystemEventsConfig
|
|
156
|
+
|
|
157
|
+
// ── Deprecated individual props (use timelineSystemEventsConfig instead) ──
|
|
158
|
+
/** @deprecated Use `timelineSystemEventsConfig.toggleLabel`. */
|
|
159
|
+
timelineSystemEventsToggleLabel?: string
|
|
160
|
+
/** @deprecated Use `timelineSystemEventsConfig.storageKey`. */
|
|
161
|
+
timelineSystemEventsStorageKey?: string
|
|
162
|
+
/** @deprecated Use `timelineSystemEventsConfig.defaultVisible`. */
|
|
163
|
+
timelineSystemEventsDefaultVisible?: boolean
|
|
164
|
+
/** @deprecated Use `timelineSystemEventsConfig.hiddenHint`. */
|
|
165
|
+
timelineSystemEventsHiddenHint?: string
|
|
166
|
+
/** @deprecated Use `timelineSystemEventsConfig.visibleHint`. */
|
|
167
|
+
timelineSystemEventsVisibleHint?: string
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// TimelineSection — extracted from the IIFE in DetailView for readability
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
function TimelineSection({
|
|
175
|
+
timelineEvents,
|
|
176
|
+
showTimeline,
|
|
177
|
+
setShowTimeline,
|
|
178
|
+
showSystemEvents,
|
|
179
|
+
setShowSystemEvents,
|
|
180
|
+
attentionCount,
|
|
181
|
+
sysEvtConfig,
|
|
182
|
+
lastActivityTime,
|
|
183
|
+
}: {
|
|
184
|
+
timelineEvents: TimelineEvent[]
|
|
185
|
+
showTimeline: boolean
|
|
186
|
+
setShowTimeline: React.Dispatch<React.SetStateAction<boolean>>
|
|
187
|
+
showSystemEvents: boolean
|
|
188
|
+
setShowSystemEvents: React.Dispatch<React.SetStateAction<boolean>>
|
|
189
|
+
attentionCount?: number
|
|
190
|
+
sysEvtConfig?: TimelineSystemEventsConfig
|
|
191
|
+
lastActivityTime?: string
|
|
192
|
+
}) {
|
|
193
|
+
// Single-pass partition: compute visibleEvents and hiddenCount together
|
|
194
|
+
const visibleEvents: TimelineEvent[] = []
|
|
195
|
+
let hiddenCount = 0
|
|
196
|
+
for (const e of timelineEvents) {
|
|
197
|
+
if (e.isSystemNoise) hiddenCount++
|
|
198
|
+
if (!e.isSystemNoise || showSystemEvents) visibleEvents.push(e)
|
|
199
|
+
}
|
|
200
|
+
const hasSystemNoise = hiddenCount > 0
|
|
201
|
+
|
|
202
|
+
// The toggle renders whenever there are system-noise events — even if no
|
|
203
|
+
// config was provided — so consumers that emit `isSystemNoise: true` always
|
|
204
|
+
// give users a way to reveal those events.
|
|
205
|
+
const toggleLabel = sysEvtConfig?.toggleLabel ?? "System events"
|
|
206
|
+
|
|
207
|
+
// Derive "Last activity" from the first *visible* event so the collapsed
|
|
208
|
+
// header never points at a hidden score-update. The caller-supplied
|
|
209
|
+
// `lastActivityTime` is only used when system-noise filtering is NOT active
|
|
210
|
+
// (i.e. all events are visible) since it may come from an unfiltered source
|
|
211
|
+
// such as `case.last_activity_at`.
|
|
212
|
+
const firstVisibleTime =
|
|
213
|
+
(!hasSystemNoise || showSystemEvents) && lastActivityTime
|
|
214
|
+
? lastActivityTime
|
|
215
|
+
: visibleEvents.length > 0
|
|
216
|
+
? visibleEvents[0].time
|
|
217
|
+
: ""
|
|
218
|
+
|
|
219
|
+
const visibleCount = visibleEvents.length
|
|
220
|
+
const eventCountLabel = `${visibleCount} ${visibleCount === 1 ? "event" : "events"}`
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<div className="mb-8">
|
|
224
|
+
{/* Header — outer non-interactive container */}
|
|
225
|
+
<div
|
|
226
|
+
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"
|
|
227
|
+
data-testid="timeline-header"
|
|
228
|
+
>
|
|
229
|
+
{/* Left: collapse/expand button */}
|
|
230
|
+
<button
|
|
231
|
+
type="button"
|
|
232
|
+
onClick={() => setShowTimeline((prev) => !prev)}
|
|
233
|
+
className="flex items-center gap-2 cursor-pointer bg-transparent border-0 p-0"
|
|
234
|
+
data-testid="timeline-collapse-btn"
|
|
235
|
+
>
|
|
236
|
+
<h3 className="text-xs font-bold text-muted-foreground uppercase tracking-wider group-hover/timeline:text-foreground transition-colors">Activity timeline</h3>
|
|
237
|
+
{!showTimeline && attentionCount != null && attentionCount > 0 && (
|
|
238
|
+
<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">
|
|
239
|
+
{attentionCount} new
|
|
240
|
+
</span>
|
|
241
|
+
)}
|
|
242
|
+
{!showTimeline && firstVisibleTime && (
|
|
243
|
+
<span className="text-[11px] text-muted-foreground/60" data-testid="last-activity-hint">
|
|
244
|
+
· Last activity {firstVisibleTime}
|
|
245
|
+
</span>
|
|
246
|
+
)}
|
|
247
|
+
<div className="flex items-center gap-1.5">
|
|
248
|
+
<span className="text-[11px] font-medium text-muted-foreground" data-testid="event-count">{eventCountLabel}</span>
|
|
249
|
+
<ChevronDown className={`h-3.5 w-3.5 text-muted-foreground transition-transform duration-200 ${showTimeline ? "rotate-180" : ""}`} />
|
|
250
|
+
</div>
|
|
251
|
+
</button>
|
|
252
|
+
|
|
253
|
+
{/* Right: system-events toggle — always rendered when noise events exist */}
|
|
254
|
+
{hasSystemNoise && (
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
onClick={() => setShowSystemEvents((prev) => !prev)}
|
|
258
|
+
className="flex shrink-0 items-center gap-1.5 rounded-full border border-border bg-background px-2.5 py-1 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground cursor-pointer"
|
|
259
|
+
aria-pressed={showSystemEvents}
|
|
260
|
+
data-testid="system-events-toggle"
|
|
261
|
+
>
|
|
262
|
+
{toggleLabel}
|
|
263
|
+
<span
|
|
264
|
+
className="inline-flex items-center justify-center rounded-full bg-muted px-1.5 text-[10px] font-semibold min-w-[18px] tabular-nums"
|
|
265
|
+
data-testid="hidden-count-badge"
|
|
266
|
+
>
|
|
267
|
+
{hiddenCount}
|
|
268
|
+
</span>
|
|
269
|
+
</button>
|
|
270
|
+
)}
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{/* Timeline body */}
|
|
274
|
+
{showTimeline && visibleEvents.length > 0 && (
|
|
275
|
+
<div className="mt-3">
|
|
276
|
+
<TimelineActivity events={visibleEvents} />
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{/* Footer hint */}
|
|
281
|
+
{showTimeline && !showSystemEvents && sysEvtConfig?.hiddenHint && hasSystemNoise && (
|
|
282
|
+
<p className="mt-2 text-[11px] text-muted-foreground/60 border-t border-dashed border-border pt-2" data-testid="timeline-footer-hint">
|
|
283
|
+
{sysEvtConfig.hiddenHint}
|
|
284
|
+
</p>
|
|
285
|
+
)}
|
|
286
|
+
{showTimeline && showSystemEvents && sysEvtConfig?.visibleHint && hasSystemNoise && (
|
|
287
|
+
<p className="mt-2 text-[11px] text-muted-foreground/60 border-t border-dashed border-border pt-2" data-testid="timeline-footer-hint">
|
|
288
|
+
{sysEvtConfig.visibleHint.replace("{count}", String(hiddenCount))}
|
|
289
|
+
</p>
|
|
290
|
+
)}
|
|
291
|
+
</div>
|
|
292
|
+
)
|
|
153
293
|
}
|
|
154
294
|
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Detail View
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
|
|
155
299
|
export function DetailView({
|
|
156
300
|
item,
|
|
157
301
|
sections,
|
|
@@ -184,10 +328,79 @@ export function DetailView({
|
|
|
184
328
|
opportunityPreview,
|
|
185
329
|
onRequestApproval,
|
|
186
330
|
attentionCount,
|
|
331
|
+
timelineSystemEventsConfig: configProp,
|
|
332
|
+
timelineSystemEventsToggleLabel,
|
|
333
|
+
timelineSystemEventsStorageKey,
|
|
334
|
+
timelineSystemEventsDefaultVisible,
|
|
335
|
+
timelineSystemEventsHiddenHint,
|
|
336
|
+
timelineSystemEventsVisibleHint,
|
|
187
337
|
}: DetailViewProps) {
|
|
338
|
+
// Resolve system-events config: prefer the config object, fall back to deprecated individual props.
|
|
339
|
+
const sysEvtConfig = React.useMemo<TimelineSystemEventsConfig | undefined>(() => {
|
|
340
|
+
if (configProp) return configProp
|
|
341
|
+
// Build from deprecated individual props if any are provided
|
|
342
|
+
if (
|
|
343
|
+
timelineSystemEventsToggleLabel ||
|
|
344
|
+
timelineSystemEventsStorageKey ||
|
|
345
|
+
timelineSystemEventsDefaultVisible !== undefined ||
|
|
346
|
+
timelineSystemEventsHiddenHint ||
|
|
347
|
+
timelineSystemEventsVisibleHint
|
|
348
|
+
) {
|
|
349
|
+
return {
|
|
350
|
+
toggleLabel: timelineSystemEventsToggleLabel,
|
|
351
|
+
storageKey: timelineSystemEventsStorageKey,
|
|
352
|
+
defaultVisible: timelineSystemEventsDefaultVisible,
|
|
353
|
+
hiddenHint: timelineSystemEventsHiddenHint,
|
|
354
|
+
visibleHint: timelineSystemEventsVisibleHint,
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return undefined
|
|
358
|
+
}, [
|
|
359
|
+
configProp,
|
|
360
|
+
timelineSystemEventsToggleLabel,
|
|
361
|
+
timelineSystemEventsStorageKey,
|
|
362
|
+
timelineSystemEventsDefaultVisible,
|
|
363
|
+
timelineSystemEventsHiddenHint,
|
|
364
|
+
timelineSystemEventsVisibleHint,
|
|
365
|
+
])
|
|
366
|
+
|
|
188
367
|
const [showTimeline, setShowTimeline] = React.useState(false)
|
|
189
368
|
const [extraActions, setExtraActions] = React.useState<SuggestedAction[]>([])
|
|
190
369
|
|
|
370
|
+
// ---- System-noise toggle state ----
|
|
371
|
+
const sysEvtDefaultVisible = sysEvtConfig?.defaultVisible ?? false
|
|
372
|
+
const sysEvtStorageKey = sysEvtConfig?.storageKey
|
|
373
|
+
const [showSystemEvents, setShowSystemEvents] = React.useState(sysEvtDefaultVisible)
|
|
374
|
+
const initialReadDoneRef = React.useRef(false)
|
|
375
|
+
|
|
376
|
+
// Read persisted value from localStorage on mount
|
|
377
|
+
React.useEffect(() => {
|
|
378
|
+
if (!sysEvtStorageKey) {
|
|
379
|
+
initialReadDoneRef.current = true
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
const stored = localStorage.getItem(sysEvtStorageKey)
|
|
384
|
+
if (stored !== null) {
|
|
385
|
+
setShowSystemEvents(stored === "true")
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
// localStorage unavailable — ignore
|
|
389
|
+
}
|
|
390
|
+
initialReadDoneRef.current = true
|
|
391
|
+
}, [sysEvtStorageKey])
|
|
392
|
+
|
|
393
|
+
// Write to localStorage when the toggle changes (skip initial if matching default)
|
|
394
|
+
React.useEffect(() => {
|
|
395
|
+
if (!sysEvtStorageKey) return
|
|
396
|
+
if (!initialReadDoneRef.current) return
|
|
397
|
+
try {
|
|
398
|
+
localStorage.setItem(sysEvtStorageKey, String(showSystemEvents))
|
|
399
|
+
} catch {
|
|
400
|
+
// localStorage unavailable — ignore
|
|
401
|
+
}
|
|
402
|
+
}, [showSystemEvents, sysEvtStorageKey])
|
|
403
|
+
|
|
191
404
|
React.useEffect(() => {
|
|
192
405
|
setShowTimeline(false)
|
|
193
406
|
setExtraActions([])
|
|
@@ -355,36 +568,16 @@ export function DetailView({
|
|
|
355
568
|
|
|
356
569
|
{/* Activity Timeline */}
|
|
357
570
|
{sections.timeline && timelineEvents.length > 0 && (
|
|
358
|
-
<
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
{attentionCount} new
|
|
369
|
-
</span>
|
|
370
|
-
)}
|
|
371
|
-
{!showTimeline && (lastActivityTime || (timelineEvents.length > 0 && timelineEvents[0].time)) && (
|
|
372
|
-
<span className="text-[11px] text-muted-foreground/60">
|
|
373
|
-
· Last activity {lastActivityTime ?? timelineEvents[0]?.time ?? ''}
|
|
374
|
-
</span>
|
|
375
|
-
)}
|
|
376
|
-
</div>
|
|
377
|
-
<div className="flex items-center gap-1.5">
|
|
378
|
-
<span className="text-[11px] font-medium text-muted-foreground">{timelineEvents.length} events</span>
|
|
379
|
-
<ChevronDown className={`h-3.5 w-3.5 text-muted-foreground transition-transform duration-200 ${showTimeline ? "rotate-180" : ""}`} />
|
|
380
|
-
</div>
|
|
381
|
-
</button>
|
|
382
|
-
{showTimeline && (
|
|
383
|
-
<div className="mt-3">
|
|
384
|
-
<TimelineActivity events={timelineEvents} />
|
|
385
|
-
</div>
|
|
386
|
-
)}
|
|
387
|
-
</div>
|
|
571
|
+
<TimelineSection
|
|
572
|
+
timelineEvents={timelineEvents}
|
|
573
|
+
showTimeline={showTimeline}
|
|
574
|
+
setShowTimeline={setShowTimeline}
|
|
575
|
+
showSystemEvents={showSystemEvents}
|
|
576
|
+
setShowSystemEvents={setShowSystemEvents}
|
|
577
|
+
attentionCount={attentionCount}
|
|
578
|
+
sysEvtConfig={sysEvtConfig}
|
|
579
|
+
lastActivityTime={lastActivityTime}
|
|
580
|
+
/>
|
|
388
581
|
)}
|
|
389
582
|
</div>
|
|
390
583
|
|
|
@@ -453,6 +646,8 @@ export function PrototypeInboxView({
|
|
|
453
646
|
renderBeforeScore,
|
|
454
647
|
renderAfterScore,
|
|
455
648
|
lastActivityTime,
|
|
649
|
+
timelineSystemEventsConfig,
|
|
650
|
+
attentionCount,
|
|
456
651
|
renderTitleExtra,
|
|
457
652
|
renderTitleSubtext,
|
|
458
653
|
sortOptions,
|
|
@@ -693,6 +888,8 @@ export function PrototypeInboxView({
|
|
|
693
888
|
renderBeforeScore,
|
|
694
889
|
renderAfterScore,
|
|
695
890
|
lastActivityTime,
|
|
891
|
+
timelineSystemEventsConfig,
|
|
892
|
+
attentionCount,
|
|
696
893
|
renderTitleExtra,
|
|
697
894
|
renderTitleSubtext,
|
|
698
895
|
onOpenSignalBucket,
|