@handled-ai/design-system 0.18.20 → 0.18.22

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.
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const badgeVariants: (props?: ({
6
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
6
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | null | undefined;
7
7
  } & class_variance_authority_types.ClassProp) | undefined) => string;
8
8
  declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
9
9
  asChild?: boolean;
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
6
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
@@ -12,7 +12,7 @@ import { VariantProps } from 'class-variance-authority';
12
12
  */
13
13
  type PillStatus = "success" | "warning" | "error" | "neutral" | "info";
14
14
  declare const pillVariants: (props?: ({
15
- variant?: "error" | "default" | "secondary" | "destructive" | "outline" | "ghost" | "info" | "neutral" | "warning" | "success" | null | undefined;
15
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "error" | "info" | "neutral" | "warning" | "success" | null | undefined;
16
16
  } & class_variance_authority_types.ClassProp) | undefined) => string;
17
17
  interface PillProps extends React.ComponentProps<"span">, VariantProps<typeof pillVariants> {
18
18
  }
@@ -24,9 +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
- import { getScoreColor } from "./score-ring.js";
27
+ import { getScoreToneClasses } from "./score-semantics.js";
28
28
  function getFactorBarColor(score, intent = "positive") {
29
- return getScoreColor(score, 100, intent).replace("text-", "bg-");
29
+ return getScoreToneClasses(score, intent).bar;
30
30
  }
31
31
  function getRiskBadgeStyle(risk) {
32
32
  switch (risk) {
@@ -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\"\nimport type { ScoreIntent } from \"./score-semantics\"\nimport { getScoreColor } from \"./score-ring\"\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 getScoreColor(score, 100, intent).replace(\"text-\", \"bg-\")\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
+ {"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,8 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { ScoreIntent } from './score-semantics.js';
3
3
 
4
- declare function getScoreColor(score: number, denominator: number, intent?: ScoreIntent): "text-orange-500" | "text-red-600" | "text-amber-500" | "text-neutral-400" | "text-emerald-500" | "text-red-500";
5
- declare function getScoreTrackColor(score: number, denominator: number, intent?: ScoreIntent): "text-red-600/15" | "text-orange-500/15" | "text-amber-500/15" | "text-neutral-400/15" | "text-emerald-500/15" | "text-red-500/15";
4
+ declare function getScoreColor(score: number, denominator: number, intent?: ScoreIntent): string;
5
+ declare function getScoreTrackColor(score: number, denominator: number, intent?: ScoreIntent): string;
6
6
  interface ScoreRingProps {
7
7
  score: number;
8
8
  denominator?: number;
@@ -1,38 +1,11 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/utils.js";
3
+ import { getScoreToneClasses } from "./score-semantics.js";
3
4
  function getScoreColor(score, denominator, intent = "positive") {
4
- const pct = score / denominator * 100;
5
- if (intent === "urgency") {
6
- if (pct >= 80) return "text-red-600";
7
- if (pct >= 60) return "text-orange-500";
8
- if (pct >= 35) return "text-amber-500";
9
- return "text-neutral-400";
10
- }
11
- if (intent === "risk") {
12
- if (pct >= 70) return "text-red-600";
13
- if (pct >= 40) return "text-amber-500";
14
- return "text-neutral-400";
15
- }
16
- if (pct >= 70) return "text-emerald-500";
17
- if (pct >= 40) return "text-amber-500";
18
- return "text-red-500";
5
+ return getScoreToneClasses(score / denominator * 100, intent).text;
19
6
  }
20
7
  function getScoreTrackColor(score, denominator, intent = "positive") {
21
- const pct = score / denominator * 100;
22
- if (intent === "urgency") {
23
- if (pct >= 80) return "text-red-600/15";
24
- if (pct >= 60) return "text-orange-500/15";
25
- if (pct >= 35) return "text-amber-500/15";
26
- return "text-neutral-400/15";
27
- }
28
- if (intent === "risk") {
29
- if (pct >= 70) return "text-red-600/15";
30
- if (pct >= 40) return "text-amber-500/15";
31
- return "text-neutral-400/15";
32
- }
33
- if (pct >= 70) return "text-emerald-500/15";
34
- if (pct >= 40) return "text-amber-500/15";
35
- return "text-red-500/15";
8
+ return getScoreToneClasses(score / denominator * 100, intent).track;
36
9
  }
37
10
  function ScoreRing({
38
11
  score,
@@ -1 +1 @@
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\"\n\nfunction getScoreColor(score: number, denominator: number, intent: ScoreIntent = \"positive\") {\n const pct = (score / denominator) * 100\n\n if (intent === \"urgency\") {\n if (pct >= 80) return \"text-red-600\"\n if (pct >= 60) return \"text-orange-500\"\n if (pct >= 35) return \"text-amber-500\"\n return \"text-neutral-400\"\n }\n\n if (intent === \"risk\") {\n if (pct >= 70) return \"text-red-600\"\n if (pct >= 40) return \"text-amber-500\"\n return \"text-neutral-400\"\n }\n\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, intent: ScoreIntent = \"positive\") {\n const pct = (score / denominator) * 100\n\n if (intent === \"urgency\") {\n if (pct >= 80) return \"text-red-600/15\"\n if (pct >= 60) return \"text-orange-500/15\"\n if (pct >= 35) return \"text-amber-500/15\"\n return \"text-neutral-400/15\"\n }\n\n if (intent === \"risk\") {\n if (pct >= 70) return \"text-red-600/15\"\n if (pct >= 40) return \"text-amber-500/15\"\n return \"text-neutral-400/15\"\n }\n\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 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":"AAwEM,SAOE,KAPF;AAvEN,SAAS,UAAU;AAGnB,SAAS,cAAc,OAAe,aAAqB,SAAsB,YAAY;AAC3F,QAAM,MAAO,QAAQ,cAAe;AAEpC,MAAI,WAAW,WAAW;AACxB,QAAI,OAAO,GAAI,QAAO;AACtB,QAAI,OAAO,GAAI,QAAO;AACtB,QAAI,OAAO,GAAI,QAAO;AACtB,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,QAAQ;AACrB,QAAI,OAAO,GAAI,QAAO;AACtB,QAAI,OAAO,GAAI,QAAO;AACtB,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,GAAI,QAAO;AACtB,MAAI,OAAO,GAAI,QAAO;AACtB,SAAO;AACT;AAEA,SAAS,mBAAmB,OAAe,aAAqB,SAAsB,YAAY;AAChG,QAAM,MAAO,QAAQ,cAAe;AAEpC,MAAI,WAAW,WAAW;AACxB,QAAI,OAAO,GAAI,QAAO;AACtB,QAAI,OAAO,GAAI,QAAO;AACtB,QAAI,OAAO,GAAI,QAAO;AACtB,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,QAAQ;AACrB,QAAI,OAAO,GAAI,QAAO;AACtB,QAAI,OAAO,GAAI,QAAO;AACtB,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,GAAI,QAAO;AACtB,MAAI,OAAO,GAAI,QAAO;AACtB,SAAO;AACT;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":[]}
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":[]}
@@ -9,6 +9,9 @@ type ScoreSemanticClasses = {
9
9
  trigger: string;
10
10
  hover: string;
11
11
  open: string;
12
+ text: string;
13
+ track: string;
14
+ bar: string;
12
15
  };
13
16
  declare const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses>;
14
17
  declare const RISK_SCORE_CLASSES: Record<RiskLevel, ScoreSemanticClasses>;
@@ -5,7 +5,10 @@ const URGENCY_SCORE_CLASSES = {
5
5
  dot: "bg-red-600",
6
6
  trigger: "border-red-300 bg-red-50 text-red-800",
7
7
  hover: "hover:bg-red-100",
8
- open: "bg-red-100"
8
+ open: "bg-red-100",
9
+ text: "text-red-600",
10
+ track: "text-red-600/15",
11
+ bar: "bg-red-600"
9
12
  },
10
13
  High: {
11
14
  solid: "bg-orange-500 text-white",
@@ -13,7 +16,10 @@ const URGENCY_SCORE_CLASSES = {
13
16
  dot: "bg-orange-500",
14
17
  trigger: "border-orange-300 bg-orange-50 text-orange-800",
15
18
  hover: "hover:bg-orange-100",
16
- open: "bg-orange-100"
19
+ open: "bg-orange-100",
20
+ text: "text-orange-500",
21
+ track: "text-orange-500/15",
22
+ bar: "bg-orange-500"
17
23
  },
18
24
  Medium: {
19
25
  solid: "bg-amber-500 text-white",
@@ -21,7 +27,10 @@ const URGENCY_SCORE_CLASSES = {
21
27
  dot: "bg-amber-500",
22
28
  trigger: "border-amber-300 bg-amber-50 text-amber-800",
23
29
  hover: "hover:bg-amber-100",
24
- open: "bg-amber-100"
30
+ open: "bg-amber-100",
31
+ text: "text-amber-500",
32
+ track: "text-amber-500/15",
33
+ bar: "bg-amber-500"
25
34
  },
26
35
  Low: {
27
36
  solid: "bg-neutral-300 text-neutral-900",
@@ -29,7 +38,10 @@ const URGENCY_SCORE_CLASSES = {
29
38
  dot: "bg-neutral-400",
30
39
  trigger: "border-neutral-200 bg-neutral-50 text-neutral-700",
31
40
  hover: "hover:bg-neutral-100",
32
- open: "bg-neutral-100"
41
+ open: "bg-neutral-100",
42
+ text: "text-neutral-400",
43
+ track: "text-neutral-400/15",
44
+ bar: "bg-neutral-400"
33
45
  }
34
46
  };
35
47
  const RISK_SCORE_CLASSES = {
@@ -39,15 +51,21 @@ const RISK_SCORE_CLASSES = {
39
51
  dot: "bg-red-600",
40
52
  trigger: "border-red-300 bg-red-50 text-red-800",
41
53
  hover: "hover:bg-red-100",
42
- open: "bg-red-100"
54
+ open: "bg-red-100",
55
+ text: "text-red-600",
56
+ track: "text-red-600/15",
57
+ bar: "bg-red-600"
43
58
  },
44
59
  "Medium Risk": {
45
- solid: "bg-orange-500 text-white",
46
- outline: "border-orange-300 bg-orange-50 text-orange-800",
47
- dot: "bg-orange-500",
48
- trigger: "border-orange-300 bg-orange-50 text-orange-800",
49
- hover: "hover:bg-orange-100",
50
- open: "bg-orange-100"
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"
51
69
  },
52
70
  "Low Risk": {
53
71
  solid: "bg-neutral-300 text-neutral-900",
@@ -55,7 +73,10 @@ const RISK_SCORE_CLASSES = {
55
73
  dot: "bg-neutral-400",
56
74
  trigger: "border-neutral-200 bg-neutral-50 text-neutral-700",
57
75
  hover: "hover:bg-neutral-100",
58
- open: "bg-neutral-100"
76
+ open: "bg-neutral-100",
77
+ text: "text-neutral-400",
78
+ track: "text-neutral-400/15",
79
+ bar: "bg-neutral-400"
59
80
  }
60
81
  };
61
82
  const POSITIVE_SCORE_CLASSES = {
@@ -65,7 +86,10 @@ const POSITIVE_SCORE_CLASSES = {
65
86
  dot: "bg-emerald-500",
66
87
  trigger: "border-emerald-300 bg-emerald-50 text-emerald-800",
67
88
  hover: "hover:bg-emerald-100",
68
- open: "bg-emerald-100"
89
+ open: "bg-emerald-100",
90
+ text: "text-emerald-500",
91
+ track: "text-emerald-500/15",
92
+ bar: "bg-emerald-500"
69
93
  },
70
94
  Medium: {
71
95
  solid: "bg-amber-500 text-white",
@@ -73,7 +97,10 @@ const POSITIVE_SCORE_CLASSES = {
73
97
  dot: "bg-amber-500",
74
98
  trigger: "border-amber-300 bg-amber-50 text-amber-800",
75
99
  hover: "hover:bg-amber-100",
76
- open: "bg-amber-100"
100
+ open: "bg-amber-100",
101
+ text: "text-amber-500",
102
+ track: "text-amber-500/15",
103
+ bar: "bg-amber-500"
77
104
  },
78
105
  Low: {
79
106
  solid: "bg-red-500 text-white",
@@ -81,7 +108,10 @@ const POSITIVE_SCORE_CLASSES = {
81
108
  dot: "bg-red-500",
82
109
  trigger: "border-red-300 bg-red-50 text-red-800",
83
110
  hover: "hover:bg-red-100",
84
- open: "bg-red-100"
111
+ open: "bg-red-100",
112
+ text: "text-red-500",
113
+ track: "text-red-500/15",
114
+ bar: "bg-red-500"
85
115
  }
86
116
  };
87
117
  const SCORE_TONE_CLASSES = {
@@ -1 +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}\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 },\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 },\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 },\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 },\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 },\n \"Medium Risk\": {\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 },\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 },\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 },\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 },\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 },\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":"AAeO,MAAM,wBAAoE;AAAA,EAC/E,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,KAAK;AAAA,IACH,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACF;AAEO,MAAM,qBAA8D;AAAA,EACzE,aAAa;AAAA,IACX,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,eAAe;AAAA,IACb,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,YAAY;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACF;AAEO,MAAM,yBAAsE;AAAA,EACjF,MAAM;AAAA,IACJ,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,KAAK;AAAA,IACH,OAAO;AAAA,IACP,SAAS;AAAA,IACT,KAAK;AAAA,IACL,SAAS;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACF;AAEO,MAAM,qBAA6C;AAAA,EACxD,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AACR;AAEO,MAAM,2BAA2B;AAEjC,SAAS,gBAAgB,OAA6B;AAC3D,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAEO,SAAS,gBAAgB,OAA6B;AAC3D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAEO,SAAS,aAAa,OAA0B;AACrD,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAEO,SAAS,iBAAiB,OAA8B;AAC7D,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAEO,SAAS,oBAAoB,OAAe,QAA2C;AAC5F,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,sBAAsB,gBAAgB,KAAK,CAAC;AAAA,IACrD,KAAK;AACH,aAAO,mBAAmB,aAAa,KAAK,CAAC;AAAA,IAC/C,KAAK;AACH,aAAO,uBAAuB,iBAAiB,KAAK,CAAC;AAAA,EACzD;AACF;","names":[]}
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":[]}
@@ -5,7 +5,7 @@ import { Tabs as Tabs$1 } from 'radix-ui';
5
5
 
6
6
  declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof Tabs$1.Root>): React.JSX.Element;
7
7
  declare const tabsListVariants: (props?: ({
8
- variant?: "line" | "default" | null | undefined;
8
+ variant?: "default" | "line" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare function TabsList({ className, variant, ...props }: React.ComponentProps<typeof Tabs$1.List> & VariantProps<typeof tabsListVariants>): React.JSX.Element;
11
11
  declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof Tabs$1.Trigger>): React.JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.18.20",
3
+ "version": "0.18.22",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -32,23 +32,29 @@ describe("score semantics", () => {
32
32
  })
33
33
 
34
34
  it("uses the approved urgency palette", () => {
35
- expect(URGENCY_SCORE_CLASSES.Urgent).toEqual({
35
+ expect(URGENCY_SCORE_CLASSES.Urgent).toMatchObject({
36
36
  solid: "bg-red-600 text-white",
37
37
  outline: "border-red-300 bg-red-50 text-red-800",
38
38
  dot: "bg-red-600",
39
39
  trigger: "border-red-300 bg-red-50 text-red-800",
40
40
  hover: "hover:bg-red-100",
41
41
  open: "bg-red-100",
42
+ text: "text-red-600",
43
+ track: "text-red-600/15",
44
+ bar: "bg-red-600",
42
45
  })
43
46
  expect(URGENCY_SCORE_CLASSES.High.trigger).toBe("border-orange-300 bg-orange-50 text-orange-800")
44
47
  expect(URGENCY_SCORE_CLASSES.Medium.dot).toBe("bg-amber-500")
45
- expect(URGENCY_SCORE_CLASSES.Low).toEqual({
48
+ expect(URGENCY_SCORE_CLASSES.Low).toMatchObject({
46
49
  solid: "bg-neutral-300 text-neutral-900",
47
50
  outline: "border-neutral-200 bg-neutral-50 text-neutral-700",
48
51
  dot: "bg-neutral-400",
49
52
  trigger: "border-neutral-200 bg-neutral-50 text-neutral-700",
50
53
  hover: "hover:bg-neutral-100",
51
54
  open: "bg-neutral-100",
55
+ text: "text-neutral-400",
56
+ track: "text-neutral-400/15",
57
+ bar: "bg-neutral-400",
52
58
  })
53
59
  })
54
60
 
@@ -59,7 +65,7 @@ describe("score semantics", () => {
59
65
  expect(getRiskLevel(69)).toBe("Medium Risk")
60
66
  expect(getRiskLevel(70)).toBe("High Risk")
61
67
  expect(RISK_SCORE_CLASSES["High Risk"].solid).toBe("bg-red-600 text-white")
62
- expect(RISK_SCORE_CLASSES["Medium Risk"].dot).toBe("bg-orange-500")
68
+ expect(RISK_SCORE_CLASSES["Medium Risk"].dot).toBe("bg-amber-500")
63
69
  expect(RISK_SCORE_CLASSES["Low Risk"].trigger).toBe("border-neutral-200 bg-neutral-50 text-neutral-700")
64
70
  })
65
71
 
@@ -4,7 +4,7 @@ import * as React from "react"
4
4
  import { ThumbsUp, ThumbsDown } from "lucide-react"
5
5
  import { cn } from "../lib/utils"
6
6
  import type { ScoreIntent } from "./score-semantics"
7
- import { getScoreColor } from "./score-ring"
7
+ import { getScoreToneClasses } from "./score-semantics"
8
8
 
9
9
  export interface ScoreFactor {
10
10
  key: string
@@ -15,7 +15,7 @@ export interface ScoreFactor {
15
15
  }
16
16
 
17
17
  function getFactorBarColor(score: number, intent: ScoreIntent = "positive") {
18
- return getScoreColor(score, 100, intent).replace("text-", "bg-")
18
+ return getScoreToneClasses(score, intent).bar
19
19
  }
20
20
 
21
21
  function getRiskBadgeStyle(risk: "Low" | "Medium" | "High") {
@@ -1,47 +1,14 @@
1
1
  import * as React from "react"
2
2
  import { cn } from "../lib/utils"
3
3
  import type { ScoreIntent } from "./score-semantics"
4
+ import { getScoreToneClasses } from "./score-semantics"
4
5
 
5
6
  function getScoreColor(score: number, denominator: number, intent: ScoreIntent = "positive") {
6
- const pct = (score / denominator) * 100
7
-
8
- if (intent === "urgency") {
9
- if (pct >= 80) return "text-red-600"
10
- if (pct >= 60) return "text-orange-500"
11
- if (pct >= 35) return "text-amber-500"
12
- return "text-neutral-400"
13
- }
14
-
15
- if (intent === "risk") {
16
- if (pct >= 70) return "text-red-600"
17
- if (pct >= 40) return "text-amber-500"
18
- return "text-neutral-400"
19
- }
20
-
21
- if (pct >= 70) return "text-emerald-500"
22
- if (pct >= 40) return "text-amber-500"
23
- return "text-red-500"
7
+ return getScoreToneClasses((score / denominator) * 100, intent).text
24
8
  }
25
9
 
26
10
  function getScoreTrackColor(score: number, denominator: number, intent: ScoreIntent = "positive") {
27
- const pct = (score / denominator) * 100
28
-
29
- if (intent === "urgency") {
30
- if (pct >= 80) return "text-red-600/15"
31
- if (pct >= 60) return "text-orange-500/15"
32
- if (pct >= 35) return "text-amber-500/15"
33
- return "text-neutral-400/15"
34
- }
35
-
36
- if (intent === "risk") {
37
- if (pct >= 70) return "text-red-600/15"
38
- if (pct >= 40) return "text-amber-500/15"
39
- return "text-neutral-400/15"
40
- }
41
-
42
- if (pct >= 70) return "text-emerald-500/15"
43
- if (pct >= 40) return "text-amber-500/15"
44
- return "text-red-500/15"
11
+ return getScoreToneClasses((score / denominator) * 100, intent).track
45
12
  }
46
13
 
47
14
  interface ScoreRingProps {
@@ -11,6 +11,9 @@ export type ScoreSemanticClasses = {
11
11
  trigger: string
12
12
  hover: string
13
13
  open: string
14
+ text: string
15
+ track: string
16
+ bar: string
14
17
  }
15
18
 
16
19
  export const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses> = {
@@ -21,6 +24,9 @@ export const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses> =
21
24
  trigger: "border-red-300 bg-red-50 text-red-800",
22
25
  hover: "hover:bg-red-100",
23
26
  open: "bg-red-100",
27
+ text: "text-red-600",
28
+ track: "text-red-600/15",
29
+ bar: "bg-red-600",
24
30
  },
25
31
  High: {
26
32
  solid: "bg-orange-500 text-white",
@@ -29,6 +35,9 @@ export const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses> =
29
35
  trigger: "border-orange-300 bg-orange-50 text-orange-800",
30
36
  hover: "hover:bg-orange-100",
31
37
  open: "bg-orange-100",
38
+ text: "text-orange-500",
39
+ track: "text-orange-500/15",
40
+ bar: "bg-orange-500",
32
41
  },
33
42
  Medium: {
34
43
  solid: "bg-amber-500 text-white",
@@ -37,6 +46,9 @@ export const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses> =
37
46
  trigger: "border-amber-300 bg-amber-50 text-amber-800",
38
47
  hover: "hover:bg-amber-100",
39
48
  open: "bg-amber-100",
49
+ text: "text-amber-500",
50
+ track: "text-amber-500/15",
51
+ bar: "bg-amber-500",
40
52
  },
41
53
  Low: {
42
54
  solid: "bg-neutral-300 text-neutral-900",
@@ -45,6 +57,9 @@ export const URGENCY_SCORE_CLASSES: Record<UrgencyLevel, ScoreSemanticClasses> =
45
57
  trigger: "border-neutral-200 bg-neutral-50 text-neutral-700",
46
58
  hover: "hover:bg-neutral-100",
47
59
  open: "bg-neutral-100",
60
+ text: "text-neutral-400",
61
+ track: "text-neutral-400/15",
62
+ bar: "bg-neutral-400",
48
63
  },
49
64
  }
50
65
 
@@ -56,14 +71,20 @@ export const RISK_SCORE_CLASSES: Record<RiskLevel, ScoreSemanticClasses> = {
56
71
  trigger: "border-red-300 bg-red-50 text-red-800",
57
72
  hover: "hover:bg-red-100",
58
73
  open: "bg-red-100",
74
+ text: "text-red-600",
75
+ track: "text-red-600/15",
76
+ bar: "bg-red-600",
59
77
  },
60
78
  "Medium Risk": {
61
- solid: "bg-orange-500 text-white",
62
- outline: "border-orange-300 bg-orange-50 text-orange-800",
63
- dot: "bg-orange-500",
64
- trigger: "border-orange-300 bg-orange-50 text-orange-800",
65
- hover: "hover:bg-orange-100",
66
- open: "bg-orange-100",
79
+ solid: "bg-amber-500 text-white",
80
+ outline: "border-amber-300 bg-amber-50 text-amber-800",
81
+ dot: "bg-amber-500",
82
+ trigger: "border-amber-300 bg-amber-50 text-amber-800",
83
+ hover: "hover:bg-amber-100",
84
+ open: "bg-amber-100",
85
+ text: "text-amber-500",
86
+ track: "text-amber-500/15",
87
+ bar: "bg-amber-500",
67
88
  },
68
89
  "Low Risk": {
69
90
  solid: "bg-neutral-300 text-neutral-900",
@@ -72,6 +93,9 @@ export const RISK_SCORE_CLASSES: Record<RiskLevel, ScoreSemanticClasses> = {
72
93
  trigger: "border-neutral-200 bg-neutral-50 text-neutral-700",
73
94
  hover: "hover:bg-neutral-100",
74
95
  open: "bg-neutral-100",
96
+ text: "text-neutral-400",
97
+ track: "text-neutral-400/15",
98
+ bar: "bg-neutral-400",
75
99
  },
76
100
  }
77
101
 
@@ -83,6 +107,9 @@ export const POSITIVE_SCORE_CLASSES: Record<PositiveLevel, ScoreSemanticClasses>
83
107
  trigger: "border-emerald-300 bg-emerald-50 text-emerald-800",
84
108
  hover: "hover:bg-emerald-100",
85
109
  open: "bg-emerald-100",
110
+ text: "text-emerald-500",
111
+ track: "text-emerald-500/15",
112
+ bar: "bg-emerald-500",
86
113
  },
87
114
  Medium: {
88
115
  solid: "bg-amber-500 text-white",
@@ -91,6 +118,9 @@ export const POSITIVE_SCORE_CLASSES: Record<PositiveLevel, ScoreSemanticClasses>
91
118
  trigger: "border-amber-300 bg-amber-50 text-amber-800",
92
119
  hover: "hover:bg-amber-100",
93
120
  open: "bg-amber-100",
121
+ text: "text-amber-500",
122
+ track: "text-amber-500/15",
123
+ bar: "bg-amber-500",
94
124
  },
95
125
  Low: {
96
126
  solid: "bg-red-500 text-white",
@@ -99,6 +129,9 @@ export const POSITIVE_SCORE_CLASSES: Record<PositiveLevel, ScoreSemanticClasses>
99
129
  trigger: "border-red-300 bg-red-50 text-red-800",
100
130
  hover: "hover:bg-red-100",
101
131
  open: "bg-red-100",
132
+ text: "text-red-500",
133
+ track: "text-red-500/15",
134
+ bar: "bg-red-500",
102
135
  },
103
136
  }
104
137