@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,24 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Label as LabelPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ function Label({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
12
+ return (
13
+ <LabelPrimitive.Root
14
+ data-slot="label"
15
+ className={cn(
16
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export { Label }
@@ -0,0 +1,83 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import type { ComponentProps, HTMLAttributes } from "react"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "../lib/utils"
8
+ import { Avatar, AvatarFallback, AvatarImage } from "./avatar"
9
+
10
+ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
11
+ from: "user" | "assistant"
12
+ }
13
+
14
+ export const Message = ({ className, from, ...props }: MessageProps) => (
15
+ <div
16
+ data-slot="message"
17
+ className={cn(
18
+ "group flex w-full items-end gap-3 py-2",
19
+ from === "user" ? "is-user justify-end" : "is-assistant justify-start",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ )
25
+
26
+ const messageContentVariants = cva(
27
+ "flex flex-col gap-2 overflow-hidden text-sm leading-relaxed shadow-sm whitespace-pre-wrap",
28
+ {
29
+ variants: {
30
+ variant: {
31
+ contained: [
32
+ "max-w-[85%] px-6 py-4 rounded-[24px]",
33
+ "group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground group-[.is-user]:rounded-br-sm",
34
+ "group-[.is-assistant]:bg-muted group-[.is-assistant]:text-foreground group-[.is-assistant]:rounded-bl-sm",
35
+ ],
36
+ flat: [
37
+ "group-[.is-user]:max-w-[85%] group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
38
+ "group-[.is-assistant]:text-foreground",
39
+ ],
40
+ },
41
+ },
42
+ defaultVariants: {
43
+ variant: "contained",
44
+ },
45
+ }
46
+ )
47
+
48
+ export type MessageContentProps = HTMLAttributes<HTMLDivElement> &
49
+ VariantProps<typeof messageContentVariants>
50
+
51
+ export const MessageContent = ({
52
+ children,
53
+ className,
54
+ variant,
55
+ ...props
56
+ }: MessageContentProps) => (
57
+ <div
58
+ data-slot="message-content"
59
+ className={cn(messageContentVariants({ variant, className }))}
60
+ {...props}
61
+ >
62
+ {children}
63
+ </div>
64
+ )
65
+
66
+ export type MessageAvatarProps = ComponentProps<typeof Avatar> & {
67
+ src?: string
68
+ name?: string
69
+ }
70
+
71
+ export const MessageAvatar = ({
72
+ src,
73
+ name,
74
+ className,
75
+ children,
76
+ ...props
77
+ }: MessageAvatarProps) => (
78
+ <Avatar className={cn("ring-border size-8 ring-1 shrink-0", className)} {...props}>
79
+ {children}
80
+ {src && <AvatarImage alt="" className="mt-0 mb-0" src={src} />}
81
+ {!children && !src && <AvatarFallback>{name?.slice(0, 2) || "ME"}</AvatarFallback>}
82
+ </Avatar>
83
+ )
@@ -0,0 +1,178 @@
1
+ import * as React from "react"
2
+ import { ArrowUp, ArrowDown, Info, ExternalLink } from "lucide-react"
3
+ import { cn } from "../lib/utils"
4
+
5
+ export interface MetricDataPoint {
6
+ label: string
7
+ value: number | string
8
+ color?: string
9
+ }
10
+
11
+ export interface MetricCardProps {
12
+ title: string
13
+ value?: string | number
14
+ unit?: string
15
+ subtitle?: string
16
+ change?: {
17
+ value: string
18
+ direction: "up" | "down" | "neutral"
19
+ isGood?: boolean // if true, up is green. if false, up is red (e.g. churn).
20
+ }
21
+ footerText?: string
22
+ dataPoints?: MetricDataPoint[]
23
+ showExternalLink?: boolean
24
+ showInfo?: boolean
25
+ }
26
+
27
+ export function MetricCard({
28
+ title,
29
+ value,
30
+ unit,
31
+ subtitle,
32
+ change,
33
+ footerText,
34
+ dataPoints,
35
+ showExternalLink,
36
+ showInfo = true,
37
+ }: MetricCardProps) {
38
+ // SVG Donut Chart logic for variants with dataPoints
39
+ const renderDonut = () => {
40
+ if (!dataPoints || dataPoints.length === 0 || value === undefined) return null
41
+
42
+ // Simple pseudo-donut chart logic assuming specific colors from the image
43
+ // In a real prod environment we'd use recharts/visx, but for this standalone component
44
+ // we can draw an SVG circle with stroke-dasharray based on the data
45
+ const size = 80
46
+ const strokeWidth = 12
47
+ const radius = (size - strokeWidth) / 2
48
+ const circumference = 2 * Math.PI * radius
49
+
50
+ // Calculate total to distribute the circle
51
+ const total = dataPoints.reduce((sum, dp) => sum + (typeof dp.value === 'number' ? dp.value : 0), 0)
52
+ let currentOffset = 0
53
+
54
+ return (
55
+ <div className="relative" style={{ width: size, height: size }}>
56
+ <svg width={size} height={size} className="transform -rotate-90">
57
+ {dataPoints.map((dp, i) => {
58
+ const val = typeof dp.value === 'number' ? dp.value : 0
59
+ const percentage = val / total
60
+ const strokeLength = percentage * circumference
61
+ const offset = currentOffset
62
+ currentOffset += strokeLength
63
+
64
+ // Fallback colors matching the image's teal/green palette
65
+ const colors = ["#166534", "#22c55e", "#6ee7b7", "#ccfbf1", "#f1f5f9"]
66
+ const color = dp.color || colors[i % colors.length]
67
+
68
+ return (
69
+ <circle
70
+ key={dp.label}
71
+ cx={size / 2}
72
+ cy={size / 2}
73
+ r={radius}
74
+ fill="none"
75
+ stroke={color}
76
+ strokeWidth={strokeWidth}
77
+ strokeDasharray={`${Math.max(strokeLength - 2, 0)} ${circumference}`}
78
+ strokeDashoffset={-offset}
79
+ className="transition-all duration-300"
80
+ />
81
+ )
82
+ })}
83
+ </svg>
84
+ <div className="absolute inset-0 flex items-center justify-center flex-col">
85
+ <span className="text-xl font-bold text-foreground leading-none">{value}</span>
86
+ </div>
87
+ </div>
88
+ )
89
+ }
90
+
91
+ return (
92
+ <div className="flex flex-col rounded-xl border border-border bg-card p-5 shadow-sm h-full w-full">
93
+ <div className={cn("flex justify-between items-start", title ? "mb-4" : "mb-4")}>
94
+ {title ? (
95
+ <h3 className="font-semibold text-sm text-foreground/80">{title}</h3>
96
+ ) : (
97
+ <div className="flex flex-col">
98
+ <div className="flex items-baseline gap-1">
99
+ <span className="text-3xl font-bold tracking-tight text-foreground">{value}</span>
100
+ {unit && <span className="text-2xl font-bold tracking-tight text-foreground">{unit}</span>}
101
+ </div>
102
+ {subtitle && (
103
+ <p className="text-sm font-medium text-muted-foreground mt-2">{subtitle}</p>
104
+ )}
105
+ </div>
106
+ )}
107
+ <div className="flex items-center gap-1.5 text-muted-foreground shrink-0 mt-0.5">
108
+ {showExternalLink && <ExternalLink className="w-3.5 h-3.5 cursor-pointer hover:text-foreground transition-colors" />}
109
+ {showInfo && <Info className="w-3.5 h-3.5 cursor-pointer hover:text-foreground transition-colors" />}
110
+ </div>
111
+ </div>
112
+
113
+ <div className="flex-1 flex flex-col min-w-0">
114
+ {dataPoints && dataPoints.length > 0 ? (
115
+ // Donut Chart Variant
116
+ <div className="flex items-center gap-4 mt-2 mb-6">
117
+ <div className="shrink-0">
118
+ {renderDonut()}
119
+ </div>
120
+ <div className="flex flex-col gap-2 flex-1 min-w-0">
121
+ {dataPoints.slice(0, 5).map((dp, i) => {
122
+ const colors = ["bg-[#166534]", "bg-[#22c55e]", "bg-[#6ee7b7]", "bg-[#ccfbf1]", "bg-[#f1f5f9]"]
123
+ return (
124
+ <div key={dp.label} className="flex items-center justify-between gap-2 text-[11px] font-medium min-w-0">
125
+ <div className="flex items-center gap-1.5 text-muted-foreground min-w-0">
126
+ <div className={cn("w-1.5 h-1.5 rounded-full shrink-0", dp.color ? "" : colors[i % colors.length])} style={dp.color ? { backgroundColor: dp.color } : {}} />
127
+ <span className="whitespace-nowrap">{dp.label}</span>
128
+ </div>
129
+ <span className="text-foreground font-semibold shrink-0">{dp.value}</span>
130
+ </div>
131
+ )
132
+ })}
133
+ </div>
134
+ </div>
135
+ ) : title && (
136
+ // Standard Big Number Variant (only if title exists)
137
+ <div className="mb-6">
138
+ <div className="flex items-baseline gap-1">
139
+ <span className="text-4xl font-bold tracking-tight text-foreground">{value}</span>
140
+ {unit && <span className="text-2xl font-bold tracking-tight text-foreground">{unit}</span>}
141
+ </div>
142
+ {subtitle && (
143
+ <p className="text-sm font-medium text-muted-foreground mt-1">{subtitle}</p>
144
+ )}
145
+ </div>
146
+ )}
147
+
148
+ {/* Footer section (Change indicator & extra text) */}
149
+ <div className="mt-auto flex flex-col gap-1.5">
150
+ {change && (
151
+ <div className="flex items-center gap-1">
152
+ {(() => {
153
+ // Determine color based on isGood property
154
+ // By default, up is green (good), down is red (bad)
155
+ const isGoodDirection = change.isGood !== undefined
156
+ ? change.isGood
157
+ : change.direction === "up";
158
+
159
+ const colorClass = isGoodDirection ? "text-emerald-600" : "text-red-600";
160
+ const Icon = change.direction === "down" ? ArrowDown : ArrowUp;
161
+
162
+ return (
163
+ <span className={cn("text-xs font-semibold flex items-center gap-0.5", colorClass)}>
164
+ <Icon className="w-3 h-3 stroke-[3]" />
165
+ {change.value}
166
+ </span>
167
+ )
168
+ })()}
169
+ </div>
170
+ )}
171
+ {footerText && (
172
+ <span className="text-[11px] text-muted-foreground font-medium">{footerText}</span>
173
+ )}
174
+ </div>
175
+ </div>
176
+ </div>
177
+ )
178
+ }