@handled-ai/design-system 0.18.23 → 0.18.25

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.
Files changed (55) hide show
  1. package/dist/components/case-panel-detail.d.ts +3 -1
  2. package/dist/components/case-panel-detail.js +29 -15
  3. package/dist/components/case-panel-detail.js.map +1 -1
  4. package/dist/components/case-panel-email-composer.d.ts +2 -1
  5. package/dist/components/case-panel-email-composer.js +4 -2
  6. package/dist/components/case-panel-email-composer.js.map +1 -1
  7. package/dist/components/data-table.js +1 -0
  8. package/dist/components/data-table.js.map +1 -1
  9. package/dist/components/score-analysis-modal.d.ts +8 -2
  10. package/dist/components/score-analysis-modal.js +19 -6
  11. package/dist/components/score-analysis-modal.js.map +1 -1
  12. package/dist/components/score-breakdown.d.ts +3 -1
  13. package/dist/components/score-breakdown.js +5 -6
  14. package/dist/components/score-breakdown.js.map +1 -1
  15. package/dist/components/score-ring.d.ts +6 -3
  16. package/dist/components/score-ring.js +11 -14
  17. package/dist/components/score-ring.js.map +1 -1
  18. package/dist/components/score-semantics.d.ts +27 -0
  19. package/dist/components/score-semantics.js +173 -0
  20. package/dist/components/score-semantics.js.map +1 -0
  21. package/dist/components/score-why-chips.d.ts +3 -2
  22. package/dist/components/score-why-chips.js +10 -21
  23. package/dist/components/score-why-chips.js.map +1 -1
  24. package/dist/components/signal-priority-popover.d.ts +1 -0
  25. package/dist/components/signal-priority-popover.js +20 -20
  26. package/dist/components/signal-priority-popover.js.map +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/prototype/index.d.ts +1 -0
  31. package/dist/prototype/prototype-accounts-view.d.ts +1 -0
  32. package/dist/prototype/prototype-admin-view.d.ts +1 -0
  33. package/dist/prototype/prototype-config.d.ts +1 -0
  34. package/dist/prototype/prototype-inbox-view.d.ts +1 -0
  35. package/dist/prototype/prototype-insights-view.d.ts +1 -0
  36. package/dist/prototype/prototype-shell.d.ts +1 -0
  37. package/package.json +1 -1
  38. package/src/components/__tests__/case-panel-detail.test.tsx +17 -1
  39. package/src/components/__tests__/case-panel-email-composer.test.tsx +7 -0
  40. package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +4 -1
  41. package/src/components/__tests__/score-analysis-modal.test.tsx +55 -0
  42. package/src/components/__tests__/score-breakdown-intent.test.tsx +47 -0
  43. package/src/components/__tests__/score-ring.test.tsx +43 -0
  44. package/src/components/__tests__/score-semantics.test.ts +107 -0
  45. package/src/components/__tests__/signal-priority-popover.test.tsx +7 -5
  46. package/src/components/case-panel-detail.tsx +31 -13
  47. package/src/components/case-panel-email-composer.tsx +25 -21
  48. package/src/components/data-table.tsx +1 -0
  49. package/src/components/score-analysis-modal.tsx +22 -5
  50. package/src/components/score-breakdown.tsx +7 -6
  51. package/src/components/score-ring.tsx +11 -13
  52. package/src/components/score-semantics.ts +187 -0
  53. package/src/components/score-why-chips.tsx +12 -23
  54. package/src/components/signal-priority-popover.tsx +21 -21
  55. package/src/index.ts +1 -0
@@ -8,8 +8,19 @@ import { ScoreRing } from "./score-ring.js";
8
8
  import { ScoreBreakdown } from "./score-breakdown.js";
9
9
  import { SignalApproval } from "./signal-feedback-inline.js";
10
10
  import { X } from "lucide-react";
11
- function getScoreLabel(score, denominator) {
11
+ function getScoreLabel(score, denominator, intent = "positive") {
12
12
  const pct = score / denominator * 100;
13
+ if (intent === "urgency") {
14
+ if (pct >= 80) return { text: "URGENT", className: "text-red-600" };
15
+ if (pct >= 60) return { text: "HIGH", className: "text-orange-600" };
16
+ if (pct >= 35) return { text: "MEDIUM", className: "text-amber-600" };
17
+ return { text: "LOW", className: "text-neutral-600" };
18
+ }
19
+ if (intent === "risk") {
20
+ if (pct >= 70) return { text: "High Risk", className: "text-red-600" };
21
+ if (pct >= 40) return { text: "Medium Risk", className: "text-amber-600" };
22
+ return { text: "Low Risk", className: "text-neutral-600" };
23
+ }
13
24
  if (pct >= 70) return { text: "HIGH", className: "text-emerald-600" };
14
25
  if (pct >= 40) return { text: "MEDIUM", className: "text-amber-600" };
15
26
  return { text: "LOW", className: "text-red-600" };
@@ -30,9 +41,10 @@ function ScoreAnalysisModal({
30
41
  onApprove,
31
42
  onApproveFeedback,
32
43
  onDismiss,
33
- useInlinePanel = false
44
+ useInlinePanel = false,
45
+ scoreIntent = "positive"
34
46
  }) {
35
- const label = getScoreLabel(score, denominator);
47
+ const label = getScoreLabel(score, denominator, scoreIntent);
36
48
  const panelContent = /* @__PURE__ */ jsxs(
37
49
  SignalApproval.Root,
38
50
  {
@@ -59,7 +71,7 @@ function ScoreAnalysisModal({
59
71
  /* @__PURE__ */ jsx("div", { className: "mb-8", children: /* @__PURE__ */ jsx(SignalApproval.Actions, {}) }),
60
72
  /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
61
73
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
62
- /* @__PURE__ */ jsx(ScoreRing, { score, denominator, size: 120, strokeWidth: 10 }),
74
+ /* @__PURE__ */ jsx(ScoreRing, { score, denominator, size: 120, strokeWidth: 10, intent: scoreIntent }),
63
75
  /* @__PURE__ */ jsxs(Badge, { variant: "outline", children: [
64
76
  Math.round(score / denominator * 100),
65
77
  "% Score",
@@ -80,7 +92,7 @@ function ScoreAnalysisModal({
80
92
  ] }),
81
93
  factors && factors.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
82
94
  /* @__PURE__ */ jsx("h3", { className: "font-semibold mb-2 text-sm", children: "Score Breakdown" }),
83
- /* @__PURE__ */ jsx(ScoreBreakdown, { factors, onFactorFeedback })
95
+ /* @__PURE__ */ jsx(ScoreBreakdown, { factors, onFactorFeedback, scoreIntent })
84
96
  ] })
85
97
  ] })
86
98
  ] })
@@ -136,6 +148,7 @@ function ScoreAnalysisModal({
136
148
  const ScoreAnalysisPanel = ScoreAnalysisModal;
137
149
  export {
138
150
  ScoreAnalysisModal,
139
- ScoreAnalysisPanel
151
+ ScoreAnalysisPanel,
152
+ getScoreLabel
140
153
  };
141
154
  //# sourceMappingURL=score-analysis-modal.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/score-analysis-modal.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from \"./sheet\"\nimport { Badge } from \"./badge\"\nimport { ScoreRing } from \"./score-ring\"\nimport { ScoreBreakdown, type ScoreFactor } from \"./score-breakdown\"\nimport { SignalApproval } from \"./signal-feedback-inline\"\nimport { X } from \"lucide-react\"\n\ninterface ScoreAnalysisModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n title: string\n description: string\n score: number\n denominator?: number\n whyNow: string\n evidence: React.ReactNode[]\n factors?: ScoreFactor[]\n onFactorFeedback?: (factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n companyName?: string\n opportunityUrl?: string\n onApprove?: () => void\n onApproveFeedback?: (reasons: string[], detail: string) => void\n onDismiss?: (reasons: string[], detail: string) => void\n /** 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. */\n useInlinePanel?: boolean\n}\n\nfunction getScoreLabel(score: number, denominator: number) {\n const pct = (score / denominator) * 100\n if (pct >= 70) return { text: \"HIGH\", className: \"text-emerald-600\" }\n if (pct >= 40) return { text: \"MEDIUM\", className: \"text-amber-600\" }\n return { text: \"LOW\", className: \"text-red-600\" }\n}\n\nfunction ScoreAnalysisModal({\n open,\n onOpenChange,\n title,\n description,\n score,\n denominator = 100,\n whyNow,\n evidence,\n factors,\n onFactorFeedback,\n companyName = \"Account\",\n opportunityUrl,\n onApprove,\n onApproveFeedback,\n onDismiss,\n useInlinePanel = false,\n}: ScoreAnalysisModalProps) {\n const label = getScoreLabel(score, denominator)\n\n const panelContent = (\n <SignalApproval.Root\n companyName={companyName}\n opportunityUrl={opportunityUrl}\n onApprove={onApprove}\n onApproveFeedback={onApproveFeedback}\n onDismiss={onDismiss}\n >\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border shrink-0\">\n <h2 className=\"text-lg font-semibold text-foreground\">{title}</h2>\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"p-1.5 rounded-md text-muted-foreground hover:bg-secondary transition-colors\"\n >\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n\n <div className=\"flex-1 overflow-y-auto px-6 py-6\">\n <p className=\"text-sm text-muted-foreground mb-6\">{description}</p>\n\n <div className=\"mb-8\">\n <SignalApproval.Actions />\n </div>\n\n <div className=\"space-y-6\">\n <div className=\"flex flex-col items-center gap-3\">\n <ScoreRing score={score} denominator={denominator} size={120} strokeWidth={10} />\n <Badge variant=\"outline\">\n {Math.round((score / denominator) * 100)}% Score\n {\" \\u2014 \"}\n <span className={label.className}>{label.text}</span>\n </Badge>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-2 text-sm\">Why Now</h3>\n <p className=\"text-sm text-muted-foreground leading-relaxed\">{whyNow}</p>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-2 text-sm\">Supporting Evidence</h3>\n <ul className=\"space-y-2\">\n {evidence.map((item, index) => (\n <li key={index} className=\"flex items-start gap-2 text-sm\">\n <div className=\"w-1.5 h-1.5 bg-primary rounded-full mt-2 flex-shrink-0\" />\n <span className=\"text-muted-foreground leading-relaxed\">{item}</span>\n </li>\n ))}\n </ul>\n </div>\n\n {factors && factors.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-2 text-sm\">Score Breakdown</h3>\n <ScoreBreakdown factors={factors} onFactorFeedback={onFactorFeedback} />\n </div>\n )}\n </div>\n </div>\n </SignalApproval.Root>\n )\n\n if (useInlinePanel) {\n if (!open) return null\n return (\n <div style={{ position: \"absolute\", inset: 0, zIndex: 50, pointerEvents: \"auto\", overflow: \"hidden\" }}>\n <div\n onClick={() => onOpenChange(false)}\n style={{ position: \"absolute\", inset: 0, backgroundColor: \"rgba(0,0,0,0.5)\", transition: \"opacity 200ms\" }}\n />\n <div\n style={{\n position: \"absolute\",\n top: 0,\n right: 0,\n bottom: 0,\n width: 500,\n maxWidth: \"100%\",\n zIndex: 50,\n display: \"flex\",\n flexDirection: \"column\",\n overflow: \"hidden\",\n borderLeft: \"1px solid var(--border)\",\n backgroundColor: \"var(--background, #fff)\",\n boxShadow: \"-4px 0 24px rgba(0,0,0,0.12)\",\n }}\n >\n {panelContent}\n </div>\n </div>\n )\n }\n\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetContent\n side=\"right\"\n className=\"w-full sm:w-[500px] sm:max-w-[600px] overflow-hidden p-0 bg-background border-l border-border flex flex-col\"\n showCloseButton={false}\n >\n <SheetHeader className=\"sr-only p-0\">\n <SheetTitle>{title}</SheetTitle>\n </SheetHeader>\n {panelContent}\n </SheetContent>\n </Sheet>\n )\n}\n\nconst ScoreAnalysisPanel = ScoreAnalysisModal\n\nexport { ScoreAnalysisModal, ScoreAnalysisPanel }\nexport type { ScoreAnalysisModalProps }\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/score-analysis-modal.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from \"./sheet\"\nimport { Badge } from \"./badge\"\nimport { ScoreRing } from \"./score-ring\"\nimport { ScoreBreakdown, type ScoreFactor } from \"./score-breakdown\"\nimport { SignalApproval } from \"./signal-feedback-inline\"\nimport { X } from \"lucide-react\"\nimport type { ScoreIntent } from \"./score-semantics\"\n\ninterface ScoreAnalysisModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n title: string\n description: string\n score: number\n denominator?: number\n whyNow: string\n evidence: React.ReactNode[]\n factors?: ScoreFactor[]\n onFactorFeedback?: (factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n companyName?: string\n opportunityUrl?: string\n onApprove?: () => void\n onApproveFeedback?: (reasons: string[], detail: string) => void\n onDismiss?: (reasons: string[], detail: string) => void\n /** 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. */\n useInlinePanel?: boolean\n scoreIntent?: ScoreIntent\n}\n\nfunction getScoreLabel(score: number, denominator: number, intent: ScoreIntent = \"positive\") {\n const pct = (score / denominator) * 100\n\n if (intent === \"urgency\") {\n if (pct >= 80) return { text: \"URGENT\", className: \"text-red-600\" }\n if (pct >= 60) return { text: \"HIGH\", className: \"text-orange-600\" }\n if (pct >= 35) return { text: \"MEDIUM\", className: \"text-amber-600\" }\n return { text: \"LOW\", className: \"text-neutral-600\" }\n }\n\n if (intent === \"risk\") {\n if (pct >= 70) return { text: \"High Risk\", className: \"text-red-600\" }\n if (pct >= 40) return { text: \"Medium Risk\", className: \"text-amber-600\" }\n return { text: \"Low Risk\", className: \"text-neutral-600\" }\n }\n\n if (pct >= 70) return { text: \"HIGH\", className: \"text-emerald-600\" }\n if (pct >= 40) return { text: \"MEDIUM\", className: \"text-amber-600\" }\n return { text: \"LOW\", className: \"text-red-600\" }\n}\n\nfunction ScoreAnalysisModal({\n open,\n onOpenChange,\n title,\n description,\n score,\n denominator = 100,\n whyNow,\n evidence,\n factors,\n onFactorFeedback,\n companyName = \"Account\",\n opportunityUrl,\n onApprove,\n onApproveFeedback,\n onDismiss,\n useInlinePanel = false,\n scoreIntent = \"positive\",\n}: ScoreAnalysisModalProps) {\n const label = getScoreLabel(score, denominator, scoreIntent)\n\n const panelContent = (\n <SignalApproval.Root\n companyName={companyName}\n opportunityUrl={opportunityUrl}\n onApprove={onApprove}\n onApproveFeedback={onApproveFeedback}\n onDismiss={onDismiss}\n >\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border shrink-0\">\n <h2 className=\"text-lg font-semibold text-foreground\">{title}</h2>\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"p-1.5 rounded-md text-muted-foreground hover:bg-secondary transition-colors\"\n >\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n\n <div className=\"flex-1 overflow-y-auto px-6 py-6\">\n <p className=\"text-sm text-muted-foreground mb-6\">{description}</p>\n\n <div className=\"mb-8\">\n <SignalApproval.Actions />\n </div>\n\n <div className=\"space-y-6\">\n <div className=\"flex flex-col items-center gap-3\">\n <ScoreRing score={score} denominator={denominator} size={120} strokeWidth={10} intent={scoreIntent} />\n <Badge variant=\"outline\">\n {Math.round((score / denominator) * 100)}% Score\n {\" \\u2014 \"}\n <span className={label.className}>{label.text}</span>\n </Badge>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-2 text-sm\">Why Now</h3>\n <p className=\"text-sm text-muted-foreground leading-relaxed\">{whyNow}</p>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-2 text-sm\">Supporting Evidence</h3>\n <ul className=\"space-y-2\">\n {evidence.map((item, index) => (\n <li key={index} className=\"flex items-start gap-2 text-sm\">\n <div className=\"w-1.5 h-1.5 bg-primary rounded-full mt-2 flex-shrink-0\" />\n <span className=\"text-muted-foreground leading-relaxed\">{item}</span>\n </li>\n ))}\n </ul>\n </div>\n\n {factors && factors.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-2 text-sm\">Score Breakdown</h3>\n <ScoreBreakdown factors={factors} onFactorFeedback={onFactorFeedback} scoreIntent={scoreIntent} />\n </div>\n )}\n </div>\n </div>\n </SignalApproval.Root>\n )\n\n if (useInlinePanel) {\n if (!open) return null\n return (\n <div style={{ position: \"absolute\", inset: 0, zIndex: 50, pointerEvents: \"auto\", overflow: \"hidden\" }}>\n <div\n onClick={() => onOpenChange(false)}\n style={{ position: \"absolute\", inset: 0, backgroundColor: \"rgba(0,0,0,0.5)\", transition: \"opacity 200ms\" }}\n />\n <div\n style={{\n position: \"absolute\",\n top: 0,\n right: 0,\n bottom: 0,\n width: 500,\n maxWidth: \"100%\",\n zIndex: 50,\n display: \"flex\",\n flexDirection: \"column\",\n overflow: \"hidden\",\n borderLeft: \"1px solid var(--border)\",\n backgroundColor: \"var(--background, #fff)\",\n boxShadow: \"-4px 0 24px rgba(0,0,0,0.12)\",\n }}\n >\n {panelContent}\n </div>\n </div>\n )\n }\n\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetContent\n side=\"right\"\n className=\"w-full sm:w-[500px] sm:max-w-[600px] overflow-hidden p-0 bg-background border-l border-border flex flex-col\"\n showCloseButton={false}\n >\n <SheetHeader className=\"sr-only p-0\">\n <SheetTitle>{title}</SheetTitle>\n </SheetHeader>\n {panelContent}\n </SheetContent>\n </Sheet>\n )\n}\n\nconst ScoreAnalysisPanel = ScoreAnalysisModal\n\nexport { ScoreAnalysisModal, ScoreAnalysisPanel, getScoreLabel }\nexport type { ScoreAnalysisModalProps }\n"],"mappings":"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","names":[]}
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { ScoreIntent } from './score-semantics.js';
2
3
 
3
4
  interface ScoreFactor {
4
5
  key: string;
@@ -15,7 +16,8 @@ interface ScoreBreakdownProps {
15
16
  type: "up" | "down";
16
17
  detail: string;
17
18
  }>;
19
+ scoreIntent?: ScoreIntent;
18
20
  }
19
- declare function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback }: ScoreBreakdownProps): React.JSX.Element;
21
+ declare function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback, scoreIntent }: ScoreBreakdownProps): React.JSX.Element;
20
22
 
21
23
  export { ScoreBreakdown, type ScoreBreakdownProps, type ScoreFactor };
@@ -24,10 +24,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
24
24
  import * as React from "react";
25
25
  import { ThumbsUp, ThumbsDown } from "lucide-react";
26
26
  import { cn } from "../lib/utils.js";
27
- function getFactorBarColor(score) {
28
- if (score >= 70) return "bg-emerald-500";
29
- if (score >= 40) return "bg-amber-500";
30
- return "bg-red-500";
27
+ import { getScoreToneClasses } from "./score-semantics.js";
28
+ function getFactorBarColor(score, intent = "positive") {
29
+ return getScoreToneClasses(score, intent).bar;
31
30
  }
32
31
  function getRiskBadgeStyle(risk) {
33
32
  switch (risk) {
@@ -45,7 +44,7 @@ function deriveInitialState(init, mapFn, filterFn) {
45
44
  const filtered = filterFn ? entries.filter(([, v]) => filterFn(v)) : entries;
46
45
  return Object.fromEntries(filtered.map(([k, v]) => [k, mapFn(v)]));
47
46
  }
48
- function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback }) {
47
+ function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback, scoreIntent = "positive" }) {
49
48
  const [feedback, setFeedback] = React.useState(
50
49
  () => deriveInitialState(initialFeedback, (v) => v.type)
51
50
  );
@@ -155,7 +154,7 @@ function ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback
155
154
  factor.score !== null && /* @__PURE__ */ jsx("div", { className: "w-full h-1 bg-muted rounded-full overflow-hidden", children: /* @__PURE__ */ jsx(
156
155
  "div",
157
156
  {
158
- className: cn("h-full rounded-full", getFactorBarColor(factor.score)),
157
+ className: cn("h-full rounded-full", getFactorBarColor(factor.score, scoreIntent)),
159
158
  style: { width: `${factor.score}%` }
160
159
  }
161
160
  ) }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/score-breakdown.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\nexport interface ScoreFactor {\n key: string\n label: string\n score: number | null\n risk?: \"Low\" | \"Medium\" | \"High\"\n why: string\n}\n\nfunction getFactorBarColor(score: number) {\n if (score >= 70) return \"bg-emerald-500\"\n if (score >= 40) return \"bg-amber-500\"\n return \"bg-red-500\"\n}\n\nfunction getRiskBadgeStyle(risk: \"Low\" | \"Medium\" | \"High\") {\n switch (risk) {\n case \"Low\":\n return \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n case \"Medium\":\n return \"text-amber-600 bg-amber-50 dark:text-amber-400 dark:bg-amber-950/30\"\n case \"High\":\n return \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n }\n}\n\ninterface ScoreBreakdownProps {\n factors: ScoreFactor[]\n onFactorFeedback?: (factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n className?: string\n initialFeedback?: Record<string, { type: \"up\" | \"down\"; detail: string }>\n}\n\nfunction deriveInitialState<T>(\n init: Record<string, { type: \"up\" | \"down\"; detail: string }> | undefined,\n mapFn: (v: { type: \"up\" | \"down\"; detail: string }) => T,\n filterFn?: (v: { type: \"up\" | \"down\"; detail: string }) => boolean,\n): Record<string, T> {\n if (!init) return {}\n const entries = Object.entries(init)\n const filtered = filterFn ? entries.filter(([, v]) => filterFn(v)) : entries\n return Object.fromEntries(filtered.map(([k, v]) => [k, mapFn(v)]))\n}\n\nfunction ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback }: ScoreBreakdownProps) {\n const [feedback, setFeedback] = React.useState<Record<string, \"up\" | \"down\" | null>>(\n () => deriveInitialState(initialFeedback, (v) => v.type)\n )\n const [feedbackText, setFeedbackText] = React.useState<Record<string, string>>(\n () => deriveInitialState(initialFeedback, (v) => v.detail)\n )\n const [savedText, setSavedText] = React.useState<Record<string, string>>(\n () => deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail)\n )\n const [editingKey, setEditingKey] = React.useState<string | null>(null)\n\n // Sync state when initialFeedback prop changes (e.g. async hydration).\n // Skip keys the user is actively editing to avoid clobbering in-progress input.\n React.useEffect(() => {\n setFeedback((prev) => {\n const next: Record<string, \"up\" | \"down\" | null> = deriveInitialState(initialFeedback, (v) => v.type)\n // Preserve any key the user is currently editing\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n setFeedbackText((prev) => {\n const next = deriveInitialState(initialFeedback, (v) => v.detail)\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n setSavedText((prev) => {\n const next = deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail)\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n }, [initialFeedback]) // eslint-disable-line react-hooks/exhaustive-deps -- reads editingKey as guard, not trigger\n\n const handleFeedback = (factorKey: string, type: \"up\" | \"down\") => {\n const newState = feedback[factorKey] === type ? null : type\n setFeedback((prev) => ({ ...prev, [factorKey]: newState }))\n if (newState === null) {\n setEditingKey(null)\n onFactorFeedback?.(factorKey, null)\n } else {\n setEditingKey(factorKey)\n onFactorFeedback?.(factorKey, newState)\n }\n }\n\n const submitFeedbackText = (factorKey: string) => {\n const text = (feedbackText[factorKey] ?? \"\").trim()\n if (feedback[factorKey]) {\n onFactorFeedback?.(factorKey, feedback[factorKey]!, text ?? \"\")\n if (text) {\n setSavedText((prev) => ({ ...prev, [factorKey]: text }))\n }\n }\n setEditingKey(null)\n }\n\n return (\n <div className={cn(\"border border-border rounded-lg overflow-hidden divide-y divide-border/40\", className)}>\n {factors.map((factor) => {\n const feedbackState = feedback[factor.key]\n const hasThumb = feedbackState === \"up\" || feedbackState === \"down\"\n const isEditing = editingKey === factor.key\n const saved = savedText[factor.key]\n\n return (\n <div key={factor.key} className=\"px-3 py-3 space-y-1.5\">\n {/* Row 1: Factor name + score + rating buttons */}\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[13px] font-semibold text-foreground flex-1\">{factor.label}</span>\n\n {factor.score !== null ? (\n <span className=\"text-xs font-bold text-foreground tabular-nums\">{factor.score}<span className=\"text-muted-foreground font-normal\">/100</span></span>\n ) : factor.risk ? (\n <span\n className={cn(\n \"text-[11px] font-semibold px-1.5 py-0.5 rounded\",\n getRiskBadgeStyle(factor.risk)\n )}\n >\n {factor.risk}\n </span>\n ) : null}\n\n <div className=\"flex items-center gap-0.5 ml-1\">\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"up\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"up\"\n ? \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n : \"text-muted-foreground/40 hover:text-emerald-600 hover:bg-emerald-50/50\"\n )}\n title=\"This factor is accurate\"\n >\n <ThumbsUp className=\"w-3 h-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"down\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"down\"\n ? \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n : \"text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50\"\n )}\n title=\"Report issue with this factor\"\n >\n <ThumbsDown className=\"w-3 h-3\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Score bar */}\n {factor.score !== null && (\n <div className=\"w-full h-1 bg-muted rounded-full overflow-hidden\">\n <div\n className={cn(\"h-full rounded-full\", getFactorBarColor(factor.score))}\n style={{ width: `${factor.score}%` }}\n />\n </div>\n )}\n\n {/* Row 3: Why text */}\n <p className=\"text-xs text-muted-foreground leading-relaxed\">{factor.why}</p>\n\n {/* Feedback input */}\n {hasThumb && isEditing && (\n <input\n type=\"text\"\n autoFocus\n value={feedbackText[factor.key] ?? \"\"}\n onChange={(e) =>\n setFeedbackText((prev) => ({ ...prev, [factor.key]: e.target.value }))\n }\n onKeyDown={(e) => {\n if (e.key === \"Enter\") submitFeedbackText(factor.key)\n if (e.key === \"Escape\") setEditingKey(null)\n }}\n onBlur={() => submitFeedbackText(factor.key)}\n placeholder={\n feedbackState === \"up\"\n ? \"What\\u2019s accurate? (optional)\"\n : \"What\\u2019s wrong? (optional)\"\n }\n className=\"w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring\"\n />\n )}\n {hasThumb && !isEditing && saved && (\n <button\n type=\"button\"\n onClick={() => {\n setFeedbackText((prev) => ({ ...prev, [factor.key]: saved }))\n setEditingKey(factor.key)\n }}\n className=\"text-[11px] text-muted-foreground/70 hover:text-muted-foreground transition-colors text-left leading-snug\"\n >\n {saved}\n </button>\n )}\n </div>\n )\n })}\n </div>\n )\n}\n\nexport { ScoreBreakdown }\nexport type { ScoreBreakdownProps }\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/score-breakdown.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\nimport type { ScoreIntent } from \"./score-semantics\"\nimport { getScoreToneClasses } from \"./score-semantics\"\n\nexport interface ScoreFactor {\n key: string\n label: string\n score: number | null\n risk?: \"Low\" | \"Medium\" | \"High\"\n why: string\n}\n\nfunction getFactorBarColor(score: number, intent: ScoreIntent = \"positive\") {\n return getScoreToneClasses(score, intent).bar\n}\n\nfunction getRiskBadgeStyle(risk: \"Low\" | \"Medium\" | \"High\") {\n switch (risk) {\n case \"Low\":\n return \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n case \"Medium\":\n return \"text-amber-600 bg-amber-50 dark:text-amber-400 dark:bg-amber-950/30\"\n case \"High\":\n return \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n }\n}\n\ninterface ScoreBreakdownProps {\n factors: ScoreFactor[]\n onFactorFeedback?: (factorKey: string, type: \"up\" | \"down\" | null, detail?: string) => void\n className?: string\n initialFeedback?: Record<string, { type: \"up\" | \"down\"; detail: string }>\n scoreIntent?: ScoreIntent\n}\n\nfunction deriveInitialState<T>(\n init: Record<string, { type: \"up\" | \"down\"; detail: string }> | undefined,\n mapFn: (v: { type: \"up\" | \"down\"; detail: string }) => T,\n filterFn?: (v: { type: \"up\" | \"down\"; detail: string }) => boolean,\n): Record<string, T> {\n if (!init) return {}\n const entries = Object.entries(init)\n const filtered = filterFn ? entries.filter(([, v]) => filterFn(v)) : entries\n return Object.fromEntries(filtered.map(([k, v]) => [k, mapFn(v)]))\n}\n\nfunction ScoreBreakdown({ factors, onFactorFeedback, className, initialFeedback, scoreIntent = \"positive\" }: ScoreBreakdownProps) {\n const [feedback, setFeedback] = React.useState<Record<string, \"up\" | \"down\" | null>>(\n () => deriveInitialState(initialFeedback, (v) => v.type)\n )\n const [feedbackText, setFeedbackText] = React.useState<Record<string, string>>(\n () => deriveInitialState(initialFeedback, (v) => v.detail)\n )\n const [savedText, setSavedText] = React.useState<Record<string, string>>(\n () => deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail)\n )\n const [editingKey, setEditingKey] = React.useState<string | null>(null)\n\n // Sync state when initialFeedback prop changes (e.g. async hydration).\n // Skip keys the user is actively editing to avoid clobbering in-progress input.\n React.useEffect(() => {\n setFeedback((prev) => {\n const next: Record<string, \"up\" | \"down\" | null> = deriveInitialState(initialFeedback, (v) => v.type)\n // Preserve any key the user is currently editing\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n setFeedbackText((prev) => {\n const next = deriveInitialState(initialFeedback, (v) => v.detail)\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n setSavedText((prev) => {\n const next = deriveInitialState(initialFeedback, (v) => v.detail, (v) => !!v.detail)\n if (editingKey && prev[editingKey] !== undefined) {\n next[editingKey] = prev[editingKey]\n }\n return next\n })\n }, [initialFeedback]) // eslint-disable-line react-hooks/exhaustive-deps -- reads editingKey as guard, not trigger\n\n const handleFeedback = (factorKey: string, type: \"up\" | \"down\") => {\n const newState = feedback[factorKey] === type ? null : type\n setFeedback((prev) => ({ ...prev, [factorKey]: newState }))\n if (newState === null) {\n setEditingKey(null)\n onFactorFeedback?.(factorKey, null)\n } else {\n setEditingKey(factorKey)\n onFactorFeedback?.(factorKey, newState)\n }\n }\n\n const submitFeedbackText = (factorKey: string) => {\n const text = (feedbackText[factorKey] ?? \"\").trim()\n if (feedback[factorKey]) {\n onFactorFeedback?.(factorKey, feedback[factorKey]!, text ?? \"\")\n if (text) {\n setSavedText((prev) => ({ ...prev, [factorKey]: text }))\n }\n }\n setEditingKey(null)\n }\n\n return (\n <div className={cn(\"border border-border rounded-lg overflow-hidden divide-y divide-border/40\", className)}>\n {factors.map((factor) => {\n const feedbackState = feedback[factor.key]\n const hasThumb = feedbackState === \"up\" || feedbackState === \"down\"\n const isEditing = editingKey === factor.key\n const saved = savedText[factor.key]\n\n return (\n <div key={factor.key} className=\"px-3 py-3 space-y-1.5\">\n {/* Row 1: Factor name + score + rating buttons */}\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[13px] font-semibold text-foreground flex-1\">{factor.label}</span>\n\n {factor.score !== null ? (\n <span className=\"text-xs font-bold text-foreground tabular-nums\">{factor.score}<span className=\"text-muted-foreground font-normal\">/100</span></span>\n ) : factor.risk ? (\n <span\n className={cn(\n \"text-[11px] font-semibold px-1.5 py-0.5 rounded\",\n getRiskBadgeStyle(factor.risk)\n )}\n >\n {factor.risk}\n </span>\n ) : null}\n\n <div className=\"flex items-center gap-0.5 ml-1\">\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"up\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"up\"\n ? \"text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30\"\n : \"text-muted-foreground/40 hover:text-emerald-600 hover:bg-emerald-50/50\"\n )}\n title=\"This factor is accurate\"\n >\n <ThumbsUp className=\"w-3 h-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleFeedback(factor.key, \"down\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n feedbackState === \"down\"\n ? \"text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30\"\n : \"text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50\"\n )}\n title=\"Report issue with this factor\"\n >\n <ThumbsDown className=\"w-3 h-3\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Score bar */}\n {factor.score !== null && (\n <div className=\"w-full h-1 bg-muted rounded-full overflow-hidden\">\n <div\n className={cn(\"h-full rounded-full\", getFactorBarColor(factor.score, scoreIntent))}\n style={{ width: `${factor.score}%` }}\n />\n </div>\n )}\n\n {/* Row 3: Why text */}\n <p className=\"text-xs text-muted-foreground leading-relaxed\">{factor.why}</p>\n\n {/* Feedback input */}\n {hasThumb && isEditing && (\n <input\n type=\"text\"\n autoFocus\n value={feedbackText[factor.key] ?? \"\"}\n onChange={(e) =>\n setFeedbackText((prev) => ({ ...prev, [factor.key]: e.target.value }))\n }\n onKeyDown={(e) => {\n if (e.key === \"Enter\") submitFeedbackText(factor.key)\n if (e.key === \"Escape\") setEditingKey(null)\n }}\n onBlur={() => submitFeedbackText(factor.key)}\n placeholder={\n feedbackState === \"up\"\n ? \"What\\u2019s accurate? (optional)\"\n : \"What\\u2019s wrong? (optional)\"\n }\n className=\"w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring\"\n />\n )}\n {hasThumb && !isEditing && saved && (\n <button\n type=\"button\"\n onClick={() => {\n setFeedbackText((prev) => ({ ...prev, [factor.key]: saved }))\n setEditingKey(factor.key)\n }}\n className=\"text-[11px] text-muted-foreground/70 hover:text-muted-foreground transition-colors text-left leading-snug\"\n >\n {saved}\n </button>\n )}\n </div>\n )\n })}\n </div>\n )\n}\n\nexport { ScoreBreakdown }\nexport type { ScoreBreakdownProps }\n"],"mappings":"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","names":[]}
@@ -1,6 +1,8 @@
1
1
  import * as React from 'react';
2
+ import { ScoreIntent } from './score-semantics.js';
2
3
 
3
- declare function getScoreColor(score: number, denominator: number): "text-emerald-500" | "text-amber-500" | "text-red-500";
4
+ declare function getScoreColor(score: number, denominator: number, intent?: ScoreIntent): string;
5
+ declare function getScoreTrackColor(score: number, denominator: number, intent?: ScoreIntent): string;
4
6
  interface ScoreRingProps {
5
7
  score: number;
6
8
  denominator?: number;
@@ -8,7 +10,8 @@ interface ScoreRingProps {
8
10
  strokeWidth?: number;
9
11
  className?: string;
10
12
  showLabel?: boolean;
13
+ intent?: ScoreIntent;
11
14
  }
12
- declare function ScoreRing({ score, denominator, size, strokeWidth, className, showLabel, }: ScoreRingProps): React.JSX.Element;
15
+ declare function ScoreRing({ score, denominator, size, strokeWidth, className, showLabel, intent, }: ScoreRingProps): React.JSX.Element;
13
16
 
14
- export { ScoreRing, type ScoreRingProps, getScoreColor };
17
+ export { ScoreRing, type ScoreRingProps, getScoreColor, getScoreTrackColor };
@@ -1,16 +1,11 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/utils.js";
3
- function getScoreColor(score, denominator) {
4
- const pct = score / denominator * 100;
5
- if (pct >= 70) return "text-emerald-500";
6
- if (pct >= 40) return "text-amber-500";
7
- return "text-red-500";
3
+ import { getScoreToneClasses } from "./score-semantics.js";
4
+ function getScoreColor(score, denominator, intent = "positive") {
5
+ return getScoreToneClasses(score / denominator * 100, intent).text;
8
6
  }
9
- function getScoreTrackColor(score, denominator) {
10
- const pct = score / denominator * 100;
11
- if (pct >= 70) return "text-emerald-500/15";
12
- if (pct >= 40) return "text-amber-500/15";
13
- return "text-red-500/15";
7
+ function getScoreTrackColor(score, denominator, intent = "positive") {
8
+ return getScoreToneClasses(score / denominator * 100, intent).track;
14
9
  }
15
10
  function ScoreRing({
16
11
  score,
@@ -18,7 +13,8 @@ function ScoreRing({
18
13
  size = 120,
19
14
  strokeWidth = 10,
20
15
  className,
21
- showLabel = true
16
+ showLabel = true,
17
+ intent = "positive"
22
18
  }) {
23
19
  const radius = (size - strokeWidth) / 2;
24
20
  const circumference = 2 * Math.PI * radius;
@@ -42,7 +38,7 @@ function ScoreRing({
42
38
  fill: "none",
43
39
  stroke: "currentColor",
44
40
  strokeWidth,
45
- className: getScoreTrackColor(score, denominator)
41
+ className: getScoreTrackColor(score, denominator, intent)
46
42
  }
47
43
  ),
48
44
  /* @__PURE__ */ jsx(
@@ -57,7 +53,7 @@ function ScoreRing({
57
53
  strokeLinecap: "round",
58
54
  strokeDasharray: circumference,
59
55
  strokeDashoffset: offset,
60
- className: cn("transition-all duration-500", getScoreColor(score, denominator))
56
+ className: cn("transition-all duration-500", getScoreColor(score, denominator, intent))
61
57
  }
62
58
  )
63
59
  ]
@@ -74,6 +70,7 @@ function ScoreRing({
74
70
  }
75
71
  export {
76
72
  ScoreRing,
77
- getScoreColor
73
+ getScoreColor,
74
+ getScoreTrackColor
78
75
  };
79
76
  //# sourceMappingURL=score-ring.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/score-ring.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { cn } from \"../lib/utils\"\n\nfunction getScoreColor(score: number, denominator: number) {\n const pct = (score / denominator) * 100\n if (pct >= 70) return \"text-emerald-500\"\n if (pct >= 40) return \"text-amber-500\"\n return \"text-red-500\"\n}\n\nfunction getScoreTrackColor(score: number, denominator: number) {\n const pct = (score / denominator) * 100\n if (pct >= 70) return \"text-emerald-500/15\"\n if (pct >= 40) return \"text-amber-500/15\"\n return \"text-red-500/15\"\n}\n\ninterface ScoreRingProps {\n score: number\n denominator?: number\n size?: number\n strokeWidth?: number\n className?: string\n showLabel?: boolean\n}\n\nfunction ScoreRing({\n score,\n denominator = 100,\n size = 120,\n strokeWidth = 10,\n className,\n showLabel = true,\n}: ScoreRingProps) {\n const radius = (size - strokeWidth) / 2\n const circumference = 2 * Math.PI * radius\n const pct = Math.min(score / denominator, 1)\n const offset = circumference * (1 - pct)\n\n return (\n <div className={cn(\"relative inline-flex items-center justify-center\", className)}>\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n className=\"-rotate-90\"\n >\n {/* Track */}\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n className={getScoreTrackColor(score, denominator)}\n />\n {/* Fill */}\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n className={cn(\"transition-all duration-500\", getScoreColor(score, denominator))}\n />\n </svg>\n {showLabel && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-2xl font-bold text-foreground leading-none\">\n {score}\n </span>\n <span className=\"text-xs text-muted-foreground mt-0.5\">\n /{denominator}\n </span>\n </div>\n )}\n </div>\n )\n}\n\nexport { ScoreRing, getScoreColor }\nexport type { ScoreRingProps }\n"],"mappings":"AAyCM,SAOE,KAPF;AAxCN,SAAS,UAAU;AAEnB,SAAS,cAAc,OAAe,aAAqB;AACzD,QAAM,MAAO,QAAQ,cAAe;AACpC,MAAI,OAAO,GAAI,QAAO;AACtB,MAAI,OAAO,GAAI,QAAO;AACtB,SAAO;AACT;AAEA,SAAS,mBAAmB,OAAe,aAAqB;AAC9D,QAAM,MAAO,QAAQ,cAAe;AACpC,MAAI,OAAO,GAAI,QAAO;AACtB,MAAI,OAAO,GAAI,QAAO;AACtB,SAAO;AACT;AAWA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA,cAAc;AAAA,EACd,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AAAA,EACA,YAAY;AACd,GAAmB;AACjB,QAAM,UAAU,OAAO,eAAe;AACtC,QAAM,gBAAgB,IAAI,KAAK,KAAK;AACpC,QAAM,MAAM,KAAK,IAAI,QAAQ,aAAa,CAAC;AAC3C,QAAM,SAAS,iBAAiB,IAAI;AAEpC,SACE,qBAAC,SAAI,WAAW,GAAG,oDAAoD,SAAS,GAC9E;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,SAAS,OAAO,IAAI,IAAI,IAAI;AAAA,QAC5B,WAAU;AAAA,QAGV;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,MAAK;AAAA,cACL,QAAO;AAAA,cACP;AAAA,cACA,WAAW,mBAAmB,OAAO,WAAW;AAAA;AAAA,UAClD;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,MAAK;AAAA,cACL,QAAO;AAAA,cACP;AAAA,cACA,eAAc;AAAA,cACd,iBAAiB;AAAA,cACjB,kBAAkB;AAAA,cAClB,WAAW,GAAG,+BAA+B,cAAc,OAAO,WAAW,CAAC;AAAA;AAAA,UAChF;AAAA;AAAA;AAAA,IACF;AAAA,IACC,aACC,qBAAC,SAAI,WAAU,8DACb;AAAA,0BAAC,UAAK,WAAU,mDACb,iBACH;AAAA,MACA,qBAAC,UAAK,WAAU,wCAAuC;AAAA;AAAA,QACnD;AAAA,SACJ;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/score-ring.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { cn } from \"../lib/utils\"\nimport type { ScoreIntent } from \"./score-semantics\"\nimport { getScoreToneClasses } from \"./score-semantics\"\n\nfunction getScoreColor(score: number, denominator: number, intent: ScoreIntent = \"positive\") {\n return getScoreToneClasses((score / denominator) * 100, intent).text\n}\n\nfunction getScoreTrackColor(score: number, denominator: number, intent: ScoreIntent = \"positive\") {\n return getScoreToneClasses((score / denominator) * 100, intent).track\n}\n\ninterface ScoreRingProps {\n score: number\n denominator?: number\n size?: number\n strokeWidth?: number\n className?: string\n showLabel?: boolean\n intent?: ScoreIntent\n}\n\nfunction ScoreRing({\n score,\n denominator = 100,\n size = 120,\n strokeWidth = 10,\n className,\n showLabel = true,\n intent = \"positive\",\n}: ScoreRingProps) {\n const radius = (size - strokeWidth) / 2\n const circumference = 2 * Math.PI * radius\n const pct = Math.min(score / denominator, 1)\n const offset = circumference * (1 - pct)\n\n return (\n <div className={cn(\"relative inline-flex items-center justify-center\", className)}>\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n className=\"-rotate-90\"\n >\n {/* Track */}\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n className={getScoreTrackColor(score, denominator, intent)}\n />\n {/* Fill */}\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n className={cn(\"transition-all duration-500\", getScoreColor(score, denominator, intent))}\n />\n </svg>\n {showLabel && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center\">\n <span className=\"text-2xl font-bold text-foreground leading-none\">\n {score}\n </span>\n <span className=\"text-xs text-muted-foreground mt-0.5\">\n /{denominator}\n </span>\n </div>\n )}\n </div>\n )\n}\n\nexport { ScoreRing, getScoreColor, getScoreTrackColor }\nexport type { ScoreRingProps }\n"],"mappings":"AAuCM,SAOE,KAPF;AAtCN,SAAS,UAAU;AAEnB,SAAS,2BAA2B;AAEpC,SAAS,cAAc,OAAe,aAAqB,SAAsB,YAAY;AAC3F,SAAO,oBAAqB,QAAQ,cAAe,KAAK,MAAM,EAAE;AAClE;AAEA,SAAS,mBAAmB,OAAe,aAAqB,SAAsB,YAAY;AAChG,SAAO,oBAAqB,QAAQ,cAAe,KAAK,MAAM,EAAE;AAClE;AAYA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA,cAAc;AAAA,EACd,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AAAA,EACA,YAAY;AAAA,EACZ,SAAS;AACX,GAAmB;AACjB,QAAM,UAAU,OAAO,eAAe;AACtC,QAAM,gBAAgB,IAAI,KAAK,KAAK;AACpC,QAAM,MAAM,KAAK,IAAI,QAAQ,aAAa,CAAC;AAC3C,QAAM,SAAS,iBAAiB,IAAI;AAEpC,SACE,qBAAC,SAAI,WAAW,GAAG,oDAAoD,SAAS,GAC9E;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,SAAS,OAAO,IAAI,IAAI,IAAI;AAAA,QAC5B,WAAU;AAAA,QAGV;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,MAAK;AAAA,cACL,QAAO;AAAA,cACP;AAAA,cACA,WAAW,mBAAmB,OAAO,aAAa,MAAM;AAAA;AAAA,UAC1D;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,MAAK;AAAA,cACL,QAAO;AAAA,cACP;AAAA,cACA,eAAc;AAAA,cACd,iBAAiB;AAAA,cACjB,kBAAkB;AAAA,cAClB,WAAW,GAAG,+BAA+B,cAAc,OAAO,aAAa,MAAM,CAAC;AAAA;AAAA,UACxF;AAAA;AAAA;AAAA,IACF;AAAA,IACC,aACC,qBAAC,SAAI,WAAU,8DACb;AAAA,0BAAC,UAAK,WAAU,mDACb,iBACH;AAAA,MACA,qBAAC,UAAK,WAAU,wCAAuC;AAAA;AAAA,QACnD;AAAA,SACJ;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
@@ -0,0 +1,27 @@
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 };
@@ -0,0 +1,173 @@
1
+ const URGENCY_SCORE_CLASSES = {
2
+ Urgent: {
3
+ solid: "bg-red-600 text-white",
4
+ outline: "border-red-300 bg-red-50 text-red-800",
5
+ dot: "bg-red-600",
6
+ trigger: "border-red-300 bg-red-50 text-red-800",
7
+ hover: "hover:bg-red-100",
8
+ open: "bg-red-100",
9
+ text: "text-red-600",
10
+ track: "text-red-600/15",
11
+ bar: "bg-red-600"
12
+ },
13
+ High: {
14
+ solid: "bg-orange-500 text-white",
15
+ outline: "border-orange-300 bg-orange-50 text-orange-800",
16
+ dot: "bg-orange-500",
17
+ trigger: "border-orange-300 bg-orange-50 text-orange-800",
18
+ hover: "hover:bg-orange-100",
19
+ open: "bg-orange-100",
20
+ text: "text-orange-500",
21
+ track: "text-orange-500/15",
22
+ bar: "bg-orange-500"
23
+ },
24
+ Medium: {
25
+ solid: "bg-amber-500 text-white",
26
+ outline: "border-amber-300 bg-amber-50 text-amber-800",
27
+ dot: "bg-amber-500",
28
+ trigger: "border-amber-300 bg-amber-50 text-amber-800",
29
+ hover: "hover:bg-amber-100",
30
+ open: "bg-amber-100",
31
+ text: "text-amber-500",
32
+ track: "text-amber-500/15",
33
+ bar: "bg-amber-500"
34
+ },
35
+ Low: {
36
+ solid: "bg-neutral-300 text-neutral-900",
37
+ outline: "border-neutral-200 bg-neutral-50 text-neutral-700",
38
+ dot: "bg-neutral-400",
39
+ trigger: "border-neutral-200 bg-neutral-50 text-neutral-700",
40
+ hover: "hover:bg-neutral-100",
41
+ open: "bg-neutral-100",
42
+ text: "text-neutral-400",
43
+ track: "text-neutral-400/15",
44
+ bar: "bg-neutral-400"
45
+ }
46
+ };
47
+ const RISK_SCORE_CLASSES = {
48
+ "High Risk": {
49
+ solid: "bg-red-600 text-white",
50
+ outline: "border-red-300 bg-red-50 text-red-800",
51
+ dot: "bg-red-600",
52
+ trigger: "border-red-300 bg-red-50 text-red-800",
53
+ hover: "hover:bg-red-100",
54
+ open: "bg-red-100",
55
+ text: "text-red-600",
56
+ track: "text-red-600/15",
57
+ bar: "bg-red-600"
58
+ },
59
+ "Medium Risk": {
60
+ solid: "bg-amber-500 text-white",
61
+ outline: "border-amber-300 bg-amber-50 text-amber-800",
62
+ dot: "bg-amber-500",
63
+ trigger: "border-amber-300 bg-amber-50 text-amber-800",
64
+ hover: "hover:bg-amber-100",
65
+ open: "bg-amber-100",
66
+ text: "text-amber-500",
67
+ track: "text-amber-500/15",
68
+ bar: "bg-amber-500"
69
+ },
70
+ "Low Risk": {
71
+ solid: "bg-neutral-300 text-neutral-900",
72
+ outline: "border-neutral-200 bg-neutral-50 text-neutral-700",
73
+ dot: "bg-neutral-400",
74
+ trigger: "border-neutral-200 bg-neutral-50 text-neutral-700",
75
+ hover: "hover:bg-neutral-100",
76
+ open: "bg-neutral-100",
77
+ text: "text-neutral-400",
78
+ track: "text-neutral-400/15",
79
+ bar: "bg-neutral-400"
80
+ }
81
+ };
82
+ const POSITIVE_SCORE_CLASSES = {
83
+ High: {
84
+ solid: "bg-emerald-500 text-white",
85
+ outline: "border-emerald-300 bg-emerald-50 text-emerald-800",
86
+ dot: "bg-emerald-500",
87
+ trigger: "border-emerald-300 bg-emerald-50 text-emerald-800",
88
+ hover: "hover:bg-emerald-100",
89
+ open: "bg-emerald-100",
90
+ text: "text-emerald-500",
91
+ track: "text-emerald-500/15",
92
+ bar: "bg-emerald-500"
93
+ },
94
+ Medium: {
95
+ solid: "bg-amber-500 text-white",
96
+ outline: "border-amber-300 bg-amber-50 text-amber-800",
97
+ dot: "bg-amber-500",
98
+ trigger: "border-amber-300 bg-amber-50 text-amber-800",
99
+ hover: "hover:bg-amber-100",
100
+ open: "bg-amber-100",
101
+ text: "text-amber-500",
102
+ track: "text-amber-500/15",
103
+ bar: "bg-amber-500"
104
+ },
105
+ Low: {
106
+ solid: "bg-red-500 text-white",
107
+ outline: "border-red-300 bg-red-50 text-red-800",
108
+ dot: "bg-red-500",
109
+ trigger: "border-red-300 bg-red-50 text-red-800",
110
+ hover: "hover:bg-red-100",
111
+ open: "bg-red-100",
112
+ text: "text-red-500",
113
+ track: "text-red-500/15",
114
+ bar: "bg-red-500"
115
+ }
116
+ };
117
+ const SCORE_TONE_CLASSES = {
118
+ alert: "bg-red-50 text-red-600",
119
+ warn: "bg-amber-50 text-amber-600",
120
+ info: "bg-blue-50 text-blue-600"
121
+ };
122
+ const DEFAULT_SCORE_TONE_CLASS = "bg-muted text-muted-foreground";
123
+ function getUrgencyLevel(score) {
124
+ if (score >= 80) return "Urgent";
125
+ if (score >= 60) return "High";
126
+ if (score >= 35) return "Medium";
127
+ return "Low";
128
+ }
129
+ function getUrgencyRange(label) {
130
+ switch (label) {
131
+ case "Urgent":
132
+ return "80-100";
133
+ case "High":
134
+ return "60-79";
135
+ case "Medium":
136
+ return "35-59";
137
+ case "Low":
138
+ return "0-34";
139
+ }
140
+ }
141
+ function getRiskLevel(score) {
142
+ if (score >= 70) return "High Risk";
143
+ if (score >= 40) return "Medium Risk";
144
+ return "Low Risk";
145
+ }
146
+ function getPositiveLevel(score) {
147
+ if (score >= 70) return "High";
148
+ if (score >= 40) return "Medium";
149
+ return "Low";
150
+ }
151
+ function getScoreToneClasses(score, intent) {
152
+ switch (intent) {
153
+ case "urgency":
154
+ return URGENCY_SCORE_CLASSES[getUrgencyLevel(score)];
155
+ case "risk":
156
+ return RISK_SCORE_CLASSES[getRiskLevel(score)];
157
+ case "positive":
158
+ return POSITIVE_SCORE_CLASSES[getPositiveLevel(score)];
159
+ }
160
+ }
161
+ export {
162
+ DEFAULT_SCORE_TONE_CLASS,
163
+ POSITIVE_SCORE_CLASSES,
164
+ RISK_SCORE_CLASSES,
165
+ SCORE_TONE_CLASSES,
166
+ URGENCY_SCORE_CLASSES,
167
+ getPositiveLevel,
168
+ getRiskLevel,
169
+ getScoreToneClasses,
170
+ getUrgencyLevel,
171
+ getUrgencyRange
172
+ };
173
+ //# sourceMappingURL=score-semantics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/score-semantics.ts"],"sourcesContent":["export type ScoreIntent = \"urgency\" | \"risk\" | \"positive\"\n\nexport type UrgencyLevel = \"Low\" | \"Medium\" | \"High\" | \"Urgent\"\nexport type RiskLevel = \"Low Risk\" | \"Medium Risk\" | \"High Risk\"\nexport type PositiveLevel = \"Low\" | \"Medium\" | \"High\"\n\nexport type ScoreSemanticClasses = {\n solid: string\n outline: string\n dot: string\n trigger: string\n hover: string\n open: string\n text: string\n track: string\n bar: string\n}\n\nexport const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses> = {\n Urgent: {\n solid: \"bg-red-600 text-white\",\n outline: \"border-red-300 bg-red-50 text-red-800\",\n dot: \"bg-red-600\",\n trigger: \"border-red-300 bg-red-50 text-red-800\",\n hover: \"hover:bg-red-100\",\n open: \"bg-red-100\",\n text: \"text-red-600\",\n track: \"text-red-600/15\",\n bar: \"bg-red-600\",\n },\n High: {\n solid: \"bg-orange-500 text-white\",\n outline: \"border-orange-300 bg-orange-50 text-orange-800\",\n dot: \"bg-orange-500\",\n trigger: \"border-orange-300 bg-orange-50 text-orange-800\",\n hover: \"hover:bg-orange-100\",\n open: \"bg-orange-100\",\n text: \"text-orange-500\",\n track: \"text-orange-500/15\",\n bar: \"bg-orange-500\",\n },\n Medium: {\n solid: \"bg-amber-500 text-white\",\n outline: \"border-amber-300 bg-amber-50 text-amber-800\",\n dot: \"bg-amber-500\",\n trigger: \"border-amber-300 bg-amber-50 text-amber-800\",\n hover: \"hover:bg-amber-100\",\n open: \"bg-amber-100\",\n text: \"text-amber-500\",\n track: \"text-amber-500/15\",\n bar: \"bg-amber-500\",\n },\n Low: {\n solid: \"bg-neutral-300 text-neutral-900\",\n outline: \"border-neutral-200 bg-neutral-50 text-neutral-700\",\n dot: \"bg-neutral-400\",\n trigger: \"border-neutral-200 bg-neutral-50 text-neutral-700\",\n hover: \"hover:bg-neutral-100\",\n open: \"bg-neutral-100\",\n text: \"text-neutral-400\",\n track: \"text-neutral-400/15\",\n bar: \"bg-neutral-400\",\n },\n}\n\nexport const RISK_SCORE_CLASSES: Record<RiskLevel, ScoreSemanticClasses> = {\n \"High Risk\": {\n solid: \"bg-red-600 text-white\",\n outline: \"border-red-300 bg-red-50 text-red-800\",\n dot: \"bg-red-600\",\n trigger: \"border-red-300 bg-red-50 text-red-800\",\n hover: \"hover:bg-red-100\",\n open: \"bg-red-100\",\n text: \"text-red-600\",\n track: \"text-red-600/15\",\n bar: \"bg-red-600\",\n },\n \"Medium Risk\": {\n solid: \"bg-amber-500 text-white\",\n outline: \"border-amber-300 bg-amber-50 text-amber-800\",\n dot: \"bg-amber-500\",\n trigger: \"border-amber-300 bg-amber-50 text-amber-800\",\n hover: \"hover:bg-amber-100\",\n open: \"bg-amber-100\",\n text: \"text-amber-500\",\n track: \"text-amber-500/15\",\n bar: \"bg-amber-500\",\n },\n \"Low Risk\": {\n solid: \"bg-neutral-300 text-neutral-900\",\n outline: \"border-neutral-200 bg-neutral-50 text-neutral-700\",\n dot: \"bg-neutral-400\",\n trigger: \"border-neutral-200 bg-neutral-50 text-neutral-700\",\n hover: \"hover:bg-neutral-100\",\n open: \"bg-neutral-100\",\n text: \"text-neutral-400\",\n track: \"text-neutral-400/15\",\n bar: \"bg-neutral-400\",\n },\n}\n\nexport const POSITIVE_SCORE_CLASSES: Record<PositiveLevel, ScoreSemanticClasses> = {\n High: {\n solid: \"bg-emerald-500 text-white\",\n outline: \"border-emerald-300 bg-emerald-50 text-emerald-800\",\n dot: \"bg-emerald-500\",\n trigger: \"border-emerald-300 bg-emerald-50 text-emerald-800\",\n hover: \"hover:bg-emerald-100\",\n open: \"bg-emerald-100\",\n text: \"text-emerald-500\",\n track: \"text-emerald-500/15\",\n bar: \"bg-emerald-500\",\n },\n Medium: {\n solid: \"bg-amber-500 text-white\",\n outline: \"border-amber-300 bg-amber-50 text-amber-800\",\n dot: \"bg-amber-500\",\n trigger: \"border-amber-300 bg-amber-50 text-amber-800\",\n hover: \"hover:bg-amber-100\",\n open: \"bg-amber-100\",\n text: \"text-amber-500\",\n track: \"text-amber-500/15\",\n bar: \"bg-amber-500\",\n },\n Low: {\n solid: \"bg-red-500 text-white\",\n outline: \"border-red-300 bg-red-50 text-red-800\",\n dot: \"bg-red-500\",\n trigger: \"border-red-300 bg-red-50 text-red-800\",\n hover: \"hover:bg-red-100\",\n open: \"bg-red-100\",\n text: \"text-red-500\",\n track: \"text-red-500/15\",\n bar: \"bg-red-500\",\n },\n}\n\nexport const SCORE_TONE_CLASSES: Record<string, string> = {\n alert: \"bg-red-50 text-red-600\",\n warn: \"bg-amber-50 text-amber-600\",\n info: \"bg-blue-50 text-blue-600\",\n}\n\nexport const DEFAULT_SCORE_TONE_CLASS = \"bg-muted text-muted-foreground\"\n\nexport function getUrgencyLevel(score: number): UrgencyLevel {\n if (score >= 80) return \"Urgent\"\n if (score >= 60) return \"High\"\n if (score >= 35) return \"Medium\"\n return \"Low\"\n}\n\nexport function getUrgencyRange(label: UrgencyLevel): string {\n switch (label) {\n case \"Urgent\":\n return \"80-100\"\n case \"High\":\n return \"60-79\"\n case \"Medium\":\n return \"35-59\"\n case \"Low\":\n return \"0-34\"\n }\n}\n\nexport function getRiskLevel(score: number): RiskLevel {\n if (score >= 70) return \"High Risk\"\n if (score >= 40) return \"Medium Risk\"\n return \"Low Risk\"\n}\n\nexport function getPositiveLevel(score: number): PositiveLevel {\n if (score >= 70) return \"High\"\n if (score >= 40) return \"Medium\"\n return \"Low\"\n}\n\nexport function getScoreToneClasses(score: number, intent: ScoreIntent): ScoreSemanticClasses {\n switch (intent) {\n case \"urgency\":\n return URGENCY_SCORE_CLASSES[getUrgencyLevel(score)]\n case \"risk\":\n return RISK_SCORE_CLASSES[getRiskLevel(score)]\n case \"positive\":\n return POSITIVE_SCORE_CLASSES[getPositiveLevel(score)]\n }\n}\n"],"mappings":"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","names":[]}
@@ -4,6 +4,7 @@ import './feedback-primitives.js';
4
4
  import './quick-action-sidebar-nav.js';
5
5
  import './quick-action-modal.js';
6
6
  import './score-breakdown.js';
7
+ import './score-semantics.js';
7
8
  import './suggested-actions.js';
8
9
  import './detail-view.js';
9
10
  import './inbox-toolbar.js';
@@ -18,9 +19,9 @@ import 'lucide-react';
18
19
 
19
20
  declare function getSignalScoreUrgencyLabel(score: number, providedLabel?: SignalScoreUrgencyLabel): SignalScoreUrgencyLabel;
20
21
  declare function scoreRangeForUrgency(label: SignalScoreUrgencyLabel): string;
21
- /** Shared tone-to-class map. Re-exported for signal-priority-popover. */
22
+ /** Shared tone-to-class map. Re-exported for backward compatibility. */
22
23
  declare const SIGNAL_TONE_CLASSES: Record<string, string>;
23
- /** Default tone for missing/unknown tone values */
24
+ /** Default tone for missing/unknown tone values. Re-exported for backward compatibility. */
24
25
  declare const DEFAULT_TONE_CLASS = "bg-muted text-muted-foreground";
25
26
  interface ScoreWhyChipsProps {
26
27
  item: QueueItem;
@@ -17,24 +17,17 @@ import {
17
17
  } from "lucide-react";
18
18
  import { FeedbackFooter } from "./feedback-primitives.js";
19
19
  import { cn } from "../lib/utils.js";
20
+ import {
21
+ DEFAULT_SCORE_TONE_CLASS,
22
+ SCORE_TONE_CLASSES,
23
+ getUrgencyLevel,
24
+ getUrgencyRange
25
+ } from "./score-semantics.js";
20
26
  function getSignalScoreUrgencyLabel(score, providedLabel) {
21
- if (providedLabel) return providedLabel;
22
- if (score >= 80) return "Urgent";
23
- if (score >= 60) return "High";
24
- if (score >= 35) return "Medium";
25
- return "Low";
27
+ return providedLabel != null ? providedLabel : getUrgencyLevel(score);
26
28
  }
27
29
  function scoreRangeForUrgency(label) {
28
- switch (label) {
29
- case "Urgent":
30
- return "80-100";
31
- case "High":
32
- return "60-79";
33
- case "Medium":
34
- return "35-59";
35
- case "Low":
36
- return "0-34";
37
- }
30
+ return getUrgencyRange(label);
38
31
  }
39
32
  function makeDomId(...parts) {
40
33
  return parts.filter((part) => Boolean(part)).join("-").replace(/[^A-Za-z0-9_-]+/g, "-");
@@ -70,12 +63,8 @@ function resolveIcon(iconName) {
70
63
  if (!iconName) return Activity;
71
64
  return (_a = SIGNAL_TYPE_ICONS[iconName]) != null ? _a : Activity;
72
65
  }
73
- const SIGNAL_TONE_CLASSES = {
74
- alert: "bg-red-50 text-red-600",
75
- warn: "bg-amber-50 text-amber-600",
76
- info: "bg-blue-50 text-blue-600"
77
- };
78
- const DEFAULT_TONE_CLASS = "bg-muted text-muted-foreground";
66
+ const SIGNAL_TONE_CLASSES = SCORE_TONE_CLASSES;
67
+ const DEFAULT_TONE_CLASS = DEFAULT_SCORE_TONE_CLASS;
79
68
  function slotValue(value) {
80
69
  return value && value.trim().length > 0 ? value : "";
81
70
  }