@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,591 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Check, CirclePlus, ExternalLink, Lock, ThumbsDown } from "lucide-react"
5
+
6
+ const dismissReasons = [
7
+ "Bad timing",
8
+ "Inaccurate data",
9
+ "Wrong account",
10
+ "Already handled",
11
+ "Not actionable",
12
+ "Other",
13
+ ]
14
+
15
+ const approveReasons = [
16
+ "Right timing",
17
+ "Accurate data",
18
+ "Good prospect fit",
19
+ "Actionable",
20
+ ]
21
+
22
+ type ApprovalState = "pending" | "confirming" | "approving-feedback" | "dismissing" | "approved" | "dismissed" | "auto-approved"
23
+
24
+ interface SignalApprovalLabels {
25
+ approveButton?: string
26
+ dismissButton?: string
27
+ approvedStatus?: string
28
+ dismissedStatus?: string
29
+ confirmPrompt?: string
30
+ dismissPrompt?: string
31
+ feedbackPrompt?: string
32
+ }
33
+
34
+ const DEFAULT_LABELS: Required<SignalApprovalLabels> = {
35
+ approveButton: "Approve action",
36
+ dismissButton: "Not Helpful",
37
+ approvedStatus: "Action Approved",
38
+ dismissedStatus: "Action Dismissed",
39
+ confirmPrompt: "This will approve this action for",
40
+ dismissPrompt: "What\u2019s the issue with this action?",
41
+ feedbackPrompt: "Quick feedback \u2014 what made this action useful?",
42
+ }
43
+
44
+ interface SignalApprovalContextValue {
45
+ approvalState: ApprovalState
46
+ companyName: string
47
+ opportunityUrl?: string
48
+ scheduledTime?: string
49
+ labels: Required<SignalApprovalLabels>
50
+ approve: () => void
51
+ submitApproveFeedback: (reasons: string[], detail: string) => void
52
+ skipApproveFeedback: () => void
53
+ dismiss: (reasons: string[], detail: string) => void
54
+ requestApproval: () => void
55
+ requestDismiss: () => void
56
+ cancel: () => void
57
+ }
58
+
59
+ const SignalApprovalCtx = React.createContext<SignalApprovalContextValue | null>(null)
60
+
61
+ export function useSignalApproval() {
62
+ const ctx = React.useContext(SignalApprovalCtx)
63
+ if (!ctx) throw new Error("SignalApproval components must be used within SignalApproval.Root")
64
+ return ctx
65
+ }
66
+
67
+ interface RootProps {
68
+ children: React.ReactNode
69
+ companyName: string
70
+ opportunityUrl?: string
71
+ scheduledTime?: string
72
+ initialApprovalState?: ApprovalState
73
+ labels?: SignalApprovalLabels
74
+ onApprove?: () => void
75
+ onApproveFeedback?: (reasons: string[], detail: string) => void
76
+ onDismiss?: (reasons: string[], detail: string) => void
77
+ }
78
+
79
+ function Root({ children, companyName, opportunityUrl, scheduledTime, initialApprovalState, labels: labelOverrides, onApprove, onApproveFeedback, onDismiss }: RootProps) {
80
+ const labels = React.useMemo(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides])
81
+ const [approvalState, setApprovalState] = React.useState<ApprovalState>(initialApprovalState ?? "pending")
82
+
83
+ const requestApproval = React.useCallback(() => {
84
+ setApprovalState("confirming")
85
+ }, [])
86
+
87
+ const requestDismiss = React.useCallback(() => {
88
+ setApprovalState("dismissing")
89
+ }, [])
90
+
91
+ const cancel = React.useCallback(() => {
92
+ setApprovalState("pending")
93
+ }, [])
94
+
95
+ const approve = React.useCallback(() => {
96
+ setApprovalState("approving-feedback")
97
+ onApprove?.()
98
+ }, [onApprove])
99
+
100
+ const submitApproveFeedback = React.useCallback(
101
+ (reasons: string[], detail: string) => {
102
+ setApprovalState("approved")
103
+ onApproveFeedback?.(reasons, detail)
104
+ },
105
+ [onApproveFeedback]
106
+ )
107
+
108
+ const skipApproveFeedback = React.useCallback(() => {
109
+ setApprovalState("approved")
110
+ }, [])
111
+
112
+ const dismiss = React.useCallback(
113
+ (reasons: string[], detail: string) => {
114
+ setApprovalState("dismissed")
115
+ onDismiss?.(reasons, detail)
116
+ },
117
+ [onDismiss]
118
+ )
119
+
120
+ return (
121
+ <SignalApprovalCtx.Provider
122
+ value={{ approvalState, companyName, opportunityUrl, scheduledTime, labels, approve, submitApproveFeedback, skipApproveFeedback, dismiss, requestApproval, requestDismiss, cancel }}
123
+ >
124
+ {children}
125
+ </SignalApprovalCtx.Provider>
126
+ )
127
+ }
128
+
129
+ function SubmittedFeedback({
130
+ reasons,
131
+ detail,
132
+ variant,
133
+ onEdit,
134
+ }: {
135
+ reasons: string[]
136
+ detail: string
137
+ variant: "approve" | "dismiss"
138
+ onEdit: () => void
139
+ }) {
140
+ if (reasons.length === 0 && !detail) return null
141
+ const pillClass =
142
+ variant === "approve"
143
+ ? "border-emerald-200/60 bg-emerald-50/50 text-emerald-700/70"
144
+ : "border-red-200/60 bg-red-50/50 text-red-700/70"
145
+
146
+ return (
147
+ <button
148
+ type="button"
149
+ onClick={onEdit}
150
+ className="w-full text-left space-y-1.5 group cursor-pointer"
151
+ >
152
+ {reasons.length > 0 && (
153
+ <div className="flex flex-wrap gap-1">
154
+ {reasons.map((r) => (
155
+ <span
156
+ key={r}
157
+ className={`rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors group-hover:opacity-80 ${pillClass}`}
158
+ >
159
+ {r}
160
+ </span>
161
+ ))}
162
+ </div>
163
+ )}
164
+ {detail && (
165
+ <p className="text-[11px] text-muted-foreground/70 leading-snug group-hover:text-muted-foreground transition-colors">{detail}</p>
166
+ )}
167
+ </button>
168
+ )
169
+ }
170
+
171
+ function Actions() {
172
+ const { approvalState, companyName, opportunityUrl, scheduledTime, labels, approve, submitApproveFeedback, skipApproveFeedback, dismiss, requestApproval, requestDismiss, cancel } =
173
+ useSignalApproval()
174
+ const [selectedReasons, setSelectedReasons] = React.useState<string[]>([])
175
+ const [detailText, setDetailText] = React.useState("")
176
+ const [submittedFeedback, setSubmittedFeedback] = React.useState<{ reasons: string[]; detail: string } | null>(null)
177
+ const [isEditing, setIsEditing] = React.useState(false)
178
+
179
+ const otherSelected = selectedReasons.includes("Other")
180
+ const canSubmitDismiss = selectedReasons.length > 0 && (!otherSelected || detailText.trim().length > 0)
181
+ const canSubmitApprove = selectedReasons.length > 0 || detailText.trim().length > 0
182
+
183
+ const toggleReason = (reason: string) => {
184
+ setSelectedReasons((prev) =>
185
+ prev.includes(reason) ? prev.filter((r) => r !== reason) : [...prev, reason]
186
+ )
187
+ }
188
+
189
+ const startEditing = () => {
190
+ if (submittedFeedback) {
191
+ setSelectedReasons([...submittedFeedback.reasons])
192
+ setDetailText(submittedFeedback.detail)
193
+ }
194
+ setIsEditing(true)
195
+ }
196
+
197
+ const handleDismissSubmit = () => {
198
+ if (!canSubmitDismiss) return
199
+ const fb = { reasons: [...selectedReasons], detail: detailText.trim() }
200
+ setSubmittedFeedback(fb)
201
+ dismiss(selectedReasons, detailText.trim())
202
+ setSelectedReasons([])
203
+ setDetailText("")
204
+ setIsEditing(false)
205
+ }
206
+
207
+ const handleApproveSubmit = () => {
208
+ const fb = { reasons: [...selectedReasons], detail: detailText.trim() }
209
+ setSubmittedFeedback(fb)
210
+ submitApproveFeedback(selectedReasons, detailText.trim())
211
+ setSelectedReasons([])
212
+ setDetailText("")
213
+ setIsEditing(false)
214
+ }
215
+
216
+ const handleEditCancel = () => {
217
+ setSelectedReasons([])
218
+ setDetailText("")
219
+ setIsEditing(false)
220
+ }
221
+
222
+ const handleCancel = () => {
223
+ cancel()
224
+ setSelectedReasons([])
225
+ setDetailText("")
226
+ }
227
+
228
+ if (approvalState === "approved") {
229
+ if (isEditing) {
230
+ return (
231
+ <div className="space-y-3">
232
+ <div className="flex items-center gap-1.5 text-xs font-medium text-emerald-600 mb-2">
233
+ <Check className="h-3.5 w-3.5" />
234
+ {opportunityUrl ? (
235
+ <a href={opportunityUrl} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:underline underline-offset-2">
236
+ {labels.approvedStatus} <ExternalLink className="h-3 w-3" />
237
+ </a>
238
+ ) : (
239
+ <span>{labels.approvedStatus}</span>
240
+ )}
241
+ </div>
242
+ <p className="text-xs font-medium text-muted-foreground">Edit your feedback</p>
243
+ <div className="flex flex-wrap gap-1.5">
244
+ {approveReasons.map((reason) => {
245
+ const selected = selectedReasons.includes(reason)
246
+ return (
247
+ <button key={reason} type="button" onClick={() => toggleReason(reason)}
248
+ className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
249
+ selected ? "border-emerald-200 bg-emerald-100 text-emerald-700" : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
250
+ }`}>{reason}</button>
251
+ )
252
+ })}
253
+ </div>
254
+ <input type="text" value={detailText} onChange={(e) => setDetailText(e.target.value)}
255
+ onKeyDown={(e) => { if (e.key === "Enter" && canSubmitApprove) handleApproveSubmit() }}
256
+ placeholder="Tell us more (optional)"
257
+ className="h-7 w-full rounded-md border border-border bg-muted/20 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring" />
258
+ <div className="flex items-center gap-2">
259
+ <button type="button" onClick={handleApproveSubmit} disabled={!canSubmitApprove}
260
+ className={`inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-xs font-semibold transition-colors ${canSubmitApprove ? "bg-foreground text-background hover:bg-foreground/90" : "cursor-not-allowed bg-muted text-muted-foreground"}`}>
261
+ Save
262
+ </button>
263
+ <button type="button" onClick={handleEditCancel}
264
+ className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
265
+ Cancel
266
+ </button>
267
+ </div>
268
+ </div>
269
+ )
270
+ }
271
+
272
+ return (
273
+ <div className="space-y-2">
274
+ <div className="flex items-center gap-1.5 text-xs font-medium text-emerald-600">
275
+ <Check className="h-3.5 w-3.5" />
276
+ {opportunityUrl ? (
277
+ <a
278
+ href={opportunityUrl}
279
+ target="_blank"
280
+ rel="noopener noreferrer"
281
+ className="inline-flex items-center gap-1 hover:underline underline-offset-2"
282
+ >
283
+ Opportunity Created
284
+ <ExternalLink className="h-3 w-3" />
285
+ </a>
286
+ ) : (
287
+ <span>Opportunity Created</span>
288
+ )}
289
+ </div>
290
+ {submittedFeedback && (
291
+ <SubmittedFeedback
292
+ reasons={submittedFeedback.reasons}
293
+ detail={submittedFeedback.detail}
294
+ variant="approve"
295
+ onEdit={startEditing}
296
+ />
297
+ )}
298
+ </div>
299
+ )
300
+ }
301
+
302
+ if (approvalState === "auto-approved") {
303
+ return (
304
+ <div className="space-y-2">
305
+ <div className="flex items-center gap-1.5 text-xs font-medium text-emerald-600">
306
+ <Check className="h-3.5 w-3.5" />
307
+ <span>{labels.approvedStatus}</span>
308
+ </div>
309
+ {scheduledTime && (
310
+ <p className="text-[11px] text-muted-foreground">Scheduled: {scheduledTime}</p>
311
+ )}
312
+ </div>
313
+ )
314
+ }
315
+
316
+ if (approvalState === "approving-feedback") {
317
+ return (
318
+ <div className="space-y-3">
319
+ <div className="flex items-center gap-1.5 text-xs font-medium text-emerald-600 mb-2">
320
+ <Check className="h-3.5 w-3.5" />
321
+ {opportunityUrl ? (
322
+ <a
323
+ href={opportunityUrl}
324
+ target="_blank"
325
+ rel="noopener noreferrer"
326
+ className="inline-flex items-center gap-1 hover:underline underline-offset-2"
327
+ >
328
+ Opportunity Created
329
+ <ExternalLink className="h-3 w-3" />
330
+ </a>
331
+ ) : (
332
+ <span>Opportunity Created</span>
333
+ )}
334
+ </div>
335
+ <p className="text-xs font-medium text-muted-foreground">{labels.feedbackPrompt}</p>
336
+ <div className="flex flex-wrap gap-1.5">
337
+ {approveReasons.map((reason) => {
338
+ const selected = selectedReasons.includes(reason)
339
+ return (
340
+ <button
341
+ key={reason}
342
+ type="button"
343
+ onClick={() => toggleReason(reason)}
344
+ className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
345
+ selected
346
+ ? "border-emerald-200 bg-emerald-100 text-emerald-700"
347
+ : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
348
+ }`}
349
+ >
350
+ {reason}
351
+ </button>
352
+ )
353
+ })}
354
+ </div>
355
+
356
+ <input
357
+ type="text"
358
+ value={detailText}
359
+ onChange={(e) => setDetailText(e.target.value)}
360
+ onKeyDown={(e) => {
361
+ if (e.key === "Enter" && canSubmitApprove) handleApproveSubmit()
362
+ }}
363
+ placeholder="Tell us more (optional)"
364
+ className="h-7 w-full rounded-md border border-border bg-muted/20 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring"
365
+ />
366
+
367
+ <div className="flex items-center gap-2">
368
+ <button
369
+ type="button"
370
+ onClick={handleApproveSubmit}
371
+ disabled={!canSubmitApprove}
372
+ className={`inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-xs font-semibold transition-colors ${
373
+ canSubmitApprove
374
+ ? "bg-foreground text-background hover:bg-foreground/90"
375
+ : "cursor-not-allowed bg-muted text-muted-foreground"
376
+ }`}
377
+ >
378
+ Submit
379
+ </button>
380
+ <button
381
+ type="button"
382
+ onClick={skipApproveFeedback}
383
+ className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
384
+ >
385
+ Skip
386
+ </button>
387
+ </div>
388
+ </div>
389
+ )
390
+ }
391
+
392
+ if (approvalState === "dismissed") {
393
+ if (isEditing) {
394
+ return (
395
+ <div className="space-y-3">
396
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground mb-2">
397
+ <ThumbsDown className="h-3.5 w-3.5" />
398
+ <span>{labels.dismissedStatus}</span>
399
+ </div>
400
+ <p className="text-xs font-medium text-muted-foreground">Edit your feedback</p>
401
+ <div className="flex flex-wrap gap-1.5">
402
+ {dismissReasons.map((reason) => {
403
+ const selected = selectedReasons.includes(reason)
404
+ return (
405
+ <button key={reason} type="button" onClick={() => toggleReason(reason)}
406
+ className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
407
+ selected ? "border-red-200 bg-red-100 text-red-700" : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
408
+ }`}>{reason}</button>
409
+ )
410
+ })}
411
+ </div>
412
+ <input type="text" value={detailText} onChange={(e) => setDetailText(e.target.value)}
413
+ onKeyDown={(e) => { if (e.key === "Enter" && canSubmitDismiss) handleDismissSubmit() }}
414
+ placeholder={otherSelected ? "Please describe (required)" : "Provide additional feedback..."}
415
+ className="h-7 w-full rounded-md border border-border bg-muted/20 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring" />
416
+ <div className="flex items-center gap-2">
417
+ <button type="button" onClick={handleDismissSubmit} disabled={!canSubmitDismiss}
418
+ className={`inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-xs font-semibold transition-colors ${canSubmitDismiss ? "bg-foreground text-background hover:bg-foreground/90" : "cursor-not-allowed bg-muted text-muted-foreground"}`}>
419
+ Save
420
+ </button>
421
+ <button type="button" onClick={handleEditCancel}
422
+ className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
423
+ Cancel
424
+ </button>
425
+ </div>
426
+ </div>
427
+ )
428
+ }
429
+
430
+ return (
431
+ <div className="space-y-2">
432
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
433
+ <ThumbsDown className="h-3.5 w-3.5" />
434
+ <span>Signal Dismissed</span>
435
+ </div>
436
+ {submittedFeedback && (
437
+ <SubmittedFeedback
438
+ reasons={submittedFeedback.reasons}
439
+ detail={submittedFeedback.detail}
440
+ variant="dismiss"
441
+ onEdit={startEditing}
442
+ />
443
+ )}
444
+ </div>
445
+ )
446
+ }
447
+
448
+ if (approvalState === "confirming") {
449
+ return (
450
+ <div className="space-y-3">
451
+ <div className="rounded-md border border-border bg-muted/30 p-3">
452
+ <p className="text-sm text-foreground">
453
+ {labels.confirmPrompt} <strong>{companyName}</strong>. Confirm?
454
+ </p>
455
+ </div>
456
+ <div className="flex items-center gap-2">
457
+ <button
458
+ type="button"
459
+ onClick={approve}
460
+ className="inline-flex h-7 items-center gap-1.5 rounded-md bg-foreground px-3 text-xs font-semibold text-background transition-colors hover:bg-foreground/90"
461
+ >
462
+ <Check className="h-3 w-3" />
463
+ Confirm
464
+ </button>
465
+ <button
466
+ type="button"
467
+ onClick={handleCancel}
468
+ className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
469
+ >
470
+ Cancel
471
+ </button>
472
+ </div>
473
+ </div>
474
+ )
475
+ }
476
+
477
+ if (approvalState === "dismissing") {
478
+ return (
479
+ <div className="space-y-3">
480
+ <p className="text-xs font-medium text-muted-foreground">{labels.dismissPrompt}</p>
481
+ <div className="flex flex-wrap gap-1.5">
482
+ {dismissReasons.map((reason) => {
483
+ const selected = selectedReasons.includes(reason)
484
+ return (
485
+ <button
486
+ key={reason}
487
+ type="button"
488
+ onClick={() => toggleReason(reason)}
489
+ className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
490
+ selected
491
+ ? "border-red-200 bg-red-100 text-red-700"
492
+ : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
493
+ }`}
494
+ >
495
+ {reason}
496
+ </button>
497
+ )
498
+ })}
499
+ </div>
500
+
501
+ {(selectedReasons.length > 0 || otherSelected) && (
502
+ <input
503
+ type="text"
504
+ value={detailText}
505
+ onChange={(e) => setDetailText(e.target.value)}
506
+ onKeyDown={(e) => {
507
+ if (e.key === "Enter" && canSubmitDismiss) handleDismissSubmit()
508
+ }}
509
+ placeholder={otherSelected ? "Please describe (required)" : "Provide additional feedback..."}
510
+ className="h-7 w-full rounded-md border border-border bg-muted/20 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring"
511
+ />
512
+ )}
513
+
514
+ <div className="flex items-center gap-2">
515
+ <button
516
+ type="button"
517
+ onClick={handleDismissSubmit}
518
+ disabled={!canSubmitDismiss}
519
+ className={`inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-xs font-semibold transition-colors ${
520
+ canSubmitDismiss
521
+ ? "bg-foreground text-background hover:bg-foreground/90"
522
+ : "cursor-not-allowed bg-muted text-muted-foreground"
523
+ }`}
524
+ >
525
+ Submit
526
+ </button>
527
+ <button
528
+ type="button"
529
+ onClick={handleCancel}
530
+ className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
531
+ >
532
+ Cancel
533
+ </button>
534
+ </div>
535
+ </div>
536
+ )
537
+ }
538
+
539
+ return (
540
+ <div className="flex items-center gap-2">
541
+ <button
542
+ type="button"
543
+ onClick={requestApproval}
544
+ className="inline-flex h-7 items-center gap-1.5 rounded-md border border-border bg-foreground px-3 text-xs font-semibold text-background shadow-none transition-colors hover:bg-foreground/90"
545
+ >
546
+ <CirclePlus className="h-3.5 w-3.5" />
547
+ {labels.approveButton}
548
+ </button>
549
+ <button
550
+ type="button"
551
+ onClick={requestDismiss}
552
+ className="inline-flex h-7 items-center gap-1.5 rounded-md border border-border px-3 text-xs font-medium text-muted-foreground shadow-none transition-colors hover:bg-muted hover:text-foreground"
553
+ >
554
+ <ThumbsDown className="h-3.5 w-3.5" />
555
+ {labels.dismissButton}
556
+ </button>
557
+ </div>
558
+ )
559
+ }
560
+
561
+ function Gate({ children }: { children: React.ReactNode }) {
562
+ const { approvalState } = useSignalApproval()
563
+ const isLocked =
564
+ approvalState === "pending" || approvalState === "confirming" || approvalState === "dismissing"
565
+
566
+ return (
567
+ <div className="relative">
568
+ {isLocked && (
569
+ <div className="pointer-events-none absolute inset-x-0 top-4 z-10 flex justify-center">
570
+ <div className="flex items-center gap-1.5 rounded-full border border-border bg-background px-3 py-1.5 text-xs text-muted-foreground shadow-sm">
571
+ <Lock className="h-3 w-3" />
572
+ Approve or dismiss the signal above to unlock
573
+ </div>
574
+ </div>
575
+ )}
576
+ <div
577
+ className={`transition-opacity duration-300 ${isLocked ? "pointer-events-none select-none opacity-40" : "opacity-100"}`}
578
+ >
579
+ {children}
580
+ </div>
581
+ </div>
582
+ )
583
+ }
584
+
585
+ export {
586
+ Root as SignalApprovalRoot,
587
+ Actions as SignalApprovalActions,
588
+ Gate as SignalApprovalGate,
589
+ }
590
+ export const SignalApproval = { Root, Actions, Gate }
591
+ export type { ApprovalState, SignalApprovalLabels, SignalApprovalContextValue, RootProps as SignalApprovalRootProps }