@wealthx/shadcn 1.1.0 → 1.2.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 (300) hide show
  1. package/.turbo/turbo-build.log +235 -154
  2. package/CHANGELOG.md +6 -0
  3. package/dist/{chunk-6OJF6XRN.mjs → chunk-24FUO7TD.mjs} +4 -8
  4. package/dist/{chunk-4AJ5HWHD.mjs → chunk-2I5S2AMY.mjs} +3 -3
  5. package/dist/chunk-2SF672SZ.mjs +161 -0
  6. package/dist/{chunk-GPRJQ24C.mjs → chunk-34NWQURD.mjs} +2 -2
  7. package/dist/{chunk-MQ72DIBH.mjs → chunk-3GF7OVTP.mjs} +14 -5
  8. package/dist/chunk-3WMX6KWS.mjs +245 -0
  9. package/dist/{chunk-PMKODV6M.mjs → chunk-462HMNO4.mjs} +6 -10
  10. package/dist/chunk-4CX4SBRO.mjs +153 -0
  11. package/dist/chunk-4MN6UQHG.mjs +443 -0
  12. package/dist/{chunk-GLW2UO6O.mjs → chunk-5QQVZTVZ.mjs} +82 -61
  13. package/dist/{chunk-BGP2N52Z.mjs → chunk-66MI7Q4B.mjs} +5 -5
  14. package/dist/chunk-6FCGKSZX.mjs +268 -0
  15. package/dist/{chunk-CGOKTPXU.mjs → chunk-6JQFUE5I.mjs} +20 -23
  16. package/dist/{chunk-Z3MK2KKZ.mjs → chunk-7DHU4VGG.mjs} +7 -3
  17. package/dist/{chunk-VZ2NR7L3.mjs → chunk-7PYJD5JI.mjs} +35 -27
  18. package/dist/{chunk-JU2RUWHF.mjs → chunk-7XJHLGUV.mjs} +1 -1
  19. package/dist/{chunk-BMFN37JH.mjs → chunk-7YAU5CY6.mjs} +1 -1
  20. package/dist/chunk-A56YQQHG.mjs +402 -0
  21. package/dist/chunk-AH52LG6N.mjs +315 -0
  22. package/dist/{chunk-SLWCCURD.mjs → chunk-CLIN5525.mjs} +8 -4
  23. package/dist/{chunk-3VQNJ235.mjs → chunk-CSDO6VBW.mjs} +7 -0
  24. package/dist/chunk-D4ILTPOG.mjs +293 -0
  25. package/dist/{chunk-HS7TFG7V.mjs → chunk-D6ID6M4V.mjs} +1 -1
  26. package/dist/chunk-DOH3EHX7.mjs +378 -0
  27. package/dist/{chunk-MJIEMGRD.mjs → chunk-EFRENWEJ.mjs} +9 -17
  28. package/dist/{chunk-YBXCIF5Q.mjs → chunk-ERGGHC2V.mjs} +36 -49
  29. package/dist/{chunk-OXQQNQZI.mjs → chunk-FEZKMUCF.mjs} +10 -1
  30. package/dist/{chunk-55CEW76V.mjs → chunk-FH6QVUVZ.mjs} +1 -1
  31. package/dist/chunk-FMAXJ2SI.mjs +71 -0
  32. package/dist/chunk-FZIXGLMV.mjs +173 -0
  33. package/dist/{chunk-DS2AMHN2.mjs → chunk-GYMYRIZP.mjs} +2 -2
  34. package/dist/{chunk-KQDD5MU3.mjs → chunk-H45TKD34.mjs} +5 -5
  35. package/dist/{chunk-BBJBJSXQ.mjs → chunk-J5UICVJS.mjs} +1 -1
  36. package/dist/{chunk-RL772EH7.mjs → chunk-JHJHG4GO.mjs} +4 -12
  37. package/dist/{chunk-RN67642N.mjs → chunk-KMCGSZTX.mjs} +47 -41
  38. package/dist/{chunk-FHNT55I5.mjs → chunk-KUDCQ4FI.mjs} +4 -4
  39. package/dist/chunk-LE6YFY6D.mjs +209 -0
  40. package/dist/{chunk-NLLKTU4B.mjs → chunk-LLVQKSU3.mjs} +21 -17
  41. package/dist/{chunk-KKHTJNMM.mjs → chunk-MARPPFOJ.mjs} +8 -4
  42. package/dist/{chunk-6AFMNC42.mjs → chunk-N2PT566P.mjs} +15 -11
  43. package/dist/chunk-NLCKVHWB.mjs +161 -0
  44. package/dist/{chunk-YN5SYTOO.mjs → chunk-NQPOYKAQ.mjs} +9 -5
  45. package/dist/{chunk-ZZV5JVNW.mjs → chunk-NSLMILBT.mjs} +3 -7
  46. package/dist/chunk-NXA3CZ7A.mjs +248 -0
  47. package/dist/chunk-OGOYQ7BG.mjs +150 -0
  48. package/dist/{chunk-3NQGYJEZ.mjs → chunk-P6AM5V7O.mjs} +10 -18
  49. package/dist/{chunk-CZ3BW5GL.mjs → chunk-P76HMUI6.mjs} +5 -11
  50. package/dist/chunk-PCPLO5HT.mjs +671 -0
  51. package/dist/chunk-PG6K5XEC.mjs +475 -0
  52. package/dist/{chunk-5JGQAAQV.mjs → chunk-PJHPSRYD.mjs} +84 -62
  53. package/dist/{chunk-DDPA2XXS.mjs → chunk-PMB3A7V3.mjs} +2 -2
  54. package/dist/chunk-PR6V5XKM.mjs +209 -0
  55. package/dist/{chunk-46OFHMQA.mjs → chunk-Q76O3RIQ.mjs} +10 -6
  56. package/dist/chunk-QVKWW6KE.mjs +272 -0
  57. package/dist/chunk-RGU7HOEC.mjs +140 -0
  58. package/dist/{chunk-JF4PHPD5.mjs → chunk-RGVKLTLH.mjs} +4 -4
  59. package/dist/{chunk-VG6UF6UT.mjs → chunk-RP3SQYA3.mjs} +2 -2
  60. package/dist/chunk-RRBS6D63.mjs +163 -0
  61. package/dist/{chunk-UEL4RD5P.mjs → chunk-SMQ3DG25.mjs} +80 -67
  62. package/dist/chunk-SPJ5KXW7.mjs +199 -0
  63. package/dist/chunk-SYOD63OZ.mjs +225 -0
  64. package/dist/chunk-UFYSFDER.mjs +42 -0
  65. package/dist/chunk-VACKZOMY.mjs +190 -0
  66. package/dist/chunk-VLQZANBF.mjs +42 -0
  67. package/dist/chunk-WA6O6EUR.mjs +1885 -0
  68. package/dist/{chunk-E3K6O4FZ.mjs → chunk-WAZD7NFU.mjs} +5 -2
  69. package/dist/chunk-WG6JGJXB.mjs +165 -0
  70. package/dist/{chunk-I64K754C.mjs → chunk-WNGWBVLV.mjs} +2 -2
  71. package/dist/{chunk-3U7SD3MS.mjs → chunk-WOEHFRGB.mjs} +3 -3
  72. package/dist/{chunk-DKZRJOMF.mjs → chunk-XIRTEFKH.mjs} +12 -12
  73. package/dist/chunk-Y6DWJSKZ.mjs +79 -0
  74. package/dist/chunk-YKPROFLB.mjs +161 -0
  75. package/dist/{chunk-CJ46PDXE.mjs → chunk-ZRO5JO3H.mjs} +106 -66
  76. package/dist/{chunk-VYMHBV6D.mjs → chunk-ZU4NV6RG.mjs} +5 -3
  77. package/dist/components/ui/accordion.js +40 -4
  78. package/dist/components/ui/accordion.mjs +2 -2
  79. package/dist/components/ui/add-column-modal.js +789 -0
  80. package/dist/components/ui/add-column-modal.mjs +17 -0
  81. package/dist/components/ui/add-lead-modal.js +647 -0
  82. package/dist/components/ui/add-lead-modal.mjs +16 -0
  83. package/dist/components/ui/ai-assistant-drawer.js +686 -0
  84. package/dist/components/ui/ai-assistant-drawer.mjs +16 -0
  85. package/dist/components/ui/alert-dialog.js +37 -5
  86. package/dist/components/ui/alert-dialog.mjs +4 -4
  87. package/dist/components/ui/alert.js +37 -11
  88. package/dist/components/ui/alert.mjs +2 -2
  89. package/dist/components/ui/avatar.js +36 -8
  90. package/dist/components/ui/avatar.mjs +2 -2
  91. package/dist/components/ui/backoffice-alert-history-chart.js +624 -0
  92. package/dist/components/ui/backoffice-alert-history-chart.mjs +16 -0
  93. package/dist/components/ui/backoffice-contact-history-chart.js +687 -0
  94. package/dist/components/ui/backoffice-contact-history-chart.mjs +16 -0
  95. package/dist/components/ui/badge.js +37 -2
  96. package/dist/components/ui/badge.mjs +2 -2
  97. package/dist/components/ui/borrowing-capacity-line-chart.js +639 -0
  98. package/dist/components/ui/borrowing-capacity-line-chart.mjs +16 -0
  99. package/dist/components/ui/button.js +35 -3
  100. package/dist/components/ui/button.mjs +2 -2
  101. package/dist/components/ui/calendar.js +43 -19
  102. package/dist/components/ui/calendar.mjs +3 -3
  103. package/dist/components/ui/card.js +40 -4
  104. package/dist/components/ui/card.mjs +2 -2
  105. package/dist/components/ui/cash-balance-line-chart.js +627 -0
  106. package/dist/components/ui/cash-balance-line-chart.mjs +16 -0
  107. package/dist/components/ui/cashflow-bar-chart.js +123 -69
  108. package/dist/components/ui/cashflow-bar-chart.mjs +8 -8
  109. package/dist/components/ui/checkbox.js +36 -5
  110. package/dist/components/ui/checkbox.mjs +2 -3
  111. package/dist/components/ui/chip.js +37 -2
  112. package/dist/components/ui/chip.mjs +3 -3
  113. package/dist/components/ui/combobox.js +68 -49
  114. package/dist/components/ui/combobox.mjs +2 -2
  115. package/dist/components/ui/data-table.js +160 -88
  116. package/dist/components/ui/data-table.mjs +10 -11
  117. package/dist/components/ui/date-picker.js +44 -20
  118. package/dist/components/ui/date-picker.mjs +6 -7
  119. package/dist/components/ui/dialog.js +44 -12
  120. package/dist/components/ui/dialog.mjs +4 -4
  121. package/dist/components/ui/drawer.js +46 -10
  122. package/dist/components/ui/drawer.mjs +3 -3
  123. package/dist/components/ui/dropdown-menu.js +40 -16
  124. package/dist/components/ui/dropdown-menu.mjs +3 -3
  125. package/dist/components/ui/empty.js +41 -5
  126. package/dist/components/ui/empty.mjs +2 -2
  127. package/dist/components/ui/expense-bar-chart.js +165 -66
  128. package/dist/components/ui/expense-bar-chart.mjs +8 -8
  129. package/dist/components/ui/field.js +53 -21
  130. package/dist/components/ui/field.mjs +4 -4
  131. package/dist/components/ui/financial-cards.js +1002 -0
  132. package/dist/components/ui/financial-cards.mjs +24 -0
  133. package/dist/components/ui/financial-drawers.js +637 -0
  134. package/dist/components/ui/financial-drawers.mjs +17 -0
  135. package/dist/components/ui/financial-primitives.js +218 -0
  136. package/dist/components/ui/financial-primitives.mjs +22 -0
  137. package/dist/components/ui/financial-sections.js +1422 -0
  138. package/dist/components/ui/financial-sections.mjs +30 -0
  139. package/dist/components/ui/form-primitives.js +682 -0
  140. package/dist/components/ui/form-primitives.mjs +19 -0
  141. package/dist/components/ui/income-bar-chart.js +163 -65
  142. package/dist/components/ui/income-bar-chart.mjs +8 -8
  143. package/dist/components/ui/input-group.js +43 -7
  144. package/dist/components/ui/input-group.mjs +5 -5
  145. package/dist/components/ui/input-otp.js +39 -3
  146. package/dist/components/ui/input-otp.mjs +2 -2
  147. package/dist/components/ui/input.js +34 -2
  148. package/dist/components/ui/input.mjs +2 -2
  149. package/dist/components/ui/kanban-column.js +1143 -0
  150. package/dist/components/ui/kanban-column.mjs +20 -0
  151. package/dist/components/ui/label.js +35 -7
  152. package/dist/components/ui/label.mjs +2 -2
  153. package/dist/components/ui/opportunity-card.js +960 -0
  154. package/dist/components/ui/opportunity-card.mjs +20 -0
  155. package/dist/components/ui/opportunity-edit-modals.js +3360 -0
  156. package/dist/components/ui/opportunity-edit-modals.mjs +37 -0
  157. package/dist/components/ui/opportunity-summary-tab.js +4365 -0
  158. package/dist/components/ui/opportunity-summary-tab.mjs +34 -0
  159. package/dist/components/ui/pagination.js +35 -3
  160. package/dist/components/ui/pagination.mjs +3 -3
  161. package/dist/components/ui/pipeline-alerts.js +103 -0
  162. package/dist/components/ui/pipeline-alerts.mjs +8 -0
  163. package/dist/components/ui/pipeline-board.js +1408 -0
  164. package/dist/components/ui/pipeline-board.mjs +24 -0
  165. package/dist/components/ui/pipeline-chart.js +216 -0
  166. package/dist/components/ui/pipeline-chart.mjs +10 -0
  167. package/dist/components/ui/pipeline-dialogs.js +1183 -0
  168. package/dist/components/ui/pipeline-dialogs.mjs +23 -0
  169. package/dist/components/ui/pipeline-primitives.js +300 -0
  170. package/dist/components/ui/pipeline-primitives.mjs +11 -0
  171. package/dist/components/ui/popover.js +45 -4
  172. package/dist/components/ui/popover.mjs +3 -3
  173. package/dist/components/ui/progress.js +33 -1
  174. package/dist/components/ui/progress.mjs +2 -2
  175. package/dist/components/ui/property-cashflow-doughnut-chart.js +523 -0
  176. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +16 -0
  177. package/dist/components/ui/property-debt-equity-doughnut-chart.js +521 -0
  178. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +16 -0
  179. package/dist/components/ui/property-mobile-estimate-line-chart.js +682 -0
  180. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +16 -0
  181. package/dist/components/ui/radio-group.js +33 -1
  182. package/dist/components/ui/radio-group.mjs +2 -2
  183. package/dist/components/ui/select.js +66 -26
  184. package/dist/components/ui/select.mjs +3 -3
  185. package/dist/components/ui/separator.js +33 -1
  186. package/dist/components/ui/separator.mjs +2 -2
  187. package/dist/components/ui/sheet.js +37 -9
  188. package/dist/components/ui/sheet.mjs +3 -3
  189. package/dist/components/ui/skeleton.js +33 -1
  190. package/dist/components/ui/skeleton.mjs +2 -2
  191. package/dist/components/ui/slider.js +86 -102
  192. package/dist/components/ui/slider.mjs +2 -2
  193. package/dist/components/ui/spinner.js +33 -1
  194. package/dist/components/ui/spinner.mjs +2 -2
  195. package/dist/components/ui/stage-timeline.js +579 -0
  196. package/dist/components/ui/stage-timeline.mjs +15 -0
  197. package/dist/components/ui/switch.js +37 -4
  198. package/dist/components/ui/switch.mjs +2 -3
  199. package/dist/components/ui/table.js +37 -5
  200. package/dist/components/ui/table.mjs +2 -2
  201. package/dist/components/ui/tabs.js +36 -12
  202. package/dist/components/ui/tabs.mjs +2 -2
  203. package/dist/components/ui/textarea.js +34 -2
  204. package/dist/components/ui/textarea.mjs +2 -2
  205. package/dist/components/ui/toggle-group.js +35 -4
  206. package/dist/components/ui/toggle-group.mjs +3 -4
  207. package/dist/components/ui/toggle.js +35 -4
  208. package/dist/components/ui/toggle.mjs +2 -3
  209. package/dist/components/ui/tooltip.js +51 -22
  210. package/dist/components/ui/tooltip.mjs +3 -3
  211. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +528 -0
  212. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +16 -0
  213. package/dist/components/ui/transactions-income-expense-bar-chart.js +76 -38
  214. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +8 -8
  215. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +528 -0
  216. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +16 -0
  217. package/dist/index.js +11616 -3831
  218. package/dist/index.mjs +333 -161
  219. package/dist/lib/theme-provider.js +10 -1
  220. package/dist/lib/theme-provider.mjs +1 -1
  221. package/dist/lib/typography.js +8 -0
  222. package/dist/lib/typography.mjs +3 -1
  223. package/dist/lib/utils.js +33 -1
  224. package/dist/lib/utils.mjs +1 -1
  225. package/dist/styles.css +1 -1
  226. package/package.json +140 -5
  227. package/src/components/index.tsx +296 -42
  228. package/src/components/ui/accordion.tsx +6 -3
  229. package/src/components/ui/add-column-modal.tsx +339 -0
  230. package/src/components/ui/add-lead-modal.tsx +290 -0
  231. package/src/components/ui/ai-assistant-drawer.tsx +408 -0
  232. package/src/components/ui/alert-dialog.tsx +80 -54
  233. package/src/components/ui/alert.tsx +28 -28
  234. package/src/components/ui/avatar.tsx +30 -29
  235. package/src/components/ui/backoffice-alert-history-chart.tsx +260 -0
  236. package/src/components/ui/backoffice-contact-history-chart.tsx +325 -0
  237. package/src/components/ui/badge.tsx +17 -15
  238. package/src/components/ui/borrowing-capacity-line-chart.tsx +357 -0
  239. package/src/components/ui/button.tsx +30 -27
  240. package/src/components/ui/calendar.tsx +53 -67
  241. package/src/components/ui/card.tsx +27 -24
  242. package/src/components/ui/cash-balance-line-chart.tsx +302 -0
  243. package/src/components/ui/cashflow-bar-chart.tsx +104 -77
  244. package/src/components/ui/chart-shared.tsx +176 -15
  245. package/src/components/ui/checkbox.tsx +30 -26
  246. package/src/components/ui/combobox.tsx +78 -72
  247. package/src/components/ui/data-table.tsx +160 -99
  248. package/src/components/ui/date-picker.tsx +0 -2
  249. package/src/components/ui/dialog.tsx +70 -60
  250. package/src/components/ui/drawer.tsx +57 -48
  251. package/src/components/ui/dropdown-menu.tsx +90 -82
  252. package/src/components/ui/empty.tsx +31 -27
  253. package/src/components/ui/expense-bar-chart.tsx +83 -65
  254. package/src/components/ui/field.tsx +70 -62
  255. package/src/components/ui/financial-cards.tsx +830 -0
  256. package/src/components/ui/financial-drawers.tsx +339 -0
  257. package/src/components/ui/financial-primitives.tsx +331 -0
  258. package/src/components/ui/financial-sections.tsx +672 -0
  259. package/src/components/ui/form-primitives.tsx +536 -0
  260. package/src/components/ui/income-bar-chart.tsx +79 -60
  261. package/src/components/ui/input-group.tsx +41 -34
  262. package/src/components/ui/input-otp.tsx +29 -24
  263. package/src/components/ui/input.tsx +8 -8
  264. package/src/components/ui/kanban-column.tsx +333 -0
  265. package/src/components/ui/label.tsx +9 -12
  266. package/src/components/ui/opportunity-card.tsx +616 -0
  267. package/src/components/ui/opportunity-edit-modals.tsx +2528 -0
  268. package/src/components/ui/opportunity-summary-tab.tsx +579 -0
  269. package/src/components/ui/pipeline-alerts.tsx +74 -0
  270. package/src/components/ui/pipeline-board.tsx +268 -0
  271. package/src/components/ui/pipeline-chart.tsx +173 -0
  272. package/src/components/ui/pipeline-dialogs.tsx +303 -0
  273. package/src/components/ui/pipeline-primitives.tsx +108 -0
  274. package/src/components/ui/popover.tsx +41 -36
  275. package/src/components/ui/property-cashflow-doughnut-chart.tsx +188 -0
  276. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +185 -0
  277. package/src/components/ui/property-mobile-estimate-line-chart.tsx +393 -0
  278. package/src/components/ui/select.tsx +65 -52
  279. package/src/components/ui/sheet.tsx +55 -52
  280. package/src/components/ui/slider.tsx +54 -77
  281. package/src/components/ui/stage-timeline.tsx +205 -0
  282. package/src/components/ui/switch.tsx +42 -29
  283. package/src/components/ui/table.tsx +28 -28
  284. package/src/components/ui/tabs.tsx +22 -28
  285. package/src/components/ui/textarea.tsx +8 -8
  286. package/src/components/ui/toggle-group.tsx +0 -2
  287. package/src/components/ui/toggle.tsx +13 -15
  288. package/src/components/ui/tooltip.tsx +30 -28
  289. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +191 -0
  290. package/src/components/ui/transactions-income-expense-bar-chart.tsx +45 -38
  291. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +191 -0
  292. package/src/lib/theme-provider.tsx +10 -0
  293. package/src/lib/typography.ts +9 -0
  294. package/src/lib/utils.ts +41 -3
  295. package/src/styles/globals.css +371 -124
  296. package/src/styles/styles-css.ts +1 -1
  297. package/tsup.config.ts +27 -0
  298. package/dist/chunk-3EQP72AW.mjs +0 -58
  299. package/dist/chunk-K74JRTJR.mjs +0 -105
  300. package/dist/chunk-V7CNWJT3.mjs +0 -10
@@ -0,0 +1,408 @@
1
+ import * as React from "react";
2
+ import { Bot, RotateCcw, Send, X } from "lucide-react";
3
+ import { cn } from "@/lib/utils";
4
+ import { Sheet, SheetContent } from "@/components/ui/sheet";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Badge } from "@/components/ui/badge";
7
+ import { Spinner } from "@/components/ui/spinner";
8
+ import { Textarea } from "@/components/ui/textarea";
9
+
10
+ /**
11
+ * AiAssistantDrawer — WealthX DS (L5 Drawer)
12
+ *
13
+ * Right-side panel that provides an AI conversation interface for a specific
14
+ * opportunity/deal. Used in the Pipeline board via the "Launch Assistant" button
15
+ * on OpportunityCard.
16
+ *
17
+ * Layout:
18
+ * • Header — bot icon + title + reload + close
19
+ * • Content — empty state (task suggestions) or chat message list
20
+ * • Footer — textarea input + send button
21
+ *
22
+ * Pure display component: all message state and API calls are managed by the
23
+ * consuming page. The drawer only handles local `inputValue` state.
24
+ *
25
+ * Data source: `ai-chat.ts` / `conversation.ts` hooks in the backoffice
26
+ */
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Types
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export interface AiChatMessage {
33
+ id: string;
34
+ role: "user" | "assistant";
35
+ content: string;
36
+ /** True while the assistant response is still streaming in. */
37
+ isStreaming?: boolean;
38
+ /** True if the message failed to send or receive. */
39
+ isErrored?: boolean;
40
+ }
41
+
42
+ export interface AiTaskSuggestion {
43
+ id: string;
44
+ /** Short label shown as the suggestion title. */
45
+ title: string;
46
+ /** Supporting description shown below the title. */
47
+ description: string;
48
+ }
49
+
50
+ export interface AiAssistantDrawerProps {
51
+ open: boolean;
52
+ onClose: () => void;
53
+ /** Opportunity or contact name shown in the header subtitle. */
54
+ opportunityName?: string;
55
+ /**
56
+ * Suggested tasks shown in the empty state.
57
+ * Typically the opportunity's incomplete tasks.
58
+ * Clicking a suggestion pre-fills the input.
59
+ */
60
+ taskSuggestions?: AiTaskSuggestion[];
61
+ /** Chat message history. Empty array = show empty/welcome state. */
62
+ messages?: AiChatMessage[];
63
+ /** True while the assistant is generating a response. */
64
+ isStreaming?: boolean;
65
+ /** True while initial data is loading (shows full-panel spinner). */
66
+ isLoading?: boolean;
67
+ /** Called when the user submits a message. Input is cleared after this fires. */
68
+ onSendMessage?: (text: string) => void;
69
+ /** Called when the user clicks the reload/reset button. */
70
+ onReset?: () => void;
71
+ className?: string;
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Typing indicator (three bouncing dots)
76
+ // ---------------------------------------------------------------------------
77
+
78
+ function AiTypingIndicator() {
79
+ return (
80
+ <span className="flex items-center gap-1 py-1" aria-label="AI is thinking">
81
+ {[0, 150, 300].map((delay) => (
82
+ <span
83
+ key={delay}
84
+ className="size-1.5 rounded-full bg-current animate-bounce"
85
+ style={{ animationDelay: `${delay}ms` }}
86
+ aria-hidden="true"
87
+ />
88
+ ))}
89
+ </span>
90
+ );
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Chat message bubble
95
+ // ---------------------------------------------------------------------------
96
+
97
+ function AiChatBubble({ message }: { message: AiChatMessage }) {
98
+ const isUser = message.role === "user";
99
+ const isEmpty = !message.content.trim();
100
+
101
+ return (
102
+ <div
103
+ className={cn("flex w-full", isUser ? "justify-end" : "justify-start")}
104
+ >
105
+ <div
106
+ className={cn(
107
+ "max-w-[85%] px-3 py-2 text-sm",
108
+ isUser
109
+ ? "bg-primary text-primary-foreground"
110
+ : "bg-muted text-foreground",
111
+ message.isErrored && "bg-destructive/10 text-destructive",
112
+ )}
113
+ >
114
+ {/* Content or streaming indicators */}
115
+ {isEmpty && message.isStreaming ? (
116
+ <AiTypingIndicator />
117
+ ) : (
118
+ <span className="whitespace-pre-wrap break-words leading-relaxed">
119
+ {message.content}
120
+ </span>
121
+ )}
122
+ {message.isErrored && (
123
+ <p className="mt-1 text-xs opacity-70">
124
+ Failed to send. Please try again.
125
+ </p>
126
+ )}
127
+ </div>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Task suggestion card
134
+ // ---------------------------------------------------------------------------
135
+
136
+ function AiTaskCard({
137
+ suggestion,
138
+ onSelect,
139
+ }: {
140
+ suggestion: AiTaskSuggestion;
141
+ onSelect: (text: string) => void;
142
+ }) {
143
+ return (
144
+ <button
145
+ type="button"
146
+ onClick={() => onSelect(suggestion.title)}
147
+ className="flex flex-col gap-1 border border-border bg-background p-3 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
148
+ >
149
+ <span className="text-sm font-medium text-foreground">
150
+ {suggestion.title}
151
+ </span>
152
+ <span className="text-xs text-muted-foreground">
153
+ {suggestion.description}
154
+ </span>
155
+ </button>
156
+ );
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // AiAssistantDrawer
161
+ // ---------------------------------------------------------------------------
162
+
163
+ const DEFAULT_SUGGESTIONS: AiTaskSuggestion[] = [
164
+ {
165
+ id: "s1",
166
+ title: "Update pricing strategy",
167
+ description: "Review and adjust pricing based on market trends",
168
+ },
169
+ {
170
+ id: "s2",
171
+ title: "Enhance marketing outreach",
172
+ description: "Develop new campaigns to increase customer engagement",
173
+ },
174
+ {
175
+ id: "s3",
176
+ title: "Optimise social media strategy",
177
+ description: "Analyse performance and adjust content for better visibility",
178
+ },
179
+ {
180
+ id: "s4",
181
+ title: "Implement customer feedback loops",
182
+ description: "Create mechanisms to collect and act on customer insights",
183
+ },
184
+ ];
185
+
186
+ export function AiAssistantDrawer({
187
+ open,
188
+ onClose,
189
+ opportunityName,
190
+ taskSuggestions,
191
+ messages = [],
192
+ isStreaming = false,
193
+ isLoading = false,
194
+ onSendMessage,
195
+ onReset,
196
+ className,
197
+ }: AiAssistantDrawerProps) {
198
+ const [inputValue, setInputValue] = React.useState("");
199
+ const messagesEndRef = React.useRef<HTMLDivElement>(null);
200
+ const textareaRef = React.useRef<HTMLTextAreaElement>(null);
201
+
202
+ const suggestions = taskSuggestions ?? DEFAULT_SUGGESTIONS;
203
+ const hasMessages = messages.length > 0;
204
+ const canSend = inputValue.trim().length > 0 && !isStreaming && !isLoading;
205
+
206
+ // Auto-scroll to latest message
207
+ React.useEffect(() => {
208
+ if (!messagesEndRef.current) return;
209
+ messagesEndRef.current.scrollIntoView({
210
+ behavior: "smooth",
211
+ block: "nearest",
212
+ });
213
+ }, [messages.length]);
214
+
215
+ // Auto-resize textarea on value change
216
+ React.useEffect(() => {
217
+ const el = textareaRef.current;
218
+ if (!el) return;
219
+ el.style.height = "auto";
220
+ el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
221
+ }, [inputValue]);
222
+
223
+ const handleSend = React.useCallback(() => {
224
+ const text = inputValue.trim();
225
+ if (!text || !canSend) return;
226
+ onSendMessage?.(text);
227
+ setInputValue("");
228
+ }, [inputValue, canSend, onSendMessage]);
229
+
230
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
231
+ if (e.key === "Enter" && !e.shiftKey) {
232
+ e.preventDefault();
233
+ handleSend();
234
+ }
235
+ };
236
+
237
+ const handleSuggestionSelect = (text: string) => {
238
+ setInputValue(text);
239
+ textareaRef.current?.focus();
240
+ };
241
+
242
+ return (
243
+ <Sheet open={open} onOpenChange={(o) => !o && onClose()}>
244
+ <SheetContent
245
+ side="right"
246
+ showCloseButton={false}
247
+ className={cn("w-[480px] max-w-full gap-0 p-0", className)}
248
+ data-slot="ai-assistant-drawer"
249
+ >
250
+ {/* Header */}
251
+ <div className="flex items-center justify-between border-b border-border px-4 py-3">
252
+ <div className="flex items-center gap-3">
253
+ <span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-primary">
254
+ <Bot className="size-4 text-primary-foreground" />
255
+ </span>
256
+ <div className="flex flex-col">
257
+ <span className="text-sm font-semibold text-foreground">
258
+ AI Assistant
259
+ </span>
260
+ {opportunityName && (
261
+ <span className="text-xs text-muted-foreground">
262
+ {opportunityName}
263
+ </span>
264
+ )}
265
+ </div>
266
+ </div>
267
+ <div className="flex items-center gap-1">
268
+ {onReset && (
269
+ <Button
270
+ variant="ghost"
271
+ size="icon"
272
+ className="size-8"
273
+ onClick={onReset}
274
+ title="Reset conversation"
275
+ disabled={isLoading}
276
+ >
277
+ <RotateCcw className="size-4" />
278
+ <span className="sr-only">Reset conversation</span>
279
+ </Button>
280
+ )}
281
+ <Button
282
+ variant="ghost"
283
+ size="icon"
284
+ className="size-8"
285
+ onClick={onClose}
286
+ title="Close"
287
+ >
288
+ <X className="size-4" />
289
+ <span className="sr-only">Close</span>
290
+ </Button>
291
+ </div>
292
+ </div>
293
+
294
+ {/* Content */}
295
+ <div className="flex flex-1 flex-col overflow-y-auto">
296
+ {isLoading ? (
297
+ /* Loading state */
298
+ <div className="flex flex-1 items-center justify-center py-20">
299
+ <div className="flex flex-col items-center gap-3">
300
+ <Spinner size="lg" className="text-muted-foreground" />
301
+ <p className="text-sm text-muted-foreground">Initialising…</p>
302
+ </div>
303
+ </div>
304
+ ) : !hasMessages ? (
305
+ /* Empty / welcome state */
306
+ <div className="flex flex-col gap-6 p-4">
307
+ {/* Hero card */}
308
+ <div className="flex flex-col gap-3 border border-border bg-muted/30 p-4">
309
+ <Badge variant="outline" className="w-fit gap-1.5">
310
+ <span
311
+ className="size-1.5 rounded-full bg-primary"
312
+ aria-hidden="true"
313
+ />
314
+ AI Chat for Brokers
315
+ </Badge>
316
+ <p className="text-sm text-muted-foreground">
317
+ A safe and secure way to chat about insights that run your
318
+ business and utilise AI. All chats stay within your
319
+ environment, all closed off and compliant.
320
+ </p>
321
+ <div className="flex gap-2">
322
+ {(
323
+ [
324
+ { emoji: "🤖", label: "Smart" },
325
+ { emoji: "⚡", label: "Fast" },
326
+ { emoji: "🔒", label: "Secure" },
327
+ ] as const
328
+ ).map(({ emoji, label }) => (
329
+ <span
330
+ key={label}
331
+ className="border border-border px-2.5 py-1 text-xs text-foreground"
332
+ >
333
+ {emoji} {label}
334
+ </span>
335
+ ))}
336
+ </div>
337
+ </div>
338
+
339
+ {/* Task suggestions */}
340
+ {suggestions.length > 0 && (
341
+ <div className="flex flex-col gap-2">
342
+ <p className="text-xs font-medium text-muted-foreground">
343
+ Suggested tasks
344
+ </p>
345
+ <div className="grid grid-cols-2 gap-2">
346
+ {suggestions.map((s) => (
347
+ <AiTaskCard
348
+ key={s.id}
349
+ suggestion={s}
350
+ onSelect={handleSuggestionSelect}
351
+ />
352
+ ))}
353
+ </div>
354
+ </div>
355
+ )}
356
+ </div>
357
+ ) : (
358
+ /* Message list */
359
+ <div className="flex flex-col gap-3 p-4">
360
+ {messages.map((msg) => (
361
+ <AiChatBubble key={msg.id} message={msg} />
362
+ ))}
363
+ {/* Streaming indicator when last message is user's */}
364
+ {isStreaming &&
365
+ messages[messages.length - 1]?.role === "user" && (
366
+ <div className="flex justify-start">
367
+ <div className="bg-muted px-3 py-2 text-muted-foreground">
368
+ <AiTypingIndicator />
369
+ </div>
370
+ </div>
371
+ )}
372
+ <div ref={messagesEndRef} />
373
+ </div>
374
+ )}
375
+ </div>
376
+
377
+ {/* Footer — input bar */}
378
+ <div className="border-t border-border p-3">
379
+ <div className="flex items-end gap-2">
380
+ <Textarea
381
+ ref={textareaRef}
382
+ value={inputValue}
383
+ onChange={(e) => setInputValue(e.target.value)}
384
+ onKeyDown={handleKeyDown}
385
+ placeholder="Ask me anything… (Enter to send, Shift+Enter for new line)"
386
+ rows={1}
387
+ disabled={isLoading}
388
+ className="min-h-0 flex-1 resize-none overflow-hidden py-2 text-sm"
389
+ />
390
+ <Button
391
+ size="icon"
392
+ onClick={handleSend}
393
+ disabled={!canSend}
394
+ className="shrink-0"
395
+ title="Send"
396
+ >
397
+ <Send className="size-4" />
398
+ <span className="sr-only">Send message</span>
399
+ </Button>
400
+ </div>
401
+ <p className="mt-1.5 text-[10px] text-muted-foreground">
402
+ Enter to send · Shift+Enter for new line
403
+ </p>
404
+ </div>
405
+ </SheetContent>
406
+ </Sheet>
407
+ );
408
+ }
@@ -8,42 +8,46 @@
8
8
  * - AlertDialogFooter: always row, right-aligned; `border-t border-border pt-4` separator
9
9
  * - AlertDialogMedia: borderless icon slot — `size-10` default, `size-8` sm
10
10
  */
11
- import { type ReactElement } from "react"
12
- import * as React from "react"
13
- import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
14
- import { cn } from "@/lib/utils"
15
- import { useThemeVars } from "@/lib/theme-provider"
16
- import { buttonVariants } from "@/components/ui/button"
11
+ import { type ReactElement } from "react";
12
+ import * as React from "react";
13
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
14
+ import { cn } from "@/lib/utils";
15
+ import { useThemeVars } from "@/lib/theme-provider";
16
+ import { buttonVariants } from "@/components/ui/button";
17
17
 
18
- export type AlertDialogProps = React.ComponentProps<typeof AlertDialogPrimitive.Root>
18
+ export type AlertDialogProps = React.ComponentProps<
19
+ typeof AlertDialogPrimitive.Root
20
+ >;
19
21
 
20
- function AlertDialog({
21
- ...props
22
- }: AlertDialogProps): ReactElement {
23
- return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
22
+ function AlertDialog({ ...props }: AlertDialogProps): ReactElement {
23
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
24
24
  }
25
25
 
26
- export type AlertDialogTriggerProps = React.ComponentProps<typeof AlertDialogPrimitive.Trigger>
26
+ export type AlertDialogTriggerProps = React.ComponentProps<
27
+ typeof AlertDialogPrimitive.Trigger
28
+ >;
27
29
 
28
30
  function AlertDialogTrigger({
29
31
  ...props
30
32
  }: AlertDialogTriggerProps): ReactElement {
31
33
  return (
32
34
  <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
33
- )
35
+ );
34
36
  }
35
37
 
36
- export type AlertDialogPortalProps = React.ComponentProps<typeof AlertDialogPrimitive.Portal>
38
+ export type AlertDialogPortalProps = React.ComponentProps<
39
+ typeof AlertDialogPrimitive.Portal
40
+ >;
37
41
 
38
- function AlertDialogPortal({
39
- ...props
40
- }: AlertDialogPortalProps): ReactElement {
42
+ function AlertDialogPortal({ ...props }: AlertDialogPortalProps): ReactElement {
41
43
  return (
42
44
  <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
43
- )
45
+ );
44
46
  }
45
47
 
46
- export type AlertDialogOverlayProps = React.ComponentProps<typeof AlertDialogPrimitive.Backdrop>
48
+ export type AlertDialogOverlayProps = React.ComponentProps<
49
+ typeof AlertDialogPrimitive.Backdrop
50
+ >;
47
51
 
48
52
  function AlertDialogOverlay({
49
53
  className,
@@ -54,17 +58,19 @@ function AlertDialogOverlay({
54
58
  className={cn(
55
59
  // WealthX: foreground/50 scrim — matches Figma foreground token at 50% opacity
56
60
  "fixed inset-0 z-50 bg-foreground/50 data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:fill-mode-forwards data-open:animate-in data-open:fade-in-0",
57
- className
61
+ className,
58
62
  )}
59
63
  data-slot="alert-dialog-overlay"
60
64
  {...props}
61
65
  />
62
- )
66
+ );
63
67
  }
64
68
 
65
- export type AlertDialogContentProps = React.ComponentProps<typeof AlertDialogPrimitive.Popup> & {
66
- size?: "default" | "sm"
67
- }
69
+ export type AlertDialogContentProps = React.ComponentProps<
70
+ typeof AlertDialogPrimitive.Popup
71
+ > & {
72
+ size?: "default" | "sm";
73
+ };
68
74
 
69
75
  function AlertDialogContent({
70
76
  className,
@@ -80,7 +86,7 @@ function AlertDialogContent({
80
86
  className={cn(
81
87
  // WealthX: removed rounded-lg (sharp corners), shadow-lg (flat panels)
82
88
  "group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 duration-200 data-[size=sm]:max-w-xs data-ending-style:animate-out data-ending-style:fade-out-0 data-ending-style:zoom-out-95 data-ending-style:fill-mode-forwards data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[size=default]:sm:max-w-lg",
83
- className
89
+ className,
84
90
  )}
85
91
  data-size={size}
86
92
  data-slot="alert-dialog-content"
@@ -88,10 +94,10 @@ function AlertDialogContent({
88
94
  {...props}
89
95
  />
90
96
  </AlertDialogPortal>
91
- )
97
+ );
92
98
  }
93
99
 
94
- export type AlertDialogHeaderProps = React.ComponentProps<"div">
100
+ export type AlertDialogHeaderProps = React.ComponentProps<"div">;
95
101
 
96
102
  function AlertDialogHeader({
97
103
  className,
@@ -101,15 +107,15 @@ function AlertDialogHeader({
101
107
  <div
102
108
  className={cn(
103
109
  "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
104
- className
110
+ className,
105
111
  )}
106
112
  data-slot="alert-dialog-header"
107
113
  {...props}
108
114
  />
109
- )
115
+ );
110
116
  }
111
117
 
112
- export type AlertDialogFooterProps = React.ComponentProps<"div">
118
+ export type AlertDialogFooterProps = React.ComponentProps<"div">;
113
119
 
114
120
  function AlertDialogFooter({
115
121
  className,
@@ -120,15 +126,17 @@ function AlertDialogFooter({
120
126
  className={cn(
121
127
  // WealthX: always row, right-aligned, separator above footer (matches Figma)
122
128
  "flex flex-row justify-end gap-2 border-t border-border pt-4",
123
- className
129
+ className,
124
130
  )}
125
131
  data-slot="alert-dialog-footer"
126
132
  {...props}
127
133
  />
128
- )
134
+ );
129
135
  }
130
136
 
131
- export type AlertDialogTitleProps = React.ComponentProps<typeof AlertDialogPrimitive.Title>
137
+ export type AlertDialogTitleProps = React.ComponentProps<
138
+ typeof AlertDialogPrimitive.Title
139
+ >;
132
140
 
133
141
  function AlertDialogTitle({
134
142
  className,
@@ -137,16 +145,18 @@ function AlertDialogTitle({
137
145
  return (
138
146
  <AlertDialogPrimitive.Title
139
147
  className={cn(
140
- "text-lg font-semibold sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
141
- className
148
+ "text-h5 sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
149
+ className,
142
150
  )}
143
151
  data-slot="alert-dialog-title"
144
152
  {...props}
145
153
  />
146
- )
154
+ );
147
155
  }
148
156
 
149
- export type AlertDialogDescriptionProps = React.ComponentProps<typeof AlertDialogPrimitive.Description>
157
+ export type AlertDialogDescriptionProps = React.ComponentProps<
158
+ typeof AlertDialogPrimitive.Description
159
+ >;
150
160
 
151
161
  function AlertDialogDescription({
152
162
  className,
@@ -155,16 +165,16 @@ function AlertDialogDescription({
155
165
  return (
156
166
  <AlertDialogPrimitive.Description
157
167
  className={cn(
158
- "text-sm text-muted-foreground sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
159
- className
168
+ "text-body-small text-muted-foreground sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
169
+ className,
160
170
  )}
161
171
  data-slot="alert-dialog-description"
162
172
  {...props}
163
173
  />
164
- )
174
+ );
165
175
  }
166
176
 
167
- export type AlertDialogMediaProps = React.ComponentProps<"div">
177
+ export type AlertDialogMediaProps = React.ComponentProps<"div">;
168
178
 
169
179
  function AlertDialogMedia({
170
180
  className,
@@ -175,18 +185,26 @@ function AlertDialogMedia({
175
185
  className={cn(
176
186
  // WealthX: borderless icon slot — size-10 default, size-8 sm
177
187
  "mb-2 inline-flex size-10 items-center justify-center group-data-[size=sm]/alert-dialog-content:size-8 sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-5",
178
- className
188
+ className,
179
189
  )}
180
190
  data-slot="alert-dialog-media"
181
191
  {...props}
182
192
  />
183
- )
193
+ );
184
194
  }
185
195
 
186
- export type AlertDialogActionProps = React.ComponentProps<typeof AlertDialogPrimitive.Close> & {
187
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
188
- size?: "default" | "sm" | "lg" | "xs" | "icon"
189
- }
196
+ export type AlertDialogActionProps = React.ComponentProps<
197
+ typeof AlertDialogPrimitive.Close
198
+ > & {
199
+ variant?:
200
+ | "default"
201
+ | "destructive"
202
+ | "outline"
203
+ | "secondary"
204
+ | "ghost"
205
+ | "link";
206
+ size?: "default" | "sm" | "lg" | "xs" | "icon";
207
+ };
190
208
 
191
209
  function AlertDialogAction({
192
210
  className,
@@ -200,13 +218,21 @@ function AlertDialogAction({
200
218
  data-slot="alert-dialog-action"
201
219
  {...props}
202
220
  />
203
- )
221
+ );
204
222
  }
205
223
 
206
- export type AlertDialogCancelProps = React.ComponentProps<typeof AlertDialogPrimitive.Close> & {
207
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
208
- size?: "default" | "sm" | "lg" | "xs" | "icon"
209
- }
224
+ export type AlertDialogCancelProps = React.ComponentProps<
225
+ typeof AlertDialogPrimitive.Close
226
+ > & {
227
+ variant?:
228
+ | "default"
229
+ | "destructive"
230
+ | "outline"
231
+ | "secondary"
232
+ | "ghost"
233
+ | "link";
234
+ size?: "default" | "sm" | "lg" | "xs" | "icon";
235
+ };
210
236
 
211
237
  function AlertDialogCancel({
212
238
  className,
@@ -220,7 +246,7 @@ function AlertDialogCancel({
220
246
  data-slot="alert-dialog-cancel"
221
247
  {...props}
222
248
  />
223
- )
249
+ );
224
250
  }
225
251
 
226
252
  export {
@@ -236,4 +262,4 @@ export {
236
262
  AlertDialogPortal,
237
263
  AlertDialogTitle,
238
264
  AlertDialogTrigger,
239
- }
265
+ };