@handled-ai/design-system 0.18.22 → 0.18.23
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.d.ts +100 -0
- package/dist/components/case-panel-activity-timeline.js +270 -0
- package/dist/components/case-panel-activity-timeline.js.map +1 -0
- package/dist/components/case-panel-detail.d.ts +60 -0
- package/dist/components/case-panel-detail.js +129 -0
- package/dist/components/case-panel-detail.js.map +1 -0
- package/dist/components/case-panel-email-composer.d.ts +61 -0
- package/dist/components/case-panel-email-composer.js +304 -0
- package/dist/components/case-panel-email-composer.js.map +1 -0
- package/dist/components/case-panel-why.d.ts +35 -0
- package/dist/components/case-panel-why.js +149 -0
- package/dist/components/case-panel-why.js.map +1 -0
- package/dist/components/contextual-quick-action-launcher.d.ts +7 -3
- package/dist/components/contextual-quick-action-launcher.js +99 -27
- package/dist/components/contextual-quick-action-launcher.js.map +1 -1
- package/dist/components/data-table.js +0 -1
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/score-analysis-modal.d.ts +2 -8
- package/dist/components/score-analysis-modal.js +6 -19
- package/dist/components/score-analysis-modal.js.map +1 -1
- package/dist/components/score-breakdown.d.ts +1 -3
- package/dist/components/score-breakdown.js +6 -5
- package/dist/components/score-breakdown.js.map +1 -1
- package/dist/components/score-ring.d.ts +3 -6
- package/dist/components/score-ring.js +14 -11
- package/dist/components/score-ring.js.map +1 -1
- package/dist/components/score-why-chips.d.ts +2 -3
- package/dist/components/score-why-chips.js +21 -10
- package/dist/components/score-why-chips.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +0 -1
- package/dist/components/signal-priority-popover.js +20 -20
- package/dist/components/signal-priority-popover.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/prototype/index.d.ts +0 -1
- package/dist/prototype/prototype-accounts-view.d.ts +0 -1
- package/dist/prototype/prototype-admin-view.d.ts +0 -1
- package/dist/prototype/prototype-config.d.ts +0 -1
- package/dist/prototype/prototype-inbox-view.d.ts +0 -1
- package/dist/prototype/prototype-insights-view.d.ts +0 -1
- package/dist/prototype/prototype-shell.d.ts +0 -1
- package/package.json +1 -1
- package/src/components/__tests__/case-panel-activity-timeline.test.tsx +152 -0
- package/src/components/__tests__/case-panel-detail.test.tsx +138 -0
- package/src/components/__tests__/case-panel-email-composer.test.tsx +171 -0
- package/src/components/__tests__/case-panel-why.test.tsx +152 -0
- package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +87 -0
- package/src/components/__tests__/signal-priority-popover.test.tsx +5 -7
- package/src/components/case-panel-activity-timeline.tsx +414 -0
- package/src/components/case-panel-detail.tsx +228 -0
- package/src/components/case-panel-email-composer.tsx +341 -0
- package/src/components/case-panel-why.tsx +214 -0
- package/src/components/contextual-quick-action-launcher.tsx +92 -15
- package/src/components/data-table.tsx +0 -1
- package/src/components/score-analysis-modal.tsx +5 -22
- package/src/components/score-breakdown.tsx +6 -7
- package/src/components/score-ring.tsx +13 -11
- package/src/components/score-why-chips.tsx +23 -12
- package/src/components/signal-priority-popover.tsx +21 -21
- package/src/index.ts +4 -1
- package/dist/components/score-semantics.d.ts +0 -27
- package/dist/components/score-semantics.js +0 -173
- package/dist/components/score-semantics.js.map +0 -1
- package/src/components/__tests__/score-analysis-modal.test.tsx +0 -55
- package/src/components/__tests__/score-breakdown-intent.test.tsx +0 -47
- package/src/components/__tests__/score-ring.test.tsx +0 -43
- package/src/components/__tests__/score-semantics.test.ts +0 -107
- package/src/components/score-semantics.ts +0 -187
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import {
|
|
4
|
+
import { ChevronDown, Zap } from "lucide-react"
|
|
5
5
|
|
|
6
6
|
import { cn } from "../lib/utils"
|
|
7
7
|
import {
|
|
@@ -19,11 +19,15 @@ export interface ContextualQuickActionItem {
|
|
|
19
19
|
icon?: React.ReactNode
|
|
20
20
|
disabled?: boolean
|
|
21
21
|
disabledReason?: string
|
|
22
|
+
meta?: React.ReactNode
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export type ContextualQuickActionLauncherVariant = "default" | "case-panel"
|
|
26
|
+
|
|
24
27
|
export interface ContextualQuickActionContextLabelProps {
|
|
25
28
|
contextLabel: string
|
|
26
29
|
contextSecondary?: string
|
|
30
|
+
variant?: ContextualQuickActionLauncherVariant
|
|
27
31
|
className?: string
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -35,6 +39,7 @@ export interface ContextualQuickActionLauncherProps {
|
|
|
35
39
|
onBrowseAll?: () => void
|
|
36
40
|
showHint?: boolean
|
|
37
41
|
align?: "start" | "end"
|
|
42
|
+
variant?: ContextualQuickActionLauncherVariant
|
|
38
43
|
className?: string
|
|
39
44
|
open?: boolean
|
|
40
45
|
defaultOpen?: boolean
|
|
@@ -44,19 +49,35 @@ export interface ContextualQuickActionLauncherProps {
|
|
|
44
49
|
function ContextualQuickActionContextLabel({
|
|
45
50
|
contextLabel,
|
|
46
51
|
contextSecondary,
|
|
52
|
+
variant = "default",
|
|
47
53
|
className,
|
|
48
54
|
}: ContextualQuickActionContextLabelProps) {
|
|
55
|
+
const isCasePanel = variant === "case-panel"
|
|
56
|
+
|
|
49
57
|
return (
|
|
50
58
|
<div
|
|
51
59
|
data-slot="contextual-quick-action-context-label"
|
|
60
|
+
data-variant={variant}
|
|
52
61
|
className={cn(
|
|
53
|
-
|
|
62
|
+
isCasePanel
|
|
63
|
+
? "-mx-2 -mt-2 mb-1 flex items-center gap-1.5 whitespace-nowrap border-b px-3 py-2.5 text-[13px] text-muted-foreground"
|
|
64
|
+
: "-mx-1 -mt-1 mb-1 flex items-center gap-1.5 border-b px-3 py-2 text-[11px] text-muted-foreground",
|
|
54
65
|
className
|
|
55
66
|
)}
|
|
56
67
|
>
|
|
57
|
-
<Zap
|
|
68
|
+
<Zap
|
|
69
|
+
className={cn(isCasePanel ? "h-3.5 w-3.5 shrink-0" : "h-3 w-3 shrink-0")}
|
|
70
|
+
strokeWidth={2.25}
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
/>
|
|
58
73
|
<span>Acting on</span>
|
|
59
|
-
<strong
|
|
74
|
+
<strong
|
|
75
|
+
className={cn(
|
|
76
|
+
isCasePanel
|
|
77
|
+
? "max-w-[260px] truncate font-semibold text-foreground"
|
|
78
|
+
: "max-w-[180px] truncate font-semibold text-foreground"
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
60
81
|
{contextLabel}
|
|
61
82
|
</strong>
|
|
62
83
|
{contextSecondary ? (
|
|
@@ -80,12 +101,14 @@ function ContextualQuickActionLauncher({
|
|
|
80
101
|
onBrowseAll,
|
|
81
102
|
showHint = false,
|
|
82
103
|
align = "start",
|
|
104
|
+
variant = "default",
|
|
83
105
|
className,
|
|
84
106
|
open,
|
|
85
107
|
defaultOpen,
|
|
86
108
|
onOpenChange,
|
|
87
109
|
}: ContextualQuickActionLauncherProps) {
|
|
88
110
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false)
|
|
111
|
+
const isCasePanel = variant === "case-panel"
|
|
89
112
|
const isControlled = open !== undefined
|
|
90
113
|
const isOpen = isControlled ? open : uncontrolledOpen
|
|
91
114
|
|
|
@@ -154,11 +177,16 @@ function ContextualQuickActionLauncher({
|
|
|
154
177
|
align={align}
|
|
155
178
|
side="bottom"
|
|
156
179
|
sideOffset={6}
|
|
157
|
-
className=
|
|
180
|
+
className={cn(
|
|
181
|
+
isCasePanel
|
|
182
|
+
? "pointer-events-auto w-[432px] rounded-[13px] border border-border bg-popover p-2 text-[13px] shadow-[0_18px_44px_rgba(0,0,0,0.14),0_2px_10px_rgba(0,0,0,0.06)]"
|
|
183
|
+
: "pointer-events-auto w-[308px] rounded-[10px] p-1.5 text-[12.5px] shadow-[0_12px_28px_rgba(0,0,0,0.12),0_2px_6px_rgba(0,0,0,0.04)]"
|
|
184
|
+
)}
|
|
158
185
|
>
|
|
159
186
|
<ContextualQuickActionContextLabel
|
|
160
187
|
contextLabel={contextLabel}
|
|
161
188
|
contextSecondary={contextSecondary}
|
|
189
|
+
variant={variant}
|
|
162
190
|
/>
|
|
163
191
|
|
|
164
192
|
{items.map((item) => (
|
|
@@ -167,48 +195,97 @@ function ContextualQuickActionLauncher({
|
|
|
167
195
|
disabled={item.disabled}
|
|
168
196
|
onSelect={(event) => handleSelect(item, event)}
|
|
169
197
|
className={cn(
|
|
170
|
-
|
|
198
|
+
isCasePanel
|
|
199
|
+
? "grid cursor-pointer grid-cols-[34px_minmax(0,1fr)_auto] items-center gap-3 rounded-[9px] px-2.5 py-[9px] text-left outline-none focus:bg-muted/60 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-100"
|
|
200
|
+
: "flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-2 text-left outline-none focus:bg-accent data-[disabled]:cursor-not-allowed data-[disabled]:opacity-100",
|
|
171
201
|
item.disabled && "text-muted-foreground"
|
|
172
202
|
)}
|
|
173
203
|
>
|
|
174
204
|
<span
|
|
175
205
|
data-slot="contextual-quick-action-item-icon"
|
|
176
206
|
className={cn(
|
|
177
|
-
|
|
207
|
+
isCasePanel
|
|
208
|
+
? "flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-[9px] border border-border/70 bg-muted/60 text-foreground [&_svg]:h-[18px] [&_svg]:w-[18px]"
|
|
209
|
+
: "flex h-[26px] w-[26px] shrink-0 items-center justify-center rounded-md bg-secondary text-foreground",
|
|
178
210
|
item.disabled && "opacity-60"
|
|
179
211
|
)}
|
|
180
212
|
>
|
|
181
213
|
{item.icon ?? <DefaultActionIcon />}
|
|
182
214
|
</span>
|
|
183
215
|
<span className="min-w-0 flex-1">
|
|
184
|
-
<span
|
|
216
|
+
<span
|
|
217
|
+
className={cn(
|
|
218
|
+
isCasePanel
|
|
219
|
+
? "block truncate text-[13.5px] font-semibold leading-tight text-current"
|
|
220
|
+
: "block truncate text-[12.5px] font-medium leading-tight text-current"
|
|
221
|
+
)}
|
|
222
|
+
>
|
|
185
223
|
{item.label}
|
|
186
224
|
</span>
|
|
187
225
|
{item.description ? (
|
|
188
|
-
<span
|
|
226
|
+
<span
|
|
227
|
+
className={cn(
|
|
228
|
+
isCasePanel
|
|
229
|
+
? "mt-1 block truncate text-[12px] leading-tight text-muted-foreground"
|
|
230
|
+
: "mt-0.5 block truncate text-[11px] leading-tight text-muted-foreground"
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
189
233
|
{item.description}
|
|
190
234
|
</span>
|
|
191
235
|
) : null}
|
|
192
236
|
</span>
|
|
193
237
|
{item.disabled && item.disabledReason ? (
|
|
194
|
-
<span
|
|
238
|
+
<span
|
|
239
|
+
data-slot="contextual-quick-action-item-disabled-meta"
|
|
240
|
+
className={cn(
|
|
241
|
+
isCasePanel
|
|
242
|
+
? "ml-auto shrink-0 whitespace-nowrap text-[12px] italic text-muted-foreground/70"
|
|
243
|
+
: "ml-auto shrink-0 text-[10.5px] italic text-muted-foreground/80"
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
195
246
|
{item.disabledReason}
|
|
196
247
|
</span>
|
|
248
|
+
) : item.meta ? (
|
|
249
|
+
<span
|
|
250
|
+
data-slot="contextual-quick-action-item-meta"
|
|
251
|
+
className={cn(
|
|
252
|
+
isCasePanel
|
|
253
|
+
? "ml-auto shrink-0 whitespace-nowrap text-[12px] italic text-muted-foreground/70"
|
|
254
|
+
: "ml-auto shrink-0 text-[10.5px] text-muted-foreground/80"
|
|
255
|
+
)}
|
|
256
|
+
>
|
|
257
|
+
{item.meta}
|
|
258
|
+
</span>
|
|
197
259
|
) : null}
|
|
198
260
|
</DropdownMenuItem>
|
|
199
261
|
))}
|
|
200
262
|
|
|
201
|
-
<DropdownMenuSeparator className="-mx-1.5 my-1" />
|
|
202
|
-
<div
|
|
263
|
+
<DropdownMenuSeparator className={cn(isCasePanel ? "-mx-2 mt-1 mb-1" : "-mx-1.5 my-1")} />
|
|
264
|
+
<div
|
|
265
|
+
className={cn(
|
|
266
|
+
isCasePanel
|
|
267
|
+
? "flex items-center justify-between whitespace-nowrap px-2.5 py-2 text-[13px] text-muted-foreground"
|
|
268
|
+
: "flex items-center justify-between px-2 py-1.5 text-[11px] text-muted-foreground"
|
|
269
|
+
)}
|
|
270
|
+
>
|
|
203
271
|
<button
|
|
204
272
|
type="button"
|
|
205
|
-
className=
|
|
273
|
+
className={cn(
|
|
274
|
+
isCasePanel
|
|
275
|
+
? "inline-flex items-center gap-1 font-semibold text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
276
|
+
: "inline-flex items-center gap-1 font-medium text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
277
|
+
)}
|
|
206
278
|
onClick={handleBrowseAll}
|
|
207
279
|
>
|
|
208
280
|
Browse all actions
|
|
209
|
-
<ArrowRight className="h-3 w-3" strokeWidth={2} aria-hidden="true" />
|
|
210
281
|
</button>
|
|
211
|
-
<span
|
|
282
|
+
<span
|
|
283
|
+
className={cn(
|
|
284
|
+
isCasePanel
|
|
285
|
+
? "inline-flex items-center rounded-[5px] border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] font-medium leading-none text-muted-foreground"
|
|
286
|
+
: "inline-flex items-center rounded border border-border px-1 py-0.5 text-[10px] font-medium leading-none text-muted-foreground"
|
|
287
|
+
)}
|
|
288
|
+
>
|
|
212
289
|
⌘K
|
|
213
290
|
</span>
|
|
214
291
|
</div>
|
|
@@ -892,7 +892,6 @@ export function DataTable({
|
|
|
892
892
|
title={data.title}
|
|
893
893
|
description={data.description}
|
|
894
894
|
score={scoreModal.type === "Risk" ? scoreModal.row.riskScore : scoreModal.row.expansionScore}
|
|
895
|
-
scoreIntent={scoreModal.type === "Risk" ? "risk" : "positive"}
|
|
896
895
|
whyNow={data.whyNow}
|
|
897
896
|
evidence={data.evidence}
|
|
898
897
|
factors={data.factors}
|
|
@@ -7,7 +7,6 @@ import { ScoreRing } from "./score-ring"
|
|
|
7
7
|
import { ScoreBreakdown, type ScoreFactor } from "./score-breakdown"
|
|
8
8
|
import { SignalApproval } from "./signal-feedback-inline"
|
|
9
9
|
import { X } from "lucide-react"
|
|
10
|
-
import type { ScoreIntent } from "./score-semantics"
|
|
11
10
|
|
|
12
11
|
interface ScoreAnalysisModalProps {
|
|
13
12
|
open: boolean
|
|
@@ -27,25 +26,10 @@ interface ScoreAnalysisModalProps {
|
|
|
27
26
|
onDismiss?: (reasons: string[], detail: string) => void
|
|
28
27
|
/** When true, renders as an absolute-positioned inline panel instead of a Radix Sheet portal. Useful when the component is inside a container that should not be escaped. */
|
|
29
28
|
useInlinePanel?: boolean
|
|
30
|
-
scoreIntent?: ScoreIntent
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
function getScoreLabel(score: number, denominator: number
|
|
31
|
+
function getScoreLabel(score: number, denominator: number) {
|
|
34
32
|
const pct = (score / denominator) * 100
|
|
35
|
-
|
|
36
|
-
if (intent === "urgency") {
|
|
37
|
-
if (pct >= 80) return { text: "URGENT", className: "text-red-600" }
|
|
38
|
-
if (pct >= 60) return { text: "HIGH", className: "text-orange-600" }
|
|
39
|
-
if (pct >= 35) return { text: "MEDIUM", className: "text-amber-600" }
|
|
40
|
-
return { text: "LOW", className: "text-neutral-600" }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (intent === "risk") {
|
|
44
|
-
if (pct >= 70) return { text: "High Risk", className: "text-red-600" }
|
|
45
|
-
if (pct >= 40) return { text: "Medium Risk", className: "text-amber-600" }
|
|
46
|
-
return { text: "Low Risk", className: "text-neutral-600" }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
33
|
if (pct >= 70) return { text: "HIGH", className: "text-emerald-600" }
|
|
50
34
|
if (pct >= 40) return { text: "MEDIUM", className: "text-amber-600" }
|
|
51
35
|
return { text: "LOW", className: "text-red-600" }
|
|
@@ -68,9 +52,8 @@ function ScoreAnalysisModal({
|
|
|
68
52
|
onApproveFeedback,
|
|
69
53
|
onDismiss,
|
|
70
54
|
useInlinePanel = false,
|
|
71
|
-
scoreIntent = "positive",
|
|
72
55
|
}: ScoreAnalysisModalProps) {
|
|
73
|
-
const label = getScoreLabel(score, denominator
|
|
56
|
+
const label = getScoreLabel(score, denominator)
|
|
74
57
|
|
|
75
58
|
const panelContent = (
|
|
76
59
|
<SignalApproval.Root
|
|
@@ -100,7 +83,7 @@ function ScoreAnalysisModal({
|
|
|
100
83
|
|
|
101
84
|
<div className="space-y-6">
|
|
102
85
|
<div className="flex flex-col items-center gap-3">
|
|
103
|
-
<ScoreRing score={score} denominator={denominator} size={120} strokeWidth={10}
|
|
86
|
+
<ScoreRing score={score} denominator={denominator} size={120} strokeWidth={10} />
|
|
104
87
|
<Badge variant="outline">
|
|
105
88
|
{Math.round((score / denominator) * 100)}% Score
|
|
106
89
|
{" \u2014 "}
|
|
@@ -128,7 +111,7 @@ function ScoreAnalysisModal({
|
|
|
128
111
|
{factors && factors.length > 0 && (
|
|
129
112
|
<div>
|
|
130
113
|
<h3 className="font-semibold mb-2 text-sm">Score Breakdown</h3>
|
|
131
|
-
<ScoreBreakdown factors={factors} onFactorFeedback={onFactorFeedback}
|
|
114
|
+
<ScoreBreakdown factors={factors} onFactorFeedback={onFactorFeedback} />
|
|
132
115
|
</div>
|
|
133
116
|
)}
|
|
134
117
|
</div>
|
|
@@ -185,5 +168,5 @@ function ScoreAnalysisModal({
|
|
|
185
168
|
|
|
186
169
|
const ScoreAnalysisPanel = ScoreAnalysisModal
|
|
187
170
|
|
|
188
|
-
export { ScoreAnalysisModal, ScoreAnalysisPanel
|
|
171
|
+
export { ScoreAnalysisModal, ScoreAnalysisPanel }
|
|
189
172
|
export type { ScoreAnalysisModalProps }
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { ThumbsUp, ThumbsDown } from "lucide-react"
|
|
5
5
|
import { cn } from "../lib/utils"
|
|
6
|
-
import type { ScoreIntent } from "./score-semantics"
|
|
7
|
-
import { getScoreToneClasses } from "./score-semantics"
|
|
8
6
|
|
|
9
7
|
export interface ScoreFactor {
|
|
10
8
|
key: string
|
|
@@ -14,8 +12,10 @@ export interface ScoreFactor {
|
|
|
14
12
|
why: string
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
function getFactorBarColor(score: number
|
|
18
|
-
|
|
15
|
+
function getFactorBarColor(score: number) {
|
|
16
|
+
if (score >= 70) return "bg-emerald-500"
|
|
17
|
+
if (score >= 40) return "bg-amber-500"
|
|
18
|
+
return "bg-red-500"
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function getRiskBadgeStyle(risk: "Low" | "Medium" | "High") {
|
|
@@ -34,7 +34,6 @@ interface ScoreBreakdownProps {
|
|
|
34
34
|
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
35
35
|
className?: string
|
|
36
36
|
initialFeedback?: Record<string, { type: "up" | "down"; detail: string }>
|
|
37
|
-
scoreIntent?: ScoreIntent
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
function deriveInitialState<T>(
|
|
@@ -48,7 +47,7 @@ function deriveInitialState<T>(
|
|
|
48
47
|
return Object.fromEntries(filtered.map(([k, v]) => [k, mapFn(v)]))
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback
|
|
50
|
+
function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback }: ScoreBreakdownProps) {
|
|
52
51
|
const [feedback, setFeedback] = React.useState<Record<string, "up" | "down" | null>>(
|
|
53
52
|
() => deriveInitialState(initialFeedback, (v) => v.type)
|
|
54
53
|
)
|
|
@@ -171,7 +170,7 @@ function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback,
|
|
|
171
170
|
{factor.score !== null && (
|
|
172
171
|
<div className="w-full h-1 bg-muted rounded-full overflow-hidden">
|
|
173
172
|
<div
|
|
174
|
-
className={cn("h-full rounded-full", getFactorBarColor(factor.score
|
|
173
|
+
className={cn("h-full rounded-full", getFactorBarColor(factor.score))}
|
|
175
174
|
style={{ width: `${factor.score}%` }}
|
|
176
175
|
/>
|
|
177
176
|
</div>
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { cn } from "../lib/utils"
|
|
3
|
-
import type { ScoreIntent } from "./score-semantics"
|
|
4
|
-
import { getScoreToneClasses } from "./score-semantics"
|
|
5
3
|
|
|
6
|
-
function getScoreColor(score: number, denominator: number
|
|
7
|
-
|
|
4
|
+
function getScoreColor(score: number, denominator: number) {
|
|
5
|
+
const pct = (score / denominator) * 100
|
|
6
|
+
if (pct >= 70) return "text-emerald-500"
|
|
7
|
+
if (pct >= 40) return "text-amber-500"
|
|
8
|
+
return "text-red-500"
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
function getScoreTrackColor(score: number, denominator: number
|
|
11
|
-
|
|
11
|
+
function getScoreTrackColor(score: number, denominator: number) {
|
|
12
|
+
const pct = (score / denominator) * 100
|
|
13
|
+
if (pct >= 70) return "text-emerald-500/15"
|
|
14
|
+
if (pct >= 40) return "text-amber-500/15"
|
|
15
|
+
return "text-red-500/15"
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
interface ScoreRingProps {
|
|
@@ -18,7 +22,6 @@ interface ScoreRingProps {
|
|
|
18
22
|
strokeWidth?: number
|
|
19
23
|
className?: string
|
|
20
24
|
showLabel?: boolean
|
|
21
|
-
intent?: ScoreIntent
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
function ScoreRing({
|
|
@@ -28,7 +31,6 @@ function ScoreRing({
|
|
|
28
31
|
strokeWidth = 10,
|
|
29
32
|
className,
|
|
30
33
|
showLabel = true,
|
|
31
|
-
intent = "positive",
|
|
32
34
|
}: ScoreRingProps) {
|
|
33
35
|
const radius = (size - strokeWidth) / 2
|
|
34
36
|
const circumference = 2 * Math.PI * radius
|
|
@@ -51,7 +53,7 @@ function ScoreRing({
|
|
|
51
53
|
fill="none"
|
|
52
54
|
stroke="currentColor"
|
|
53
55
|
strokeWidth={strokeWidth}
|
|
54
|
-
className={getScoreTrackColor(score, denominator
|
|
56
|
+
className={getScoreTrackColor(score, denominator)}
|
|
55
57
|
/>
|
|
56
58
|
{/* Fill */}
|
|
57
59
|
<circle
|
|
@@ -64,7 +66,7 @@ function ScoreRing({
|
|
|
64
66
|
strokeLinecap="round"
|
|
65
67
|
strokeDasharray={circumference}
|
|
66
68
|
strokeDashoffset={offset}
|
|
67
|
-
className={cn("transition-all duration-500", getScoreColor(score, denominator
|
|
69
|
+
className={cn("transition-all duration-500", getScoreColor(score, denominator))}
|
|
68
70
|
/>
|
|
69
71
|
</svg>
|
|
70
72
|
{showLabel && (
|
|
@@ -81,5 +83,5 @@ function ScoreRing({
|
|
|
81
83
|
)
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
export { ScoreRing, getScoreColor
|
|
86
|
+
export { ScoreRing, getScoreColor }
|
|
85
87
|
export type { ScoreRingProps }
|
|
@@ -17,12 +17,6 @@ import type { LucideIcon } from "lucide-react"
|
|
|
17
17
|
import { FeedbackFooter } from "./feedback-primitives"
|
|
18
18
|
import type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from "./feedback-primitives"
|
|
19
19
|
import { cn } from "../lib/utils"
|
|
20
|
-
import {
|
|
21
|
-
DEFAULT_SCORE_TONE_CLASS,
|
|
22
|
-
SCORE_TONE_CLASSES,
|
|
23
|
-
getUrgencyLevel,
|
|
24
|
-
getUrgencyRange,
|
|
25
|
-
} from "./score-semantics"
|
|
26
20
|
import type {
|
|
27
21
|
QueueItem,
|
|
28
22
|
SignalScoreData,
|
|
@@ -39,11 +33,24 @@ export function getSignalScoreUrgencyLabel(
|
|
|
39
33
|
score: number,
|
|
40
34
|
providedLabel?: SignalScoreUrgencyLabel,
|
|
41
35
|
): SignalScoreUrgencyLabel {
|
|
42
|
-
|
|
36
|
+
if (providedLabel) return providedLabel
|
|
37
|
+
if (score >= 80) return "Urgent"
|
|
38
|
+
if (score >= 60) return "High"
|
|
39
|
+
if (score >= 35) return "Medium"
|
|
40
|
+
return "Low"
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
export function scoreRangeForUrgency(label: SignalScoreUrgencyLabel): string {
|
|
46
|
-
|
|
44
|
+
switch (label) {
|
|
45
|
+
case "Urgent":
|
|
46
|
+
return "80-100"
|
|
47
|
+
case "High":
|
|
48
|
+
return "60-79"
|
|
49
|
+
case "Medium":
|
|
50
|
+
return "35-59"
|
|
51
|
+
case "Low":
|
|
52
|
+
return "0-34"
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
function makeDomId(...parts: Array<string | undefined>): string {
|
|
@@ -100,11 +107,15 @@ function resolveIcon(iconName?: string): LucideIcon {
|
|
|
100
107
|
// Static tone class maps (REQUIRED for Tailwind v4 source scanning)
|
|
101
108
|
// ---------------------------------------------------------------------------
|
|
102
109
|
|
|
103
|
-
/** Shared tone-to-class map. Re-exported for
|
|
104
|
-
export const SIGNAL_TONE_CLASSES: Record<string, string> =
|
|
110
|
+
/** Shared tone-to-class map. Re-exported for signal-priority-popover. */
|
|
111
|
+
export const SIGNAL_TONE_CLASSES: Record<string, string> = {
|
|
112
|
+
alert: "bg-red-50 text-red-600",
|
|
113
|
+
warn: "bg-amber-50 text-amber-600",
|
|
114
|
+
info: "bg-blue-50 text-blue-600",
|
|
115
|
+
}
|
|
105
116
|
|
|
106
|
-
/** Default tone for missing/unknown tone values
|
|
107
|
-
export const DEFAULT_TONE_CLASS =
|
|
117
|
+
/** Default tone for missing/unknown tone values */
|
|
118
|
+
export const DEFAULT_TONE_CLASS = "bg-muted text-muted-foreground"
|
|
108
119
|
|
|
109
120
|
// ---------------------------------------------------------------------------
|
|
110
121
|
// Em-dash fallback for missing slot data
|
|
@@ -22,7 +22,7 @@ import { cn } from "../lib/utils"
|
|
|
22
22
|
import { FeedbackFooter, InlineFeedbackControl } from "./feedback-primitives"
|
|
23
23
|
import type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from "./feedback-primitives"
|
|
24
24
|
import type { SignalScoreUrgencyLabel } from "../prototype/prototype-config"
|
|
25
|
-
import {
|
|
25
|
+
import { getSignalScoreUrgencyLabel, scoreRangeForUrgency, SIGNAL_TONE_CLASSES } from "./score-why-chips"
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
// Types
|
|
@@ -75,28 +75,28 @@ export interface SignalPriorityPopoverProps {
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
|
|
77
77
|
const URGENCY_TRIGGER_DEFAULT: Record<SignalScoreUrgencyLabel, string> = {
|
|
78
|
-
Urgent:
|
|
79
|
-
High:
|
|
80
|
-
Medium:
|
|
81
|
-
Low:
|
|
78
|
+
Urgent: "border-red-200 bg-red-50 text-red-700",
|
|
79
|
+
High: "border-orange-200 bg-orange-50 text-orange-700",
|
|
80
|
+
Medium: "border-amber-200 bg-amber-50 text-amber-700",
|
|
81
|
+
Low: "border-blue-200 bg-blue-50 text-blue-700",
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const URGENCY_TRIGGER_HOVER: Record<SignalScoreUrgencyLabel, string> = {
|
|
85
|
-
Urgent:
|
|
86
|
-
High:
|
|
87
|
-
Medium:
|
|
88
|
-
Low:
|
|
85
|
+
Urgent: "hover:bg-red-100",
|
|
86
|
+
High: "hover:bg-orange-100",
|
|
87
|
+
Medium: "hover:bg-amber-100",
|
|
88
|
+
Low: "hover:bg-blue-100",
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
const URGENCY_TRIGGER_OPEN: Record<SignalScoreUrgencyLabel, string> = {
|
|
92
|
-
Urgent:
|
|
93
|
-
High:
|
|
94
|
-
Medium:
|
|
95
|
-
Low:
|
|
92
|
+
Urgent: "bg-red-100",
|
|
93
|
+
High: "bg-orange-100",
|
|
94
|
+
Medium: "bg-amber-100",
|
|
95
|
+
Low: "bg-blue-100",
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/** Re-use shared tone classes from score-
|
|
99
|
-
const TONE_ICON_CLASSES: Record<PriorityFactor["tone"], string> =
|
|
98
|
+
/** Re-use shared tone classes from score-why-chips. */
|
|
99
|
+
const TONE_ICON_CLASSES: Record<PriorityFactor["tone"], string> = SIGNAL_TONE_CLASSES as Record<PriorityFactor["tone"], string>
|
|
100
100
|
|
|
101
101
|
const DIRECTION_CLASSES: Record<PriorityFactor["direction"], string> = {
|
|
102
102
|
raises: "text-red-600",
|
|
@@ -124,10 +124,10 @@ const FACTOR_ICONS: Record<string, LucideIcon> = {
|
|
|
124
124
|
// ---------------------------------------------------------------------------
|
|
125
125
|
|
|
126
126
|
const URGENCY_DOT_CLASSES: Record<SignalScoreUrgencyLabel, string> = {
|
|
127
|
-
Urgent:
|
|
128
|
-
High:
|
|
129
|
-
Medium:
|
|
130
|
-
Low:
|
|
127
|
+
Urgent: "bg-red-500",
|
|
128
|
+
High: "bg-orange-500",
|
|
129
|
+
Medium: "bg-amber-500",
|
|
130
|
+
Low: "bg-blue-500",
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// ---------------------------------------------------------------------------
|
|
@@ -283,8 +283,8 @@ export function SignalPriorityPopover({
|
|
|
283
283
|
onFactorFeedback,
|
|
284
284
|
initialPriorityFeedback,
|
|
285
285
|
}: SignalPriorityPopoverProps) {
|
|
286
|
-
const urgencyLabel =
|
|
287
|
-
const scoreRange =
|
|
286
|
+
const urgencyLabel = getSignalScoreUrgencyLabel(score, providedLabel)
|
|
287
|
+
const scoreRange = scoreRangeForUrgency(urgencyLabel)
|
|
288
288
|
|
|
289
289
|
const [open, setOpen] = React.useState(false)
|
|
290
290
|
const [feedback, setFeedback] = React.useState<"positive" | "negative" | null>(null)
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,10 @@ export * from "./components/avatar"
|
|
|
20
20
|
export * from "./components/badge"
|
|
21
21
|
export * from "./components/button"
|
|
22
22
|
export * from "./components/card"
|
|
23
|
+
export * from "./components/case-panel-email-composer"
|
|
24
|
+
export * from "./components/case-panel-activity-timeline"
|
|
25
|
+
export * from "./components/case-panel-detail"
|
|
26
|
+
export * from "./components/case-panel-why"
|
|
23
27
|
export { CollapsibleSection, type CollapsibleSectionProps } from "./components/collapsible-section"
|
|
24
28
|
export * from "./components/compliance-badge"
|
|
25
29
|
export * from "./components/contact-chip"
|
|
@@ -77,7 +81,6 @@ export * from "./components/rich-text-toolbar"
|
|
|
77
81
|
export * from "./components/score-analysis-modal"
|
|
78
82
|
export * from "./components/score-breakdown"
|
|
79
83
|
export * from "./components/score-feedback"
|
|
80
|
-
export * from "./components/score-semantics"
|
|
81
84
|
export * from "./components/score-why-chips"
|
|
82
85
|
export * from "./components/score-ring"
|
|
83
86
|
export * from "./components/scroll-area"
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
type ScoreIntent = "urgency" | "risk" | "positive";
|
|
2
|
-
type UrgencyLevel = "Low" | "Medium" | "High" | "Urgent";
|
|
3
|
-
type RiskLevel = "Low Risk" | "Medium Risk" | "High Risk";
|
|
4
|
-
type PositiveLevel = "Low" | "Medium" | "High";
|
|
5
|
-
type ScoreSemanticClasses = {
|
|
6
|
-
solid: string;
|
|
7
|
-
outline: string;
|
|
8
|
-
dot: string;
|
|
9
|
-
trigger: string;
|
|
10
|
-
hover: string;
|
|
11
|
-
open: string;
|
|
12
|
-
text: string;
|
|
13
|
-
track: string;
|
|
14
|
-
bar: string;
|
|
15
|
-
};
|
|
16
|
-
declare const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses>;
|
|
17
|
-
declare const RISK_SCORE_CLASSES: Record<RiskLevel, ScoreSemanticClasses>;
|
|
18
|
-
declare const POSITIVE_SCORE_CLASSES: Record<PositiveLevel, ScoreSemanticClasses>;
|
|
19
|
-
declare const SCORE_TONE_CLASSES: Record<string, string>;
|
|
20
|
-
declare const DEFAULT_SCORE_TONE_CLASS = "bg-muted text-muted-foreground";
|
|
21
|
-
declare function getUrgencyLevel(score: number): UrgencyLevel;
|
|
22
|
-
declare function getUrgencyRange(label: UrgencyLevel): string;
|
|
23
|
-
declare function getRiskLevel(score: number): RiskLevel;
|
|
24
|
-
declare function getPositiveLevel(score: number): PositiveLevel;
|
|
25
|
-
declare function getScoreToneClasses(score: number, intent: ScoreIntent): ScoreSemanticClasses;
|
|
26
|
-
|
|
27
|
-
export { DEFAULT_SCORE_TONE_CLASS, POSITIVE_SCORE_CLASSES, type PositiveLevel, RISK_SCORE_CLASSES, type RiskLevel, SCORE_TONE_CLASSES, type ScoreIntent, type ScoreSemanticClasses, URGENCY_SCORE_CLASSES, type UrgencyLevel, getPositiveLevel, getRiskLevel, getScoreToneClasses, getUrgencyLevel, getUrgencyRange };
|