@handled-ai/design-system 0.8.0 → 0.9.0

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 (336) hide show
  1. package/README.md +14 -4
  2. package/dist/charts/bar-chart-component.d.ts +24 -0
  3. package/dist/charts/bar-chart-component.js +123 -0
  4. package/dist/charts/bar-chart-component.js.map +1 -0
  5. package/dist/charts/chart-tooltip.d.ts +26 -0
  6. package/dist/charts/chart-tooltip.js +69 -0
  7. package/dist/charts/chart-tooltip.js.map +1 -0
  8. package/dist/charts/chart.d.ts +64 -0
  9. package/dist/charts/chart.js +285 -0
  10. package/dist/charts/chart.js.map +1 -0
  11. package/dist/charts/donut-chart.d.ts +21 -0
  12. package/dist/charts/donut-chart.js +96 -0
  13. package/dist/charts/donut-chart.js.map +1 -0
  14. package/dist/charts/index.d.ts +11 -0
  15. package/dist/charts/index.js +10 -0
  16. package/dist/charts/index.js.map +1 -0
  17. package/dist/charts/pipeline-overview.d.ts +76 -0
  18. package/dist/charts/pipeline-overview.js +372 -0
  19. package/dist/charts/pipeline-overview.js.map +1 -0
  20. package/dist/charts/sankey-chart.d.ts +52 -0
  21. package/dist/charts/sankey-chart.js +219 -0
  22. package/dist/charts/sankey-chart.js.map +1 -0
  23. package/dist/charts/top-line-metrics.d.ts +26 -0
  24. package/dist/charts/top-line-metrics.js +224 -0
  25. package/dist/charts/top-line-metrics.js.map +1 -0
  26. package/dist/charts/trend-area-chart.d.ts +21 -0
  27. package/dist/charts/trend-area-chart.js +150 -0
  28. package/dist/charts/trend-area-chart.js.map +1 -0
  29. package/dist/charts/volume-analysis-chart.d.ts +19 -0
  30. package/dist/charts/volume-analysis-chart.js +121 -0
  31. package/dist/charts/volume-analysis-chart.js.map +1 -0
  32. package/dist/components/activity-detail.d.ts +38 -0
  33. package/dist/components/activity-detail.js +163 -0
  34. package/dist/components/activity-detail.js.map +1 -0
  35. package/dist/components/activity-log.d.ts +21 -0
  36. package/dist/components/activity-log.js +61 -0
  37. package/dist/components/activity-log.js.map +1 -0
  38. package/dist/components/agent-popover.d.ts +71 -0
  39. package/dist/components/agent-popover.js +282 -0
  40. package/dist/components/agent-popover.js.map +1 -0
  41. package/dist/components/agent-widget.d.ts +24 -0
  42. package/dist/components/agent-widget.js +117 -0
  43. package/dist/components/agent-widget.js.map +1 -0
  44. package/dist/components/avatar.d.ts +13 -0
  45. package/dist/components/avatar.js +140 -0
  46. package/dist/components/avatar.js.map +1 -0
  47. package/dist/components/badge.d.ts +12 -0
  48. package/dist/components/badge.js +75 -0
  49. package/dist/components/badge.js.map +1 -0
  50. package/dist/components/button.d.ts +13 -0
  51. package/dist/components/button.js +83 -0
  52. package/dist/components/button.js.map +1 -0
  53. package/dist/components/card.d.ts +11 -0
  54. package/dist/components/card.js +119 -0
  55. package/dist/components/card.js.map +1 -0
  56. package/dist/components/contact-list.d.ts +34 -0
  57. package/dist/components/contact-list.js +84 -0
  58. package/dist/components/contact-list.js.map +1 -0
  59. package/dist/components/dashboard-cards.d.ts +10 -0
  60. package/dist/components/dashboard-cards.js +164 -0
  61. package/dist/components/dashboard-cards.js.map +1 -0
  62. package/dist/components/data-table-display.d.ts +19 -0
  63. package/dist/components/data-table-display.js +109 -0
  64. package/dist/components/data-table-display.js.map +1 -0
  65. package/dist/components/data-table-filter.d.ts +18 -0
  66. package/dist/components/data-table-filter.js +107 -0
  67. package/dist/components/data-table-filter.js.map +1 -0
  68. package/dist/components/data-table-quick-views.d.ts +13 -0
  69. package/dist/components/data-table-quick-views.js +90 -0
  70. package/dist/components/data-table-quick-views.js.map +1 -0
  71. package/dist/components/data-table-toolbar.d.ts +18 -0
  72. package/dist/components/data-table-toolbar.js +45 -0
  73. package/dist/components/data-table-toolbar.js.map +1 -0
  74. package/dist/components/data-table.d.ts +39 -0
  75. package/dist/components/data-table.js +821 -0
  76. package/dist/components/data-table.js.map +1 -0
  77. package/dist/components/detail-view.d.ts +44 -0
  78. package/dist/components/detail-view.js +165 -0
  79. package/dist/components/detail-view.js.map +1 -0
  80. package/dist/components/dialog.d.ts +19 -0
  81. package/dist/components/dialog.js +188 -0
  82. package/dist/components/dialog.js.map +1 -0
  83. package/dist/components/dropdown-menu.d.ts +27 -0
  84. package/dist/components/dropdown-menu.js +279 -0
  85. package/dist/components/dropdown-menu.js.map +1 -0
  86. package/dist/components/entity-panel.d.ts +69 -0
  87. package/dist/components/entity-panel.js +584 -0
  88. package/dist/components/entity-panel.js.map +1 -0
  89. package/dist/components/inbox-row.d.ts +27 -0
  90. package/dist/components/inbox-row.js +139 -0
  91. package/dist/components/inbox-row.js.map +1 -0
  92. package/dist/components/inbox-toolbar.d.ts +21 -0
  93. package/dist/components/inbox-toolbar.js +203 -0
  94. package/dist/components/inbox-toolbar.js.map +1 -0
  95. package/dist/components/input.d.ts +5 -0
  96. package/dist/components/input.js +50 -0
  97. package/dist/components/input.js.map +1 -0
  98. package/dist/components/insights-filter-bar.d.ts +21 -0
  99. package/dist/components/insights-filter-bar.js +99 -0
  100. package/dist/components/insights-filter-bar.js.map +1 -0
  101. package/dist/components/item-list-display.d.ts +22 -0
  102. package/dist/components/item-list-display.js +240 -0
  103. package/dist/components/item-list-display.js.map +1 -0
  104. package/dist/components/item-list-filter.d.ts +16 -0
  105. package/dist/components/item-list-filter.js +87 -0
  106. package/dist/components/item-list-filter.js.map +1 -0
  107. package/dist/components/item-list-toolbar.d.ts +25 -0
  108. package/dist/components/item-list-toolbar.js +79 -0
  109. package/dist/components/item-list-toolbar.js.map +1 -0
  110. package/dist/components/item-list.d.ts +20 -0
  111. package/dist/components/item-list.js +702 -0
  112. package/dist/components/item-list.js.map +1 -0
  113. package/dist/components/label.d.ts +6 -0
  114. package/dist/components/label.js +55 -0
  115. package/dist/components/label.js.map +1 -0
  116. package/dist/components/message.d.ts +23 -0
  117. package/dist/components/message.js +117 -0
  118. package/dist/components/message.js.map +1 -0
  119. package/dist/components/metric-card.d.ts +25 -0
  120. package/dist/components/metric-card.js +107 -0
  121. package/dist/components/metric-card.js.map +1 -0
  122. package/dist/components/performance-metrics-table.d.ts +38 -0
  123. package/dist/components/performance-metrics-table.js +342 -0
  124. package/dist/components/performance-metrics-table.js.map +1 -0
  125. package/dist/components/preview-list.d.ts +14 -0
  126. package/dist/components/preview-list.js +83 -0
  127. package/dist/components/preview-list.js.map +1 -0
  128. package/dist/components/progress.d.ts +6 -0
  129. package/dist/components/progress.js +69 -0
  130. package/dist/components/progress.js.map +1 -0
  131. package/dist/components/quick-action-chat-area.d.ts +24 -0
  132. package/dist/components/quick-action-chat-area.js +178 -0
  133. package/dist/components/quick-action-chat-area.js.map +1 -0
  134. package/dist/components/quick-action-modal.d.ts +30 -0
  135. package/dist/components/quick-action-modal.js +288 -0
  136. package/dist/components/quick-action-modal.js.map +1 -0
  137. package/dist/components/quick-action-sidebar-nav.d.ts +51 -0
  138. package/dist/components/quick-action-sidebar-nav.js +528 -0
  139. package/dist/components/quick-action-sidebar-nav.js.map +1 -0
  140. package/dist/components/recommended-actions-section.d.ts +23 -0
  141. package/dist/components/recommended-actions-section.js +215 -0
  142. package/dist/components/recommended-actions-section.js.map +1 -0
  143. package/dist/components/report-card.d.ts +26 -0
  144. package/dist/components/report-card.js +69 -0
  145. package/dist/components/report-card.js.map +1 -0
  146. package/dist/components/score-analysis-modal.d.ts +26 -0
  147. package/dist/components/score-analysis-modal.js +141 -0
  148. package/dist/components/score-analysis-modal.js.map +1 -0
  149. package/dist/components/score-breakdown.d.ts +17 -0
  150. package/dist/components/score-breakdown.js +162 -0
  151. package/dist/components/score-breakdown.js.map +1 -0
  152. package/dist/components/score-feedback.d.ts +40 -0
  153. package/dist/components/score-feedback.js +209 -0
  154. package/dist/components/score-feedback.js.map +1 -0
  155. package/dist/components/score-ring.d.ts +14 -0
  156. package/dist/components/score-ring.js +79 -0
  157. package/dist/components/score-ring.js.map +1 -0
  158. package/dist/components/scroll-area.d.ts +7 -0
  159. package/dist/components/scroll-area.js +101 -0
  160. package/dist/components/scroll-area.js.map +1 -0
  161. package/dist/components/select.d.ts +17 -0
  162. package/dist/components/select.js +228 -0
  163. package/dist/components/select.js.map +1 -0
  164. package/dist/components/separator.d.ts +6 -0
  165. package/dist/components/separator.js +61 -0
  166. package/dist/components/separator.js.map +1 -0
  167. package/dist/components/sheet.d.ts +16 -0
  168. package/dist/components/sheet.js +168 -0
  169. package/dist/components/sheet.js.map +1 -0
  170. package/dist/components/sidebar.d.ts +73 -0
  171. package/dist/components/sidebar.js +723 -0
  172. package/dist/components/sidebar.js.map +1 -0
  173. package/dist/components/signal-feedback-inline.d.ts +51 -0
  174. package/dist/components/signal-feedback-inline.js +548 -0
  175. package/dist/components/signal-feedback-inline.js.map +1 -0
  176. package/dist/components/simple-data-table.d.ts +15 -0
  177. package/dist/components/simple-data-table.js +91 -0
  178. package/dist/components/simple-data-table.js.map +1 -0
  179. package/dist/components/skeleton.d.ts +5 -0
  180. package/dist/components/skeleton.js +44 -0
  181. package/dist/components/skeleton.js.map +1 -0
  182. package/dist/components/status-badge.d.ts +10 -0
  183. package/dist/components/status-badge.js +82 -0
  184. package/dist/components/status-badge.js.map +1 -0
  185. package/dist/components/styled-bar-list.d.ts +20 -0
  186. package/dist/components/styled-bar-list.js +59 -0
  187. package/dist/components/styled-bar-list.js.map +1 -0
  188. package/dist/components/suggested-actions.d.ts +110 -0
  189. package/dist/components/suggested-actions.js +1538 -0
  190. package/dist/components/suggested-actions.js.map +1 -0
  191. package/dist/components/table.d.ts +12 -0
  192. package/dist/components/table.js +147 -0
  193. package/dist/components/table.js.map +1 -0
  194. package/dist/components/tabs.d.ts +14 -0
  195. package/dist/components/tabs.js +129 -0
  196. package/dist/components/tabs.js.map +1 -0
  197. package/dist/components/textarea.d.ts +5 -0
  198. package/dist/components/textarea.js +47 -0
  199. package/dist/components/textarea.js.map +1 -0
  200. package/dist/components/timeline-activity.d.ts +34 -0
  201. package/dist/components/timeline-activity.js +181 -0
  202. package/dist/components/timeline-activity.js.map +1 -0
  203. package/dist/components/tooltip.d.ts +9 -0
  204. package/dist/components/tooltip.js +93 -0
  205. package/dist/components/tooltip.js.map +1 -0
  206. package/dist/components/view-mode-toggle.d.ts +16 -0
  207. package/dist/components/view-mode-toggle.js +24 -0
  208. package/dist/components/view-mode-toggle.js.map +1 -0
  209. package/dist/hooks/use-mobile.d.ts +3 -0
  210. package/dist/hooks/use-mobile.js +21 -0
  211. package/dist/hooks/use-mobile.js.map +1 -0
  212. package/dist/index.d.ts +68 -1878
  213. package/dist/index.js +69 -10918
  214. package/dist/index.js.map +1 -1
  215. package/dist/lib/icons.d.ts +18 -0
  216. package/dist/lib/icons.js +21 -0
  217. package/dist/lib/icons.js.map +1 -0
  218. package/dist/lib/utils.d.ts +5 -0
  219. package/dist/lib/utils.js +9 -0
  220. package/dist/lib/utils.js.map +1 -0
  221. package/dist/prototype/index.d.ts +20 -0
  222. package/dist/prototype/index.js +8 -0
  223. package/dist/prototype/index.js.map +1 -0
  224. package/dist/prototype/prototype-accounts-view.d.ts +22 -0
  225. package/dist/prototype/prototype-accounts-view.js +70 -0
  226. package/dist/prototype/prototype-accounts-view.js.map +1 -0
  227. package/dist/prototype/prototype-admin-view.d.ts +21 -0
  228. package/dist/prototype/prototype-admin-view.js +53 -0
  229. package/dist/prototype/prototype-admin-view.js.map +1 -0
  230. package/dist/prototype/prototype-config.d.ts +226 -0
  231. package/dist/prototype/prototype-config.js +1 -0
  232. package/dist/prototype/prototype-config.js.map +1 -0
  233. package/dist/prototype/prototype-inbox-view.d.ts +48 -0
  234. package/dist/prototype/prototype-inbox-view.js +701 -0
  235. package/dist/prototype/prototype-inbox-view.js.map +1 -0
  236. package/dist/prototype/prototype-insights-view.d.ts +23 -0
  237. package/dist/prototype/prototype-insights-view.js +335 -0
  238. package/dist/prototype/prototype-insights-view.js.map +1 -0
  239. package/dist/prototype/prototype-shell.d.ts +40 -0
  240. package/dist/prototype/prototype-shell.js +190 -0
  241. package/dist/prototype/prototype-shell.js.map +1 -0
  242. package/dist/prototype/prototype-work-queue-view.d.ts +8 -0
  243. package/dist/prototype/prototype-work-queue-view.js +17 -0
  244. package/dist/prototype/prototype-work-queue-view.js.map +1 -0
  245. package/dist/three/agent-orb.d.ts +39 -0
  246. package/dist/three/agent-orb.js +500 -0
  247. package/dist/three/agent-orb.js.map +1 -0
  248. package/dist/three/index.d.ts +2 -0
  249. package/dist/three/index.js +2 -0
  250. package/dist/three/index.js.map +1 -0
  251. package/package.json +98 -17
  252. package/src/charts/bar-chart-component.tsx +150 -0
  253. package/src/charts/chart-tooltip.tsx +86 -0
  254. package/src/charts/chart.tsx +371 -0
  255. package/src/charts/donut-chart.tsx +112 -0
  256. package/src/charts/index.ts +13 -0
  257. package/src/charts/pipeline-overview.tsx +476 -0
  258. package/src/charts/sankey-chart.tsx +290 -0
  259. package/src/charts/top-line-metrics.tsx +261 -0
  260. package/src/charts/trend-area-chart.tsx +150 -0
  261. package/src/charts/volume-analysis-chart.tsx +124 -0
  262. package/src/components/activity-detail.tsx +233 -0
  263. package/src/components/activity-log.tsx +89 -0
  264. package/src/components/agent-popover.tsx +373 -0
  265. package/src/components/agent-widget.tsx +163 -0
  266. package/src/components/avatar.tsx +109 -0
  267. package/src/components/badge.tsx +48 -0
  268. package/src/components/button.tsx +59 -0
  269. package/src/components/card.tsx +92 -0
  270. package/src/components/contact-list.tsx +121 -0
  271. package/src/components/dashboard-cards.tsx +170 -0
  272. package/src/components/data-table-display.tsx +139 -0
  273. package/src/components/data-table-filter.tsx +138 -0
  274. package/src/components/data-table-quick-views.tsx +103 -0
  275. package/src/components/data-table-toolbar.tsx +56 -0
  276. package/src/components/data-table.tsx +915 -0
  277. package/src/components/detail-view.tsx +237 -0
  278. package/src/components/dialog.tsx +158 -0
  279. package/src/components/dropdown-menu.tsx +257 -0
  280. package/src/components/entity-panel.tsx +767 -0
  281. package/src/components/inbox-row.tsx +132 -0
  282. package/src/components/inbox-toolbar.tsx +213 -0
  283. package/src/components/input.tsx +21 -0
  284. package/src/components/insights-filter-bar.tsx +132 -0
  285. package/src/components/item-list-display.tsx +278 -0
  286. package/src/components/item-list-filter.tsx +118 -0
  287. package/src/components/item-list-toolbar.tsx +97 -0
  288. package/src/components/item-list.tsx +843 -0
  289. package/src/components/label.tsx +24 -0
  290. package/src/components/message.tsx +83 -0
  291. package/src/components/metric-card.tsx +178 -0
  292. package/src/components/performance-metrics-table.tsx +442 -0
  293. package/src/components/preview-list.tsx +62 -0
  294. package/src/components/progress.tsx +31 -0
  295. package/src/components/quick-action-chat-area.tsx +156 -0
  296. package/src/components/quick-action-modal.tsx +331 -0
  297. package/src/components/quick-action-sidebar-nav.tsx +592 -0
  298. package/src/components/recommended-actions-section.tsx +258 -0
  299. package/src/components/report-card.tsx +106 -0
  300. package/src/components/score-analysis-modal.tsx +172 -0
  301. package/src/components/score-breakdown.tsx +179 -0
  302. package/src/components/score-feedback.tsx +288 -0
  303. package/src/components/score-ring.tsx +87 -0
  304. package/src/components/scroll-area.tsx +58 -0
  305. package/src/components/select.tsx +190 -0
  306. package/src/components/separator.tsx +28 -0
  307. package/src/components/sheet.tsx +143 -0
  308. package/src/components/sidebar.tsx +726 -0
  309. package/src/components/signal-feedback-inline.tsx +591 -0
  310. package/src/components/simple-data-table.tsx +124 -0
  311. package/src/components/skeleton.tsx +15 -0
  312. package/src/components/status-badge.tsx +63 -0
  313. package/src/components/styled-bar-list.tsx +70 -0
  314. package/src/components/suggested-actions.tsx +1985 -0
  315. package/src/components/table.tsx +116 -0
  316. package/src/components/tabs.tsx +91 -0
  317. package/src/components/textarea.tsx +18 -0
  318. package/src/components/timeline-activity.tsx +234 -0
  319. package/src/components/tooltip.tsx +57 -0
  320. package/src/components/view-mode-toggle.tsx +39 -0
  321. package/src/hooks/use-mobile.ts +21 -0
  322. package/src/index.ts +77 -0
  323. package/src/lib/icons.ts +18 -0
  324. package/src/lib/utils.ts +6 -0
  325. package/src/prototype/index.ts +11 -0
  326. package/src/prototype/prototype-accounts-view.tsx +112 -0
  327. package/src/prototype/prototype-admin-view.tsx +67 -0
  328. package/src/prototype/prototype-config.ts +243 -0
  329. package/src/prototype/prototype-inbox-view.tsx +810 -0
  330. package/src/prototype/prototype-insights-view.tsx +379 -0
  331. package/src/prototype/prototype-shell.tsx +219 -0
  332. package/src/prototype/prototype-work-queue-view.tsx +30 -0
  333. package/src/styles/globals.css +299 -0
  334. package/src/three/agent-orb.tsx +557 -0
  335. package/src/three/index.ts +5 -0
  336. package/src/types/r3f.d.ts +8 -0
@@ -0,0 +1,179 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ThumbsUp, ThumbsDown } from "lucide-react"
5
+ import { cn } from "../lib/utils"
6
+
7
+ export interface ScoreFactor {
8
+ key: string
9
+ label: string
10
+ score: number | null
11
+ risk?: "Low" | "Medium" | "High"
12
+ why: string
13
+ }
14
+
15
+ function getFactorBarColor(score: number) {
16
+ if (score >= 70) return "bg-emerald-500"
17
+ if (score >= 40) return "bg-amber-500"
18
+ return "bg-red-500"
19
+ }
20
+
21
+ function getRiskBadgeStyle(risk: "Low" | "Medium" | "High") {
22
+ switch (risk) {
23
+ case "Low":
24
+ return "text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30"
25
+ case "Medium":
26
+ return "text-amber-600 bg-amber-50 dark:text-amber-400 dark:bg-amber-950/30"
27
+ case "High":
28
+ return "text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30"
29
+ }
30
+ }
31
+
32
+ interface ScoreBreakdownProps {
33
+ factors: ScoreFactor[]
34
+ onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
35
+ className?: string
36
+ }
37
+
38
+ function ScoreBreakdown({ factors, onFactorFeedback, className }: ScoreBreakdownProps) {
39
+ const [feedback, setFeedback] = React.useState<Record<string, "up" | "down" | null>>({})
40
+ const [feedbackText, setFeedbackText] = React.useState<Record<string, string>>({})
41
+ const [savedText, setSavedText] = React.useState<Record<string, string>>({})
42
+ const [editingKey, setEditingKey] = React.useState<string | null>(null)
43
+
44
+ const handleFeedback = (factorKey: string, type: "up" | "down") => {
45
+ const newState = feedback[factorKey] === type ? null : type
46
+ setFeedback((prev) => ({ ...prev, [factorKey]: newState }))
47
+ if (newState === null) {
48
+ setEditingKey(null)
49
+ onFactorFeedback?.(factorKey, null)
50
+ } else {
51
+ setEditingKey(factorKey)
52
+ onFactorFeedback?.(factorKey, newState)
53
+ }
54
+ }
55
+
56
+ const submitFeedbackText = (factorKey: string) => {
57
+ const text = (feedbackText[factorKey] ?? "").trim()
58
+ if (feedback[factorKey]) {
59
+ onFactorFeedback?.(factorKey, feedback[factorKey]!, text || undefined)
60
+ if (text) {
61
+ setSavedText((prev) => ({ ...prev, [factorKey]: text }))
62
+ }
63
+ }
64
+ setEditingKey(null)
65
+ }
66
+
67
+ return (
68
+ <div className={cn("border border-border rounded-lg overflow-hidden divide-y divide-border/40", className)}>
69
+ {factors.map((factor) => {
70
+ const feedbackState = feedback[factor.key]
71
+ const hasThumb = feedbackState === "up" || feedbackState === "down"
72
+ const isEditing = editingKey === factor.key
73
+ const saved = savedText[factor.key]
74
+
75
+ return (
76
+ <div key={factor.key} className="px-3 py-3 space-y-1.5">
77
+ {/* Row 1: Factor name + score + rating buttons */}
78
+ <div className="flex items-center gap-2">
79
+ <span className="text-[13px] font-semibold text-foreground flex-1">{factor.label}</span>
80
+
81
+ {factor.score !== null ? (
82
+ <span className="text-xs font-bold text-foreground tabular-nums">{factor.score}<span className="text-muted-foreground font-normal">/100</span></span>
83
+ ) : factor.risk ? (
84
+ <span
85
+ className={cn(
86
+ "text-[11px] font-semibold px-1.5 py-0.5 rounded",
87
+ getRiskBadgeStyle(factor.risk)
88
+ )}
89
+ >
90
+ {factor.risk}
91
+ </span>
92
+ ) : null}
93
+
94
+ <div className="flex items-center gap-0.5 ml-1">
95
+ <button
96
+ type="button"
97
+ onClick={() => handleFeedback(factor.key, "up")}
98
+ className={cn(
99
+ "p-1 rounded transition-colors",
100
+ feedbackState === "up"
101
+ ? "text-emerald-600 bg-emerald-50 dark:text-emerald-400 dark:bg-emerald-950/30"
102
+ : "text-muted-foreground/40 hover:text-emerald-600 hover:bg-emerald-50/50"
103
+ )}
104
+ title="This factor is accurate"
105
+ >
106
+ <ThumbsUp className="w-3 h-3" />
107
+ </button>
108
+ <button
109
+ type="button"
110
+ onClick={() => handleFeedback(factor.key, "down")}
111
+ className={cn(
112
+ "p-1 rounded transition-colors",
113
+ feedbackState === "down"
114
+ ? "text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-950/30"
115
+ : "text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50"
116
+ )}
117
+ title="Report issue with this factor"
118
+ >
119
+ <ThumbsDown className="w-3 h-3" />
120
+ </button>
121
+ </div>
122
+ </div>
123
+
124
+ {/* Row 2: Score bar */}
125
+ {factor.score !== null && (
126
+ <div className="w-full h-1 bg-muted rounded-full overflow-hidden">
127
+ <div
128
+ className={cn("h-full rounded-full", getFactorBarColor(factor.score))}
129
+ style={{ width: `${factor.score}%` }}
130
+ />
131
+ </div>
132
+ )}
133
+
134
+ {/* Row 3: Why text */}
135
+ <p className="text-xs text-muted-foreground leading-relaxed">{factor.why}</p>
136
+
137
+ {/* Feedback input */}
138
+ {hasThumb && isEditing && (
139
+ <input
140
+ type="text"
141
+ autoFocus
142
+ value={feedbackText[factor.key] ?? ""}
143
+ onChange={(e) =>
144
+ setFeedbackText((prev) => ({ ...prev, [factor.key]: e.target.value }))
145
+ }
146
+ onKeyDown={(e) => {
147
+ if (e.key === "Enter") submitFeedbackText(factor.key)
148
+ if (e.key === "Escape") setEditingKey(null)
149
+ }}
150
+ onBlur={() => submitFeedbackText(factor.key)}
151
+ placeholder={
152
+ feedbackState === "up"
153
+ ? "What\u2019s accurate? (optional)"
154
+ : "What\u2019s wrong? (optional)"
155
+ }
156
+ 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"
157
+ />
158
+ )}
159
+ {hasThumb && !isEditing && saved && (
160
+ <button
161
+ type="button"
162
+ onClick={() => {
163
+ setFeedbackText((prev) => ({ ...prev, [factor.key]: saved }))
164
+ setEditingKey(factor.key)
165
+ }}
166
+ className="text-[11px] text-muted-foreground/70 hover:text-muted-foreground transition-colors text-left leading-snug"
167
+ >
168
+ {saved}
169
+ </button>
170
+ )}
171
+ </div>
172
+ )
173
+ })}
174
+ </div>
175
+ )
176
+ }
177
+
178
+ export { ScoreBreakdown }
179
+ export type { ScoreBreakdownProps }
@@ -0,0 +1,288 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ThumbsUp, ThumbsDown, Check } from "lucide-react"
5
+ import { cn } from "../lib/utils"
6
+
7
+ const positivePills = [
8
+ "Right timing",
9
+ "Accurate data",
10
+ "Good prospect fit",
11
+ "Actionable",
12
+ ]
13
+
14
+ const negativePills = [
15
+ "Bad timing",
16
+ "Inaccurate data",
17
+ "Wrong prospect",
18
+ "Already handled",
19
+ "Not actionable",
20
+ "Other",
21
+ ]
22
+
23
+ interface SubmittedScoreFeedback {
24
+ type: "up" | "down"
25
+ pills: string[]
26
+ detail: string
27
+ }
28
+
29
+ interface ScoreFeedbackState {
30
+ thumbState: "up" | "down" | null
31
+ selectedPills: string[]
32
+ detailText: string
33
+ notedType: "up" | "down" | null
34
+ submittedFeedback: SubmittedScoreFeedback | null
35
+ otherSelected: boolean
36
+ hasRequiredInput: boolean
37
+ handleThumbClick: (type: "up" | "down") => void
38
+ togglePill: (pill: string) => void
39
+ setDetailText: (text: string) => void
40
+ handleSubmit: () => void
41
+ editSubmitted: () => void
42
+ }
43
+
44
+ const ScoreFeedbackCtx = React.createContext<ScoreFeedbackState | null>(null)
45
+
46
+ function useScoreFeedback() {
47
+ const ctx = React.useContext(ScoreFeedbackCtx)
48
+ if (!ctx) throw new Error("Must be used within ScoreFeedback.Root")
49
+ return ctx
50
+ }
51
+
52
+ interface RootProps {
53
+ children: React.ReactNode
54
+ onSubmitFeedback?: (type: "up" | "down", pills: string[], detail: string) => void
55
+ }
56
+
57
+ function Root({ children, onSubmitFeedback }: RootProps) {
58
+ const [thumbState, setThumbState] = React.useState<"up" | "down" | null>(null)
59
+ const [selectedPills, setSelectedPills] = React.useState<string[]>([])
60
+ const [detailText, setDetailTextState] = React.useState("")
61
+ const [notedType, setNotedType] = React.useState<"up" | "down" | null>(null)
62
+ const [submittedFeedback, setSubmittedFeedback] = React.useState<SubmittedScoreFeedback | null>(null)
63
+
64
+ const otherSelected = selectedPills.includes("Other")
65
+
66
+ const hasRequiredInput =
67
+ thumbState === "down"
68
+ ? selectedPills.length > 0 && (!otherSelected || detailText.trim().length > 0)
69
+ : selectedPills.length > 0 || detailText.trim().length > 0
70
+
71
+ const togglePill = React.useCallback((pill: string) => {
72
+ setSelectedPills((prev) =>
73
+ prev.includes(pill) ? prev.filter((p) => p !== pill) : [...prev, pill]
74
+ )
75
+ }, [])
76
+
77
+ const handleThumbClick = React.useCallback((type: "up" | "down") => {
78
+ setThumbState((prev) => (prev === type ? null : type))
79
+ setSelectedPills([])
80
+ setDetailTextState("")
81
+ }, [])
82
+
83
+ const handleSubmit = React.useCallback(() => {
84
+ if (!thumbState) return
85
+ onSubmitFeedback?.(thumbState, selectedPills, detailText)
86
+ setSubmittedFeedback({ type: thumbState, pills: [...selectedPills], detail: detailText.trim() })
87
+ setNotedType(thumbState)
88
+ setThumbState(null)
89
+ setSelectedPills([])
90
+ setDetailTextState("")
91
+ setTimeout(() => setNotedType(null), 3000)
92
+ }, [thumbState, selectedPills, detailText, onSubmitFeedback])
93
+
94
+ const editSubmitted = React.useCallback(() => {
95
+ if (!submittedFeedback) return
96
+ setThumbState(submittedFeedback.type)
97
+ setSelectedPills([...submittedFeedback.pills])
98
+ setDetailTextState(submittedFeedback.detail)
99
+ setNotedType(null)
100
+ }, [submittedFeedback])
101
+
102
+ return (
103
+ <ScoreFeedbackCtx.Provider
104
+ value={{
105
+ thumbState,
106
+ selectedPills,
107
+ detailText,
108
+ notedType,
109
+ submittedFeedback,
110
+ otherSelected,
111
+ hasRequiredInput,
112
+ handleThumbClick,
113
+ togglePill,
114
+ setDetailText: setDetailTextState,
115
+ handleSubmit,
116
+ editSubmitted,
117
+ }}
118
+ >
119
+ {children}
120
+ </ScoreFeedbackCtx.Provider>
121
+ )
122
+ }
123
+
124
+ function Trigger({ className }: { className?: string }) {
125
+ const { thumbState, notedType, submittedFeedback, handleThumbClick, editSubmitted } = useScoreFeedback()
126
+
127
+ if (notedType || (submittedFeedback && !thumbState)) {
128
+ return (
129
+ <div className={cn("shrink-0", className)}>
130
+ <div className="flex items-center gap-1">
131
+ <Check className="w-3 h-3 text-emerald-500" />
132
+ <span className="text-[11px] text-muted-foreground">
133
+ {notedType
134
+ ? notedType === "up" ? "Noted" : "Recorded"
135
+ : submittedFeedback?.type === "up" ? "Noted" : "Recorded"}
136
+ </span>
137
+ </div>
138
+ {submittedFeedback && (submittedFeedback.pills.length > 0 || submittedFeedback.detail) && (
139
+ <button
140
+ type="button"
141
+ onClick={editSubmitted}
142
+ className="mt-1.5 w-full text-left space-y-1 group cursor-pointer"
143
+ >
144
+ {submittedFeedback.pills.length > 0 && (
145
+ <div className="flex flex-wrap gap-1">
146
+ {submittedFeedback.pills.map((p) => (
147
+ <span
148
+ key={p}
149
+ className={cn(
150
+ "rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors group-hover:opacity-80",
151
+ submittedFeedback.type === "up"
152
+ ? "border-emerald-200/60 bg-emerald-50/50 text-emerald-700/70"
153
+ : "border-red-200/60 bg-red-50/50 text-red-700/70"
154
+ )}
155
+ >
156
+ {p}
157
+ </span>
158
+ ))}
159
+ </div>
160
+ )}
161
+ {submittedFeedback.detail && (
162
+ <p className="text-[11px] text-muted-foreground/70 leading-snug group-hover:text-muted-foreground transition-colors">{submittedFeedback.detail}</p>
163
+ )}
164
+ </button>
165
+ )}
166
+ </div>
167
+ )
168
+ }
169
+
170
+ return (
171
+ <div className={cn("flex gap-0.5 shrink-0", className)}>
172
+ <button
173
+ type="button"
174
+ onClick={() => handleThumbClick("up")}
175
+ className={cn(
176
+ "p-1.5 rounded transition-colors",
177
+ thumbState === "up"
178
+ ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400"
179
+ : "hover:bg-muted text-muted-foreground hover:text-foreground"
180
+ )}
181
+ >
182
+ <ThumbsUp className="w-3.5 h-3.5" fill={thumbState === "up" ? "currentColor" : "none"} />
183
+ </button>
184
+ <button
185
+ type="button"
186
+ onClick={() => handleThumbClick("down")}
187
+ className={cn(
188
+ "p-1.5 rounded transition-colors",
189
+ thumbState === "down"
190
+ ? "bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400"
191
+ : "hover:bg-muted text-muted-foreground hover:text-foreground"
192
+ )}
193
+ >
194
+ <ThumbsDown className="w-3.5 h-3.5" fill={thumbState === "down" ? "currentColor" : "none"} />
195
+ </button>
196
+ </div>
197
+ )
198
+ }
199
+
200
+ function Panel({ className }: { className?: string }) {
201
+ const {
202
+ thumbState,
203
+ selectedPills,
204
+ detailText,
205
+ otherSelected,
206
+ hasRequiredInput,
207
+ togglePill,
208
+ setDetailText,
209
+ handleSubmit,
210
+ } = useScoreFeedback()
211
+
212
+ if (!thumbState) return null
213
+
214
+ return (
215
+ <div className={cn("overflow-hidden", className)}>
216
+ <div className="mt-4 pt-4 pb-1 space-y-3 border-t border-border/60">
217
+ <span className="text-[11px] font-bold text-muted-foreground/70 uppercase tracking-wider">
218
+ How&apos;s this score?
219
+ </span>
220
+ <div>
221
+ <span className="text-xs text-muted-foreground mb-2 block font-medium">
222
+ {thumbState === "up" ? "What was useful?" : "What\u2019s the issue?"}
223
+ </span>
224
+ <div className="flex flex-wrap gap-1.5">
225
+ {(thumbState === "up" ? positivePills : negativePills).map((pill) => (
226
+ <button
227
+ key={pill}
228
+ type="button"
229
+ onClick={() => togglePill(pill)}
230
+ className={cn(
231
+ "px-2.5 py-1 rounded-full text-[11px] font-medium border transition-colors",
232
+ selectedPills.includes(pill)
233
+ ? thumbState === "up"
234
+ ? "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800"
235
+ : "bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800"
236
+ : "bg-background text-muted-foreground border-border hover:bg-muted/50 hover:text-foreground"
237
+ )}
238
+ >
239
+ {pill}
240
+ </button>
241
+ ))}
242
+ </div>
243
+ </div>
244
+
245
+ <div className="space-y-1">
246
+ <input
247
+ type="text"
248
+ value={detailText}
249
+ onChange={(e) => setDetailText(e.target.value)}
250
+ onKeyDown={(e) => {
251
+ if (e.key === "Enter" && hasRequiredInput) handleSubmit()
252
+ }}
253
+ placeholder={
254
+ thumbState === "up"
255
+ ? "Tell us more (optional)"
256
+ : "e.g., The risk factors are outdated"
257
+ }
258
+ className="w-full text-xs bg-background border border-border rounded-md px-2.5 py-2 text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring"
259
+ />
260
+ {otherSelected && detailText.trim().length === 0 && (
261
+ <span className="text-[10px] text-red-500">
262
+ Please describe when &ldquo;Other&rdquo; is selected
263
+ </span>
264
+ )}
265
+ </div>
266
+
267
+ <div className="flex items-center gap-2 pt-1">
268
+ <button
269
+ type="button"
270
+ onClick={handleSubmit}
271
+ disabled={!hasRequiredInput}
272
+ className={cn(
273
+ "flex-1 py-1.5 rounded-md text-xs font-semibold transition-colors",
274
+ hasRequiredInput
275
+ ? "bg-foreground text-background hover:bg-foreground/90"
276
+ : "bg-muted text-muted-foreground cursor-not-allowed"
277
+ )}
278
+ >
279
+ Submit
280
+ </button>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ )
285
+ }
286
+
287
+ export const ScoreFeedback = { Root, Trigger, Panel }
288
+ export { useScoreFeedback }
@@ -0,0 +1,87 @@
1
+ import * as React from "react"
2
+ import { cn } from "../lib/utils"
3
+
4
+ function getScoreColor(score: number, denominator: number) {
5
+ const pct = (score / denominator) * 100
6
+ if (pct >= 70) return "text-emerald-500"
7
+ if (pct >= 40) return "text-amber-500"
8
+ return "text-red-500"
9
+ }
10
+
11
+ function getScoreTrackColor(score: number, denominator: number) {
12
+ const pct = (score / denominator) * 100
13
+ if (pct >= 70) return "text-emerald-500/15"
14
+ if (pct >= 40) return "text-amber-500/15"
15
+ return "text-red-500/15"
16
+ }
17
+
18
+ interface ScoreRingProps {
19
+ score: number
20
+ denominator?: number
21
+ size?: number
22
+ strokeWidth?: number
23
+ className?: string
24
+ showLabel?: boolean
25
+ }
26
+
27
+ function ScoreRing({
28
+ score,
29
+ denominator = 100,
30
+ size = 120,
31
+ strokeWidth = 10,
32
+ className,
33
+ showLabel = true,
34
+ }: ScoreRingProps) {
35
+ const radius = (size - strokeWidth) / 2
36
+ const circumference = 2 * Math.PI * radius
37
+ const pct = Math.min(score / denominator, 1)
38
+ const offset = circumference * (1 - pct)
39
+
40
+ return (
41
+ <div className={cn("relative inline-flex items-center justify-center", className)}>
42
+ <svg
43
+ width={size}
44
+ height={size}
45
+ viewBox={`0 0 ${size} ${size}`}
46
+ className="-rotate-90"
47
+ >
48
+ {/* Track */}
49
+ <circle
50
+ cx={size / 2}
51
+ cy={size / 2}
52
+ r={radius}
53
+ fill="none"
54
+ stroke="currentColor"
55
+ strokeWidth={strokeWidth}
56
+ className={getScoreTrackColor(score, denominator)}
57
+ />
58
+ {/* Fill */}
59
+ <circle
60
+ cx={size / 2}
61
+ cy={size / 2}
62
+ r={radius}
63
+ fill="none"
64
+ stroke="currentColor"
65
+ strokeWidth={strokeWidth}
66
+ strokeLinecap="round"
67
+ strokeDasharray={circumference}
68
+ strokeDashoffset={offset}
69
+ className={cn("transition-all duration-500", getScoreColor(score, denominator))}
70
+ />
71
+ </svg>
72
+ {showLabel && (
73
+ <div className="absolute inset-0 flex flex-col items-center justify-center">
74
+ <span className="text-2xl font-bold text-foreground leading-none">
75
+ {score}
76
+ </span>
77
+ <span className="text-xs text-muted-foreground mt-0.5">
78
+ /{denominator}
79
+ </span>
80
+ </div>
81
+ )}
82
+ </div>
83
+ )
84
+ }
85
+
86
+ export { ScoreRing, getScoreColor }
87
+ export type { ScoreRingProps }
@@ -0,0 +1,58 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ function ScrollArea({
9
+ className,
10
+ children,
11
+ ...props
12
+ }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
13
+ return (
14
+ <ScrollAreaPrimitive.Root
15
+ data-slot="scroll-area"
16
+ className={cn("relative", className)}
17
+ {...props}
18
+ >
19
+ <ScrollAreaPrimitive.Viewport
20
+ data-slot="scroll-area-viewport"
21
+ className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
22
+ >
23
+ {children}
24
+ </ScrollAreaPrimitive.Viewport>
25
+ <ScrollBar />
26
+ <ScrollAreaPrimitive.Corner />
27
+ </ScrollAreaPrimitive.Root>
28
+ )
29
+ }
30
+
31
+ function ScrollBar({
32
+ className,
33
+ orientation = "vertical",
34
+ ...props
35
+ }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
36
+ return (
37
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
38
+ data-slot="scroll-area-scrollbar"
39
+ orientation={orientation}
40
+ className={cn(
41
+ "flex touch-none p-px transition-colors select-none",
42
+ orientation === "vertical" &&
43
+ "h-full w-2.5 border-l border-l-transparent",
44
+ orientation === "horizontal" &&
45
+ "h-2.5 flex-col border-t border-t-transparent",
46
+ className
47
+ )}
48
+ {...props}
49
+ >
50
+ <ScrollAreaPrimitive.ScrollAreaThumb
51
+ data-slot="scroll-area-thumb"
52
+ className="bg-border relative flex-1 rounded-full"
53
+ />
54
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
55
+ )
56
+ }
57
+
58
+ export { ScrollArea, ScrollBar }