@handled-ai/design-system 0.18.25 → 0.18.27
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/case-panel-activity-timeline.js +5 -5
- package/dist/components/case-panel-activity-timeline.js.map +1 -1
- package/dist/components/case-panel-detail.d.ts +1 -1
- package/dist/components/case-panel-detail.js +3 -2
- package/dist/components/case-panel-detail.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/case-panel-activity-timeline.test.tsx +14 -1
- package/src/components/__tests__/case-panel-detail.test.tsx +14 -3
- package/src/components/case-panel-activity-timeline.tsx +2 -2
- package/src/components/case-panel-detail.tsx +4 -3
|
@@ -155,26 +155,26 @@ function PayloadCard({
|
|
|
155
155
|
] });
|
|
156
156
|
}
|
|
157
157
|
function renderPayloadContent(payload, eventId, onPayloadAction) {
|
|
158
|
-
var _a, _b
|
|
158
|
+
var _a, _b;
|
|
159
159
|
switch (payload.kind) {
|
|
160
160
|
case "signal":
|
|
161
161
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
162
162
|
/* @__PURE__ */ jsx("p", { className: "text-foreground", children: payload.summary }),
|
|
163
163
|
payload.detail ? /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: payload.detail }) : null,
|
|
164
|
-
onPayloadAction ? /* @__PURE__ */ jsx(
|
|
164
|
+
onPayloadAction && payload.actionLabel ? /* @__PURE__ */ jsx(
|
|
165
165
|
"button",
|
|
166
166
|
{
|
|
167
167
|
type: "button",
|
|
168
168
|
className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground",
|
|
169
169
|
onClick: () => onPayloadAction({ kind: "openSignal", key: payload.key, eventId }),
|
|
170
|
-
children:
|
|
170
|
+
children: payload.actionLabel
|
|
171
171
|
}
|
|
172
172
|
) : null
|
|
173
173
|
] });
|
|
174
174
|
case "scoreUpdate":
|
|
175
175
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
176
176
|
/* @__PURE__ */ jsxs("p", { className: "text-foreground", children: [
|
|
177
|
-
(
|
|
177
|
+
(_a = payload.label) != null ? _a : "Score",
|
|
178
178
|
": ",
|
|
179
179
|
payload.previousScore !== void 0 ? `${payload.previousScore} \u2192 ` : "",
|
|
180
180
|
payload.nextScore
|
|
@@ -216,7 +216,7 @@ function renderPayloadContent(payload, eventId, onPayloadAction) {
|
|
|
216
216
|
rel: "noreferrer noopener",
|
|
217
217
|
className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground",
|
|
218
218
|
children: [
|
|
219
|
-
(
|
|
219
|
+
(_b = payload.deepLink.label) != null ? _b : "Open in Salesforce",
|
|
220
220
|
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
|
|
221
221
|
]
|
|
222
222
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/case-panel-activity-timeline.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDown, ExternalLink } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\nimport { TONE_CLASSES, type TimelineEventTone } from \"./timeline-activity\"\n\nexport type CasePanelActivityTone = TimelineEventTone\n\nexport type CasePanelActivityActor =\n | { kind: \"system\" }\n | { kind: \"integration\"; name: string; iconUrl?: string }\n | { kind: \"user\"; name: string; avatarUrl?: string; verb?: string }\n\nexport type CasePanelPayloadAction = {\n kind: \"openSignal\"\n key: string\n eventId: string\n}\n\nexport type CasePanelActivityPayload =\n | {\n kind: \"signal\"\n key: string\n summary: string\n detail?: string\n actionLabel?: string\n }\n | {\n kind: \"scoreUpdate\"\n label?: string\n previousScore?: number\n nextScore: number\n reason?: string\n }\n | {\n kind: \"recommendation\"\n recommendation: string\n rationale?: string\n actionLabel?: string\n }\n | {\n kind: \"email\"\n from: string\n to?: string\n subject: string\n preview?: string\n body?: string\n }\n | {\n kind: \"salesforce\"\n objectLabel: string\n recordLabel?: string\n changeSummary: string\n deepLink?: {\n href: string\n label?: string\n }\n }\n | {\n kind: \"deadline\"\n dueLabel: string\n status?: \"upcoming\" | \"due\" | \"overdue\" | \"met\"\n description?: string\n }\n | {\n kind: \"operatorNote\"\n note: string\n }\n | {\n kind: \"assignment\"\n assignee: string\n from?: string\n role?: string\n }\n | {\n kind: \"caseOpened\"\n source?: string\n openedBy?: string\n description?: string\n }\n | {\n kind: \"generic\"\n description: string\n metadata?: Array<{ label: string; value: string }>\n }\n\nexport interface CasePanelActivityEvent {\n id: string\n title: string\n timeLabel: string\n tone: CasePanelActivityTone\n actor?: CasePanelActivityActor\n isSystemNoise?: boolean\n payload: CasePanelActivityPayload\n}\n\nexport interface CasePanelActivityTimelineProps {\n events: CasePanelActivityEvent[]\n className?: string\n title?: string\n defaultExpanded?: boolean\n defaultShowSystemEvents?: boolean\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n}\n\nconst NEUTRAL_DOT_CLASSES = \"bg-background border-border/60\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\nconst PAYLOAD_LABELS: Record<CasePanelActivityPayload[\"kind\"], string> = {\n signal: \"Signal\",\n scoreUpdate: \"Score update\",\n recommendation: \"Recommendation\",\n email: \"Email\",\n salesforce: \"Salesforce\",\n deadline: \"Deadline\",\n operatorNote: \"Operator note\",\n assignment: \"Assignment\",\n caseOpened: \"Case opened\",\n generic: \"Update\",\n}\n\nconst STATUS_CLASSES: Record<NonNullable<Extract<CasePanelActivityPayload, { kind: \"deadline\" }>[\"status\"]>, string> = {\n upcoming: \"border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-900/40 dark:bg-blue-950/30 dark:text-blue-300\",\n due: \"border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/40 dark:bg-amber-950/30 dark:text-amber-300\",\n overdue: \"border-red-200 bg-red-50 text-red-700 dark:border-red-900/40 dark:bg-red-950/30 dark:text-red-300\",\n met: \"border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/40 dark:bg-emerald-950/30 dark:text-emerald-300\",\n}\n\nexport function CasePanelActivityTimeline({\n events,\n className,\n title = \"Activity\",\n defaultExpanded = false,\n defaultShowSystemEvents = false,\n onPayloadAction,\n}: CasePanelActivityTimelineProps) {\n const [expanded, setExpanded] = React.useState(defaultExpanded)\n const [showSystemEvents, setShowSystemEvents] = React.useState(defaultShowSystemEvents)\n\n const nonSystemEvents = events.filter((event) => !event.isSystemNoise)\n const visibleEvents = showSystemEvents ? events : nonSystemEvents\n const systemNoiseCount = events.length - nonSystemEvents.length\n const lastActivityText = nonSystemEvents[0]?.timeLabel ?? events[0]?.timeLabel ?? \"No activity yet\"\n\n return (\n <section className={cn(\"rounded-xl border border-border bg-card text-card-foreground shadow-sm\", className)}>\n <button\n type=\"button\"\n className=\"flex w-full items-center justify-between gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/30\"\n aria-expanded={expanded}\n onClick={() => setExpanded((current) => !current)}\n >\n <span className=\"min-w-0\">\n <span className=\"flex items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">{title}</span>\n <span className=\"rounded-full border border-border bg-muted/40 px-2 py-0.5 text-[11px] font-medium text-muted-foreground\">\n {nonSystemEvents.length} {nonSystemEvents.length === 1 ? \"event\" : \"events\"}\n </span>\n </span>\n <span className=\"mt-1 block truncate text-xs text-muted-foreground\">Last activity {lastActivityText}</span>\n </span>\n <ChevronDown className={cn(\"h-4 w-4 shrink-0 text-muted-foreground transition-transform\", expanded && \"rotate-180\")} />\n </button>\n\n {expanded ? (\n <div className=\"border-t border-border px-4 py-4\">\n {systemNoiseCount > 0 ? (\n <label className=\"mb-4 flex items-center justify-between gap-3 rounded-lg border border-border/70 bg-muted/20 px-3 py-2 text-xs text-muted-foreground\">\n <span>Show system events</span>\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border-border text-primary accent-current\"\n checked={showSystemEvents}\n onChange={(event) => setShowSystemEvents(event.target.checked)}\n />\n </label>\n ) : null}\n\n {visibleEvents.length > 0 ? (\n <div className=\"space-y-0\">\n {visibleEvents.map((event, index) => (\n <CasePanelActivityTimelineItem\n key={event.id}\n event={event}\n isLast={index === visibleEvents.length - 1}\n onPayloadAction={onPayloadAction}\n />\n ))}\n </div>\n ) : (\n <p className=\"rounded-lg border border-dashed border-border px-3 py-6 text-center text-sm text-muted-foreground\">\n No activity to show.\n </p>\n )}\n </div>\n ) : null}\n </section>\n )\n}\n\nfunction CasePanelActivityTimelineItem({\n event,\n isLast,\n onPayloadAction,\n}: {\n event: CasePanelActivityEvent\n isLast: boolean\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n}) {\n const toneStyle = TONE_CLASSES[event.tone]\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <article className=\"group relative flex gap-3.5\" data-testid=\"case-panel-activity-event\">\n {!isLast ? <div className=\"absolute bottom-[-6px] left-[9px] top-5 w-px bg-border/60\" /> : null}\n <div className=\"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-card\">\n <span\n aria-hidden=\"true\"\n className={cn(\"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-card\", dotClasses, iconClasses)}\n data-testid=\"case-panel-activity-dot\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-current\" />\n </span>\n </div>\n <div className=\"min-w-0 flex-1 pb-5 pt-0.5\">\n <div className=\"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\">\n <h3 className=\"pr-4 text-[13px] font-medium leading-relaxed text-foreground\">{event.title}</h3>\n <time className=\"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\">{event.timeLabel}</time>\n </div>\n <ActorByline actor={event.actor} timeLabel={event.timeLabel} />\n <PayloadCard event={event} onPayloadAction={onPayloadAction} />\n </div>\n </article>\n )\n}\n\nfunction ActorByline({ actor, timeLabel }: { actor?: CasePanelActivityActor; timeLabel: string }) {\n if (!actor || actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"case-panel-activity-byline\">\n {actor.iconUrl ? <img src={actor.iconUrl} alt=\"\" className=\"h-4 w-4 rounded-sm object-cover\" /> : null}\n <span className=\"font-medium text-foreground\">{actor.name}</span>\n <span>synced this update</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{timeLabel}</span>\n </div>\n )\n }\n\n const verb = actor.verb ?? \"updated this case\"\n const initial = actor.name.charAt(0).toUpperCase()\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"case-panel-activity-byline\">\n {actor.avatarUrl ? (\n <img src={actor.avatarUrl} alt={actor.name} className=\"h-4 w-4 rounded-full object-cover\" />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {initial}\n </span>\n )}\n <span className=\"font-medium text-foreground\">{actor.name}</span>\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{timeLabel}</span>\n </div>\n )\n}\n\nfunction PayloadCard({\n event,\n onPayloadAction,\n}: {\n event: CasePanelActivityEvent\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n}) {\n const payload = event.payload\n\n return (\n <div className=\"mt-2 rounded-lg border border-border/80 bg-muted/20 px-3 py-2.5 text-sm\" data-testid={`case-panel-payload-${payload.kind}`}>\n <div className=\"mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground\">\n {PAYLOAD_LABELS[payload.kind]}\n </div>\n {renderPayloadContent(payload, event.id, onPayloadAction)}\n </div>\n )\n}\n\nfunction renderPayloadContent(\n payload: CasePanelActivityPayload,\n eventId: string,\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n) {\n switch (payload.kind) {\n case \"signal\":\n return (\n <div className=\"space-y-2\">\n <p className=\"text-foreground\">{payload.summary}</p>\n {payload.detail ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.detail}</p> : null}\n {onPayloadAction ? (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n onClick={() => onPayloadAction({ kind: \"openSignal\", key: payload.key, eventId })}\n >\n {payload.actionLabel ?? \"Open signal\"}\n </button>\n ) : null}\n </div>\n )\n case \"scoreUpdate\":\n return (\n <div className=\"space-y-1\">\n <p className=\"text-foreground\">\n {payload.label ?? \"Score\"}: {payload.previousScore !== undefined ? `${payload.previousScore} → ` : \"\"}{payload.nextScore}\n </p>\n {payload.reason ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.reason}</p> : null}\n </div>\n )\n case \"recommendation\":\n return (\n <div className=\"space-y-1\">\n <p className=\"font-medium text-foreground\">{payload.recommendation}</p>\n {payload.rationale ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.rationale}</p> : null}\n {payload.actionLabel ? <p className=\"text-xs font-medium text-muted-foreground\">{payload.actionLabel}</p> : null}\n </div>\n )\n case \"email\":\n return (\n <div className=\"space-y-1\">\n <p className=\"font-medium text-foreground\">{payload.subject}</p>\n <p className=\"text-xs text-muted-foreground\">\n From {payload.from}{payload.to ? ` to ${payload.to}` : \"\"}\n </p>\n {payload.preview ? <p className=\"text-sm text-foreground/90\">{payload.preview}</p> : null}\n {payload.body ? <p className=\"whitespace-pre-line text-xs leading-relaxed text-muted-foreground\">{payload.body}</p> : null}\n </div>\n )\n case \"salesforce\":\n return (\n <div className=\"space-y-2\">\n <p className=\"text-foreground\">\n <span className=\"font-medium\">{payload.objectLabel}</span>\n {payload.recordLabel ? <span className=\"text-muted-foreground\"> · {payload.recordLabel}</span> : null}\n </p>\n <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.changeSummary}</p>\n {payload.deepLink ? (\n <a\n href={payload.deepLink.href}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n >\n {payload.deepLink.label ?? \"Open in Salesforce\"}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n ) : null}\n </div>\n )\n case \"deadline\":\n return (\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium text-foreground\">{payload.dueLabel}</span>\n {payload.status ? (\n <span className={cn(\"rounded-full border px-2 py-0.5 text-[11px] font-medium\", STATUS_CLASSES[payload.status])}>\n {payload.status}\n </span>\n ) : null}\n </div>\n {payload.description ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.description}</p> : null}\n </div>\n )\n case \"operatorNote\":\n return <p className=\"whitespace-pre-line text-sm leading-relaxed text-foreground\">{payload.note}</p>\n case \"assignment\":\n return (\n <p className=\"text-foreground\">\n Assigned to <span className=\"font-medium\">{payload.assignee}</span>\n {payload.role ? <span className=\"text-muted-foreground\"> as {payload.role}</span> : null}\n {payload.from ? <span className=\"text-muted-foreground\"> from {payload.from}</span> : null}\n </p>\n )\n case \"caseOpened\":\n return (\n <div className=\"space-y-1\">\n <p className=\"text-foreground\">\n Case opened{payload.openedBy ? ` by ${payload.openedBy}` : \"\"}{payload.source ? ` from ${payload.source}` : \"\"}\n </p>\n {payload.description ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.description}</p> : null}\n </div>\n )\n case \"generic\":\n return (\n <div className=\"space-y-2\">\n <p className=\"text-foreground\">{payload.description}</p>\n {payload.metadata && payload.metadata.length > 0 ? (\n <dl className=\"grid gap-1 text-xs text-muted-foreground\">\n {payload.metadata.map((item) => (\n <div key={`${item.label}-${item.value}`} className=\"flex gap-2\">\n <dt className=\"font-medium text-foreground\">{item.label}</dt>\n <dd>{item.value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n )\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/case-panel-activity-timeline.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronDown, ExternalLink } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\nimport { TONE_CLASSES, type TimelineEventTone } from \"./timeline-activity\"\n\nexport type CasePanelActivityTone = TimelineEventTone\n\nexport type CasePanelActivityActor =\n | { kind: \"system\" }\n | { kind: \"integration\"; name: string; iconUrl?: string }\n | { kind: \"user\"; name: string; avatarUrl?: string; verb?: string }\n\nexport type CasePanelPayloadAction = {\n kind: \"openSignal\"\n key: string\n eventId: string\n}\n\nexport type CasePanelActivityPayload =\n | {\n kind: \"signal\"\n key: string\n summary: string\n detail?: string\n actionLabel?: string\n }\n | {\n kind: \"scoreUpdate\"\n label?: string\n previousScore?: number\n nextScore: number\n reason?: string\n }\n | {\n kind: \"recommendation\"\n recommendation: string\n rationale?: string\n actionLabel?: string\n }\n | {\n kind: \"email\"\n from: string\n to?: string\n subject: string\n preview?: string\n body?: string\n }\n | {\n kind: \"salesforce\"\n objectLabel: string\n recordLabel?: string\n changeSummary: string\n deepLink?: {\n href: string\n label?: string\n }\n }\n | {\n kind: \"deadline\"\n dueLabel: string\n status?: \"upcoming\" | \"due\" | \"overdue\" | \"met\"\n description?: string\n }\n | {\n kind: \"operatorNote\"\n note: string\n }\n | {\n kind: \"assignment\"\n assignee: string\n from?: string\n role?: string\n }\n | {\n kind: \"caseOpened\"\n source?: string\n openedBy?: string\n description?: string\n }\n | {\n kind: \"generic\"\n description: string\n metadata?: Array<{ label: string; value: string }>\n }\n\nexport interface CasePanelActivityEvent {\n id: string\n title: string\n timeLabel: string\n tone: CasePanelActivityTone\n actor?: CasePanelActivityActor\n isSystemNoise?: boolean\n payload: CasePanelActivityPayload\n}\n\nexport interface CasePanelActivityTimelineProps {\n events: CasePanelActivityEvent[]\n className?: string\n title?: string\n defaultExpanded?: boolean\n defaultShowSystemEvents?: boolean\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n}\n\nconst NEUTRAL_DOT_CLASSES = \"bg-background border-border/60\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\nconst PAYLOAD_LABELS: Record<CasePanelActivityPayload[\"kind\"], string> = {\n signal: \"Signal\",\n scoreUpdate: \"Score update\",\n recommendation: \"Recommendation\",\n email: \"Email\",\n salesforce: \"Salesforce\",\n deadline: \"Deadline\",\n operatorNote: \"Operator note\",\n assignment: \"Assignment\",\n caseOpened: \"Case opened\",\n generic: \"Update\",\n}\n\nconst STATUS_CLASSES: Record<NonNullable<Extract<CasePanelActivityPayload, { kind: \"deadline\" }>[\"status\"]>, string> = {\n upcoming: \"border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-900/40 dark:bg-blue-950/30 dark:text-blue-300\",\n due: \"border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/40 dark:bg-amber-950/30 dark:text-amber-300\",\n overdue: \"border-red-200 bg-red-50 text-red-700 dark:border-red-900/40 dark:bg-red-950/30 dark:text-red-300\",\n met: \"border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/40 dark:bg-emerald-950/30 dark:text-emerald-300\",\n}\n\nexport function CasePanelActivityTimeline({\n events,\n className,\n title = \"Activity\",\n defaultExpanded = false,\n defaultShowSystemEvents = false,\n onPayloadAction,\n}: CasePanelActivityTimelineProps) {\n const [expanded, setExpanded] = React.useState(defaultExpanded)\n const [showSystemEvents, setShowSystemEvents] = React.useState(defaultShowSystemEvents)\n\n const nonSystemEvents = events.filter((event) => !event.isSystemNoise)\n const visibleEvents = showSystemEvents ? events : nonSystemEvents\n const systemNoiseCount = events.length - nonSystemEvents.length\n const lastActivityText = nonSystemEvents[0]?.timeLabel ?? events[0]?.timeLabel ?? \"No activity yet\"\n\n return (\n <section className={cn(\"rounded-xl border border-border bg-card text-card-foreground shadow-sm\", className)}>\n <button\n type=\"button\"\n className=\"flex w-full items-center justify-between gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/30\"\n aria-expanded={expanded}\n onClick={() => setExpanded((current) => !current)}\n >\n <span className=\"min-w-0\">\n <span className=\"flex items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">{title}</span>\n <span className=\"rounded-full border border-border bg-muted/40 px-2 py-0.5 text-[11px] font-medium text-muted-foreground\">\n {nonSystemEvents.length} {nonSystemEvents.length === 1 ? \"event\" : \"events\"}\n </span>\n </span>\n <span className=\"mt-1 block truncate text-xs text-muted-foreground\">Last activity {lastActivityText}</span>\n </span>\n <ChevronDown className={cn(\"h-4 w-4 shrink-0 text-muted-foreground transition-transform\", expanded && \"rotate-180\")} />\n </button>\n\n {expanded ? (\n <div className=\"border-t border-border px-4 py-4\">\n {systemNoiseCount > 0 ? (\n <label className=\"mb-4 flex items-center justify-between gap-3 rounded-lg border border-border/70 bg-muted/20 px-3 py-2 text-xs text-muted-foreground\">\n <span>Show system events</span>\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border-border text-primary accent-current\"\n checked={showSystemEvents}\n onChange={(event) => setShowSystemEvents(event.target.checked)}\n />\n </label>\n ) : null}\n\n {visibleEvents.length > 0 ? (\n <div className=\"space-y-0\">\n {visibleEvents.map((event, index) => (\n <CasePanelActivityTimelineItem\n key={event.id}\n event={event}\n isLast={index === visibleEvents.length - 1}\n onPayloadAction={onPayloadAction}\n />\n ))}\n </div>\n ) : (\n <p className=\"rounded-lg border border-dashed border-border px-3 py-6 text-center text-sm text-muted-foreground\">\n No activity to show.\n </p>\n )}\n </div>\n ) : null}\n </section>\n )\n}\n\nfunction CasePanelActivityTimelineItem({\n event,\n isLast,\n onPayloadAction,\n}: {\n event: CasePanelActivityEvent\n isLast: boolean\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n}) {\n const toneStyle = TONE_CLASSES[event.tone]\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <article className=\"group relative flex gap-3.5\" data-testid=\"case-panel-activity-event\">\n {!isLast ? <div className=\"absolute bottom-[-6px] left-[9px] top-5 w-px bg-border/60\" /> : null}\n <div className=\"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-card\">\n <span\n aria-hidden=\"true\"\n className={cn(\"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-card\", dotClasses, iconClasses)}\n data-testid=\"case-panel-activity-dot\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-current\" />\n </span>\n </div>\n <div className=\"min-w-0 flex-1 pb-5 pt-0.5\">\n <div className=\"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\">\n <h3 className=\"pr-4 text-[13px] font-medium leading-relaxed text-foreground\">{event.title}</h3>\n <time className=\"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\">{event.timeLabel}</time>\n </div>\n <ActorByline actor={event.actor} timeLabel={event.timeLabel} />\n <PayloadCard event={event} onPayloadAction={onPayloadAction} />\n </div>\n </article>\n )\n}\n\nfunction ActorByline({ actor, timeLabel }: { actor?: CasePanelActivityActor; timeLabel: string }) {\n if (!actor || actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"case-panel-activity-byline\">\n {actor.iconUrl ? <img src={actor.iconUrl} alt=\"\" className=\"h-4 w-4 rounded-sm object-cover\" /> : null}\n <span className=\"font-medium text-foreground\">{actor.name}</span>\n <span>synced this update</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{timeLabel}</span>\n </div>\n )\n }\n\n const verb = actor.verb ?? \"updated this case\"\n const initial = actor.name.charAt(0).toUpperCase()\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"case-panel-activity-byline\">\n {actor.avatarUrl ? (\n <img src={actor.avatarUrl} alt={actor.name} className=\"h-4 w-4 rounded-full object-cover\" />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {initial}\n </span>\n )}\n <span className=\"font-medium text-foreground\">{actor.name}</span>\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">·</span>\n <span>{timeLabel}</span>\n </div>\n )\n}\n\nfunction PayloadCard({\n event,\n onPayloadAction,\n}: {\n event: CasePanelActivityEvent\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n}) {\n const payload = event.payload\n\n return (\n <div className=\"mt-2 rounded-lg border border-border/80 bg-muted/20 px-3 py-2.5 text-sm\" data-testid={`case-panel-payload-${payload.kind}`}>\n <div className=\"mb-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground\">\n {PAYLOAD_LABELS[payload.kind]}\n </div>\n {renderPayloadContent(payload, event.id, onPayloadAction)}\n </div>\n )\n}\n\nfunction renderPayloadContent(\n payload: CasePanelActivityPayload,\n eventId: string,\n onPayloadAction?: (action: CasePanelPayloadAction) => void\n) {\n switch (payload.kind) {\n case \"signal\":\n return (\n <div className=\"space-y-2\">\n <p className=\"text-foreground\">{payload.summary}</p>\n {payload.detail ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.detail}</p> : null}\n {onPayloadAction && payload.actionLabel ? (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n onClick={() => onPayloadAction({ kind: \"openSignal\", key: payload.key, eventId })}\n >\n {payload.actionLabel}\n </button>\n ) : null}\n </div>\n )\n case \"scoreUpdate\":\n return (\n <div className=\"space-y-1\">\n <p className=\"text-foreground\">\n {payload.label ?? \"Score\"}: {payload.previousScore !== undefined ? `${payload.previousScore} → ` : \"\"}{payload.nextScore}\n </p>\n {payload.reason ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.reason}</p> : null}\n </div>\n )\n case \"recommendation\":\n return (\n <div className=\"space-y-1\">\n <p className=\"font-medium text-foreground\">{payload.recommendation}</p>\n {payload.rationale ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.rationale}</p> : null}\n {payload.actionLabel ? <p className=\"text-xs font-medium text-muted-foreground\">{payload.actionLabel}</p> : null}\n </div>\n )\n case \"email\":\n return (\n <div className=\"space-y-1\">\n <p className=\"font-medium text-foreground\">{payload.subject}</p>\n <p className=\"text-xs text-muted-foreground\">\n From {payload.from}{payload.to ? ` to ${payload.to}` : \"\"}\n </p>\n {payload.preview ? <p className=\"text-sm text-foreground/90\">{payload.preview}</p> : null}\n {payload.body ? <p className=\"whitespace-pre-line text-xs leading-relaxed text-muted-foreground\">{payload.body}</p> : null}\n </div>\n )\n case \"salesforce\":\n return (\n <div className=\"space-y-2\">\n <p className=\"text-foreground\">\n <span className=\"font-medium\">{payload.objectLabel}</span>\n {payload.recordLabel ? <span className=\"text-muted-foreground\"> · {payload.recordLabel}</span> : null}\n </p>\n <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.changeSummary}</p>\n {payload.deepLink ? (\n <a\n href={payload.deepLink.href}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n >\n {payload.deepLink.label ?? \"Open in Salesforce\"}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n ) : null}\n </div>\n )\n case \"deadline\":\n return (\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium text-foreground\">{payload.dueLabel}</span>\n {payload.status ? (\n <span className={cn(\"rounded-full border px-2 py-0.5 text-[11px] font-medium\", STATUS_CLASSES[payload.status])}>\n {payload.status}\n </span>\n ) : null}\n </div>\n {payload.description ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.description}</p> : null}\n </div>\n )\n case \"operatorNote\":\n return <p className=\"whitespace-pre-line text-sm leading-relaxed text-foreground\">{payload.note}</p>\n case \"assignment\":\n return (\n <p className=\"text-foreground\">\n Assigned to <span className=\"font-medium\">{payload.assignee}</span>\n {payload.role ? <span className=\"text-muted-foreground\"> as {payload.role}</span> : null}\n {payload.from ? <span className=\"text-muted-foreground\"> from {payload.from}</span> : null}\n </p>\n )\n case \"caseOpened\":\n return (\n <div className=\"space-y-1\">\n <p className=\"text-foreground\">\n Case opened{payload.openedBy ? ` by ${payload.openedBy}` : \"\"}{payload.source ? ` from ${payload.source}` : \"\"}\n </p>\n {payload.description ? <p className=\"text-xs leading-relaxed text-muted-foreground\">{payload.description}</p> : null}\n </div>\n )\n case \"generic\":\n return (\n <div className=\"space-y-2\">\n <p className=\"text-foreground\">{payload.description}</p>\n {payload.metadata && payload.metadata.length > 0 ? (\n <dl className=\"grid gap-1 text-xs text-muted-foreground\">\n {payload.metadata.map((item) => (\n <div key={`${item.label}-${item.value}`} className=\"flex gap-2\">\n <dt className=\"font-medium text-foreground\">{item.label}</dt>\n <dd>{item.value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n )\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
type CasePanelDetailWidth = "comfortable" | "modest" | "full";
|
|
3
|
+
type CasePanelDetailWidth = "comfortable" | "modest" | "compact" | "full";
|
|
4
4
|
interface CasePanelDetailProps {
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
/** Reading measure for the detail content column. Defaults to the handoff's comfortable 880px measure. */
|
|
@@ -13,6 +13,7 @@ import { cn } from "../lib/utils.js";
|
|
|
13
13
|
const detailWidthClasses = {
|
|
14
14
|
comfortable: "mx-auto w-full max-w-[880px] px-10 pb-[120px] pt-8",
|
|
15
15
|
modest: "mx-auto w-full max-w-[720px] px-8 pb-[112px] pt-7",
|
|
16
|
+
compact: "mx-auto w-full max-w-[640px] px-10 pb-[112px] pt-7",
|
|
16
17
|
full: "w-full px-8 pb-[120px] pt-8"
|
|
17
18
|
};
|
|
18
19
|
function CasePanelDetail({
|
|
@@ -29,9 +30,9 @@ function CasePanelHeader({
|
|
|
29
30
|
children,
|
|
30
31
|
className
|
|
31
32
|
}) {
|
|
32
|
-
return /* @__PURE__ */ jsx("header", { className: cn("mb-7", className), children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-
|
|
33
|
+
return /* @__PURE__ */ jsx("header", { className: cn("mb-7", className), children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-6", children: [
|
|
33
34
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
34
|
-
/* @__PURE__ */ jsx("h1", { className: "m-0 text-[27px] font-bold leading-[1.18] tracking-[-0.02em] text-foreground
|
|
35
|
+
/* @__PURE__ */ jsx("h1", { className: "m-0 max-w-[560px] text-[27px] font-bold leading-[1.18] tracking-[-0.02em] text-foreground", children: title }),
|
|
35
36
|
children
|
|
36
37
|
] }),
|
|
37
38
|
action ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: action }) : null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/case-panel-detail.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ArrowUpRight,\n Check,\n Copy,\n ExternalLink,\n} from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\nexport type CasePanelDetailWidth = \"comfortable\" | \"modest\" | \"full\"\n\nexport interface CasePanelDetailProps {\n children: React.ReactNode\n /** Reading measure for the detail content column. Defaults to the handoff's comfortable 880px measure. */\n width?: CasePanelDetailWidth\n /** Accessible label for the detail region. */\n \"aria-label\"?: string\n className?: string\n}\n\nconst detailWidthClasses: Record<CasePanelDetailWidth, string> = {\n comfortable: \"mx-auto w-full max-w-[880px] px-10 pb-[120px] pt-8\",\n modest: \"mx-auto w-full max-w-[720px] px-8 pb-[112px] pt-7\",\n full: \"w-full px-8 pb-[120px] pt-8\",\n}\n\nexport function CasePanelDetail({\n children,\n width = \"comfortable\",\n \"aria-label\": ariaLabel = \"Case detail\",\n className,\n}: CasePanelDetailProps) {\n return (\n <section aria-label={ariaLabel} className={cn(\"min-w-0 bg-background\", className)}>\n <div className={detailWidthClasses[width]}>{children}</div>\n </section>\n )\n}\n\nexport interface CasePanelHeaderProps {\n title: React.ReactNode\n /** Optional action, usually a Quick action trigger. */\n action?: React.ReactNode\n children?: React.ReactNode\n className?: string\n}\n\nexport function CasePanelHeader({\n title,\n action,\n children,\n className,\n}: CasePanelHeaderProps) {\n return (\n <header className={cn(\"mb-7\", className)}>\n <div className=\"flex items-start justify-between gap-5\">\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"m-0 text-[27px] font-bold leading-[1.18] tracking-[-0.02em] text-foreground [text-wrap:balance]\">\n {title}\n </h1>\n {children}\n </div>\n {action ? <div className=\"flex shrink-0 items-center gap-2\">{action}</div> : null}\n </div>\n </header>\n )\n}\n\nexport interface CasePanelIdentityLink {\n id: string\n label: string\n href?: string\n icon?: React.ReactNode\n kind?: \"icon\" | \"text\"\n disabled?: boolean\n target?: React.HTMLAttributeAnchorTarget\n rel?: string\n}\n\nexport interface CasePanelIdentitySublineProps {\n callsign?: string | null\n links?: CasePanelIdentityLink[]\n onCopyCallsign?: (callsign: string) => void\n copyLabel?: string\n copiedLabel?: string\n className?: string\n}\n\nexport function CasePanelIdentitySubline({\n callsign,\n links = [],\n onCopyCallsign,\n copyLabel = \"Copy call sign\",\n copiedLabel = \"Call sign copied\",\n className,\n}: CasePanelIdentitySublineProps) {\n const [copied, setCopied] = React.useState(false)\n const trimmedCallsign = callsign?.trim()\n const normalizedCallsign = trimmedCallsign\n ? trimmedCallsign.startsWith(\"@\")\n ? trimmedCallsign\n : `@${trimmedCallsign}`\n : null\n\n const handleCopy = React.useCallback(() => {\n if (!normalizedCallsign) return\n onCopyCallsign?.(normalizedCallsign)\n setCopied(true)\n window.setTimeout(() => setCopied(false), 1400)\n }, [normalizedCallsign, onCopyCallsign])\n\n return (\n <div className={cn(\"mt-[9px] inline-flex flex-wrap items-center gap-[7px] text-[13px] text-muted-foreground\", className)}>\n {normalizedCallsign ? (\n <>\n <span className=\"font-mono font-medium tracking-[0.01em] text-gray-700\">{normalizedCallsign}</span>\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label={copied ? copiedLabel : copyLabel}\n className=\"inline-flex h-[22px] w-[22px] items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-accent hover:text-foreground\"\n >\n {copied ? <Check className=\"h-[13px] w-[13px] text-emerald-700\" aria-hidden=\"true\" /> : <Copy className=\"h-[13px] w-[13px]\" aria-hidden=\"true\" />}\n </button>\n </>\n ) : null}\n {links.length > 0 ? (\n <>\n {normalizedCallsign ? <span aria-hidden=\"true\" className=\"mx-[3px] h-3.5 w-px bg-gray-200\" /> : null}\n <span className=\"inline-flex items-center gap-1.5\">\n {links.map((link) => (\n <CasePanelIdentityLinkButton key={link.id} link={link} />\n ))}\n </span>\n </>\n ) : null}\n </div>\n )\n}\n\nfunction CasePanelIdentityLinkButton({ link }: { link: CasePanelIdentityLink }) {\n const disabled = link.disabled || !link.href\n const iconOnly = link.kind === \"icon\"\n const content = iconOnly ? (\n <>{link.icon ?? <ExternalLink className=\"h-[13px] w-[13px]\" aria-hidden=\"true\" />}</>\n ) : (\n <>\n {link.icon ? <span className=\"inline-flex h-3.5 w-3.5 items-center justify-center\">{link.icon}</span> : null}\n <span>{link.label}</span>\n <ArrowUpRight className=\"h-[11px] w-[11px] text-gray-400\" aria-hidden=\"true\" />\n </>\n )\n\n const className = iconOnly\n ? \"inline-flex h-[26px] w-[26px] items-center justify-center rounded-[7px] border border-border bg-background text-foreground shadow-[0_1px_1.5px_rgba(0,0,0,0.03)] transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-45\"\n : \"inline-flex h-[26px] items-center gap-1.5 rounded-[7px] border border-border bg-background px-2 text-xs font-medium text-foreground shadow-[0_1px_1.5px_rgba(0,0,0,0.03)] transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-45\"\n\n if (disabled) {\n return (\n <button type=\"button\" disabled aria-label={link.label} className={className}>\n {content}\n </button>\n )\n }\n\n return (\n <a\n href={link.href}\n aria-label={link.label}\n target={link.target ?? \"_blank\"}\n rel={link.rel ?? \"noopener noreferrer\"}\n className={className}\n >\n {content}\n </a>\n )\n}\n\nexport interface CasePanelSignalBriefProps {\n children: React.ReactNode\n label?: string\n className?: string\n}\n\nexport function CasePanelSignalBrief({\n children,\n label = \"Signal brief\",\n className,\n}: CasePanelSignalBriefProps) {\n return (\n <section className={cn(\"mt-7\", className)} aria-labelledby=\"case-panel-signal-brief-heading\">\n <div id=\"case-panel-signal-brief-heading\" className=\"mb-2.5 text-[11px] font-semibold uppercase tracking-[0.07em] text-muted-foreground\">\n {label}\n </div>\n <div className=\"text-base leading-[1.6] text-foreground [text-wrap:pretty]\">{children}</div>\n </section>\n )\n}\n\nexport interface CasePanelMetadataRowProps {\n children: React.ReactNode\n label?: string\n className?: string\n}\n\nexport function CasePanelMetadataRow({\n children,\n label = \"Case metadata\",\n className,\n}: CasePanelMetadataRowProps) {\n return (\n <div aria-label={label} className={cn(\"mt-[18px] flex flex-wrap items-center gap-2\", className)}>\n {children}\n </div>\n )\n}\n\nexport interface CasePanelDetailSlotsProps {\n why?: React.ReactNode\n actions?: React.ReactNode\n opportunity?: React.ReactNode\n timeline?: React.ReactNode\n playPanel?: React.ReactNode\n className?: string\n}\n\nexport function CasePanelDetailSlots({\n why,\n actions,\n opportunity,\n timeline,\n playPanel,\n className,\n}: CasePanelDetailSlotsProps) {\n return (\n <div className={cn(\"mt-7 space-y-6\", className)}>\n {why ? <div data-slot=\"why\">{why}</div> : null}\n {actions ? <div data-slot=\"actions\">{actions}</div> : null}\n {opportunity ? <div data-slot=\"opportunity\">{opportunity}</div> : null}\n {timeline ? <div data-slot=\"timeline\">{timeline}</div> : null}\n {playPanel ? <div data-slot=\"play-panel\">{playPanel}</div> : null}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/case-panel-detail.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ArrowUpRight,\n Check,\n Copy,\n ExternalLink,\n} from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\nexport type CasePanelDetailWidth = \"comfortable\" | \"modest\" | \"compact\" | \"full\"\n\nexport interface CasePanelDetailProps {\n children: React.ReactNode\n /** Reading measure for the detail content column. Defaults to the handoff's comfortable 880px measure. */\n width?: CasePanelDetailWidth\n /** Accessible label for the detail region. */\n \"aria-label\"?: string\n className?: string\n}\n\nconst detailWidthClasses: Record<CasePanelDetailWidth, string> = {\n comfortable: \"mx-auto w-full max-w-[880px] px-10 pb-[120px] pt-8\",\n modest: \"mx-auto w-full max-w-[720px] px-8 pb-[112px] pt-7\",\n compact: \"mx-auto w-full max-w-[640px] px-10 pb-[112px] pt-7\",\n full: \"w-full px-8 pb-[120px] pt-8\",\n}\n\nexport function CasePanelDetail({\n children,\n width = \"comfortable\",\n \"aria-label\": ariaLabel = \"Case detail\",\n className,\n}: CasePanelDetailProps) {\n return (\n <section aria-label={ariaLabel} className={cn(\"min-w-0 bg-background\", className)}>\n <div className={detailWidthClasses[width]}>{children}</div>\n </section>\n )\n}\n\nexport interface CasePanelHeaderProps {\n title: React.ReactNode\n /** Optional action, usually a Quick action trigger. */\n action?: React.ReactNode\n children?: React.ReactNode\n className?: string\n}\n\nexport function CasePanelHeader({\n title,\n action,\n children,\n className,\n}: CasePanelHeaderProps) {\n return (\n <header className={cn(\"mb-7\", className)}>\n <div className=\"flex items-start justify-between gap-6\">\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"m-0 max-w-[560px] text-[27px] font-bold leading-[1.18] tracking-[-0.02em] text-foreground\">\n {title}\n </h1>\n {children}\n </div>\n {action ? <div className=\"flex shrink-0 items-center gap-2\">{action}</div> : null}\n </div>\n </header>\n )\n}\n\nexport interface CasePanelIdentityLink {\n id: string\n label: string\n href?: string\n icon?: React.ReactNode\n kind?: \"icon\" | \"text\"\n disabled?: boolean\n target?: React.HTMLAttributeAnchorTarget\n rel?: string\n}\n\nexport interface CasePanelIdentitySublineProps {\n callsign?: string | null\n links?: CasePanelIdentityLink[]\n onCopyCallsign?: (callsign: string) => void\n copyLabel?: string\n copiedLabel?: string\n className?: string\n}\n\nexport function CasePanelIdentitySubline({\n callsign,\n links = [],\n onCopyCallsign,\n copyLabel = \"Copy call sign\",\n copiedLabel = \"Call sign copied\",\n className,\n}: CasePanelIdentitySublineProps) {\n const [copied, setCopied] = React.useState(false)\n const trimmedCallsign = callsign?.trim()\n const normalizedCallsign = trimmedCallsign\n ? trimmedCallsign.startsWith(\"@\")\n ? trimmedCallsign\n : `@${trimmedCallsign}`\n : null\n\n const handleCopy = React.useCallback(() => {\n if (!normalizedCallsign) return\n onCopyCallsign?.(normalizedCallsign)\n setCopied(true)\n window.setTimeout(() => setCopied(false), 1400)\n }, [normalizedCallsign, onCopyCallsign])\n\n return (\n <div className={cn(\"mt-[9px] inline-flex flex-wrap items-center gap-[7px] text-[13px] text-muted-foreground\", className)}>\n {normalizedCallsign ? (\n <>\n <span className=\"font-mono font-medium tracking-[0.01em] text-gray-700\">{normalizedCallsign}</span>\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label={copied ? copiedLabel : copyLabel}\n className=\"inline-flex h-[22px] w-[22px] items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-accent hover:text-foreground\"\n >\n {copied ? <Check className=\"h-[13px] w-[13px] text-emerald-700\" aria-hidden=\"true\" /> : <Copy className=\"h-[13px] w-[13px]\" aria-hidden=\"true\" />}\n </button>\n </>\n ) : null}\n {links.length > 0 ? (\n <>\n {normalizedCallsign ? <span aria-hidden=\"true\" className=\"mx-[3px] h-3.5 w-px bg-gray-200\" /> : null}\n <span className=\"inline-flex items-center gap-1.5\">\n {links.map((link) => (\n <CasePanelIdentityLinkButton key={link.id} link={link} />\n ))}\n </span>\n </>\n ) : null}\n </div>\n )\n}\n\nfunction CasePanelIdentityLinkButton({ link }: { link: CasePanelIdentityLink }) {\n const disabled = link.disabled || !link.href\n const iconOnly = link.kind === \"icon\"\n const content = iconOnly ? (\n <>{link.icon ?? <ExternalLink className=\"h-[13px] w-[13px]\" aria-hidden=\"true\" />}</>\n ) : (\n <>\n {link.icon ? <span className=\"inline-flex h-3.5 w-3.5 items-center justify-center\">{link.icon}</span> : null}\n <span>{link.label}</span>\n <ArrowUpRight className=\"h-[11px] w-[11px] text-gray-400\" aria-hidden=\"true\" />\n </>\n )\n\n const className = iconOnly\n ? \"inline-flex h-[26px] w-[26px] items-center justify-center rounded-[7px] border border-border bg-background text-foreground shadow-[0_1px_1.5px_rgba(0,0,0,0.03)] transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-45\"\n : \"inline-flex h-[26px] items-center gap-1.5 rounded-[7px] border border-border bg-background px-2 text-xs font-medium text-foreground shadow-[0_1px_1.5px_rgba(0,0,0,0.03)] transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-45\"\n\n if (disabled) {\n return (\n <button type=\"button\" disabled aria-label={link.label} className={className}>\n {content}\n </button>\n )\n }\n\n return (\n <a\n href={link.href}\n aria-label={link.label}\n target={link.target ?? \"_blank\"}\n rel={link.rel ?? \"noopener noreferrer\"}\n className={className}\n >\n {content}\n </a>\n )\n}\n\nexport interface CasePanelSignalBriefProps {\n children: React.ReactNode\n label?: string\n className?: string\n}\n\nexport function CasePanelSignalBrief({\n children,\n label = \"Signal brief\",\n className,\n}: CasePanelSignalBriefProps) {\n return (\n <section className={cn(\"mt-7\", className)} aria-labelledby=\"case-panel-signal-brief-heading\">\n <div id=\"case-panel-signal-brief-heading\" className=\"mb-2.5 text-[11px] font-semibold uppercase tracking-[0.07em] text-muted-foreground\">\n {label}\n </div>\n <div className=\"text-base leading-[1.6] text-foreground [text-wrap:pretty]\">{children}</div>\n </section>\n )\n}\n\nexport interface CasePanelMetadataRowProps {\n children: React.ReactNode\n label?: string\n className?: string\n}\n\nexport function CasePanelMetadataRow({\n children,\n label = \"Case metadata\",\n className,\n}: CasePanelMetadataRowProps) {\n return (\n <div aria-label={label} className={cn(\"mt-[18px] flex flex-wrap items-center gap-2\", className)}>\n {children}\n </div>\n )\n}\n\nexport interface CasePanelDetailSlotsProps {\n why?: React.ReactNode\n actions?: React.ReactNode\n opportunity?: React.ReactNode\n timeline?: React.ReactNode\n playPanel?: React.ReactNode\n className?: string\n}\n\nexport function CasePanelDetailSlots({\n why,\n actions,\n opportunity,\n timeline,\n playPanel,\n className,\n}: CasePanelDetailSlotsProps) {\n return (\n <div className={cn(\"mt-7 space-y-6\", className)}>\n {why ? <div data-slot=\"why\">{why}</div> : null}\n {actions ? <div data-slot=\"actions\">{actions}</div> : null}\n {opportunity ? <div data-slot=\"opportunity\">{opportunity}</div> : null}\n {timeline ? <div data-slot=\"timeline\">{timeline}</div> : null}\n {playPanel ? <div data-slot=\"play-panel\">{playPanel}</div> : null}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -91,7 +91,7 @@ describe("CasePanelActivityTimeline", () => {
|
|
|
91
91
|
defaultExpanded
|
|
92
92
|
onPayloadAction={onPayloadAction}
|
|
93
93
|
events={[
|
|
94
|
-
event({ id: "signal", title: "Signal", payload: { kind: "signal", key: "sig-1", summary: "Churn risk rose", detail: "Usage fell 30%." } }),
|
|
94
|
+
event({ id: "signal", title: "Signal", payload: { kind: "signal", key: "sig-1", summary: "Churn risk rose", detail: "Usage fell 30%.", actionLabel: "Open signal" } }),
|
|
95
95
|
event({ id: "score", title: "Score", payload: { kind: "scoreUpdate", previousScore: 62, nextScore: 81, reason: "Executive engagement improved." } }),
|
|
96
96
|
event({ id: "rec", title: "Recommendation", payload: { kind: "recommendation", recommendation: "Send renewal brief", rationale: "Close date is within 14 days." } }),
|
|
97
97
|
event({ id: "email", title: "Email", payload: { kind: "email", from: "rep@example.com", to: "buyer@example.com", subject: "Renewal plan", preview: "Can we meet tomorrow?" } }),
|
|
@@ -116,6 +116,19 @@ describe("CasePanelActivityTimeline", () => {
|
|
|
116
116
|
expect(onPayloadAction).toHaveBeenCalledWith({ kind: "openSignal", key: "sig-1", eventId: "signal" })
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
+
it("does not render a signal action without an explicit action label", () => {
|
|
120
|
+
render(
|
|
121
|
+
<CasePanelActivityTimeline
|
|
122
|
+
defaultExpanded
|
|
123
|
+
onPayloadAction={vi.fn()}
|
|
124
|
+
events={[event({ id: "signal", title: "Signal", payload: { kind: "signal", key: "event-id", summary: "Legacy signal event" } })]}
|
|
125
|
+
/>
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
expect(screen.queryByRole("button", { name: "Open signal" })).toBeNull()
|
|
129
|
+
expect(screen.getByText("Legacy signal event")).toBeTruthy()
|
|
130
|
+
})
|
|
131
|
+
|
|
119
132
|
it("renders the Salesforce deep-link slot", () => {
|
|
120
133
|
render(
|
|
121
134
|
<CasePanelActivityTimeline
|
|
@@ -131,14 +131,25 @@ describe("CasePanelDetail structure", () => {
|
|
|
131
131
|
expect(slots).toEqual(["why", "actions", "opportunity", "timeline", "play-panel"])
|
|
132
132
|
})
|
|
133
133
|
|
|
134
|
-
it("supports
|
|
135
|
-
const { container } = render(
|
|
134
|
+
it("supports compact and modest width variants", () => {
|
|
135
|
+
const { container, rerender } = render(
|
|
136
|
+
<CasePanelDetail width="compact">
|
|
137
|
+
<div>Content</div>
|
|
138
|
+
</CasePanelDetail>,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
let inner = container.querySelector("section > div")
|
|
142
|
+
expect(inner?.className).toContain("max-w-[640px]")
|
|
143
|
+
expect(inner?.className).toContain("px-10")
|
|
144
|
+
expect(inner?.className).not.toContain("max-w-[720px]")
|
|
145
|
+
|
|
146
|
+
rerender(
|
|
136
147
|
<CasePanelDetail width="modest">
|
|
137
148
|
<div>Content</div>
|
|
138
149
|
</CasePanelDetail>,
|
|
139
150
|
)
|
|
140
151
|
|
|
141
|
-
|
|
152
|
+
inner = container.querySelector("section > div")
|
|
142
153
|
expect(inner?.className).toContain("max-w-[720px]")
|
|
143
154
|
expect(inner?.className).not.toContain("max-w-[880px]")
|
|
144
155
|
})
|
|
@@ -301,13 +301,13 @@ function renderPayloadContent(
|
|
|
301
301
|
<div className="space-y-2">
|
|
302
302
|
<p className="text-foreground">{payload.summary}</p>
|
|
303
303
|
{payload.detail ? <p className="text-xs leading-relaxed text-muted-foreground">{payload.detail}</p> : null}
|
|
304
|
-
{onPayloadAction ? (
|
|
304
|
+
{onPayloadAction && payload.actionLabel ? (
|
|
305
305
|
<button
|
|
306
306
|
type="button"
|
|
307
307
|
className="inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
|
|
308
308
|
onClick={() => onPayloadAction({ kind: "openSignal", key: payload.key, eventId })}
|
|
309
309
|
>
|
|
310
|
-
{payload.actionLabel
|
|
310
|
+
{payload.actionLabel}
|
|
311
311
|
</button>
|
|
312
312
|
) : null}
|
|
313
313
|
</div>
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "lucide-react"
|
|
10
10
|
import { cn } from "../lib/utils"
|
|
11
11
|
|
|
12
|
-
export type CasePanelDetailWidth = "comfortable" | "modest" | "full"
|
|
12
|
+
export type CasePanelDetailWidth = "comfortable" | "modest" | "compact" | "full"
|
|
13
13
|
|
|
14
14
|
export interface CasePanelDetailProps {
|
|
15
15
|
children: React.ReactNode
|
|
@@ -23,6 +23,7 @@ export interface CasePanelDetailProps {
|
|
|
23
23
|
const detailWidthClasses: Record<CasePanelDetailWidth, string> = {
|
|
24
24
|
comfortable: "mx-auto w-full max-w-[880px] px-10 pb-[120px] pt-8",
|
|
25
25
|
modest: "mx-auto w-full max-w-[720px] px-8 pb-[112px] pt-7",
|
|
26
|
+
compact: "mx-auto w-full max-w-[640px] px-10 pb-[112px] pt-7",
|
|
26
27
|
full: "w-full px-8 pb-[120px] pt-8",
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -55,9 +56,9 @@ export function CasePanelHeader({
|
|
|
55
56
|
}: CasePanelHeaderProps) {
|
|
56
57
|
return (
|
|
57
58
|
<header className={cn("mb-7", className)}>
|
|
58
|
-
<div className="flex items-start justify-between gap-
|
|
59
|
+
<div className="flex items-start justify-between gap-6">
|
|
59
60
|
<div className="min-w-0 flex-1">
|
|
60
|
-
<h1 className="m-0 text-[27px] font-bold leading-[1.18] tracking-[-0.02em] text-foreground
|
|
61
|
+
<h1 className="m-0 max-w-[560px] text-[27px] font-bold leading-[1.18] tracking-[-0.02em] text-foreground">
|
|
61
62
|
{title}
|
|
62
63
|
</h1>
|
|
63
64
|
{children}
|